The block device driver API has changed a few times since the inception of Linux, but today it basically looks like what follows.
The initialization function calls blk_init_queue
, passing a request callback and an optional lock for that queue:
struct request_queue *q;
q = blk_init_queue(my_request_cb, &my_dev->lock);
my_request_cb
is a callback that will handle all I/O for that block device. I/O requests will be pushed into this queue and my_request_cb
will be called to handle them one after the other, when the kernel block driver layer decides. This queue is then added to the disk:
struct gendisk *disk;
disk->queue = q;
and then the disk is added to the system:
add_disk(disk);
The disk
has other informations like the major number, the first minor number and other file operations (open
, release
, ioctl
and others, but no read
and no write
like found in character devices).
Now, my_request_cb
can be called at any time, and won't necessarily be called from the context of the process that initiated a read/write on the block device. This call is asynchronous by the kernel.
This function is declared like this:
static void my_request_cb(struct request_queue *q);
The queue q
contains an ordered list of requests to this block device. The function may then look at the next request (blk_fetch_request(q)
). To mark a request as completed, it will call blk_end_request_all
(other variations exist, depending on the situation).
And this is where I answer your question: the kernel knows a particular block device request is done when its driver calls blk_end_request_all
or a similar function for this request. The driver does not have to end a request within my_request_cb
: it may, for example, start a DMA transfer, requeue the request, ignore others, and only when the interrupt for a completed DMA transfer is asserted, end it, effectively telling the kernel that this specific read/write operation is completed.
LDD3/chapter 16 can help, but some things changed since 2.6.