scsi设备的请求处理函数(request_fn)

每个块设备驱动程序的核心就是它的请求处理函数,即请求队列中对应的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函数。

你可能感兴趣的:(linux内核)