三个模块
先不管内存的分配,文件系统应该分为三个模块,1.高速缓冲区 2.节点块 3.进程文件句柄。他们的层次关系也是从低到高。高速缓冲作为数据的装入点(包括超级块,imap块,bmap块,数据块),而节点块是一个文件对象在内核中抽象的实例,最后离用户层最近的进程文件句柄,只不过是在每个进程中维护了一个数组,每个有效的数组实例都有一个指向节点的指针而已,当用户程序需要读写一个文件的时候,都会调用节点块然后把数据加载的需要的缓冲区,然后内核会把缓冲区的内容复制到用户的内存区
锁
其实前几次阅读并没有很好理解锁的机制,后来看了几本linux2.6以后版本的书,当对多核支持之后的锁机制时候,再回过来看0.11版本的锁就突然明白了。首先0.11版本的内核是单核的,也就是不存在真正的并行,只有因为进程的调度和中断的机制导致的并发,还有关键一点是0.11版本是不支持内核态抢占的,这一点尤为重要,这样我们再看代码就通透了一点。也就是说我们看的内核代码,只要没有主动放弃cpu去调度的逻辑,这部分代码就是安全的,就肯定不会有并发的可能。
那比如节点inode.c,有很多加锁解锁和wait_on_inode这样的代码,这是因为节点代码的写和读逻辑,都需要调用底层驱动程序去往外存设备读写,这是一件对cpu来说相当费事的操作,我们不可能让cpu等待,所以把指令从驱动程序交给外设之后,内核进程就主动放弃了cpu,执行了调度程序了。所以从开始读到结束读和开始写到结束写这之间的代码是不安全的,而且锁的使用原理是对物体加锁而不是对代码加锁,我们只要在读写过程中锁定这个inode节点,并要求任何想要使用节点的时候都要判断是否已经被上锁了。
然后最关键问题,我们怎么能保证我们读节点锁的代码是安全的呢,因为没有内核进程抢占,所以唯一的内核代码打断只可能是中断,那我们在取节点是否被加锁的代码可能被中断打断吗?对了,说的有点混乱,首先内核是不允许抢断的,然后只有系统调用结束的时候内核会去检查是否可以调度,剩下的唯一可能打断内核代码执行的就是硬件中断了,然后硬件中断相应逻辑当中比如硬盘中断,处理程序只是先向8259a发送收到中断指令,然后调用驱动程序干活,干完之后就回到刚才中断点执行啦。那我们仔细想一下,如果有这么一个场景:我们需要取一个干净的节点inode,我们找到一个取它的lock字段,这条指令返回的是加锁,这时候一个驱动中断过来了,硬盘完成了读写操作解锁了节点,这时候回到刚才中断位置继续执行,因为取到了是加锁,该进程就睡眠去了,但是其实这个节点是未加锁的!
所以每次取之前,cli指令把中断给关了,就不会有中断打断了,关了中断再去节点是否上锁就不会被人打断了,类似我们业务代码中取到了锁一样。如果节点被锁定了,则sleep_on函数显示的挂起当前进程,去执行schedule重新调度。但这里还有个重点,第一次看的时候,会觉得有点奇怪,重新调度之后也没有代码显示的把中断打开呀,那是不是新调度的进程就没法相应中断了?其实不是的,当调度的时候,cpu会从栈上取出新进程的eflags,这里面的中断标志会覆盖当前的。而当睡眠的进程被唤醒的时候,它的eflags被恢复的时候中断标志又是关闭的,是不是有点绕,可以仔细体会一下。因为这里面有一部分是cpu隐式的干的,所以只看代码会有点奇怪。
节点
节点代码的领会其实需要把文件系统的划分逻辑深入了解记忆的。我们知道文件系统是把外存按逻辑块划分的,而外设的最小读取单位比如磁盘就是扇区。逻辑块大小应该是扇区的倍数关系,像0.11用的MINIX文件系统就是逻辑块=扇区。有了块我们就可以划分了,因为引导盘都会用第0块作为加载点,所以文件系统都会不用第0块。
第1块就叫超级块,超级块里面存储了该文件系统的配置信息,有系统类型,imap块数,bmap数,逻辑块数等等信息,因为很多操作都要取超级块信息,所以一点该文件系统被mount进内核,那么超级块的节点会一直在内存中而不会被销毁。
一般我们按一个文件名取文件节点的步骤,是按"/"分割,链式的从最外层的文件夹取到最里面一层,文件夹的结构其实和文件很像,只不过data中存储的是其他节点的索引而不是文件数据,其他就和文件一致了。所以流程就是取外层文件夹,然后取文件夹数据遍历查找下一个文件夹节点的号,循环。最后找到文件的节点。
网友评论