scsi命令的第二次转变
来源:互联网 发布:增强现实的软件 编辑:程序博客网 时间:2024/05/29 09:48
1.6.6 scsi命令的第二次转变
一旦这种关系建立好了以后,就可以开始执行请求了。来看blk_execute_rq(),来自block/ll_rw_blk.c:
2616 int blk_execute_rq(request_queue_t *q, struct gendisk *bd_disk,
2617 struct request *rq, int at_head)
2618 {
2619 DECLARE_COMPLETION_ONSTACK(wait);
2620 char sense[SCSI_SENSE_BUFFERSIZE];
2621 int err = 0;
2622
2623 /*
2624 * we need an extra reference to the request, so we can look at
2625 * it after io completion
2626 */
2627 rq->ref_count++;
2628
2629 if (!rq->sense) {
2630 memset(sense, 0, sizeof(sense));
2631 rq->sense = sense;
2632 rq->sense_len = 0;
2633 }
2634
2635 rq->end_io_data = &wait;
2636 blk_execute_rq_nowait(q, bd_disk, rq, at_head, blk_end_sync_rq);
2637 wait_for_completion(&wait);
2638
2639 if (rq->errors)
2640 err = -EIO;
2641
2642 return err;
2643 }
抛去那些用于错误处理的代码,这个函数真正有意义的代码就是两行,blk_execute_rq_nowait和wait_for_completion。先看前者,来自block/ll_rw_blk.c:
2588 void blk_execute_rq_nowait(request_queue_t *q, struct gendisk *bd_disk,
2589 struct request *rq, int at_head,
2590 rq_end_io_fn *done)
2591 {
2592 int where = at_head ? ELEVATOR_INSERT_FRONT : ELEVATOR_INSERT_BACK;
2593
2594 rq->rq_disk = bd_disk;
2595 rq->cmd_flags |= REQ_NOMERGE;
2596 rq->end_io = done;
2597 WARN_ON(irqs_disabled());
2598 spin_lock_irq(q->queue_lock);
2599 __elv_add_request(q, rq, where, 1);
2600 __generic_unplug_device(q);
2601 spin_unlock_irq(q->queue_lock);
2602 }
首先at_head是表示往哪插。
而where用来记录at_head的值。在我们这个上下文中,at_head是从scsi_execute()中调用blk_execute_rq的时候传递下来的,当时我们设置的是1。于是where被设置为ELEVATOR_INSERT_FRONT。
回到blk_execute_rq_nowait()中,下一个被调用的函数是__generic_unplug_device,依然是来自block/ll_rw_blk.c:
1589 void __generic_unplug_device(request_queue_t *q)
1590 {
1591 if (unlikely(blk_queue_stopped(q)))
1592 return;
1593
1594 if (!blk_remove_plug(q))
1595 return;
1596
1597 q->request_fn(q);
1598 }
其实最有看点的就是1597行调用这个request_fn,struct request_queue中的一个成员request_fn_proc *request_fn,而至于request_fn_proc,其实又是typedef的小伎俩,来自include/linux/blkdev.h:
334 typedef void (request_fn_proc) (request_queue_t *q);
那么这个request_fn是多少呢?还记得当初那个scsi子系统中申请队列的函数了么?没错,就是__scsi_alloc_queue(),设置成的scsi_request_fn函数。这个函数调用elv_next_request跟我们前面看到的一样,只不过在执行scsi_prep_fn的时候,由于request的标识已经不是
按正路,我们会走到1229行这个switch语句,并且会根据scsi命令的类型而执行不同的函数:scsi_setup_blk_pc_cmnd或者scsi_setup_fs_cmnd。那么我们cmd_type究竟是什么呢?跟前面不同了,前面是REQ_CMD,而我们这次是在scsi_execute()中有这么一行:
199 req->cmd_type = REQ_BLOCK_PC;
所以,没什么好说的,我们会执行scsi_setup_blk_pc_cmnd,来自drivers/scsi/scsi_lib.c:
1090 static int scsi_setup_blk_pc_cmnd(struct scsi_device *sdev, struct request *req)
1091 {
1092 struct scsi_cmnd *cmd;
1093
1094 cmd = scsi_get_cmd_from_req(sdev, req);
1095 if (unlikely(!cmd))
1096 return BLKPREP_DEFER;
1097
1098 /*
1099 * BLOCK_PC requests may transfer data, in which case they must
1100 * a bio attached to them. Or they might contain a SCSI command
1101 * that does not transfer data, in which case they may optionally
1102 * submit a request without an attached bio.
1103 */
1104 if (req->bio) {
1105 int ret;
1106
1107 BUG_ON(!req->nr_phys_segments);
1108
1109 ret = scsi_init_io(cmd);
1110 if (unlikely(ret))
1111 return ret;
1112 } else {
1113 BUG_ON(req->data_len);
1114 BUG_ON(req->data);
1115
1116 cmd->request_bufflen = 0;
1117 cmd->request_buffer = NULL;
1118 cmd->use_sg = 0;
1119 req->buffer = NULL;
1120 }
1121
1122 BUILD_BUG_ON(sizeof(req->cmd) > sizeof(cmd->cmnd));
1123 memcpy(cmd->cmnd, req->cmd, sizeof(cmd->cmnd));
1124 cmd->cmd_len = req->cmd_len;
1125 if (!req->data_len)
1126 cmd->sc_data_direction = DMA_NONE;
1127 else if (rq_data_dir(req) == WRITE)
1128 cmd->sc_data_direction = DMA_TO_DEVICE;
1129 else
1130 cmd->sc_data_direction = DMA_FROM_DEVICE;
1131
1132 cmd->transfersize = req->data_len;
1133 cmd->allowed = req->retries;
1134 cmd->timeout_per_command = req->timeout;
1135 cmd->done = scsi_blk_pc_done;
1136 return BLKPREP_OK;
1137 }
如果曾经的你还对scsi cmd是如何形成的颇有疑义的话,那么相信此刻,你应该会明白了吧,尤其是当你在usb-storage那个故事中看到对它sc_data_direction的判断的时候,你不理解这个值是如何设定的,那么此刻,这代码活生生的展现在你面前,想必已经揭开了你心中那谜团吧。
最终,正常的话,函数返回BLKPREP_OK。prep表示prepare的意思,用我们的母语说就是准备的意思,最后BLKPREP_OK就说明准备好了,或者说准备就绪。而scsi_prep_fn()也将返回这个值,返回之前还设置了cmd_flags中的REQ_DONTPREP。(注意elv_next_request()函数741行判断的就是设这个flag。)
后面的工作就和前面“scsi块设备驱动层处理”的内容一样了。
- scsi命令的第二次转变
- scsi命令的第二次转变
- scsi命令的第一次转变
- scsi命令的第一次转变
- scsi命令的执行
- scsi命令的执行
- scsi命令的执行
- scsi命令
- 对TRIM SCSI命令的一些分析
- 我的第二次实验命令:
- scsi磁盘驱动中将request请求转化为scsi命令的过程(sd_prep_fn)
- SCSI命令详解
- scsi总线的初始化
- SCSI硬盘的优势
- 串行SCSI(SAS)与并行SCSI的比较串行SCSI(SAS)与并行SCSI的比较
- 第二次的
- 范型的转变
- 思维习惯的转变
- scsi块设备驱动层处理
- scsi命令的执行
- linux内存cached释放
- myeclipse新建maven项目时报错
- scsi命令的第一次转变
- scsi命令的第二次转变
- 一个有用的类,集合了建立、连接热点;获取本机ip和连接此热点的手机的ip地址等方法
- 日语练习4_厦門の旅行
- 第四步 使用shell操作数据库,导入分析并将结果导出成txt文件
- 学习Flex元数据标签
- telnet 和 nc
- c#中TreeView和ListView的一些用法
- ios NSNotificationCenter消息通信机制
- 线性表实现之单链表