LINUX0.11内核阅读笔记 (2)



()文件系统模块fs

1.总体结构:

Linux把所有设备都做为文件来看待。提供统一的打开,关闭,读写系统调用接口。          下面是文件系统层次关系:

<!--[if !vml]--><!--[endif]-->

4

总体来说,文件系统提供两类外部接口(系统调用),文件读写和文件管理控制。

上图中Read_write代表的是文件读写系统调用接口read,wirte。它根据操作文件的类型分别调用了四种读写函数:

字符型文件tty_read,tty_write,在kernel/chr_drv驱动模块中定义;
FIFO
文件  pipe_read,pipe_write 都是内存操作。Fs/pipe.c中定义
block_dev
块设备文件 block_read,block_wirte,间接调用bread

File_dev 常规文件。File_read,file_write,   涉及的内容是fs主要的内容。

图中Open stat fcntl 则是文件系统的系统管理控制接口如创建打开关闭,状态访问修改功能。这主要针对常规文件,如根文件系统下的全部文件。这些都需要底层文件系统函数支持,主要包括文件系统超级块,i结点位图和逻辑块位图,i结点,文件名解析等操作。而这些底层文件系统函数则建立于buffer提供的缓冲管理机制之上。这些是对上图的大体归纳吧!

在上面总结kernel的时候,没有提及blk_drvchr_drv,因为我觉得把它们放在文件系统里面来更合适。

Blk_drv目录是块设备驱动代码。实现了HD(硬盘),FD(软盘),RD(Ramdisk)三种块设备的底层驱动,并提供一个外部调用的接口ll_rw_block(dev,nr)。就是上图中右下虚框示意的层次上。

5

 

同样的,char_drv实现了字符设备(串行终端)的驱动,包括控制台(键盘屏幕),两个串口。实现供上层调用的读写接口read_tty , write_tty。下面是源码关系图:

<!--[if !vml]--><!--[endif]-->

6

2下面分别从从底层向高层总结一下各个层次中源码实现的主要细节:

2.1 块设备驱动部分 kernel/blk_drv

块设备工作流程(粗略)

1)文件设备接口调用底层块设备读写函数ll_rw_block(int rw,buffer_head *bh).这里bh要读的设备号,块号,已经写入bh, rw是读或者写指令

2)ll_rw_block(int rw,buffer_head *bh)取主设备号major,调用make_request(major,rw,bh);

3)make_request(major,rw,bh)申请一个请求项,根据rwbh相应设置填充req各字段值,     并调用add_request (major + blk_dev, req)插入到设备major的请求队列。

4)add_request (major + blk_dev, req)检查设备等待队列是否为空,为空则把req添加到队列中并马上调用设备的请求执行函数。
      
对于硬盘,这个函数就是do_hd_request,它将根据请求项的各个字段设置向硬盘发出相应的命令. 如果请求队列不为空,则按照电梯算法把req加到队列中。
       ll_rw_block
函数返回。

整个ll_rw_block()返回到上层调用(缓冲管理部分buffer.c)。然后调用进程将执行等待wait_on_buffer(bh);进程切换。

硬盘接受命令后,完成req要求的读/写一个扇区后将发出中断。hd_interrupt(定义于kernel/system_call.s)被执行。调用_do_hddo_hd是设备当前要调用的中断处理函数的指针。根据当前请求,do_hd_request在调用hd_out向硬盘控制器发命令(如读写或复位等)时根据命令类型指定do_hdread_intr, write_intr或其它。如果为读,do_hd=read_intr。写则do_hd=write_intr.

read_intr 将判断当前请求项请求读的扇区数是否已经全部读完,如果没有,再次设置do_hd=read_intr,然后返回。如果全部完成,则调用end_request(1)去唤醒等待的进程。然后调用do_hd_request去处理其余请求项。

 

write_intr 将判断当前请求项请求写的扇区数是否已经写完,如果没有,把一扇区数据复制到硬盘缓冲区内,然后再次设置do_hd=write_intr并返回。如果写完,则调用end_request(1),更新并有效缓存块,然后调用do_hd_request去处理其余请求项。

整个硬盘读写流程如下 :


7

 

对于软盘,大体的流程差不多。只是软盘有启动马达等延时写时操作,比较琐碎一些。

对于ramdisk,速度很快所以不需要中断机制,当然请求队列也最多只有当前一个。像上面的过程一样,make_request会调用add_request,而由于前面的请求队列一定为空,所以会马上执行do_rd_request. do_rd_request中直接读、写数据。然后就end_request(1).

2.2 字符设备驱动 kernel/chr_drv

串行/字符设备在linux下叫TTY,每个TTY对就一个tty_struct结构。0.11版本一共三个,一个控制台两个串口。每个tty_struct有三个缓冲区,read_q,  write_q,  secondary
read_q
保存原始的输入字符队列,write_q保存的是输出的字符队列,secondary里面是输入字符序列通过行规则处理后的字符序列。
tty_struct
中的termios保存的是终端IO属性等。这个结构通过tty_ioctl.ctty_ioctl()来对tty进行相应的控制或设置。
避开非常琐碎的行规则,从char_dev.c中函数rw_tty调用来看整个过程的粗略脉络。 rw_tty(int rw,unsigned minor,char * buf,int count, off_t * pos)。检测进程有无终端,有则根据rw调用tty_read 或者tty_write.
先看tty_read(minor,buf,count),对于要求读取的字节数nr,在定时时间内,循环读取secondary中的字符,直到读到nr个为止。如果secondary空了,  进程等待于secondary的等待队列上。如果超时,则返回。

再看tty_write(minor,buf,count),如是tty写缓冲write_q已经满了,睡眠sleep_if_full (&tty->write_q); 对于要求写字节数nr,循环拷贝到write_q中去。如果拷贝过程中write_q满了或者已经拷贝完调用写函数。没拷贝完则切换进程。剩下的工作交给中断处理程序去完成。

 

对于读操作,当tty收到一个字符,比如串口收到一个字符或者是用户按下键盘,系统将进入相应中断程序。中断程序对收到的字符进行处理,然后把字符放入对应ttyread_q中,调用do_tty_interruptdo_tty_interrupt直接调用copy_to_cooked(tty).  copy_to_cooked(tty)read_q的全部字符通过行规则处理。然后放到secondary队列中去。如果tty回显标志置位,则把转换后的字符也放到写队列write_q,并调用tty->write (tty); 如果中ISIG 标志置位,收到INTRQUITSUSP DSUSP 字符时,需要为进程产生相应的信号。最后唤醒等待该辅助缓冲队列的进程(如果有的话)。wake_up (&tty->secondary.proc_list); 中断返回。

对于写操作,如果tty是控制台,其tty写操作为con_write (tty),这个函数直接把write_q中所有字符按照格式写到显存中去或者调整光标。如果是串口,tty写操作为rs_write(tty);这个函数更简单,打开串口发送缓冲空闲允许中断位就返回。这样,CPU会马上收到一个中断,在中断程序中,写操作才会真正进行。串口写缓冲空中断执行时先判断写队列是否为空,如果为空,唤醒可能的等待队列,并且禁止发送缓冲空中断允许并中断返回。如果不空,但是写队列字符数小于256,也唤醒可能的写等待队列,然后从写队列中取一个字符写入串口发送寄存器。中断返回。

2.3文件系统之缓冲管理fs/buffer.c

缓冲管理部分两个作用,利用cache机制提供更高效的使用外部块设备,给使用块设备的其它程序提供简单的接口如bread。使得上层程序对设备操作全部变成对缓冲块的操作。给块设备如软硬盘提供一种cache机制。每个缓冲块buffer_head都对应一个设备dev和逻辑块号block,引用计数count,修改标志dirt,有效标志uptodate 类比CPU,修改标志与有效标志是cache机制必需的,而devblock号则相当于地址。缓冲管理负责设备数据块与缓冲映射块数据一致性。缓冲管理具体去调用设备驱动程序ll_rw_block().

Buffer.c主要提供的函数有申请、释放缓存,同步(buffer与设备内容一致),读取。而写操作则总是在缓冲不足的情况下利用同步进行的。读取的时候总是根据给定的devblock先查找当前缓存中是否存在有效的对应块,如果存在就不再访问设备。否则取一个空闲缓冲,调用设备驱动ll_rw_block

缓冲块链表实现了hash链表和LRU算法,所有的缓冲块都连接于链表中,链表头总是空闲度最高的缓冲块,链表尾则是最近刚申请的块。查找空闲缓冲从头开始比较空闲度,找最大的。这就实现了LRU算法。 所有具有相同hash值的缓冲块连接于同一个hash_table[nr]项上。nr值由设备号和块号经过一个hash算法得到。这样查找速度会快好多倍。

所有对外设逻辑块的读写都在这里被转化为对缓冲块的读写。每次读写前总是先根据设备号和逻辑块号到hash_table[]表中查找hash链表,若已经存在且有效,则直接对缓冲读写。写后要置修改标志dirt。这样当执行同步操作,或者getblk()找不到干净的空闲块的时候会把所有dirt1的未被占用(count=0)的缓冲块写入磁盘。

2.4 文件系统之文件系统底层操作。

文件底层操作。(bitmap.c,inode.c,namei.c,super.c,truncate.c,)这部分按照文件系统对硬盘等块设备的使用规则,实现了相应规则的操作函数。文件系统把块设备按逻辑块管理,功能划分:

引导块

超级块

i结点位图块区

逻辑块位图块区

i结点块区

数据块区

    超级块指明了整个文件系统各区段的信息。两个位图区分别指示i结点区和数据块区的占用和空闲状况,i结点区中每个i结点都代表一个文件或者目录。整个文件系统的根目录在第1i结点处。通过它可以找出任何路径下的文件的i结点。

i结点指示一个文件或者目录,一个i结点的内容主要是文件占用的数据块号。直接块号,一、二次间接块号提供了灵活的机制来线性地查找文件中一个数据块对应在哪一个具体的物理块上。目录文件的数据块内容是目录项,它包含的所在目录的全部文件和子目录的信息。每个目录项保存一个文件或者子目录的inode号和文件名。

相应地,bitmap.c提供了i结点位图,数据块位图的占用和释放操作。super.c实现对超级块的读定安装卸载操作。inode.c实现是获取指定nrinode(iget(dev,nr)),写回inode ( iput(inode) )等操作。

namei.c则实现了按照文件名来获取inode的操作。从而提供了通过文件名来管理文件(inode)的方法。

这些操作之间的层次并不十分清晰,相互调用很多。注意块设备是按块为最小单位访问的,这些操作不过是按照文件系统对设备块使用的定义对各个块以及块中的数据做解析和操作罢了。文件底层操作都貌似在访问块设备,但是却仅仅调用了缓冲管理提供的接口。它们操作了内存。缓冲管理去实现设备的读写。比如在系统安装根文件系统的时候,超级块已经读如缓冲,根据超级块的信息,将i结点位图块,逻辑位图块读入到内存缓冲中了。

下面对各个源文件的实现进行小结:

bitmap.c: 位图操作,主要提供文件系统中i结点位图,逻辑块位图相关操作,包括申请和释放inode,申请和释放block.首先文件系统的位图块在mount_root中已经缓存到buffer中,缓冲块指针由超级块s_zmap[],s_imap[]指向。所以申请释放操作主要的一部分------对位图相应位置位或者复位就变成对缓冲块置位复位了,然后修改标志dirt=1就行了。
new_block
除了要对找到的空闲位置位外,还要申请一块空闲缓冲(0)并填申请块的devblock。置有效和修改标志。这么做其实就等于一个写操作,即把申请的设备块清0(当然,可能申请后马上就要写这一块,所以这么做最高效了。)

truncate.c: 对文件(inode)长度清0。主要调用free_block对位图进行操作。直接块直接释放,对一次间接块上所有有效块号释放,然后再释放一次间接块。二次间接块同理。

inode.c:   主要提供三个函数,iget,iput,bmap.  iget是获取指定设备和i结点号的内存i结点。使用计数加1。主要调用read_inode(调用buffer管理部分iput是把一个内存i节点写回到设备中去。使用计数减1。主要调用write_inode(调用buffer管理部分)
bmap
是把文件块号对应到设备块号(逻辑块号)中去。文件块号是按直接块,一直间接,二次间接顺序计算的索引。逻辑块号则是保存在它们里面的块号。有点像页表,页的线性地址对应文件块号,页的物理地址对应逻辑块号。页表项中的保存的地址就是页物理地址。bmap有创建和不创建两种方式。创建时会根据文件块号给文件(inode)申请逻辑块存放可能需要的一、二次间接块和数据块。

super.c:对文件系统超级块的相关操作。如get_super,put_super,read_super,sys_mount,sys_umount;超级块对应一个文件系统。
get_super(dev)
在系统超级块数组中查找并返回匹配的超级块。
put_super(dev)
释放超级块数组中超级块信息,并释放超级块i结点,逻辑块位图占用的缓冲区。
read_super(dev)
先在超级块数组中查找,有直接返回,没有则先在超级块数组找一空闲项。读dev 1号块,取得超级块信息,如位图占多少块,再读位图块(i位图,逻辑块位图)到缓冲中。设置完毕返回。
sys_mount(devname
dirname,rw_flag) 在目录dirname上安装devname设备的文件系统。取dirnamedevnamei结点判断二者都有效?然后读dev超级块read_super(dev),置超级块安装结点为direnamei结点 sb->s_imount=dir_i. 置目录i结点安装标志1。所以i结点的安装标志表明该目录是否安装了一个文件系统。而要知道安装的文件系统的具体信息则要查找超级块数组,看看哪一个超级块的s_imount等于该i结点。。。

namei.c:提供文件路径名到i结点的操作。大部函数参数都直接给出文件路径名,所以它实现了通过文件名来管理文件系统的接口。如打开创建删除文件、目录,创建删除文件硬连接等。
大部分函数的原理都差不多:调用get_dir取得文件最后一层目录的i结点dir。如果是查找就调用find_entrydir的数据块中查找匹配文件名字符串的目录项。这样通过目录项就取得了文件的i结点。如果是创建(sys_mknod)就申请一个空的inode,dir的数据块中找一个空闲的目录项,把文件名和inode号填入目录项。创建目录的时候要特殊一些,要为目录申请一个数据块,至少填两个目录项,...  (sys_mkdir)
删除文件和目录的时候把要释放i结点并删除所在目录的数据块中占用的目录项。
打开函数open_namei()基本上实现了open()的绝大部分功能。它先按照上述过程通过文件路径名查找 最后一层目录i结点,在目录数据块中查找目录顶。如果没找到且有创建标志,则创建新文件,申请一个空闲的inode和目录项进行设置。 对于得到的文件inode,根据打开选项进行相应处理。成功则通过调用参数返回inode指针。
这个文件用得最多的功能函数莫过于namei();根据文件名返回i节点。
这里任何对inode的操作都是通过iget,iput这类更底层的函数去实现,igetiput所在的层次基于buffer管理和内存inode表的操作之上。

 

2.5 文件系统之文件数据访问操作  这提供读写系统调用接口read,write

主要包括文件:
block_dev.c:
定义了块设备文件的读写函数,block_write,block_read.
file_dev.c :
定义正规文件读写函数。file_read,  file_write
pipe.c   :
定义FIFO文件读写及管道系统调用。read_pipe, write_pipe, sys_pipe

char_dev.c :定义字符型设备访问读写,rw_ttyx, rw_tty, rw_char. 最终都调用tty_read,tty_write
read_write.c:
实现文件系统读写接口 read,write,lseek
read,write
的参数是文件句柄fd,这需要通过系统调用sys_open来获取。函数根据进程taskfilp[fd]指向的系统打开文件表项获取inode、读写权限、当前读写位置等。由inode的类型(上面四种之一)调用相应读写函数(参看图4)。对于正规文件,过程如下:由inode指向的内存inode项获取文件在设备上的位置大小等信息。通过inodebmap计算要读取的文件数据在设备的逻辑块号。通过bread读数据块,然后对缓冲块进行读写。到此就不用管了。缓冲管理的作用真的是太神奇了。

2.6 文件系统高层操作&管理部分

包括文件open.c,exec.c,stat.c,fcntl.c,ioctl.c 实现文件系统中对文件的管理和操作,如创建,打开,设置/读取文件信息,执行一个文件程序。这个层次位于文件底层操作之上,调用底层函数去实现。

open.c: 定义了系统调用sys_ustat,sys_access,sys_chmod,sys_chdir,sys_chroot,sys_opensys_close.参数基本上是文件名或者文件句柄。
可以分为三类:修改inode属性类(3),修改进程根/当前目录,打开关闭文件。    
第一类通过namei找到i结点,修改相关属性域,iput写回设备。
第二类通过namei找到i结点,把进程task数据相应域设为对应inode.
第三类打开时主要调用open_namei返回一个文件名对应的i结点,并设置系统打开文件表和进程打开文件表。返回文件句柄。关闭则清进程打开文件表项,处理对应的系统打开文件表项(引用减1),写回i结点。

execv.c:主要一个函数do_execve.往往在系统执行完fork之后会调用execve簇函数去执行一个全新的程序。必须重新对进程空间进行初始化。
主要流程:找到执行程序inode,进行相应判断(如如权限等),读文件头(1个数据块)信息。
如果是脚本文件,则取shell文件名和参数,以shell为执行程序去执行该脚本文件,这时重新以shell为文件名,以本脚本文件为参数,执行上述过程。
根据文件头信息得到文件各段长度,entry位置,修改进程任务结构中相应的数据。然后拷贝参数到进程空间末端,设置堆栈指针。
清空原进程空间占用的页目录和页表。 修改系统调用返回地址为进程空间的起始地址。
该系统调用返回后,新进程执行第一条语句,会引起一个缺页中断。根据要求的线性地址和executable,在中断中执行do_no_page进行共享或者需求加载。

stat.c : 系统调用sys_statsys_fstat.取文件状态信息。

fcntl.c: 实现sys_dup,sys_dup2,sys_fcntl.
dup
复制到从0开始找最小的空闲句柄。
dup2
指定开始搜索的最小句柄。
fcntl
主要根据flag参数不同。可以实现四方面的操作:复制文件句柄同dup,/close_on_exec标志。设/取文件状态和访问模式。给文件上/解锁。

ioctl.c:主要实现系统调用sys_ioctl,间接调用tty_ioctl.主要对终端设备进行命令控制、参数设置、状态读取等。

 

结束.

看这本书的剖析的linux代码之前觉得LINUX很神秘,现在觉得亲切多了。心里面对内核的动作已经有了比较清晰的概念。但是还远远不足以运用到嵌入式中去,最新的内核与0.11相比,我觉得好像自己还是啥也不知道一样,差得太多,显得很陌生。下一步必须看看2.6版本的内核分析一类的书籍,了解最新的内核。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《Linux 0.11内核完全解析》是一本介绍Linux 0.11内核的书籍,内容详尽地解析了Linux 0.11内核的设计和实现原理。 首先,书籍介绍了Linux 0.11内核的历史背景和发展过程。Linux 0.11内核Linux早期版本中的一种,由于其简洁、高效的设计而受到广泛关注。 接着,书籍着重解析了Linux 0.11内核的架构和核心组件。包括内存管理、进程管理、文件系统等模块,为读者提供了详尽的API和数据结构说明,使读者可以深入理解内核的运作原理。 此外,书籍还详细介绍了Linux 0.11内核的系统调用和设备驱动。系统调用是用户程序与内核之间的接口,书籍列举了Linux 0.11内核支持的各种系统调用,并给出了具体的调用过程和参数传递方式。设备驱动则涉及与硬件设备的交互,书籍对Linux 0.11内核中常见的设备驱动进行了解析和实例讲解。 最后,书籍还介绍了Linux 0.11内核的调试和性能优化技巧。通过学习这些技巧,读者可以更好地理解内核的运行机制,并能够在实际项目中进行问题排查和性能优化。 《Linux 0.11内核完全解析》全面而深入地解析了Linux 0.11内核的各个方面,适合对操作系统和内核开发有兴趣的读者阅读阅读本书后,读者将对Linux 0.11内核有更深入的理解,并能够应用这些知识进行内核开发和调试工作。 ### 回答2: 《Linux 0.11内核完全解析》是一本详细介绍Linux 0.11内核的书籍。Linux 0.11Linux操作系统的早期版本,具有重要的历史和技术价值。这本书对Linux 0.11内核的设计思想、代码结构、系统调用、进程管理、内存管理、文件系统、设备驱动等方面进行了全面深入的讲解和解析。 首先,这本书对Linux 0.11内核的设计思想进行了详细的介绍。它解析了Linux 0.11内核的设计理念,包括分层结构、模块化设计、中断处理、进程管理等方面。通过解析设计思想,读者可以了解到Linux 0.11内核的基本原理和设计哲学。 其次,这本书还深入解析了Linux 0.11内核的代码结构。它逐行解读了内核的关键代码,包括启动代码、中断处理程序、进程管理代码等。通过阅读和理解内核代码,读者可以了解到Linux 0.11内核的运行机制和内部实现。 此外,这本书还对Linux 0.11内核的系统调用、进程管理、内存管理、文件系统、设备驱动等方面进行了详细的解析。它介绍了系统调用的处理过程、进程管理的算法、内存管理的策略、文件系统的结构和操作、设备驱动的注册和使用等。通过全面解析这些关键功能,读者可以深入了解到Linux 0.11内核的核心功能和实现细节。 总之,《Linux 0.11内核完全解析》是一本全面深入解析Linux 0.11内核的书籍。通过阅读这本书,读者可以了解到Linux 0.11内核的设计思想、代码结构、系统调用、进程管理、内存管理、文件系统、设备驱动等方面的内容。这对于理解早期Linux的发展历程、了解操作系统的原理和学习内核开发都具有重要的参考价值。 ### 回答3: 《Linux 0.11内核完全解析》是一本关于分析Linux 0.11内核的书籍。该书主要分析了Linux 0.11内核的结构、原理和实现细节。 首先,书籍详细介绍了Linux 0.11内核的组成结构。它由一个基本的多任务、分时操作系统构成,包括进程管理、内存管理、文件系统、设备驱动等基本模块。书中对这些模块的实现细节进行了解析,帮助读者理解Linux内核的整体结构。 其次,书中深入分析了Linux 0.11内核的原理。它详细讲解了Linux内核的各个部分是如何协作的,包括进程调度、内存管理、文件系统访问等。通过深入理解Linux内核的原理,读者可以更好地理解操作系统的工作原理,并能够在需要时进行相应的调整和优化。 最后,书中还介绍了Linux 0.11内核的实现细节。它深入解析了内核源代码,讲解了内核代码中的关键部分和数据结构。通过对内核代码的深入分析,读者不仅可以了解Linux内核的具体实现方式,还可以学习到一些编程技巧和设计原则。 综上所述,《Linux 0.11内核完全解析》这本书详细介绍了Linux 0.11内核的结构、原理和实现细节,是一本帮助读者深入了解和学习Linux内核的重要参考书籍。通过阅读该书,读者可以提升对操作系统的理解和编程能力,进一步掌握和应用Linux内核的相关知识。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值