Linux SCSI 子系统 IV

当上层业务需要和磁盘交互时,包括读写请求,以及设备对象初始化时获取磁盘信息,这些都需要将请求转化为SCSI磁盘可以识别的SCSI命令下发到磁盘,这里就涉及到了SCSI命令的转化和执行。
SCSI命令的执行可以分为两种情况:发自SCSI和发自Block,前者多指在SCSI设备对象初始化过程中,比如SCSI子系统需要转动磁盘、查询磁盘信息等,后者多指源自高层经由Block层下发到SCSI子系统读写命令。本文从这两个角度阐述。

执行发自SCSI的命令

如果命令发自SCSI层,则通过scsi_execute_req()发出,下面是执行scsi_execute_req的调用关系,使用这个接口下发的命令,会被封装到一个request中插入Block层中request_queue中,通过Block层的处理逻辑择时下发,这种设计是一个低层代码调用高层代码例子。


scsi_execute_req()
    scsi_execute_req_flags()
        scsi_execute()
            req = blk_get_request(sdev->request_queue); //从设备的request_queue获取request
            blk_execute_rq()    			//insert a request into queue for execution
                blk_execute_rq_nowait(blk_end_sync_rq)  //SCSI调用Block函数,注册blk_end_sync_rq()函数
                    rq->end_io = done;      		//即blk_end_sync_rq()
                    __elv_add_request() 
                    __blk_run_queue()
                wait_for_completion_io()		//通过完成量等待IO结束,发自SCSI层的SCSI命令独有

执行发自Block的命令

发自Block层的SCSI命令多是读写请求,即Block层通过回调SCSI HLDD的scsi_request_fn()等接口,将Block层的request转化为scsi_cmnd,再通过回调queuecommand()接口下发到SCSI LLDD。然而,无论是Block还是Net等,内核中的IO执行的框架均大体相同,都可看作request-response结构,而实现这种框架,需要三部分工作:

  1. 准备完成回调,并注册之。完成回调根据返回结果将处理发送的消息,常见的有释放承载请求的buffer,相关标志置位,继续向上层通知处理结果等。
  2. 发起request。
  3. 低层执行request并根据执行结果上报response。

这一点,SCSI子系统也不无例外。

为了执行来自Block层的IO请求,SCSI子系统在构造相关对象的时候就必须要注册包括但不限于上述handler的回调接口。下面是SCSI总线扫描过程中的核心函数,可以看出,在构造scsi_device对象之时,便已经将关键的函数注册好,其中,request_queue中两个重要的回调接口:request_fn和softirq_done_fn分别在注册为scsi_request_queue和scsi_softirq_done,前者在执行request时的回调接口,后者是收到response时的回调接口。

scsi_probe_and_add_lun()        			//为SCSI总线上的LUN在内核中构造相应的scsi_device对象
    scsi_alloc_sdev()
        sdev->request_queue=scsi_alloc_queue(sdev)
            q = __scsi_alloc_queue(sdev->host, scsi_request_fn)
                blk_init_queue(request_fn)
                    blk_init_queue_node(rfn)
                        blk_init_alloc_queue_node()
                        blk_init_allocated_queue(rfn)
                            q->request_fn = rfn;        //即scsi_request_fn()
            blk_queue_prep_rq(q, scsi_prep_fn)
            blk_queue_unprep_rq(q, scsi_unprep_fn)
            blk_queue_softirq_done(q, scsi_softirq_done)
                q->softirq_done_fn=fn       		//scsi_softirq_done被注册为response的回调函数
            blk_queue_rq_timed_out(q, scsi_times_out);
            blk_queue_lld_busy(q, scsi_lld_busy);

Block层的上边界是submit_bio(),本文并不讨论Block层的逻辑,对SCSI命令的执行流程来说,当Block层下刷数据到磁盘的时候,回调request_queue中的接口request_fn(),其已经在设备对象初始化时被注册为
scsi_request_fn(),由它来负责下发数据,其中,另一个注册函数scsi_prep_fn()负责将request封装到scsi_cmnd结构,并最终通过megasas_queue_command()将CDB命令写到PCIe设备规定的地址空间中。


submit_bio()
    generic_make_request()
        q->make_request_fn()                        	//blk_queue_bio()
            blk_flush_plug_list()                     	//蓄流泄流:kblockd
                queue_unplugged()
                    blk_run_queue_async()
                    __blk_run_queue()
                        __blk_run_queue_uncond()
                            q->request_fn(q)        	//scsi_request_fn()
                                blk_peek_request()
                                    q->prep_rq_fn() 	//scsi_prep_fn() 
                                        scsi_setup_cmnd()
                                            scsi_setup_fs_cmnd()
                                cmd->scsi_done = scsi_done
                                scsi_dispatch_cmd()
                                    queuecommand()  	//megasas_queue_command()
                                        build_and_issue_cmd(instance, scmd) 	//megasas_build_and_issue_cmd()
                                        fire_cmd() 				//megasas_fire_cmd_gen2()
                                        scmd->scsi_done()   			//Invoke completion on finished SCSI command
                                            blk_complete_request()
                                                __blk_complete_request()    
                                                    if (list->next == &req->ipi_list)
                                                    raise_softirq_irqoff(BLOCK_SOFTIRQ);

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.