linux内核初探(三)

经常在阅读一些技术文章的时候会被缓存搞得晕头转向,特别是一些网络方面的,你不知道是内存缓存还是cpu缓存, 一般在操作系统级别为了平衡内存和外设的速度差异,会在内存中开辟一段空间专门与外设(如硬盘)打交道,而实际 上cpu并不能直接访问外设,而是与这些缓存了外设副本的内存区域交互,这就是内存缓存;cpu缓存是为了平衡cpu和 内存之间的差异,将一些最近或者经常访问的内存页面放入cache中,从而提升系统的内存。两者的最根本差异是cpu不 能与外设进行交互而必须借助内存,所以是内存缓冲区一般是必须的(实际上可以不使用缓冲区,那样的系统性能将 是多么糟糕),而cache并不是必须的。这里通过linux内核来看一下内存的缓存问题。

内存缓冲区

缓冲区是所有块设备数据的统一集散地,有人会问缓冲区只是增加了一次数据在内存中的倒手时间,然而真正的原因 在与缓冲区的共享,例如进程A从硬盘中读取的数据恰好是B进程需要的数据,那么B进程就不需要从硬盘中读取了, 直接使用缓冲区就可以了,所以缓冲区的设计从整体上提高了操作系统对文件操作的整体效率。可以说,缓冲区的 所有代码都是为了保证数据交互的正确性和如何让数据在缓冲区停留的时间更长设计的。

缓存区的操作涉及到两个阶段,分别是进程与缓冲区的交互和块设备与缓冲区的交互。

  • 进程与缓冲区的交互

    进程与缓冲区的交互不是以文件为单位的,而是缓冲块。其实块设备与缓冲区的交互也是以缓冲块为单位的, 而且缓冲块与硬盘块的大小一致。每个缓冲块都有唯一的一个buffer_head管理,其结构为:

       struct buffer_head{
         char *b_data;              //数据指针
         unsigned long b_blocknr;   //块号
         unsigned short b_dev;      //设备号
         unsigned char b_uptodate;  
         unsigned char b_dirt;
         unsigned char b_count;     //进程引用计数
         unsigned char b_lock;      //锁住开关
         struct task_struct *b_wait;  //等待进程队列
         struct buffer_head *b_prev;  
         struct buffer_head *b_next;
         struct buffer_head *b_prev_free;
         struct buffer_head *b_next_free;
       }
    

    从上面的结构我们可以看出缓冲区是链表结构的,其中b_dev和b_blocknr可以直接对应到硬盘的设备号和块号, 当内核需要读取文件时,可以通过i节点号与超级块上的信息出数据内容所在的b_dev和b_blocknr,buffer_head中其 他的char字段都是为了数据交互的可靠和让数据在缓冲区中停留时间尽可能长设计的。进程与文件的交互到缓冲区 就到头了。

    • 缓冲区与硬盘的交互

      缓冲区与硬盘的交互主要是依靠request结构来实现的,其结构为:

       struct request{
         int dev;          //设备号
         int cmd;          //read or write
         int errors;       
         unsigned long sector;  //块的首扇区
         unsigned long nr_sectors;   
         char *buffer;          
         struct task_struct *waiting;
         struct buffer_head *bh;
         struct request *next;
       }
      

      可以看出request也是一个链表结构,实际上请求项被设计成循环请求队列,如果一个新请求到来的时候硬盘是 空闲的就让硬盘处理当前请求项,如果硬盘的状态是忙就加入到请求队列中去。由于数据在内存中的交互速度和在 硬盘中的交互速度相差两个数量级,这里需要严格控制请求项的数量,如果请求项的数量太大,硬盘根本操作不过 来,而相反请求项的数量太少,这导致数据读写的任务无法下达,所以请求项的数量需要被控制在一个合理的值。

总之缓冲区作为系统与块设备交互的中间介质,对其的控制是一个很复杂的过程。

进程间通信

说起进程间通信我们第一个会想起管道,由于linux的保护机制,进程不能跨界访问其他进程的内存空间,所以为了 进程间的协作通信只能借助linux提供的通信服务,这里我们先介绍管道机制。

每个管道允许两个进程交互数据,一个进程向管道中写数据,一个从管道中读取数据。其实管道是操作系统在内存中 开辟的一个单独的页,这个内存页被两个进程共享但是不会分配给任何进程,只由内核掌控。由于内核的页保护机制, 进程对于内核页是无法访问到的,那么进程又是如何通过它来通信的呢?

答案是文件,内核为管道页创建一个管道文件(注意与普通文件的差别),它也有其i节点,然后将管道文件描述符返回 给用户进程,这样用户进程就可以通过文件句柄来操作管道。

信号机制与中断机制类似,所以被称为“局部类中断机制”,机理就是系统发现某个进程接收到了信号,就暂时打断进 程的执行,转而去执行该进程的信号处理程序,处理完毕后再继续执行原程序。进程用于处理信号的数据成员被设置 在task_struct中,那么系统是如何知道进程接收到的信号呢?

一般内核是在系统调用返回之前或者时钟中断产生后中断服务程序执行结束前检查进程是否接受到了信号。由于系统 大多数信号是终止进程,所以为了用户程序的健壮,一般都会自行处理信号。



Previous     Next
zhing /
Published under (CC) BY-NC-SA in categories linux  tagged with linux