scsi设备的请求处理函数(request

xiaoxiao2021-02-27  199

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

转载请注明原文地址: https://www.6miu.com/read-9198.html

最新回复(0)