16.8 Threaded Print Server

Team-FLY

This section develops a project based on producer-consumer synchronization that uses an unbounded buffer rather than a buffer of fixed size .

The lp command on most systems does not send a file directly to the specified printer. Instead, lp sends the request to a process called a print server or a printer daemon . The print server places the request in a queue and makes an identification number available to the user in case the user decides to cancel the print job. When a printer becomes free, the print server begins copying the file to the printer device. The file to be printed may not be copied to a temporary spool device unless the user explicitly specifies that it should be. Many implementations of lp try to create a hard link to the file while it is waiting to be printed, to prevent the file from being removed completely. It is not always possible for the lp command to link to the file, and the man page warns the user not to change the file until after it is printed.

Example 16.11

The following UNIX lp command outputs the file myfile.ps to the printer designated as nps .

 lp -d nps myfile.ps 

The lp command might respond with a request number similar to the following.

 Request nps-358 queued 

Use the nps-358 in a cancel command to delete the print job.

Printers are slow devices relative to process execution times, and one print server process can handle many printers. Like the problem of handling input from multiple descriptors, the problems of print serving are natural for multithreading. Figure 16.3 shows a schematic organization of a threaded print server. The server uses a dedicated thread to read user requests from an input source. The request thread allocates space for the request and adds it to the request buffer.

Figure 16.3. Schematic of a threaded print server.

graphics/16fig03.gif

The print server of Figure 16.3 has dedicated threads for handling its printers. Each printer thread removes a request from the request buffer and copies the file specified in the request to the printer. When the copying is complete, the printer thread frees the request and handles another request.

The threads within the print server require producer-consumer synchronization with a single producer (the request thread) and multiple consumers (the printer threads). The buffer itself must be protected so that items are removed and added in a consistent manner. The consumers must synchronize on the requests available in the buffer so that they do not attempt to remove nonexistent requests. The request buffer is not bounded because the request thread dynamically allocates space for requests as they come in. The request thread could also use a high-water mark to limit the number of requests that it buffers before blocking. In this more complicated situation, the request thread synchronizes on a predicate involving the size of the buffer.

Several aspects of the print server are simplified for this exercise. A real server may accept input from a network port or by remote procedure call. There is no requirement for printers to be identical, and realistic print requests allow a variety of options for users to specify how the printing is to be done. The system administrator can install default filters that act on files of particular types. The print server can analyze request types and direct requests to the best printer for the job. Printer requests may have priorities or other characteristics that affect the way in which they are printed. The individual printer threads should respond to error conditions and status reports from the printer device drivers.

This exercise describes the print server represented schematically in Figure 16.3. Keep pending requests in a request buffer. Synchronize the number of pending requests with a condition variable, called items , in a manner similar to the standard producer-consumer problem. This exercise does not require a condition variable for slots , since the request buffer can grow arbitrarily large. Represent print requests by a string consisting of an integer followed by a blank and a string specifying the full pathname of the file to be printed.

16.8.1 The request buffer

Represent the request buffer by a linked list of nodes of type prcmd_t . The following is a sample definition.

 typedef struct pr_struct {     int owner;     char filename[PATH_MAX];     struct pr_struct *nextprcmd; }  prcmd_t; static prcmd_t *prhead = NULL; static prcmd_t *prtail = NULL; static int pending = 0; static pthread_mutex_t prmutex = PTHREAD_MUTEX_INITIALIZER; 

Put the request buffer data structure in a separate file and access it only through the following functions.

 int add(prcmd_t *node); 

adds a node to the request buffer. The add function increments pending and inserts node at the end of the request buffer. If successful, add returns 0. If unsuccessful , add returns “1 and sets errno .

 int remove(prcmd_t **node); 

removes a node from the request buffer. The remove function blocks if the buffer is empty. If the buffer is not empty, the remove function decrements pending and removes the first node from the request buffer. It sets *node to point to the removed node. If remove successfully removes a node, it returns 0. If unsuccessful, remove returns “1 and sets errno .

 int getnumber(void); 

returns the size of the request buffer, which is the value of pending .

Use the synchronization strategy of Program 16.11, but eliminate the conditions for controlling the number of slots.

16.8.2 The producer thread

The producer thread, getrequests , inserts input requests in the buffer.

 void *getrequests(void *arg); 

The parameter arg points to an open file descriptor specifying the location where the requests are read. The getrequests function reads the user ID and the pathname of the file to be printed, creates a prcmd_t node to hold the information, and calls add to add the request to the printer request list. If getrequests fails to allocate space for prcmd_t or if it detects end-of-file, it returns after setting a global error flag. Otherwise, it continues to monitor the open file descriptor for the next request.

Write a main program to test getrequests . The main program creates the getrequests thread with STDIN_FILENO as the input file. It then goes into a loop in which it waits for pending to become nonzero. The main thread removes the next request from the buffer and writes the user ID and the filename to standard output. Run the program with input requests typed from the keyboard. Test the program with standard input redirected from a file.

16.8.3 The consumer threads

Each consumer thread, printer , removes a request from the printer request buffer and "prints" it. The prototype for printer is the following.

 void *printer(void *arg); 

The parameter arg points to an open file descriptor to which printer outputs the file to be printed. The printer function waits for the counter pending to become nonzero in a manner similar to consumer in Program 16.13. When a request is available, remove the request from the buffer, open the file specified by the filename member for reading, and copy the contents of the file to the output file. Then close the input file, free the space occupied by the request node, and resume waiting for more requests. If a consumer thread encounters an error when reading the input file, write an appropriate error message, close the input file, and resume waiting for more requests. Since the output file plays the role of the printer in this exercise, an output file error corresponds to a printer failure. If printer encounters an error on output, close the output file, write an appropriate error message, set a global error flag, and return.

16.8.4 The print server

Write a new main program to implement the print server. The server supports a maximum of MAX_PRINT printers. (Five should suffice for testing.) The main program takes two command-line arguments: the output file basename and the number of printers. The input requests are taken from standard input, which may be redirected to take requests from a file. The output for each printer goes to a separate file whose filename starts with the output file basename. For example, if the basename is printer.out , the output files are printer.out.1, printer.out.2 , and so on. The main program creates a thread to run get_requests and a printer thread for each printer to be supported. It then waits for all the threads to exit before exiting itself. The main program should not exit just because an error occurred in one of the printer threads. Thoroughly test the print server.

16.8.5 Other enhancements

Add facilities so that each printer thread keeps track of statistics such as total number of files printed and total number of bytes printed. When the server receives a SIGUSR1 signal, it writes the statistics for all the printers to standard error.

Add facilities so that the input now includes a command as well as a user ID and filename. The commands are as follows .

lp:

Add the request to the buffer and echo a request ID to standard output.

cancel:

Remove the request from the buffer if it is there.

lpstat:

Write to standard output a summary of all pending requests and requests currently being printed on each printer.

Modify the synchronization mechanism of the buffer to use highmark and lowmark to control the size of the request buffer. Once the number of requests reaches the highmark value, getrequests blocks until the size of the request buffer is less than lowmark .

Team-FLY


Unix Systems Programming
UNIX Systems Programming: Communication, Concurrency and Threads
ISBN: 0130424110
EAN: 2147483647
Year: 2003
Pages: 274

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net