Linux Device Drivers 2nd Edition (779877), страница 80
Текст из файла (страница 80)
In order to keep trackof everything, the kernel manages the buffer cache through buffer_head structures. One buffer_head is associated with each data buffer. This structure contains a great many fields, most of which do not concern a driver writer. There area few that are important, however, including the following:char *b_data;The actual data block associated with this buffer head.unsigned long b_size;The size of the block pointed to by b_data.kdev_t b_rdev;The device holding the block represented by this buffer head.unsigned long b_rsector;The sector number where this block lives on disk.struct buffer_head *b_reqnext;A pointer to a linked list of buffer head structures in the request queue.void (*b_end_io)(struct buffer_head *bh, int uptodate);A pointer to a function to be called when I/O on this buffer completes. bh isthe buffer head itself, and uptodate is nonzero if the I/O was successful.Every block passed to a driver’s request function either lives in the buffer cache,or, on rare occasion, lives elsewhere but has been made to look as if it lived in thebuffer cache.* As a result, every request passed to the driver deals with one ormore buffer_head structures.
The request structure contains a member(called simply bh) that points to a linked list of these structures; satisfying therequest requires performing the indicated I/O operation on each buffer in the list.Figure 12-2 shows how the request queue and buffer_head structures fittogether.Requests are not made of random lists of buffers; instead, all of the buffer headsattached to a single request will belong to a series of adjacent blocks on the disk.Thus a request is, in a sense, a single operation referring to a (perhaps long)group of blocks on the disk.
This grouping of blocks is called clustering, and wewill look at it in detail after completing our discussion of how the request listworks.* The RAM-disk driver, for example, makes its memory look as if it were in the buffercache. Since the ‘‘disk’’ buffer is already in system RAM, there’s no need to keep a copyin the buffer cache. Our sample code is thus much less efficient than a properly implemented RAM disk, not being concerned with RAM-disk-specific performance issues.33622 June 2001 16:41http://openlib.org.uaHandling Requests: The Detailed ViewStruct request_queuerequest_fn()queue_headStruct requestbhbufferStruct buffer_headStruct buffer_headb_reqnextb_datab_reqnextb_dataqueue(data)Struct requestbhbuffer(data)Struct buffer_headb_reqnextb_dataqueue(data)Figur e 12-2.
Buffers in the I/O Request QueueRequest queue manipulationThe header <linux/blkdev.h> defines a small number of functions that manipulate the request queue, most of which are implemented as preprocessor macros.Not all drivers will need to work with the queue at this level, but a familiarity withhow it all works can be helpful.
Most request queue functions will be introducedas we need them, but a few are worth mentioning here.struct request *blkdev_entry_next_request(struct list_head*head);Returns the next entry in the request list. Usually the head argument is thequeue_head member of the request_queue structure; in this case thefunction returns the first entry in the queue. The function uses the list_entrymacro to look in the list.struct request *blkdev_next_request(struct request *req);struct request *blkdev_prev_request(struct request *req);Given a request structure, return the next or previous structure in the requestqueue.33722 June 2001 16:41http://openlib.org.uaChapter 12: Loading Block Driversblkdev_dequeue_request(struct request *req);Removes a request from its request queue.blkdev_release_request(struct request *req);Releases a request structure back to the kernel when it has been completelyexecuted.
Each request queue maintains its own free list of request structures(two, actually: one for reads and one for writes); this function places a structure back on the proper free list. blkdev_r elease_r equest will also wake up anyprocesses that are waiting on a free request structure.All of these functions require that the io_request_lock be held, which we willdiscuss next.The I/O request lockThe I/O request queue is a complex data structure that is accessed in many placesin the kernel. It is entirely possible that the kernel needs to add more requests tothe queue at the same time that your driver is taking requests off.
The queue isthus subject to the usual sort of race conditions, and must be protected accordingly.In Linux 2.2 and 2.4, all request queues are protected with a single global spinlockcalled io_request_lock. Any code that manipulates a request queue must holdthat lock and disable interrupts, with one small exception: the very first entry inthe request queue is (by default) considered to be owned by the driver.
Failure toacquire the io_request_lock prior to working with the request queue cancause the queue to be corrupted, with a system crash following shortly thereafter.The simple request function shown earlier did not need to worry about this lockbecause the kernel always calls the request function with the io_request_lockheld. A driver is thus protected against corrupting the request queue; it is also protected against reentrant calls to the request function. This scheme was designed toenable drivers that are not SMP aware to function on multiprocessor systems.Note, however, that the io_request_lock is an expensive resource to hold. Aslong as your driver holds this lock, no other requests may be queued to any blockdriver in the system, and no other request functions may be called. A driver thatholds this lock for a long time may well slow down the system as a whole.Thus, well-written block drivers often drop this lock as soon as possible.
We willsee an example of how this can be done shortly. Block drivers that drop theio_request_lock must be written with a couple of important things in mind,however. First is that the request function must always reacquire this lock beforereturning, since the calling code expects it to still be held.
The other concern isthat, as soon as the io_request_lock is dropped, the possibility of reentrantcalls to the request function is very real; the function must be written to handlethat eventuality.33822 June 2001 16:41http://openlib.org.uaHandling Requests: The Detailed ViewA variant of this latter case can also occur if your request function returns while anI/O request is still active. Many drivers for real hardware will start an I/O operation, then return; the work is completed in the driver’s interrupt handler. We willlook at interrupt-driven block I/O in detail later in this chapter; for now it is worthmentioning, however, that the request function can be called while these operations are still in progress.Some drivers handle request function reentrancy by maintaining an internalrequest queue.
The request function simply removes any new requests from theI/O request queue and adds them to the internal queue, which is then processedthrough a combination of tasklets and interrupt handlers.How the blk.h macros and functions workIn our simple request function earlier, we were not concerned with buffer_headstructures or linked lists. The macros and functions in <linux/blk.h> hide thestructure of the I/O request queue in order to make the task of writing a blockdriver simpler. In many cases, however, getting reasonable performance requires adeeper understanding of how the queue works.
In this section we look at theactual steps involved in manipulating the request queue; subsequent sectionsshow some more advanced techniques for writing block request functions.The fields of the request structure that we looked at earlier—sector, current_nr_sectors, and buffer—are really just copies of the analogous information stored in the first buffer_head structure on the list. Thus, a requestfunction that uses this information from the CURRENT pointer is just processingthe first of what might be many buffers within the request. The task of splitting upa multibuffer request into (seemingly) independent, single-buffer requests is handled by two important definitions in <linux/blk.h>: the INIT_REQUESTmacro and the end_r equest function.Of the two, INIT_REQUEST is the simpler; all it really does is make a couple ofconsistency checks on the request queue and cause a return from the requestfunction if the queue is empty.
It is simply making sure that there is still work todo.The bulk of the queue management work is done by end_r equest. This function,remember, is called when the driver has processed a single ‘‘request’’ (actually onebuffer); it has several tasks to perform:1.Complete the I/O processing on the current buffer; this involves calling theb_end_io function with the status of the operation, thus waking any processthat may be sleeping on the buffer.33922 June 2001 16:41http://openlib.org.uaChapter 12: Loading Block Drivers2.Remove the buffer from the request’s linked list. If there are further buffers tobe processed, the sector, current_nr_sectors, and buffer fields inthe request structure are updated to reflect the contents of the nextbuffer_head structure in the list. In this case (there are still buffers to betransferred), end_r equest is finished for this iteration and steps 3 to 5 are notexecuted.3.Call add_blkdev_randomness to update the entropy pool, unlessDEVICE_NO_RANDOM has been defined (as is done in the sbull driver).4.Remove the finished request from the request queue by callingblkdev_dequeue_r equest.