资源描述
1. 为什么计算机启动最开始的时候执行的是BIOS代码而不是操作系统自身的代码?答:通常我们用C语言写的用户程序,必须在操作系统的平台上执行,即操作系统为应用程序创建进程并把应用程序的可执行代码加载到内存。计算机启动的时候,操作系统并没有在内存中,我们首先要把操作系统加载到内存,而这个工作最开始的部分,就是由bios 程序来实现的。所以计算机启动最开始执行的是 bios 代码2. 为什么BIOS只加载了一个扇区,后续扇区却是由bootsect代码加载?为什么BIOS没有把所有需要加载的扇区都加载?答:对 BIOS 而言,“约定”在接到启动操作系统的命令后,“定位识别”只从启动扇区把代码加载到 0x7c00 这个位置。后续扇区则由 bootsect 代码加载,这些代码由编写系统的用户负责,与 BIOS 无关。这样构建的好处是站在整个体系的高度,统一设计和统一安排,简单而有效。BIOS 和操作系统的开发都可以遵循这一约定,灵活地进行各自的设计。例如, BIOS可以不用知道内核镜像的大小以及其在软盘的分布等等信息,减轻了 BIOS 程序的复杂度,降低了硬件上的开销。而操作系统的开发者也可以按照自己的意愿,内存的规划,等等都更为灵活。另外,如果要使用BIOS进行加载,而且加载完成之后再执行,则需要很长的时间,因此Linux采用的是边执行边加载的方法。3. 为什么BIOS把bootsect加载到0x07c00,而不是0x00000?加载后又马上挪到0x90000处,是何道理?为什么不一次加载到位?答:因为BIOS首先会把中断向量表加载到0x00000-0x003ff的1KB的内存空间,在加载bootsect时约定加载到0x07c00处,符合内存布局,如下。加载之后挪到0x90000处的原因如下:首先内核会使用启动扇区中的一些数据,如第 508、509 字节处的 ROOT_DEV;其次,依据系统对内存的规划,内核占用 0x0000 开始的空间,因此 0x7c00 可能会被覆盖。因为加载到0x07c00是BIOS约定好的,操作系统只能遵守这个约定。4. bootsect、setup、head程序之间是怎么衔接的?给出代码证据。答:bootsect首先利用int 0x13中断分别加载setup程序及system模块,待bootsect程序的任务完成之后,执行jmpi 0,SETUPSEG由于 bootsect 将 setup 段加载到了 SETUPSEG:0 的地方,在实模式下,该指令跳转到setup段的第一条指令。 setup 执行了之后,内核被移到了0x00000处,系统进入了保护模式,并加载了中断描述符表和全局描述符表lidt idt_48lgdt gdt_48在保护模式下,一个重要的特征就是根据GDT决定后续执行哪里的程序。开启保护模式后,执行jmpi 0, 8根据保护模式的机制,该指令执行后跳转到以GDT第2项中的 base_addr 为基地址,以0为偏移量的地方,其中base_addr为0。由于head放置在内核的头部,因此程序跳转到head中执行5. setup程序里的cli是为了什么?答:cli是关中断指令。因为此时需要由16位实模式向32位保护模式转变,即将进行实模式下的中断向量表和保护模式下中断描述符表的交接工作,在保护模式的中断机制尚未完成时不允许响应中断,以免发生未知的错误。6. setup程序的最后是jmpi 0,8 为什么这个8不能简单的当作阿拉伯数字8看待?答:这里 8 要看成二进制 1000,最后两位00表示内核特权级,第三位0表示 GDT 表,第四位1表示根据GDT中的第2项来确定代码段的段基址和段限长等信息。这样,我们可以得到代码是从段基址 0x00000000、偏移为 0 处开始执行的,即 head 的开始位置。注意到已经开启了保护模式的机制,这里的8是保护模式下的段选择符,而不能当成简单的阿拉伯数字8来看待。7. 打开A20和打开pe究竟是什么关系,保护模式不就是32位的吗?为什么还要打开A20?有必要吗?答:有必要。A20是cpu的第 21 位地址线,A20 未打开的时候,实模式下的最大寻址为 1MB+64KB,而第21根地址线被强制为0,所以相当于 cpu“回滚”到内存地址起始处寻址。打开A20仅仅意味着CPU可以进行32位寻址,且最大寻址空间是4GB,而打开PE是使能保护模式。打开A20是打开PE的必要条件;而打开A20不一定非得打开PE。打开PE是说明系统处于保护模式下,如果不打开A20的话,可以访问的内存只能是奇数1M段,若要真正在保护模式下工作,必须打开A20,实现32位寻址。8. Linux是用C语言写的,为什么没有从main还是开始,而是先运行3个汇编程序,道理何在?答:通常用 C 语言编写的程序都是用户应用程序,这类程序的执行必须在操作系统上执行,也就是说要由操作系统为应用程序创建进程,并把应用程序的可执行代码从硬盘加载到内存。而在计算机刚刚加电时,内存中没有操作系统程序,只有BIOS 程序在运行,需要借助BIOS分别加载bootsect、setup及system模块,然后利用这3个程序来完成内存规划、建立IDT和GDT、设置分页机制等等,并实现从开机时的16位实模式到 main 函数执行需要的32位保护模式之间的转换。当计算机处在32 位的保护模式状态下时,调用main的条件才算准备完毕。9. 为什么不用call,而是用ret“调用”main函数?画出调用路线图,给出代码证据。答:CALL 指令会将 EIP 的值自动压栈,保护返回现场,然后执行被调函数,档执行到被调函数的ret指令时,自动出栈给 EIP 并还原现场,继续执行CALL 的下一行指令。在由head程序向main函数跳转时,是不需要main函数返回的;同时由于main函数已经是最底层的函数了,没有更底层的支撑函数支持其返回。所以要达到既调用 main又不需返回,就不采用 call 而是选择了 ret“调用”了。调用线路图见P42 图1-46。代码如下:(见P36 最下面)setup_paging:ret10. 保护模式的“保护”体现在哪里?答:打开了保护模式后,CPU 的寻址模式发生了变化,需要依赖于 GDT 去获取代码或数据段的基址。从 GDT 可以看出,保护模式除了段基址外,还有段限长,这样相当于增加了一个段位寄存器。既有效地防止了对代码或数据段的覆盖,又防止了代码段自身的访问超限,明显增强了保护作用。同时,保护模式中特权级的引入对于操作系统内核提供了强有力的保护。Intel 从硬件上禁止低特权级代码段使用一些关键性指令,还提供了机会允许操作系统设计者通过一些特权级的设置,禁止用户进程使用 cli、sti 等对掌控局面至关重要的指令。有了这些基础,操作系统可以把内核设计成最高特权级,把用户进程设计成最低特权级。这样,操作系统可以访问 GDT、LDT、TR,而 GDT、LDT 是逻辑地址形成线性地址的关键,因此操作系统可以掌控线性地址。物理地址是由内核将线性地址转换而成的,所以操作系统可以访问任何物理地址,而用户进程只能使用逻辑地址。 11. 特权级的目的和意义是什么?为什么特权级是基于段的?答:特权级是操作系统为了更好地管理内存空间及其访问控制而设的,提高了系统的安全性。保护模式中特权级的引入对于操作系统内核提供了强有力的保护。Intel 从硬件上禁止低特权级代码段使用一些关键性指令,还提供了机会允许操作系统设计者通过一些特权级的设置,禁止用户进程使用 cli、sti 等对掌控局面至关重要的指令。有了这些基础,操作系统可以把内核设计成最高特权级,把用户进程设计成最低特权级。这样,操作系统可以访问 GDT、LDT、TR,而 GDT、LDT 是逻辑地址形成线性地址的关键,因此操作系统可以掌控线性地址。物理地址是由内核将线性地址转换而成的,所以操作系统可以访问任何物理地址,而用户进程只能使用逻辑地址。 在操作系统设计中,一般一个段实现的功能相对完整,可以把代码放在一个段,数据放在一个段,并通过段选择符(包括 CS、SS、DS、ES、FS 和 GS)获取段的基址和特权级等信息。特权级基于段,这样当段选择子具有不匹配的特权级时,按照特权级规则判断是否可以访问。特权级基于段,是结合了程序的特点和硬件实现的一种考虑。12. 在setup程序里曾经设置过一次gdt,为什么在head程序中将其废弃,又重新设置了一个?为什么折腾两次,而不是一次搞好?答:见P33 点评。13. 在head程序执行结束的时候,在idt的前面有184个字节的head程序的剩余代码,剩余了什么?为什么要剩余?答:在idt前面有184个字节的剩余代码,包含了after_page_tables、 ignore_int 和 setup_paging代码段,其中after_page_tables往栈中压入了些参数,ignore_int 用做初始化中断时的中断处理函数,setup_paging 则是初始化分页。剩余的原因:after_page_tables 中压入了一些参数,为内核进入 main 函数的跳转做准备。为了谨慎起见,设计者在栈中压入了 L6,以使得系统可能出错时,返回到 L6 处执行。ignore_int 为中断处理函数,使用 ignore_int 将 idt 全部初始化,因此如果中断开启后,可能使用了未设置的中断向量,那么将默认跳转到 ignore_int 处执行。这样做的好处是使得系统不会跳转到随机的地方执行错误的代码,所以 ignore_int 不能被覆盖。setup_paging 用于分页,在该函数中对 0x0000 和 0x5000 的进行了初始化操作。该代码需要“剩余”用于跳转到 main,即执行”ret ”指令。14. 进程0的task_struct在哪?具体内容是什么?给出代码证据。答:进程0的task_struct是操作系统设计者事先写好的,位于内核数据区,存储在user_stack中。(因为在进程0未激活之前,使用的是boot阶段的user_stack。)static union task_union init_task=INIT_TASK;具体内容如下:包含了进程 0 的进程状态、进程 0 的 LDT、进程 0 的 TSS 等等。其中 ldt 设置了代码段和堆栈段的基址和限长(640KB),而 TSS 则保存了各种寄存器的值,包括各个段选择符。代码如下:INIT_TASK的定义见P68。15. 进程0创建进程1时,为进程1建立了自己的task_struct、内核栈,第一个页表,分别位于物理内存16MB的顶端倒数第一页、第二页。请问,这个了页究竟占用的是谁的线性地址空间,内核、进程0、进程1、还是没有占用任何线性地址空间(直接从物理地址分配)?说明理由并给出代码证据。答:占用的是内核的线性地址空间。(先理解清楚,稍后补充)16. 假设:经过一段时间的运行,操作系统中已经有5个进程在运行,且内核分别为进程4、进程5分别创建了第一个页表,这两个页表在谁的线性地址空间?用图表示这两个页表在线性地址空间和物理地址空间的映射关系。答:在内核的线性地址空间。(图片自己画,参考如下图)17. 进程0开始创建进程1,调用了fork(),跟踪代码时我们发现,fork代码执行了两次,第一次,跳过init()直接执行了for(;) pause(),第二次执行fork代码后,执行了init()。奇怪的是,我们在代码中并没有看见向后的goto语句,也没有看到循环语句,是什么原因导致反复执行?请说明理由,并给出代码证据。答:进程 0 创建进程1采用了中断机制,在中断发生时由硬件将 ss,esp,eflags,cs,eip的值压入了内核栈,其中 eip 的值指向了 int 0x80 的下一条指令。在执行fork时,通过0x80号系统调用,内核执行copy_process函数,为进程1准备其管理结构(task_struct),设置进程1的线性地址空间及物理页面,其中设置了进程1的 TSS 中 eax 的值为 0,状态为TASK_RUNNING,以及利用中断压栈的寄存器值设置进程 1 的 ss,esp,eflags,cs,eip。copy_process:p-pid = last_pid;p-tss.eip = eip;p-tss.eflags = eflags;p-tss.eax = 0;p-tss.esp = esp;p-tss.cs = cs & 0xffff;p-tss.ss = ss & 0xffff;p-state = TASK_RUNNING;return last_pid;函数copy_process的返回值是last_pid,即进程1的pid(pid不为0)。在fork返回到进程0后,进程0判断返回值非 0,因此执行代码for(;) pause();在sys_pause函数中,内核设置了进程0的状态为 TASK_INTERRUPTIBLE,并进行进程调度。由于只有进程1处于就绪态,因此调度执行进程1的指令。由于进程1在TSS中设置了eip等寄存器的值,因此从 int 0x80 的下一条指令开始执行,且设定返回 eax 的值作为 fork 的返回值(值为 0),因此进程1执行了 init 的函数。导致反复执行,主要是利用了两个系统调用 sys_fork 和 sys_pause 对进程状态的设置,以及利用了进程调度机制。18. copy_process函数的参数最后五项是:long eip,long cs,long eflags,long esp,long ss。查看栈结构确实有这五个参数,奇怪的是其他参数的压栈代码都能找得到,确找不到这五个参数的压栈代码,反汇编代码中也查不到,请解释原因。答:在fork()中,当执行“int $0x80”时产生一个软中断,该中断使CPU硬件自动将SS、ESP、EFLAGS、CS、EIP这5个寄存器的数值按照这个顺序压入进程0的内核栈。利用硬件进行压栈,可以确保 eip 的值指向正确的指令,以使在中断返回后,程序能够继续执行。19. 为什么static inline _syscall0(type,name)中需要加上关键字inline?答:inline一般是用于定义内联函数,内联函数结合了函数以及宏的优点,在定义时和函数一样,编译器会对其参数进行检查;在使用时和宏类似,内联函数的代码会被直接嵌入在它被调用的地方,这样省去了函数调用时的一些额外开销,比如保存和恢复函数返回地址等,可以加快速度。20. 根据代码详细说明copy_process函数的所有参数是如何形成的?答:一般在应用程序中,一个函数的参数是由函数定义的,而在操作系统底层中,函数参数可以由函数定义以外的程序通过压栈的方式“做”出来。 copy_process函数的所有参数正是通过压栈形成的。代码见P83页、P85页、P86页。21. 根据代码详细分析,进程0如何根据调度第一次切换到进程1的。答:通过fork(),进程0创建进程1,并将其状态设为TASK_RUNNING,fork()函数执行完毕后返回,进入for(;) pause();在sys_pause()中,将当前进程(进程0)的状态设置为TASK_INTERRUPTBLE,然后执行schedule(),遍历task数组,找到唯一的一个处于TASK_RUNNING的进程(进程1),然后切换到进程1执行,即switch_to(1)。代码见P10622. 内核的线性地址空间是如何分页的?画出从0x000000开始的7个页(包括页目录表、页表所在页)的挂接关系图,就是页目录表的前四个页目录项、第一个个页表的前7个页表项指向什么位置?给出代码证据。答:先把页目录表和4个页表放在物理内存的起始地址,从内存起始位置开始的5页空间内容全部清零(每页4KB)。然后设置页目录表的前4项,使之分别指向4个页表,将第4个页表的最后一个页表项指向寻址范围的最后一个页面,将第4个页表的倒数第二个页表项指向寻址范围的倒数第二个页面,从高地址向低地址方向填写4个页面,依次指向内存从高地址向低地址方向的各个页面。图见P39(注意要画出7个页,参考如下)代码见P39 最下面23. 用文字和图说明中断描述符表是如何初始化的,可以举例说明(比如:set_trap_gate(0,÷_error)),并给出代码证据。答:以set_trap_gate(0,÷_error)为例,其中,n是0,gate_addr是&idt0,也就是idt的第一项中断描述符的地址;type是15,dpl(描述符特权级)是0;addr是中断服务程序divide_error(void)的入口地址。见P54 图2-9 P53 代码24. 进程0 fork进程1之前,为什么先要调用move_to_user_mode()?用的是什么方法?解释其中的道理。答:因为在Linux-0.11中,除进程0之外,所有进程都是由一个已有进程在用户态下完成创建的。但是此时进程0还处于内核态,因此要调用move_to_user_mode()函数,模仿中断返回的方式,实现进程0的特权级从内核态转化为用户态。又因为在Linux-0.11中,转换特权级时采用中断和中断返回的方式,调用系统中断实现从3到0的特权级转换,中断返回时转换为3特权级。因此,进程0从0特权级到3特权级转换时采用的是模仿中断返回。 设计者首先手工写压栈代码模拟int(中断)压栈,当执行iret指令时,CPU自动将这5个寄存器的值(SS,ESP,EFLAGS,CS,EIP)按序恢复给CPU,CPU就会翻转到3特权级去执行代码。25. 进程0创建进程1时调用copy_process函数,在其中直接、间接调用了两次get_free_page函数,在物理内存中获得了两个页,分别用作什么?是怎么设置的?给出代码证据。答:第一次调用get_free_page函数申请的空闲页面用于进程1 的task_struct及内核栈。首先将申请到的页面清0,然后复制进程0的task_struct,再针对进程1作个性化设置,其中esp0 的设置,意味着设置该页末尾为进程 1 的堆栈的起始地址。代码见P90 及 P92。 第二次调用get_free_page函数申请的空闲页面用于进程1的页表。在创建进程1执行copy_process中,执行copy_mem(nr,p)时,内核为进程1拷贝了进程 0的页表(160 项),同时修改了页表项的属性为只读。代码见P98。26. 在IA-32中,有大约20多个指令是只能在0特权级下使用,其他的指令,比如cli,并没有这个约定。奇怪的是,在Linux0.11中,在3特权级的进程代码并不能使用cli指令,会报特权级错误,这是为什么?请解释并给出代码证据。答: cli指令用于复位IF标志位,其执行与CPL(当前特权级)和EFLAGSIOPL标志位有关。只有当CPL小于或等于IOPL时才可以执行该指令。如果在CPL大于IOPL的情况下执行,将会产生一个一般性保护异常,如下:set_trap_gate(13, &general_protection);由于在内核IOPL的初始值为0,且未经改变。进程0在 move_to_user_mode 中,继承了内核的 eflags,如下:move_to_user_mode()pushflnt iretn 是否正确?在TSS中明确设置了eflags的iopl为0在进程0的TSS中,设置了eflags中的 IOPL 位为 0,代码见P68,后续进程 如果没有改动的话也是0,即IOPL=0。因此,通过设置 IOPL,可以限制3特权级的进程代码使用 cli 指令。27. 根据代码详细分析操作系统是如何获得一个空闲页的。答:代码见P90 get_free_page函数。过程:(1)将EAX 设置为0,EDI 设置指向mem_map 的最后一项(mem_map+PAGING_PAGES-1),std设置扫描是从高地址向低地址。从mem_map的最后一项反向扫描,找出引用次数为0(AL)的页,如果没有则退出;如果找到,则将找到的页设引用数为1;(2) ECX左移12位得到页的相对地址,加LOW_MEM得到物理地址,将此页最后一个字节的地址赋值给EDI(LOW_MEM+4092);(3) stosl将EAX的值设置到ES:EDI所指内存,即反向清零1024*32bit,将此页清空;(4) 将页的地址(存放在EAX)返回。28. 用户进程自己设计一套LDT表,并与GDT挂接,是否可行,为什么?答:不可行。GDT和LDT放在内核数据区,属于0特权级,3特权级的用户进程无权访问修改。此外,如果用户进程可以自己设计LDT的话,表明用户进程可以访问其他进程的LDT,则会削弱进程之间的保护边界,容易引发问题。29. 保护模式下,线性地址到物理地址的转化过程是什么?答:保护模式下,线性地址到物理地址的转化是借助页目录表及页表完成的。其转化过程如图所示(见P97 图3-9)。Linux 0.11中仅有一个页目录表,其地址存放在CR3寄存器中,通过线性地址中的“页目录项”数据及CR3寄存器就可以找到页目录表中对应的页目录项,通过该页目录项可以找到对应的页表,结合线性地址中的“页表项”数据就可以找到对应的页表项,通过该页表项可以找到对应的物理页面,最后通过线性地址中的“页内偏移”落实到实际的物理地址值。30. 为什么get_free_page()将新分配的页面清0?答:Linux在回收页面时并没有将页面清0,只是将mem_map中与该页对应的位置0。在使用get_free_page申请页时,也是遍历mem_map寻找对应位为0的页,但是该页可能存在垃圾数据,如果不清0的话,若将该页用做页表,则可能导致错误的映射,引发错误,所以要将新分配的页面清0。31. 内核和普通用户进程并不在一个线性地址空间内,为什么仍然能够访问普通用户进程的页面?答:虽然内核与普通进程并不在一个线性地址空间内,但是用户进程的页面最终要从物理内存上分配,而内核的分页机制即页目录表、页表等,正好管理着16M物理内存,所以内核可以访问普通用户进程的页面。32. 详细分析一个进程从创建、加载程序、执行、退出的全过程。答:可以参考课本P273页,其中的核心部分课上都进行了介绍,包括fork()、copy_process()、do_execve()及do_exit等。参考:首先,shell调用fork开始创建进程,产生int 0x80软中断,最终映射到sys_fork(),调用find_empty_process(),为str1申请可用的pid和task64空闲位置,接着调用copy_process()为str1申请用来承载进程task_struct和内核栈的一个页面,shell把自己的task_struct复制给str1进程,然后修改str1的task_struct的部分数据,包括时间片,TSS字段等。接着调用copy_mem()为进程分段(确定段基址和段限长等),然后调用copy_page_tables为str1进程另起一套页目录项和页表项,并指向shell的页面。还要解决文件继承的问题,然后将str1进程TSS和LDT挂接在GDT的指定位置,完成这些后,将str1设为就绪态。接下来加载用户程序:首先要做一些检查工作,如可执行文件的数据长度和代码长度等;然后调用free_page_tables解除与shell的页面共享关系,接着根据程序的长度重新设置LDT,调整str1的task_struct,最后调整EIP和ESP。用户程序被调度执行,产生缺页中断,调用do_no_page为str1申请一个内存页面,并把它登记在mem_map中,将str1程序从硬盘加载到新分配的页面中,把它的物理地址映射到进程的线性地址空间内。执行加载到的程序,产生压栈动作,若栈空间不够,则产生缺页中断继续申请页面。最后,用户进程调用exit退出,释放程序所占页面,解除与文件有关的内容,并调用tell_father给父进程发信号,退出后执行调度,shell进程收到用户进程发送的信号设置为就绪态,待其执行时,释放掉用户进程task_struct所占用的页面,解除与task64的关系,这时用户进程彻底退出。33. 详细分析多个进程(无父子关系)共享一个可执行程序的完整过程。答:依次创建3个用户进程,每个进程都有自己的task。假设进程1先执行,需要压栈产生缺页中断,内核为其申请空闲物理页面,并映射到进程1的线性地址空间。这时产生时钟中断,轮到进程2执行,进程2也执行同样逻辑的程序。之后,又轮到进程3执行,也是压栈,并设置text。可见,三个进程虽程序相同,但数据独立,用TSS和LDT实现对进程的保护。34. 缺页中断是如何产生的,页写保护中断是如何产生的,操作系统是如何处理的?答:缺页中断:每个页目录项和页表项都有个标志位P,如果和一个页面建立了映射关系,P位置1,否则置0。MMU在解析线性地址时,若发现某个表项的P位为零,说明没有对应页面,就会产生缺页中断。操作系统会调用_do_no_page为进程申请空闲页面,将程序加载到新分配的页面中,并建立页目录表-页表-页面的三级映射管理关系。页写保护异常:假设两个进程共享一个页面,该页面处于写保护状态即只读,此时若某一进程执行写操作,就会产生“页写保护”异常。操作系统会调用_do_wp_page,为该进程申请空闲页面,将该进程的页表指向新申请的页面,然后将原页表的数据复制到新页面中,同时将原页面的引用计数减1。该进程得到自己的页面,就可以执行写操作。35. 为什么要设计缓冲区,有什么好处?答:缓冲区的作用主要体现在两方面:(1) 形成所有块设备数据的统一集散地,操作系统的设计更方便,更灵活;(2) 数据块复用,提高对块设备文件操作的运行效率。在计算机中,内存间的数据交换速度是内存与硬盘数据交换速度的2个量级,如果某个进程将硬盘数据读到缓冲区之后,其他进程刚好也需要读取这些数据,那么就可以直接从缓冲区中读取,比直接从硬盘读取快很多。如果缓冲区的数据能够被更多进程共享的话,计算机的整体效率就会大大提高。同样,写操作类似。36. 操作系统如何利用buffer_head中的 b_data,b_blocknr,b_dev,b_uptodate,b_dirt,b_count,b_lock,b_wait管理缓冲块的?答:b_data指向缓冲块,用于找到缓冲块的位置。进程与缓冲区及缓冲区与硬盘之间都是以缓冲块为单位进行数据交互的,而b_blocknr,b_dev唯一标识一个块,用于保证数据交换的正确性。另外缓冲区中的数据被越多进程共享,效率就越高,因此要让缓冲区中的数据块停留的时间尽可能久,而这正是由b_blocknr,b_dev决定的,内核在hash表中搜索缓冲块时,只看设备号与块号,只要缓冲块与硬盘数据的绑定关系还在,就认定数据块仍停留在缓冲块中,就可以直接用。b_uptodate与b_dirt,是为了解决缓冲块与数据块的数据正确性问题而存在的。b_uptodate针对进程方向,如果b_uptodate为1,说明缓冲块的数据已经是数据块中最新的,可以支持进程共享缓冲块中的数据;如果b_uptodate为0,提醒内核缓冲块并没有用绑定的数据块中的数据更新,不支持进程共享该缓冲块。b_dirt是针对硬盘方向的,b_dirt为1说明缓冲块的内容被进程方向的数据改写了,最终需要同步到硬盘上;b_dirt为0则说明不需要同步b_count记录每个缓冲块有多少进程共享。b_dirt大于0表明有进程在共享该缓冲块,当进程不需要共享缓冲块时,内核会解除该进程与缓冲块的关系,并将b_count数值减1,为0表明可以被当作新缓冲块来申请使用。b_lock为1说明缓冲块正与硬盘交互,内核会拦截进程对该缓冲块的操作,以免发生错误,交互完成后,置0表明进程可以操作该缓冲块。b_wait记录等待缓冲块的解锁而被挂起的进程,指向等待队列前面进程的task_struct。
展开阅读全文