Chinaunix首页 | 论坛 | 博客
  • 博客访问: 58063
  • 博文数量: 4
  • 博客积分: 25
  • 博客等级: 民兵
  • 技术积分: 189
  • 用 户 组: 普通用户
  • 注册时间: 2012-08-21 21:27
文章分类
文章存档

2017年(1)

2014年(2)

2013年(1)

分类: LINUX

2014-03-29 17:26:27

编程序在某种程度上就像是写小说。看程序则等同于读小说。

小说有三个要素:人物、故事情节、环境(自然环境和社会环境)。人物是核心,情节是骨架,环境是依托。

程序亦是如此。

人物 : 内核数据结构
            过程管理型: 典型实例:task_struct struct file 生命只是暂时的。进程死了,大家也都一起玩完了。任何情况都有例,Init task 除外。
                         
再如,查找文件相应的inode节点时,Nameidata 临时存储所要查找的相应于文件inode的结果。不胜枚举。

资源对象型: 典型实例:struct page,这里可不是指针,而是结构实例,系统初始化的时候,对系统来说,有多少可用物理内存页帧就有多少struct page实例。

故事情节
               程序将要完成的功能及其功能实现的执行链,即过程,生命在于运动,其精彩则在于过程。

   开端:函数进入实际处理之前声明的一些局部变量,辅助性质的空间的准备,就像是临时仓库。  

   发展:函数进入实际处理之前对一些传入参数的合法性检查,以此决断函数应该继续,还是直接返回,或者退出程序执行。 

   高潮:通过可能的重重阻挠,终于可以进入到实际的数据处理,这部分才是该函数之所以要定义、要存在、要执行的根本性理由。
                   理想的模块化的函数只完成一个功能,该功能的执行即为高潮所在。

   结局:无论执行结果成功或者失败, 无论成功后打出胜利的旗帜或者或者失败后优雅退出,一切都会以应有的方式结束,不以物喜,不以己悲。

环境

自然环境:

函数执行对各个数据结构的需求:即结构的分配、初始化,消亡的时间、地点。

有的在函数执行之前就已经存在了,典型的就是全局变量。有的只在程序执行过程中存在,典型的就是局部变量。

社会环境:

算法背景:执行管理、运算过程的方法、方式。如使用基数树管理缓冲常规文件内容的内存页。
    设计和架构背景:各个结构之间的链接关系,或者在内核中所处的层次。 如SystemCall-VFS-FS-Driver,设备模型等。

    

一篇小说之所以吸引人,原因可能是多方面的,但大多以情节取胜。想要读懂一篇历史小说,可能需要联系当时的风土人情时代背,才有可能把握住情节,才会弄清楚来龙去脉,才能读懂它。主人公在某些场合下所采取的抉择、行动、方式,读者会觉得在那样的环境和场合,这么做是应该的,主人公不那么做反而是不正常的,就应该这么做,事情就应该这么办,也就是产生了共鸣

 看源码也是这样风土人情时代背景,一样不风土人情,即数据结构的定义及其定义的缘由,定义该数据结构是为了解决一个什么样的问题时代背景,即算法和架构,代码的结构层次来自程序员在解决问题上追求简洁和高效的刹那间灵光一闪,“一切皆文件”般贯彻始终。如 address_space,借助于该结构体,内核实现了高速缓存、内存映射的设计理念。掌握了这些东西的基础上阅读源代码的话,这个时候,已经不是自己努力、费劲得尝试着去理解代码,而是为了心中所想,嗯,代码就应该这么走,主从关系完全逆转,心情可想而知。有些夸张了,真要做到了完全逆转,已经可以去优化内存中看着不顺心的代码了,因为你的实现或许更为优雅或者高效。所以目前的逆转只是轻微的,呵呵。

为了读懂它,可以采用分而治之的策略。结合背景知识,考书中给出的解释说明,了解数据结构定义及其定义的缘由,删繁就简,勾勒出程序执行流程的框架。然后针对实现流程所涉及到的各个不同的方面,有时间可以再单独深入研究。前面叙述故事情节只是为了概念的完整性,其实代码阅读的前期可以略过这些故事情节中的各种检查,因为不利于对函数的框架性理解,容易顾此失彼,颠倒主次,所以最好直奔主题,免得搞得自己筋疲力尽,头昏脑涨。等到有时间,有精力了,再回头去仔细深入分析,如果可以搞明白,那才是真正的精彩高潮所在。因为连各个边边角角的情况都可以理解,还有什么可以难倒自己?


    说是 read 源码分析,其实并没有拷贝很多的代码,只是列出了函数调用链,就像 PLKA 那样。抓住最主要的,才是最关键的。

交代背景:
Page Cache


内核与文件系统交互可以直接从磁盘进行存取操作,但是鉴于磁盘操作的耗时,与从主存存取相差几个数量级,与CPU执行速度相比则简直有天壤云泥之别,为了降低系统响应时间,增大吞吐率,内核通过在内存保持一个称为Buffer-ceche 或者page cache 的内部数据高速缓冲区,来减小与磁盘的交互频率,通过对高速缓冲区的周期性写回或者一些特殊场合下的写回操作来与磁盘内容保持同步。所以,据此原理实现的操作系统中,对磁盘常规文件的读写实际上都是对缓冲区的读写,对应用层来说是透明的,不可见的。当应用层存取文件时,如果内容没有在page cache中时,由内核负责从磁盘读取,但是仍然先保存在cache中,然后从cache中拷贝文件内容到用户提供的缓冲空间。对文件的写操作也是通过先写入page cache,然后通过cache写回磁盘。


    一言以蔽之,Page cache层负责处理来自应用层的常规文件的常规读写请求。此处一再强调常规,自是排除了Direct IO等几个特殊情况。

                     

VFS

系统调用向应用层提供了文件操作的统一接口,而VFS通过定义出一组泛化、通用的文件操作来隐藏不同类型文件系统(File System)的实现,从而向系统调用提供了统一的接口。只要FS模拟从Linux角度看待文件和目录的方式,提供VFS所要求的抽象接口的实现,如提供创建和删除文件的通用操作,那么内核就可以支持该FS。事实上,内核不需要去理解FS的底层实现细节,FS自己知道就行了。VFS即起着承上启下,中转枢纽的作用。【LKD3】


    

struct address_space


Struct address_space 链接了struct file (面向进程)和struct inode(面向文件系统)。就像VFS一样,起中枢、桥接作用。

实际上就是结构address_space 中的address_space_operation 提供了磁盘文件的读写操作

Address_space是内核最为关键的数据结构类型之一。多个子系统(文件系统,交换,同步,缓冲)都围绕着地址空间的概念来实现。【PLKA



系统调用 Read 图解:
   
本文目的之一就是
结合源搞清楚read系统调用的来龙去脉。

    通过文件名打开文件(open)时,VFS会调用path_lookupat函数查找文件对应的inode节点,把特定文件系统的操作对inode的相关字段赋值。以ext3文件系统为例。

             








generic_file_aio_read
    do_generic_file_read  

        mapping->a_ops->readpage(filp, page);   // 从磁盘读文件内容到cache

        ret = actor(desc, page, offset, nr);    // file_read_actor()   拷贝cache中文件内容到应用层提供的缓冲区

    read 实现就此完毕,是不是过于简化了?!可能会有人生出疑问,如果是第一次打开文件,cache中肯定不是找不到的。那该怎么办? 如果你没有看过这部分源码,你认为该怎么办?
疑问也分很多种,其中就包括虽然目前不懂得,但是知道去哪找答案。可做出合理的猜测,然后去看源码,印证心中所想内核诚然不简单,也没想象那么难!


个人认为,如果把操作系统中各个层次衔接的地方,各个子系统交互的地方搞明白了那才真的是畅得理解了操作系统。但是自己目前还没有做到,有待进一步深入学习


知道原理,明白设计,清楚框架;辨明方向,抽丝剥茧,深入探索

交流学习,相互借鉴,如有谬误,恳请指正,不胜感激。

阅读(4383) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~