Hands-on Projects for the Linux Graphics Subsystem
|
From the comment of WaitForSomething() we read:
/***************** * WaitForSomething: * Make the server suspend until there is * 1. data from clients or * 2. input events available or * 3. ddx notices something of interest (graphics * queue ready, etc.) or * 4. clients that have buffered replies/events are ready * * If the time between INPUT events is * greater than ScreenSaverTime, the display is turned off (or * saved, depending on the hardware). So, WaitForSomething() * has to handle this also (that's why the select() has a timeout. * For more info on ClientsWithInput, see ReadRequestFromClient(). * pClientsReady is an array to store ready client->index values into. *****************/ |
For WaitForSomething, whose prototype is:
int WaitForSomething(int *pClientsReady)
we read from Definition of the Porting Layer for the X v11 Sample Server:
A list of indexes (client->index) for clients with data ready to be read or processed should be returned in pClientReady, and the count of indexes returned as the result value of the call. These are not clients that have full requests ready, but any clients who have any data ready to be read or processed. The DIX dispatcher will process requests from each client in turn by calling ReadRequestFromClient(), below. |
WaitForSomething() returns therefore the count of the client indexes and also points pClientsReady at the ready client's indexes.
We read also from the "Definition of the Porting Layer":
This call should make the server suspend until one or more of the following occurs: * There is an input event from the user or hardware (see SetInputCheck()) * There are requests waiting from known clients, in which case you should return a count of clients stored in pClientReady * A new client tries to connect, in which case you should create the client and then continue waiting Before WaitForSomething() computes the masks to pass to select, poll or similar operating system interface, it needs to see if there is anything to do on the work queue; if so, it must call a DIX routine called ProcessWorkQueue. |
The biggest part of the WaitForSomething() code is a infinitive 'while' loop, which exits with some 'break' or 'return 0' instructions:
while (1) { |
/* deal with any blocked jobs */ if (workQueue) ProcessWorkQueue(); |
We read from the comment of ProcessWorkQueue():
* Scan the work queue once, calling each function. Those * which return TRUE are removed from the queue, otherwise * they will be called again. This must be reentrant with * QueueWorkProc. |
The 'Work Queue' is a queue of server jobs, queued in a previous time and processed (called the corresponding function) now, just before the server waits for input. A server job enter the queue, calling QueueWorkProc() and such a call is found latter in the WaitForSomething() when new client connections are established and EstablishNewConnections() is placed in the queue to run by ProcessWorkQueue().
if (XFD_ANYSET (&ClientsWithInput)) { . . . { XFD_COPYSET (&ClientsWithInput, &clientsReadable); break; } } |
ClientsWithInput is defined as fd_set in connection.c as:
fd_set ClientsWithInput; /* clients with FULL requests in buffer */ |
When is ClientsWithInput filled?
As seen at section 3.2.5 ClientsWithInput obtains a value at the following line of Dispatch(): result = ReadRequestFromClient(client); |
clientsReadable is also defined as fd_set in the current function. fd_set is defined in the header file sys/select.h included via the X11/Xos.h header file as:
typedef struct fd_set { fd_mask fds_bits[howmany(FD_SETSIZE, NFDBITS)]; } fd_set; |
XFD_ANYSET and XFD_COPYSET are defined in Xpoll.h. The previous says that if there are clients that have FULL requests left in their buffers (those are the ClientsWithInput copied to clientsReadable) their file descriptors are copied to clientsReadable and the 'while' loop breaks. In this case nready, the number of ready clients is zeroed and then is calculated to be the return value of WaitForSomething(). The lines that do this calculation and also fill the WaitForSomething() only parameterer pClientsReady[] are the following:
CODE LINES WHEN WHILE(1) BREAKS:
int highest_priority = 0; fd_set savedClientsReadable; XFD_COPYSET(&clientsReadable, &savedClientsReadable); for (i = 0; i < XFD_SETCOUNT(&savedClientsReadable); i++) { int client_priority, client_index; curclient = XFD_FD(&savedClientsReadable, i); client_index = GetConnectionTranslation(curclient); . . . { pClientsReady[nready++] = client_index; } . . . return nready; |
The for loop uses GetConnectionTranslation() to obtain the client index from the current client and store it to pClientsReady[]. The current client (curclient) is derived from clientsReadable via macro XFD_FD.
NEXT CODE LINES IN THE WaitForSomething() while(1) LOOP:
AllSockets are copied to LastSelectMask again with XFD_COPYSET:
XFD_COPYSET(&AllSockets, &LastSelectMask); |
AllSockets and LastSelectMask are defined in connection.c as:
fd_set AllSockets; /* select on this */ . . . fd_set LastSelectMask; /* mask returned from last select call */ |
We previously met AllSockets at Section 3.2.1 when CreateWellKnownSockets() assigned as initial value to AllSockets the socket the X server listens to. Then AllSockets gets updated in EstablishNewConnections(), which is called in WaitForSomething() via QueueWorkProc(), a routine that we refered to previously. Since AllSockets includes after the EstablishNewConnections() call the updated socket file descriptors (fd) with the new client connections the value of AllSockets is copied to LastSelectMask to be passed in the select() call.
How sockets accept new connections?
From accept() — Accept a New Connection on a Socket we read: The accept() call is used by a server to accept a connection request from a client. When a connection is available, the socket created is ready for use to read data from the process that requested the connection. The call accepts the first connection on its queue of pending connections for the given socket socket. The accept() call creates a new socket descriptor with the same properties as socket and returns it to the caller. If the queue has no pending connection requests, accept() blocks the caller unless socket is in nonblocking mode. If no connection requests are queued and socket is in nonblocking mode, accept() returns -1 and sets the error code to EWOULDBLOCK. The new socket descriptor cannot be used to accept new connections. The original socket, socket, remains available to accept more connection requests. See also the accept() man page. |
EstablishNewConnections() includes actually encapsulated accept() calls as:
new_trans_conn = _XSERVTransAccept (trans_conn, &status) |
Where is EstablishNewConnections() source code documented? EstablishNewConnections() is examined in section 3.2.5.5 |
The next instruction follows:
if (NewOutputPending) FlushAllOutput(); |
Boollean variable NewOutputPending is defined in connection.c and is altered by WriteToClient() and FlushAllOutput(). WriteToClient() is called from many places in the X server source code and we met it for instance at section 2.2. This routine is used by the server for the sending of a reply to the client. FlushAllOutput() is covered in section 3.2.5.4.
A select() system call is issued then as:
else if (AnyClientsWriteBlocked) { XFD_COPYSET(&ClientsWriteBlocked, &clientsWritable); i = Select (MaxClients, &LastSelectMask, &clientsWritable, NULL, wt); } else { i = Select (MaxClients, &LastSelectMask, NULL, NULL, wt); } |
LastSelectMask, is placed at the second argument of select() where as explained in the select man page (see the 'The select() system call' frame that follows) select() expects the set of the file descriptors that will trigger a return when data is ready to be read.
Similarly if there are file descriptors that will trigger a return when data is ready to be written to the third argument of select is non-NULL. In the X server code this is the case when AnyClientsWriteBlocked is raised and then those file descriptors are in the clientsWritable set. Those take place at FlushClient(), which is called by FlushAllOutput(), which run previously.
MaxClients is initially defined as zero to connection.c. Then it obtains a value when InitConnectionLimits() is called from main().
wt, the timeout in a select call, is NULL. However if 'timers' is used in X server extensions the value is non-NULL. As seen in the select() man page: "If timeout is a null pointer, the select blocks indefinitely".
Why WaitForSomething() does not block indefinitely? The BlockHandler(), WakeupHandler() that enclose the select() come to the rescue: BlockHandler((pointer)&wt, (pointer)&LastSelectMask); select(...); WakeupHandler(i, (pointer)&LastSelectMask); At Definition of the Porting Layer for the X v11 Sample Server we read: The DIX BlockHandler() iterates through the Screens, for each one calling its BlockHandler. A BlockHandler is declared thus: void xxxBlockHandler(nscreen, pbdata, pptv, pReadmask) The DIX WakeupHandler() calls each Screen's WakeupHandler. A WakeupHandler is declared thus: void xxxWakeupHandler(nscreen, pbdata, err, pReadmask) |
The select() system call from Using the select() and poll() methods we read: int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout); int nfds - The highest file descriptor in all given sets plus one fd_set *readfds - File descriptors that will trigger a return when data is ready to be read fd_set *writefds - File descriptors that will trigger a return when data is ready to be written to fd_set *errorfds - File descriptors that will trigger a return when an exception occurs struct timeval *timeout - The maximum period select() should wait for an event See also the select() man page for a list of the select() error return values. |
The return value of select() is examined. If i < 0 an error occurred and if i = 0 a timeout.
if (i <= 0) /* An error or timeout occurred */ |
At this moment the following test takes place, which as seen in section 3.2.5.1 it indicates available input events. In this case WaitForSomething() returns with a value of 0.
if (*checkForInput[0] != *checkForInput[1]) return 0; |
The code then examines selecterr for possible select() error (see previous frame 'The select() system call'). If selecterr becomes EBADF ( Some client disconnected ) it calls CheckConnections().
selecterr = GetErrno(); |
The code that executes next deals with the clientsWritable, the clients that are ready to be 'written'. The clientsWritable set was updated by the select() call. AnyClientsWriteBlocked is a boolean variable that become true when some client blocked on write. It is defined in connection.c. The clients that are ready to be 'written' are placed at the OutputPending set and they are also cleared from the ClientsWriteBlocked set. OutputPending is described at connection.c as 'clients with reply/event data ready to go'.
if (AnyClientsWriteBlocked && XFD_ANYSET (&clientsWritable)) { NewOutputPending = TRUE; XFD_ORSET(&OutputPending, &clientsWritable, &OutputPending); XFD_UNSET(&ClientsWriteBlocked, &clientsWritable); if (! XFD_ANYSET(&ClientsWriteBlocked)) AnyClientsWriteBlocked = FALSE; } |
Next source makes use of XFD_ANDSET(dst,b1,b2), another set manipulating macro, which also defined in Xpoll.h is used to AND the sets b1 and b2 to the set dst. LastSelectMask is the second select() argument which is updated by select() to reflect the ready file descriptors (fd) that require a read operation. Since select() does not discriminate between fds of clients, devices, etc LastSelectMask is filtered by EnabledDevices (filled in section 5.1.1) to return device fds to devicesReadable, also is filtered by AllClients to return only client fds to clientsReadable and finaly by WellKnownConnections to place fds of the permitted connections to tmp_set. For the latter set if it is not empty EstablishNewConnections is placed to QueueWorkProc to establish new connections at a latter time. If the devicesReadable or clientsReadable set is non-NULL and therefore devices or clients require a read the while(1) loop breaks.
XFD_ANDSET(&devicesReadable, &LastSelectMask, &EnabledDevices); XFD_ANDSET(&clientsReadable, &LastSelectMask, &AllClients); XFD_ANDSET(&tmp_set, &LastSelectMask, &WellKnownConnections); if (XFD_ANYSET(&tmp_set)) QueueWorkProc(EstablishNewConnections, NULL, (pointer)&LastSelectMask); if (XFD_ANYSET (&devicesReadable) || XFD_ANYSET (&clientsReadable)) break; |
Next we continue with the code after the while(1) loop. There we find an if statement with the condition clientsReadable non-empty. In that case for each member of this set GetConnectionTranslation() is called to obtain the client index from the client. This index is added to pClientsReady[] and the number of ready clients for a read operation is returned.
nready = 0; if (XFD_ANYSET (&clientsReadable)) { int highest_priority = 0; fd_set savedClientsReadable; XFD_COPYSET(&clientsReadable, &savedClientsReadable); for (i = 0; i < XFD_SETCOUNT(&savedClientsReadable); i++) { int client_priority, client_index; curclient = XFD_FD(&savedClientsReadable, i); client_index = GetConnectionTranslation(curclient); { pClientsReady[nready++] = client_index; } FD_CLR(curclient, &clientsReadable); } } return nready; |
REFERENCES
Definition of the Porting Layer for the X v11 Sample Server
Client Scheduling in X