分类: LINUX
2015-07-19 21:54:49
假设读写的scsi设备为scsi disk设备,数据处理过程为:
1. 数据先通过文件系统,进入到文件系统的Cache;
2. 文件系统的pdflush daemon会将Cache住的数据刷新到磁盘,其根据buffer head的内容构造bio,然后调用块设备接口(submit_bio)将请求发送给块设备层。
3. bio在块设备层多次转发,最后被merge到块设备的请求队列中。
4. 请求可能会在请求队列滞留一段时间,然后在软中断或者用户上下文中调用request_fn去处理请求队列。
5. 在scsi middle level驱动层,块设备的请求被转换成scsi command,然后通过queuecommand函数接口将scsi command提交给scsi host,通常scsi host会发起DMA操作将数据传输给具体的设备。
至此,数据从应用程序转移到了scsi设备,当然上述过程还没有涉及到回调过程,实际的回调会在中断上下文、软中断上下文中完成,在请求发送的每一层都保存了相应的回调上下文。
整个数据流的过程中,涉及到的函数如下:
说明:图中MD虚拟机层为linux kernel中Block层的Raid部分,图中以此举例说明请求到block层的转发方式;后续会专门讲解Block层的Raid实现。
IO请求的提交可以理解为整个IO过程的前半部,那么后半部就是IO完成的回调过程,下文分析Linux中IO回调路径的具体实现。
当一个IO事件完成之后,scsi disk会采用中断的方式通知scsihost驱动。
当scsi host的中断事件发生之后,CPU会执行host的中断服务程序,通常实际的scsi host都会以PCI设备的形式存在,考虑到中断共享问题,在中断服务程序中首先需要进行中断事件的判断,然后根据scsi host的状态寄存器进行具体中断任务的处理。
对于读写IO请求,当数据DMA到scsi disk之后会产生DMA结束中断信号,在DMA过程中可以采用聚散DMA(scatter-gather DMA)的技术,因此这一过程不会涉及到数据的内存拷贝,也就是说在读写IO过程中,数据一直处于bio的Page页中(写数据过程中会直接将Page页中的数据DMA到磁盘,读数据过程中直接将磁盘中的数据DMA到bio的Page中,这样的处理机制效率较高)。
当host确定完成请求之后,会调用scsimiddle level的回调函数,该回调函数就是著名的scsi_done。
scsi_done在queue_command的过程中被提交到scsi host层。
在scsi_done函数中直接调用了blk_complete_request函数,该函数通过raise_softirq_irqoff(BLOCK_SOFTIRQ)触发了scsi的软中断。
到目前为止,上述过程都在scsi host的中断上半部中执行。中断上半部运行时间不能过长,否则会导致中断事件的丢失。触发软中断之后,中断上半部就可以退出了。在退出上半部之后,CPU将会交给已经触发的scsi软中断服务程序,此时可以看到软中断的服务程序仍然运行在中断上下文,并不是一个可以调度的context。
软中断的执行函数是blk_done_softirq,由于是scsi command引发的中断事件,因此会调用事先注册到请求队列上的scsi_softirq_done函数,完成具体的scsi软中断下半部事件处理。
在该函数中会进行一些scsicommand执行的正确性判断:
1. 如果命令执行错误,那么可以采用重试的方法进行命令的requeue处理,当重试到一定程度之后会将执行错误的scsi命令交给scsi错误处理内核守护进程,进行最后的判决;
2. 如果执行成功,那么调用scsi_finish_command函数结束掉scsi命令。在scsi_finish_command函数中调用scsi_io_completion函数结束块级的io request,具体会调用scsi_end_request函数,然后调用blk_end_request函数,最后调到blk_end_io函数。在blk_end_io函数中会结束掉request上的所有bio,结束bio的过程可以调用bio_endio函数。Request中的所有bio都结束之后释放request资源。
至此,一个bio所在的request被scsi disk处理完毕之后,通过中断上半部和下半部已经全部处理完毕。
这里需要注意的是,IO的所有回调过程都是在中断上下文中处理的,所以在编写IO的回调函数时需要注意睡眠问题,需要考虑内存分配可能带来的睡眠,信号量的使用会导致睡眠,从而避免使系统崩溃。
通过上述分析,scsi disk的正常IO回调路径涉及的函数描述如下图:
说明:
1. blk_end_io在kernel-2.6.32源码中具体的函数名为blk_update_request,此函数处理一个IO request。具体实现方式为遍历整个request中的bio,分别调用req_bio_endio函数返回每一个bio;
2. bio_endio在源码中为块设备自己注册的IO回调函数。