by sinister
1、首先定义struct sc_buf与struct buf结构,sc_buf用于接收/发送SCSI command,如填写相关CBD等,此结构中一个字段用于与block device进行通信,名为sc_buf->bufstruct,它指向了block layer的struct buf结构,这样既完成了SCSI driver与BLOCK driver的关联。
2、填写struct buf中相关字段,其中关键的一个字段buf->b_iodone为完成I/O处理的callback函数,系统规定其要在INTIODONE的interrupt priority级别中调用,所以在函数中先要使用disable_lock()(SMP下用它来替代i_disable()函数)来raises interrupt priority。继而设置buf->flags为B_DONE。
3、使用e_wakeup()函数来唤醒buf->b_event事件。因为b_iodone所指向的callback函数要在interrupt level上运行,不能被交换出去,事先要使用pincode()函数将代码固定在内存中。如果是SMP环境下跑,那么还需要设置buf->b_flags相关位为B_MPSAFE。待struct buf填充完毕后,接下来即为其建立cross memory。
4、使用xmattach()与xmemdma()函数为DMA准备好相关页面,然后让其指向buf->b_xmemd字段。这时即可调用devstart()进行I/O传输(DMA),此函数实则是调用block device的ddstrategy例程。调用成功后使用disable_lock()调整interurpt priority为INTCLASS1,并循环判断B_DONE是否设置,是则认为一次I/O完成。
5、在block device的ddstrategy中完成一次I/O动作多数情况下是显示的调用iodone()函数完成的,这个在AIX的KERNEL开发手册中没有明确指出,只是提到了buf->b_iodone这个callback函数是由相关的strategy来触发完成I/O的,其实很多情况下即使不在该例程中仍需显示的调用iodone()来完成I/O。
6、为了明白I/O complete的最终动作,我反汇编了iodone()函数。该函数首先判断当前的interrupt level是否大于INTIODONE,如果大于调用i_disable()禁止中断并将interrupt priority调整至INTIODONE,使用(*buf->b_iodone)(buf)方法触发回调函数,接着调用i_enable()函数开中断后函数返回。
7、当前interrupt priority小于等于INTIODONE,使用disable_lock()自旋锁,调整至INTMAX级别,并将当前的struct buf插入到系统全局变量g_iodonelist中,调用mpc_send()->pal_i_soft()函数投递一个INTIODONE中断,unlock_enable()后返回。当priority调整后iodone()被再次调用,满足条件触发回调。
8、在回调前会设置struct buf->b_flags=| B_DONE;这样SCSI driver等待循环才会满足条件。INTIODONE是soft interrupt priority。AIX KERNEL下priority越小级别越高,前面判断大于INTIODONE即是优先级低于则调用回调函数。明白了iodone函数也就明白了我前面说的VMM的page fault绕过fs调用的实现。
注1:刚开始反汇编iodone()时不明白为什么在处理struct buf->b_iodone回调时用的是i_disable()/i_enable()函数,按理说我的AIX是SMP结构的,只应该是使用disable_lock()/unlock_enable()函数。后来经过分析才明白原来其是 per CPU 的,g_iodonelist才是全局的,才需要disable_lock()保护。
注2:AIX的cross memory实则是专用于share的,比如一个kernel driver在任意context访问或修改一个用户态进程地址空间中的数据,又或两个kernel process之间访问,再或在inerrupt context下使用其来访问用户进程空间,还有DMA等等...都需要使用到cross memory。kernel api xmattach()函数用于建立这种memory。
1、首先定义struct sc_buf与struct buf结构,sc_buf用于接收/发送SCSI command,如填写相关CBD等,此结构中一个字段用于与block device进行通信,名为sc_buf->bufstruct,它指向了block layer的struct buf结构,这样既完成了SCSI driver与BLOCK driver的关联。
2、填写struct buf中相关字段,其中关键的一个字段buf->b_iodone为完成I/O处理的callback函数,系统规定其要在INTIODONE的interrupt priority级别中调用,所以在函数中先要使用disable_lock()(SMP下用它来替代i_disable()函数)来raises interrupt priority。继而设置buf->flags为B_DONE。
3、使用e_wakeup()函数来唤醒buf->b_event事件。因为b_iodone所指向的callback函数要在interrupt level上运行,不能被交换出去,事先要使用pincode()函数将代码固定在内存中。如果是SMP环境下跑,那么还需要设置buf->b_flags相关位为B_MPSAFE。待struct buf填充完毕后,接下来即为其建立cross memory。
4、使用xmattach()与xmemdma()函数为DMA准备好相关页面,然后让其指向buf->b_xmemd字段。这时即可调用devstart()进行I/O传输(DMA),此函数实则是调用block device的ddstrategy例程。调用成功后使用disable_lock()调整interurpt priority为INTCLASS1,并循环判断B_DONE是否设置,是则认为一次I/O完成。
5、在block device的ddstrategy中完成一次I/O动作多数情况下是显示的调用iodone()函数完成的,这个在AIX的KERNEL开发手册中没有明确指出,只是提到了buf->b_iodone这个callback函数是由相关的strategy来触发完成I/O的,其实很多情况下即使不在该例程中仍需显示的调用iodone()来完成I/O。
6、为了明白I/O complete的最终动作,我反汇编了iodone()函数。该函数首先判断当前的interrupt level是否大于INTIODONE,如果大于调用i_disable()禁止中断并将interrupt priority调整至INTIODONE,使用(*buf->b_iodone)(buf)方法触发回调函数,接着调用i_enable()函数开中断后函数返回。
7、当前interrupt priority小于等于INTIODONE,使用disable_lock()自旋锁,调整至INTMAX级别,并将当前的struct buf插入到系统全局变量g_iodonelist中,调用mpc_send()->pal_i_soft()函数投递一个INTIODONE中断,unlock_enable()后返回。当priority调整后iodone()被再次调用,满足条件触发回调。
8、在回调前会设置struct buf->b_flags=| B_DONE;这样SCSI driver等待循环才会满足条件。INTIODONE是soft interrupt priority。AIX KERNEL下priority越小级别越高,前面判断大于INTIODONE即是优先级低于则调用回调函数。明白了iodone函数也就明白了我前面说的VMM的page fault绕过fs调用的实现。
注1:刚开始反汇编iodone()时不明白为什么在处理struct buf->b_iodone回调时用的是i_disable()/i_enable()函数,按理说我的AIX是SMP结构的,只应该是使用disable_lock()/unlock_enable()函数。后来经过分析才明白原来其是 per CPU 的,g_iodonelist才是全局的,才需要disable_lock()保护。
注2:AIX的cross memory实则是专用于share的,比如一个kernel driver在任意context访问或修改一个用户态进程地址空间中的数据,又或两个kernel process之间访问,再或在inerrupt context下使用其来访问用户进程空间,还有DMA等等...都需要使用到cross memory。kernel api xmattach()函数用于建立这种memory。