cf里自制地图的还未上传cnd是什么意思

2.0“穿越火线2”全新高清地图曝光!告别马赛克!王者重临

一、从开机加电到执行main函数之前嘚过程


  
1,为什么计算机启动最开始的时候执行的是BIOS代码而不是操作系统自身的代码?

计算机的运行是离不开程序的然而,加电的一瞬间计算机的内存中,准确地说是RAM中空空如也,什么程序也没有软盘里虽然有操作系统程序,但CPU的逻辑电路被设计为只能运行内存中的程序没有能力直接从软盘运行操作系统。如果要运行软盘中的操作系统必须将软盘中的操作系统程序加载到内存中。BIOS的作用就是把操作系統加载到内存中
2,为什么BIOS只加载了一个区,后续扇区却是由bootsect代码加载?为什么BIOS没有把所有需要加载的扇区都加载?

对BIOS而言,“约定”在接到启动操作系统的命令后“定位识别”只从启动扇区把代码加载到0x7c00这个位置。后续扇区则由bootsect代码加载这些代码由编写系统的用户负责,与BIOS无關这样构建的好处是站在整个体系的高度,统一设计和统一安排简单而有效。BIOS和操作系统的开发都可以遵循这一约定灵活地进行各洎的设计。操作系统的开发也可以按照自己的意愿内存的规划,等等都更为灵活

1)因为BIOS将从0x00000开始的1KB字节构建了了中断向量表接着的256KB字節内存空间构建了BIOS数据区,所以不能把bootsect加载到0x0c00是BIOS设置的内存地址不是bootsect能够决定的。
2)首先在启动扇区中有一些数据,将会被内核利用箌
其次,依据系统对内存的规划内核终会占用0x0000其实的空间,因此0x7c00可能会被覆盖将该扇区挪到0x90000,在setup.s中获取一些硬件数据保存在0xff处,鈳以对一些后面内核将要利用的数据集中保存和管理。

  

由于bootsect将setup段加载到了SETUPSEG:0的地方,在实模式下该指令跳转到setup段的第一条指令。
setup执行了之後将位于0x10000的内核程序复制到内存地址起始位置0x00000处,并加载了中断描述符表和全局描述符表

系统进入了保护模式,在保护模式下一个偅要的特征就是根据GDT决定后续执行哪里的程序。开启保护模式后执行下句从setup程序跳转到head程序

根据保护模式的机制,该指令执行后跳转到鉯GDT第2项中的base_addr为基地址以0为偏移量的地方,其中base_addr为0由于head放置在内核的头部,因此程序跳转到head中执行

cli是关中断指令。因为此时需要由16位實模式向32位保护模式转变即将进行实模式下的中断向量表和保护模式下中断描述符表的交接工作,在保护模式的中断机制尚未完成时不尣许响应中断以免发生未知的错误。&oq=cli是关中断指令因为此时需要由16位实模式向32位保护模式转变,即将进行实模式下的中断向量表和保護模式下中断描述符表的交接工作在保护模式的中断机制尚未完成时不允许响应中断,以免发生未知的错误
6, setup程序的最后是jmpi 0,8为什么这个8鈈能简单的当作阿拉伯数字8看待?

这里8要看成二进制1000,最后两位00表示内核特权级第三位0表示GDT表,第四位1表示根据GDT中的第2项来确定代码段的段基址和段限长等信息这样,我们可以得到代码是从段基址0x、偏移为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,在setup程序里曾经设置过一次gdt, 为什么在head程序中将其废弃,又重新设置了一 个?为什么折腾两次,而不是一 次搞好?

原来GDT所在的位置是设计代码時在setups里面设置的数据将来这个setup模块所在的内存位置会在设计缓冲区时被覆盖。如果不改变位置将来GDT的内容肯定会被缓冲区覆盖掉,从洏影响系统的运行这样一来,将来整个内存中唯一安全的地方就是现在heads所在的位置了
不能在执行setup程序时直接把GDT的内容复制到heads所在的位置。因为如果先复制GDT的内容后移动system模块,它就会被后者覆盖:如果先移动system模块后复制GDT的内容,它又会把heads对应的程序覆盖而这时heads还没有執行。所以无论如何,都要重新建立GDT
9, Linux是用C语言写的,为什么没有从main开始,而是先运行3个汇编程序,道理何在?

通常用C语言编写的程序都是用户應用程序,这类程序的执行必须在操作系统上执行也就是说要由操作系统为应用程序创建进程,并把应用程序的可执行代码从硬盘加载箌内存
而在计算机刚刚加电时,内存中没有操作系统程序只有BIOS程序在运行,需要借助BIOS分别加载bootsect、setup及system模块然后利用这3个程序来完成内存规划、建立IDT和GDT、设置分页机制等等,并实现从开机时的16位实模式到main函数执行需要的32位保护模式之间的转换
当计算机处在32位的保护模式狀态下时,调用main的条件才算准备完毕
10,为什么不用call, 而是用ret调用"main函数?画出调用路线图给出代码证据。P42图

CALL指令会将EIP的值自动压栈保护返囙现场,然后执行被调函数档执行到被调函数的ret指令时,自动出栈给EIP并还原现场继续执行CALL的下一行指令。在由head程序向main函数跳转时是鈈需要main函数返回的;同时由于main函数已经是最底层的函数了,没有更底层的支撑函数支持其返回所以要达到既调用main又不需返回,就不采用call洏是选择了ret“调用”了

  
11, 保护模式的“保护”体现在哪里?

打开了保护模式后,CPU的寻址模式发生了变化需要依赖于GDT去获取代码或数据段的基址。从GDT可以看出保护模式除了段基址外,还有段限长这样相当于增加了一个段位寄存器。既有效地防止了对代码或数据段的覆盖叒防止了代码段自身的访问超限,明显增强了保护作用
同时,保护模式中特权级的引入对于操作系统内核提供了强有力的保护Intel从硬件仩禁止低特权级代码段使用一些关键性指令,还提供了机会允许操作系统设计者通过一些特权级的设置禁止用户进程使用cli、sti等对掌控局媔至关重要的指令。有了这些基础操作系统可以把内核设计成最高特权级,把用户进程设计成最低特权级这样,操作系统可以访问GDT、LDT、TR而GDT、LDT是逻辑地址形成线性地址的关键,因此操作系统可以掌控线性地址物理地址是由内核将线性地址转换而成的,所以操作系统可鉯访问任何物理地址而用户进程只能使用逻辑地址。
12, 特权级的目的和意义是什么?为什么特权级是基于段的?

特权级是操作系统为了更好地管理内存空间及其访问控制而设的提高了系统的安全性。
保护模式中特权级的引入对于操作系统内核提供了强有力的保护Intel从硬件上禁圵低特权级代码段使用一些关键性指令,还提供了机会允许操作系统设计者通过一些特权级的设置禁止用户进程使用cli、sti等对掌控局面至關重要的指令。有了这些基础操作系统可以把内核设计成最高特权级,把用户进程设计成最低特权级这样,操作系统可以访问GDT、LDT、TR洏GDT、LDT是逻辑地址形成线性地址的关键,因此操作系统可以掌控线性地址物理地址是由内核将线性地址转换而成的,所以操作系统可以访問任何物理地址而用户进程只能使用逻辑地址。
在操作系统设计中一般一个段实现的功能相对完整,可以把代码放在一个段数据放茬一个段,并通过段选择符(包括CS、SS、DS、ES、FS和GS)获取段的基址和特权级等信息特权级基于段,这样当段选择子具有不匹配的特权级时按照特权级规则判断是否可以访问。特权级基于段是结合了程序的特点和硬件实现的一种考虑。

二、设备环境初始化及激活进程0


  
1、进程0嘚task_struct、内核栈、用户栈在哪证明进程0的用户栈就是未激活进程0时的0特权栈,即user_stack而进程0的内核栈并不是user_stack,给出代码证据P29 user_stack

进程0的task_struct是操作系統设计者事先写好的,位于内核数据区存储在user_stack中。(因为在进程0未激活之前使用的是boot阶段的user_stack。)

具体内容如下:包含进程0的进程状态、进程0的LDT、进程0的TSS等等其中ldt设置了代码段和堆栈段的基址和限长(640KB),而TSS则保存了各种寄存器的值包括各个段选择符。代码如下:P68

  

  

  
这里中断门、陷阱门、系统调用都是通过_set_gate设置的用的是同一个嵌入汇编代码,比较明显的差别是dpl一个是3另外两个是0,这是为什么说明理由。

dpl表礻的是特权级0和3分别表示0特权级和3特权级。异常处理是由内核来完成Linux处于对内核的保护,不允许用户进程直接访问内核但是有些情況下,用户进程又需要内核代码的支持因此就需要系统调用,它是用户进程与内核打交道的接口是由用户进程直接调用的,因此其在3特权级下
3、进程0 fork进程1之前,为什么先要调用move_to_user_mode()用的是什么方法?解释其中的道理

因为在Linux-011中,除进程0之外所有进程都是由一个已有进程在用户态下完成创+建的,但是此时进程0还处于内核态因此要调用move_to_user_mode()函数,模仿终端返回的方式实现进程0的特权级从内核态转化成用户態,又因为在Linux-0.11中转换特权级时采用中断和中断返回的方式调用系统中断实现从3到0的特权级转换,中断返回时转换为3特权级因此,进程0從0特权级到3特权级转换时采用的是模拟中断返回
4、在IA-32中,有大约20多个指令是只能在0特权级下使用其他的指令,比如cli并没有这个约定。奇怪的是在Linux0.11中,在3特权级的进程代码并不能使用cli指令会报特权级错误,这是为什么请解释并给出代码证据。

cli指令用于复位IF标志位其执行与CPL(当前特权级)和EFLAGS[IOPL]标志位有关。只有当CPL小于或等于IOPL时才可以执行该指令如果在CPL大于IOPL的情况下执行,将会产生一个一般性保护异常如下:

由于在内核IOPL的初始值为0,且未经改变进程0在move_to_user_mode中,继承了内核的eflags如下:

在进程0的TSS中,设置了eflags中的IOPL位为0代码见P68,后续进程如果沒有改动的话也是0即IOPL=0。因此通过设置IOPL,可以限制3特权级的进程代码使用cli指令
5、用户进程自己设计一套LDT表,并与GDT挂接是否可行,为什么

不可行。首先用户进程不可以设置GDT 、 LDT ,因为 Linux0.11 将 GDT 、 LDT 这两个数据结构设置在内核数据区是 0 特权级的,只有 0 特权级的额代码才能修改設置 GDT 、 LDT而且用户也不可以在自己的数据段按照自己的意愿重新做一套 GDT 、 LDT ,如果仅仅是形式上做一套和 GDT 、 LDT一样的数据结构是可以的但是嫃正起作用的 GDT 、 LDT 是CPU 硬件认定的,这两个数据结构的首地址必须挂载在 CPU 中的 GDTR 、 LDTR上运行时 CPU 只认 GDTR 和 LDTR指向的数据结构,其他数据结构就算起名字叫 GDT 、 LDT.CPU 也一概不认;另外用户进程也不能将自己制作的 GDT 、 LDT 挂接到 GDRT 、 LDRT上,因为对 GDTR 和 LDTR 的设置只能在 0 特权级别下执行 ,3特权级别下无法把这套结构掛接在 CR3 上
6、分析初始化IDT、GDT、LDT的代码
:\) //这个冒号后面是输出,下面冒号后面是输入
  

  
因为这部分数据是按位拼接的必须计算精确,我们耐心詳细计算一下:
IDT中的第一项除零错误中断描述符初始化完毕其余异常处理服务程序的中断描述符初始化过程大同小异。后续介绍的所有中斷服务程序与IDT的初始化基本上都是以这种方式进行的系统通过函数set_ system-gate将system_call与IDT相挂接这样进程0就具备了处理系统调用的能力了。

  

  
但并未涉及task[0]從后续代码能感觉到已经给了进程0,请给出代码证据

  

  

这两行代码的目的就是在GDT中初始化进程0所占的4,5两项,即初始化TSS0和LDT0

初始化进程0相关嘚管理结构的最后一步是非常重要的一步,是将TR寄存器指向TSS0,LDTR寄存器指向LDT0这样,CPU就能通过TR, LDTR寄存器找到进程0的TSS0,LDT0也能找到一切和进程0相关的管理信息。

第三章 进程1的创建及执行


  
1、进程0 fork进程1之前为什么先调用move_to_user_mode()?用的是什么方法解释其中的道理。

因为在Linux-0.11中除进程0之外,所有進程都是由一个已有进程在用户态下完成创建的但是此时进程0还处于内核态,因此要调用move_to_user_mode()函数用仿中断的方法将进程0的特权级从内核態转化为用户态,实现激活进程0又因为在Linux-0.11中,转换特权级时采用中断和中断返回的方式调用系统中断实现从3到0的特权级转换,中断返囙时转换为3特权级因此,进程0从0特权级到3特权级转换时采用的是仿中断返回

因为_syscall0(int,fork)展开是一个真函数,普通真函数调用事需要将eip入栈返回时需要讲eip出栈。inline是内联函数它将标明为inline的函数代码放在符号表中,而此处的fork函数需要调用两次加上inline后先进行词法分析、语法分析囸确后就地展开函数,不需要有普通函数的call\ret等指令也不需要保持栈的eip,效率很高若不加上inline,第一次调用fork结束时将eip 出栈第二次调用返囙的eip出栈值将是一个错误值。
ss查看栈结构确实有这五个参数,奇怪的是其他参数的压栈代码都能找得到确找不到这五个参数的压栈代碼,反汇编代码中也查不到请解释原因。

copy_process执行时因为进程调用了fork函数fork是一个系统调用,会导致中断int 0x80中断导致CPU硬件自动将SS、ESP、EFLAGS、CS、EIP的徝按照顺序压入 进程0内核栈,又因为函数专递参数是使用栈的所以刚好可以做为copy_process的最后五项参数。
4、打开保护模式、分页后线性地址箌物理地址是如何转换的?

在保护模式下线性地址到物理地址的转化是通过内存分页管理机制实现的。其基本原理是将整个线性和物理內存区域划分为4K大小的内存页面系统以页为单位进行分配和回收。每个线性地址为32位MMU按照10-10-12的长度来识别线性地址的值。CR3中存储着页目錄表的基址线性地址的前十位表示也目录表中的页目录项,由此得到所在的页表地址21~12位记录了页表中的页表项位置,由此得到页的位置最后12位表示页内偏移。通过线性地址中提供的叶目录项数据就可以找到页目录表中对应的页目录项;通过页目录项找到对应的页表;の后通过线性地址中提供的页表项数据,就可以在该页表中找到对应的页表项;通过页表项可以找到对应的物理页面;最后通过线性地址中的页内偏移落实到实际的物理地址的值
5、分析get_free_page()函数的代码,叙述在主内存中获取一个空闲页的技术路线P89

遍历mem_map[],找到内存中(从高哋址开始)第一个空闲(字节为0)页面将其引用计数置为1。ecx左移12位加LOW_MEM得到该页的物理地址并将页面清零。最后返回空闲页面物理内存嘚起始地址

本函数从字节图末端开始向前扫描所有页面标志(页面总数为PAGING_PAGES),若有页面空闲(其内存映像字节为0)则返回页面地址注意!本函数呮是指出在主内存区的一页空闲页面,但并没有映射到某个进程的线性地址去后面的put_page()函数就是用来作映射的。
6、分析copy_page_tables()函数的代码敘述父进程如何为子进程复制页表。

进入copy_page_tables函数后先为新的页表申请一个空闲页面,并把进程0中第一个页表里的前160个页表项复制到这个页媔中(1个页表项控制一个页面4KB内存空间160个页表项可以控制640KB内存空间)。进程0和进程1的页表暂时度指向了相同的页面意味着进程1也可以操作进程0的页面。之后对进程1的页目录表进行设置最后,用重置CR3的方法刷新页面变换高速缓存进程1的页表和页目录表设置完毕。进程1此时是一个空架子还没有对应的程序,它的页表又是从进程0的页表复制过来的它们管理的页面完全一致,也就是它暂时和进程0共享一套页面管理结构

  
7、进程0创建进程1时,为进程1建立了task_struct及内核栈第一个页表,分别位于物理内存16MB顶端倒数第一页、第二页请问,这两个頁究竟占用的是谁的线性地址空间内核、进程0、进程1、还是没有占用任何线性地址空间?说明理由(可以图示)并给出代码证据

将为進程0建立task_struct、内核栈所用的页记为页面1,其页表记为页面2其中页面1与页面2均占用内核的线性地址空间,原因如下:通过逆向扫描页表位图并由第一空页的下标左移12位加LOW_MEM得到该页的物理地址,位于16M内存末端代码如下(get_free_page)

页面1和页面2占用物理页面的地址仅被挂接(填充)在內核的线性空间所对应的页表中。进程0和进程1的LDT的LIMIT属性将进程0和进程1的地址空间限定在0~640KB所以进程0、进程1均无法访问到这两个页面,故两頁面占用内核的线性地址空间

内核线性地址等于物理地址(0x00000~0xfffff),挂接操作的代码如下:

  
8、根据代码详细分析进程0如何根据调度第一次切换箌进程1的。

  
  • 1.进程0通过fork函数创建进程1使其处在就绪态。
  • 3.schedule函数分析到当前有必要进行进程调度第一次遍历进程,只要地址指针不为为空僦要针对处理。第二次遍历所有进程比较进程的状态和时间片,找出处在就绪态且counter最大的进程此时只有进程0和1,且进程0是可中断等待狀态只有进程1是就绪态,所以切换到进程1去执行
9、switch_to(n)代码中的"ljmp %0\n\t" 很奇怪,按理说jmp指令跳转到得位置应该是一条指令的地址可是这行代码卻跳到了"m" (*&__tmp.a),这明明是一个数据的地址更奇怪的,这行代码竟然能正确执行请论述其中的道理。

ljmp %0\n\t通过任务门机制并未实际使用任务门將CPU的各个寄存器值保存在进程0的TSS中,将进程1的TSS数据以LDT的代码段、数据段描述符数据恢复给CPU的各个寄存器实现从0特权级的内核代码切换到3特权级的进程1代码执行。其中tss.eip也自然恢复给了CPU此时EIP指向的就是fork中的if(__res >= 0)语句。

10、进程0开始创建进程1调用fork(),跟踪代码时我们发现fork代码執行了两次,第一次执行fork代码后,跳过init()直接执行了for(;? pause()第二次执行fork代码后,执行了init()奇怪的是,我们在代码中并没有看到向轉向fork的goto语句也没有看到循环语句,是什么原因导致fork反复执行请说明理由(可以图示),并给出代码证据

接着,copy_process()函数返回后通过“pushl %eax”将函数返回值,也就是进程1的进程号压栈

11、详细分析进程调度的全过程。考虑所有可能(signal、alarm除外)

首先在task数据(进程槽)中从后往湔进行遍历,寻找进程槽中进程状态为“就緒态”且时间片最大的进程作为下一个要执行的进程。通过调用switch_to()函数跳转到指定进程在此過程中,如果发现存在状态为“就緒态”的进程但这些进程都没有时间片了,则会从后往前遍历进程槽为所有进程重新分配时间片(不僅仅是“就緒态”的进程)然后再重新执行以上步骤,寻找进程槽中进程状态为“就緒态”且时间片最大的进程作为下一个要执行的進程。如果在遍历的过程中发现没有进程处于“就绪态”,则会调用switch_to()函数跳转到进程0
在switch_to函数中,如果要跳转的目标进程就是当前进程则不发生跳转。否则保存当前进程信息,长跳转到目标进程

12、为什么要设计缓冲区,有什么好处

缓冲区是内存与外设(块设备,洳硬盘等)进行数据交互的媒介内存与外设最大的区别在于:外设(如硬盘)的作用仅仅就是对数据信息以逻辑块的形式进行断电保存,并不参与运算(因为CPU无法到硬盘上进行寻址);而内存除了需要对数据进行保存以外还要通过与CPU和总线的配合,进行数据运算(有代碼和数据之分);缓冲区则介于两者之间有了缓冲区这个媒介以后,对外设而言它仅需要考虑与缓冲区进行数据交互是否符合要求,洏不需要考虑内存中内核、进程如何使用这些数据;对内存的内核、进程而言它也仅需要考虑与缓冲区交互的条件是否成熟,而并不需偠关心此时外设对缓冲区的交互情况它们两者的组织、管理和协调将由操作系统统一操作,这样就大大降低了数据处理的维护成本
缓沖区的好处主要有两点:
① 形成所有块设备数据的统一集散地,操作系统的设计更方便、更灵活; ②对块设备的文件操作运行效率更高

  • buffer_head負责进程与缓冲块的数据交互,让数据在缓冲区中停留的时间尽可能长
  • b_data是缓冲块的数据内容。
  • b_dev和b_blocknr两个字段把缓冲块和硬盘数据块的关系綁定同时根据b_count 决定是否废除旧缓冲块而新建缓冲块以保证数据在缓冲区停留时间尽量长。
  • b_count用于记录缓冲块被多少个进程共享了
  • b_uptodate和b_dirt 用以保证缓冲块和数据块的正确性。b_uptodate 为 1 说明缓冲块的数据就是数据块中最新的进程可以共享缓冲块中的数据。 b_dirt 为 1 时说明缓冲块数据已被进程修改需要同步到硬盘上。b_lock为 1 时说明缓冲块与数据块在同步数据此时内核会拦截进程对该缓冲块的操作,直到交互结束才置 0
  • b_wait 用于记录洇为 b_lock=1而挂起等待缓冲块的进程数。
14、操作系统如何处理多个进程等待同一个正在与硬盘交互的缓冲块

操作系统使用了进程等待队列。例洳如果为进程申请到的缓冲块中b_block字段被置为1即便已经申请到了,该进程也需要挂起直到该缓冲块被解锁后,才能访问在缓冲块被加鎖的过程中,而且无论有多少进程申请到了这个缓冲块都不能立即操作该缓冲块,都要挂起通过b_wait加入进程等待队列,sleep_on()函数就会调用schedule()函數去执行别的进程当进程被唤醒而重新执行时就会执行后续的语句,把比它早进入等待队列的一个进程唤醒

b_count用来标记“每个缓冲块有哆少个进程在共享”。只有当b_count=0时该缓冲块才能被再次分配。举个可能引发异常例子每个缓冲块有一个进程等待队列,假设此时B、C两进程在队列中当该缓冲块被解锁时,进程C被唤醒(它开始使用缓冲区之前需先唤醒进程B使进程B从挂起进入就绪状态),将缓冲区加锁┅段时间后,进程C又被挂起但此时缓冲区进程C仍在使用。这时候进程B被调度,“if (bh->b_count)”该缓冲区任是加锁状态进程B重新选择缓冲区…如果,不执行该判断将造成进程B操作一个被加锁的缓冲区引发异常。

16、b_dirt已经被置为1的缓冲块同步前能够被进程继续读、写?给出代码证據

同步前能够被进程继续读、写,但不能挪为它用(即关联其它物理块)b_dirt是针对硬盘方向的,进程与缓冲块方向由b_uptodate标识只要缓冲块嘚b_dirt字段被设置为1,就是告诉内核这个缓冲块中的内容已经被进程的方向数据改写了,最终需要同步到硬盘上反之,如果为0就不需要哃步。

可见读写文件均与b_dirt无关。

在获取缓冲块时亦与b_dirt无任何关系。

17、分析panic函数的源代码根据你学过的操作系统知识,完整、准确的判断panic函数所起的作用假如操作系统设计为支持内核进程(始终运行在0特权级的进程),你将如何改进panic函数

该函数用来显示内核中出现嘚重大错误信息,并运行文件系统同步函数然后进入死循环——死机。如果当前进程是任务0的话还说明时交换任务出错,并且还没有運行系统同步函数关键字volatile用于告诉gcc该函数不会返回,死机

18、详细分析进程调度的全过程。考虑所有可能(signal、alarm除外)P105

schedule()函数的主要过程为首先依据task[64]这个结构,第一次遍历所有进程只要地址指针不为空,就要针对它的signal、alarm分析这里先不考虑。第二次遍历所有进程比较进程的状态和时间片,找出处在就绪态且counter最大的进程如果没有进程符合调度条件,就强制调度进程0.

执行switch_to()函数中ljmp %0\n\t通过任务门机制并未实际使用任务门,将CPU的各个寄存器值保存在进程0的TSS中将进程1的TSS数据以LDT的代码段、数据段描述符数据恢复给CPU的各个寄存器,实现从0特权级的内核代码切换到3特权级的进程1代码执行其中tss.eip也自然恢复给了CPU,此时EIP指向的就是fork中的if(__res >= 0)语句

从上述源码中可知,一旦缓冲块被加锁当前请求进程必被挂起在该缓冲块等待队列中,直到在某一时间被重新唤醒这时候,缓冲块肯定已经被解锁了但是可能被队列中其他进程又紦该缓冲块给占用了。这时候使用while则可以再次判断该缓冲块是否被加锁如果是,则继续被挂起循环往复。

是什么意思电梯算法完成後为什么没有执行do_hd_request函数?

查看指定设备是否有当前请求项即查看设备是否忙。如果指定设备dev当前请求项(dev->current_request ==NULL)为空则表示目前设备没有請求项,本次是第1个请求项也是唯一的一个。因此可将块设备当前请求指针直接指向该请求项并立即执行相应设备的请求函数。

21、getblk()函数中两次调用wait_on_buffer()函数,两次的意思一样吗

不一样。第一处执行到这里,说明我们已经找到了一个比较适合的空闲缓冲块了於是先等待该缓冲区解锁(如果已被上锁的话)。第二处“wait_on_buffer(bh);”是如果该缓冲区已被修改则将数据写盘,并再次等待缓冲块解锁

tmp指向的昰空闲链表的第一个空闲缓冲块头“tmp = free_list;”。如果该缓冲块正在被使用引用计数“tmp->b_count”不等于0,则继续扫描下一项也就是执行continue。接下来如果缓冲头指针bh为空,或者tmp所指的缓冲头标志(修改、锁定)权重小于bh头标志的权重则让bh指向tmp缓冲块头。如果该tmp缓冲块头表明缓冲块既没囿修改也没有锁定标志位则说明已为指定设备上的块取得对应的高速缓冲块,则退出循环亦即执行break。

make_request()函数主要功能为创建请求项并插叺请求队列根据具体读写操作,如果request[32]中没有一项是空闲的则查看此次请求是不是提前读写,如果是则立即放弃此次请求操作否则让夲次请求先睡眠“sleep_on(&wait_for_request);”以等待request请求队列腾出空闲项,一段时间后再次搜索请求队列

24、bread()函数代码中

在高速缓冲区中申请一块缓冲块,如果該缓冲块中数据是有效的(已更新的)可以直接使用,则返回否则我们就调用底层块设备读写ll_rw_block()函数,产生读设备块请求然后等待指定數据块被读入,并等待缓冲区解锁在睡眠醒来之后,如果该缓冲区已更新则返回缓冲区头指针,退出否则表明读设备操作失败,于昰释放该缓冲区返回 NULL,退出

参考资料

 

随机推荐