每个块设备驱动程序的核心就是它的请求处理函数,即请求队列中对应的request_fn函数
struct request_queue {
...
request_fn_proc *request_fn;
make_request_fn *make_request_fn;
prep_rq_fn *prep_rq_fn;
...
}
下面分析scsi设备的请求处理函数
static struct scsi_device *scsi_alloc_sdev(struct scsi_target *starget,
unsigned int lun, void *hostdata)
{
struct scsi_device *sdev;
...
sdev->request_queue = scsi_alloc_queue(sdev);
...
}
struct request_queue *scsi_alloc_queue(struct scsi_device *sdev)
{
struct request_queue *q;
q = __scsi_alloc_queue(sdev->host, scsi_request_fn);
...
}
struct request_queue *__scsi_alloc_queue(struct Scsi_Host *shost,
request_fn_proc *request_fn)
{
struct request_queue *q;
struct device *dev = shost->dma_dev;
q = blk_init_queue(request_fn, NULL);
...
}
熟悉块设备驱动的同学对blk_init_queue这个函数应该不陌生把,该函数原型为
struct request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock);
该函数的参数是处理这个队列的request函数指针和控制访问队列权限的自旋锁,返回一个动态创建好的请求队列地址。继续跟踪这个函数,你会发现make_request_fn,电梯调度器都是在该函数初始化的。
为了把请求队列还给系统,需调用blk_cleanup_queue。
由上面代码我们分析我们得出所有的scsi设备驱动对应的请求处理函数(request_fn)都是这个叫做scsi_request_fn的函数。
static void scsi_request_fn(struct request_queue *q)
{
struct scsi_device *sdev = q->queuedata;
struct Scsi_Host *shost;
struct scsi_cmnd *cmd;
struct request *req;
...
for (;;) {
req = blk_peek_request(q);
/*
* Remove the request from the request list.
*/
if (!(blk_queue_tagged(q) && !blk_queue_start_tag(q, req)))
blk_start_request(req);
sdev->device_busy++;
spin_unlock(q->queue_lock);
cmd = req->special;
...
scsi_init_cmd_errh(cmd);
/*
* Dispatch the command to the low-level driver.
*/
rtn = scsi_dispatch_cmd(cmd);
...
}
该函数的主要作用就是从请求队列取出请求,并把请求转换为对应的scsi命令,最终由scsi_dispatch_cmd(cmd)将命令交给下层控制器。
scsi设备只是一类挂在sici总线上的设备的总称,例如scsi磁盘,scsi磁带都叫做scsi设备,那么scsi_request_fn是怎样与每个具体的设备的驱动联系起来的呢?秘密就隐藏在
blk_peek_request函数。我们以磁盘为例,scsi磁盘驱动存放在sd.c的文件中。
struct request *blk_peek_request(struct request_queue *q)
{
struct request *rq;
int ret;
while ((rq = __elv_next_request(q)) != NULL) {
...
if (!q->prep_rq_fn)
break;
ret = q->prep_rq_fn(q, rq);
if (ret == BLKPREP_OK) {
break;
}
...
}
return rq;
}
其中blk_peek_request函数中ret = q->prep_rq_fn(q, rq);我们以磁盘驱动为例,会调用sd_prep_fn和scsi_prep_fn这两个函数赋值的地方:
struct request_queue *scsi_alloc_queue(struct scsi_device *sdev)
{
struct request_queue *q;
q = __scsi_alloc_queue(sdev->host, scsi_request_fn);
if (!q)
return NULL;
blk_queue_prep_rq(q, scsi_prep_fn);
blk_queue_softirq_done(q, scsi_softirq_done);
return q;
}
以及sd_probe函数里面的blk_queue_prep_rq(sdp->request_queue, sd_prep_fn);
int scsi_prep_fn(struct request_queue *q, struct request *req)
{
struct scsi_device *sdev = q->queuedata;
int ret = BLKPREP_KILL;
if (req->cmd_type == REQ_TYPE_BLOCK_PC)
ret = scsi_setup_blk_pc_cmnd(sdev, req);
return scsi_prep_return(q, req, ret);
}
建立一个普通的scsi命令,在sd模块没注册时会调用这个函数,sd模块注册之后就会调用下面的函数sd_prep_fn建立读写的scsi命令以及scsi_setup_blk_pc_cmnd
static int sd_probe(struct device *dev)
{
...
async_schedule_domain(sd_probe_async, sdkp, &scsi_sd_probe_domain);
...
}
static void sd_probe_async(void *data, async_cookie_t cookie)
{
...
blk_queue_prep_rq(sdp->request_queue, sd_prep_fn);
...
}
我们将在下一篇分析这个将request请求转化成scsi命令的sd_prep_fn函数。