Linux Device Drivers 2nd Edition (779877), страница 79
Текст из файла (страница 79)
The structure isdefined in <linux/blkdev.h>. By accessing the fields in the request structure, usually by way of CURRENT, the driver can retrieve all the informationneeded to transfer data between the buffer cache and the physical block device.*CURRENT is just a pointer into blk_dev[MAJOR_NR].request_queue. Thefollowing fields of a request hold information that is useful to the request function:kdev_t rq_dev;The device accessed by the request. By default, the same request function isused for every device managed by the driver. A single request function dealswith all the minor numbers; rq_dev can be used to extract the minor devicebeing acted upon.
The CURRENT_DEV macro is simply defined asDEVICE_NR(CURRENT->rq_dev).int cmd;This field describes the operation to be performed; it is either READ (from thedevice) or WRITE (to the device).unsigned long sector;The number of the first sector to be transferred in this request.unsigned long current_nr_sectors;unsigned long nr_sectors;The number of sectors to transfer for the current request. The driver shouldrefer to current_nr_sectors and ignore nr_sectors (which is listedhere just for completeness). See “Clustered Requests” later in this chapter formore detail on nr_sectors.char *buffer;The area in the buffer cache to which data should be written (cmd==READ) orfrom which data should be read (cmd==WRITE).struct buffer_head *bh;The structure describing the first buffer in the list for this request.
Buffer headsare used in the management of the buffer cache; we’ll look at them in detailshortly in “The request structure and the buffer cache.”There are other fields in the structure, but they are primarily meant for internal usein the kernel; the driver is not expected to use them.* Actually, not all blocks passed to a block driver need be in the buffer cache, but that’s atopic beyond the scope of this chapter.33222 June 2001 16:41http://openlib.org.uaHandling Requests: A Simple IntroductionThe implementation for the working request function in the sbull device is shownhere.
In the following code, the Sbull_Dev serves the same function asScull_Dev, introduced in “scull’s Memory Usage” in Chapter 3.void sbull_request(request_queue_t *q){Sbull_Dev *device;int status;while(1) {INIT_REQUEST;/* returns when queue is empty *//* Which "device" are we using? */device = sbull_locate_device (CURRENT);if (device == NULL) {end_request(0);continue;}/* Perform the transfer and clean up. */spin_lock(&device->lock);status = sbull_transfer(device, CURRENT);spin_unlock(&device->lock);end_request(status);}}This code looks little different from the empty version shown earlier; it concernsitself with request queue management and pushes off the real work to other functions.
The first, sbull_locate_device, looks at the device number in the request andfinds the right Sbull_Dev structure:static Sbull_Dev *sbull_locate_device(const struct request *req){int devno;Sbull_Dev *device;/* Check if the minor number is in range */devno = DEVICE_NR(req->rq_dev);if (devno >= sbull_devs) {static int count = 0;if (count++ < 5) /* print the message at most five times */printk(KERN_WARNING "sbull: request for unknown device\n");return NULL;}device = sbull_devices + devno; /* Pick it out of device array */return device;}The only ‘‘strange’’ feature of the function is the conditional statement that limits itto reporting five errors.
This is intended to avoid clobbering the system logs withtoo many messages, since end_request(0) already prints an ‘‘I/O error’’33322 June 2001 16:41http://openlib.org.uaChapter 12: Loading Block Driversmessage when the request fails. The static counter is a standard way to limitmessage reporting and is used several times in the kernel.The actual I/O of the request is handled by sbull_transfer:static int sbull_transfer(Sbull_Dev *device, const struct request *req){int size;u8 *ptr;ptr = device->data + req->sector * sbull_hardsect;size = req->current_nr_sectors * sbull_hardsect;/* Make sure that the transfer fits within the device. */if (ptr + size > device->data + sbull_blksize*sbull_size) {static int count = 0;if (count++ < 5)printk(KERN_WARNING "sbull: request past end of device\n");return 0;}/* Looks good, do the transfer.
*/switch(req->cmd) {case READ:memcpy(req->buffer, ptr, size); /* from sbull to buffer */return 1;case WRITE:memcpy(ptr, req->buffer, size); /* from buffer to sbull */return 1;default:/* can’t happen */return 0;}}Since sbull is just a RAM disk, its ‘‘data transfer’’ reduces to a memcpy call.Handling Requests: The Detailed ViewThe sbull driver as described earlier works very well. In simple situations (as withsbull), the macros from <linux/blk.h> can be used to easily set up a requestfunction and get a working driver. As has already been mentioned, however, blockdrivers are often a performance-critical part of the kernel.
Drivers based on thesimple code shown earlier will likely not perform very well in many situations,and can also be a drag on the system as a whole. In this section we get into thedetails of how the I/O request queue works with an eye toward writing a faster,more efficient driver.33422 June 2001 16:41http://openlib.org.uaHandling Requests: The Detailed ViewThe I/O Request QueueEach block driver works with at least one I/O request queue.
This queue contains,at any given time, all of the I/O operations that the kernel would like to see doneon the driver’s devices. The management of this queue is complicated; the performance of the system depends on how it is done.The queue is designed with physical disk drives in mind. With disks, the amountof time required to transfer a block of data is typically quite small. The amount oftime required to position the head (seek) to do that transfer, however, can be verylarge. Thus the Linux kernel works to minimize the number and extent of theseeks performed by the device.Two things are done to achieve those goals. One is the clustering of requests toadjacent sectors on the disk.
Most modern filesystems will attempt to lay out filesin consecutive sectors; as a result, requests to adjoining parts of the disk are common. The kernel also applies an ‘‘elevator’’ algorithm to the requests. An elevatorin a skyscraper is either going up or down; it will continue to move in those directions until all of its ‘‘requests’’ (people wanting on or off) have been satisfied. Inthe same way, the kernel tries to keep the disk head moving in the same directionfor as long as possible; this approach tends to minimize seek times while ensuringthat all requests get satisfied eventually.A Linux I/O request queue is represented by a structure of type request_queue,declared in <linux/blkdev.h>.
The request_queue structure looks somewhat like file_operations and other such objects, in that it contains pointersto a number of functions that operate on the queue—for example, the driver’srequest function is stored there. There is also a queue head (using the functionsfrom <linux/list.h> described in “Linked Lists” in Chapter 10), which pointsto the list of outstanding requests to the device.These requests are, of course, of type struct request; we have already lookedat some of the fields in this structure.
The reality of the request structure is a little more complicated, however; understanding it requires a brief digression intothe structure of the Linux buffer cache.The request structure and the buffer cacheThe design of the request structure is driven by the Linux memory managementscheme.
Like most Unix-like systems, Linux maintains a buffer cache, a region ofmemory that is used to hold copies of blocks stored on disk. A great many “disk”operations performed at higher levels of the kernel — such as in the filesystemcode — act only on the buffer cache and do not generate any actual I/O operations. Through aggressive caching the kernel can avoid many read operations altogether, and multiple writes can often be merged into a single physical write todisk.33522 June 2001 16:41http://openlib.org.uaChapter 12: Loading Block DriversOne unavoidable aspect of the buffer cache, however, is that blocks that are adjacent on disk are almost certainly not adjacent in memory. The buffer cache is adynamic thing, and blocks end up being scattered widely.