3.操作系统引导——操作系统启动

来源:互联网 发布:java 对象初始化 null 编辑:程序博客网 时间:2024/04/28 05:52

1.setup.s

setup中,操作系统接手硬件,初始化
开机做两件事:bootsect读入系统,setup初始化

!! setup.s (C) 1991 Linus Torvalds!! setup.s is responsible for getting the system data from the BIOS,! and putting them into the appropriate places in system memory.! both setup.s and system has been loaded by the bootblock.!! This code asks the bios for memory/disk/other parameters, and! puts them in a "safe" place: 0x90000-0x901FF, ie where the! boot-block used to be. It is then up to the protected mode! system to read them from there before the area is overwritten! for buffer-blocks.!! setup.s 负责从BIOS 中获取系统数据,并将这些数据放到系统内存的适当地方。! 此时setup.ssystem 已经由bootsect 引导块加载到内存中。!! 这段代码询问bios 有关内存/磁盘/其它参数,并将这些参数放到一个! “安全的”地方:0x90000-0x901FF,也即原来bootsect 代码块曾经在! 的地方,然后在被缓冲块覆盖掉之前由保护模式的system 读取。!entry startstart:! ok, the read went well so we get current cursor position and save it for! posterity.! ok,整个读磁盘过程都正常,现在将光标位置保存以备今后使用。mov ax,#INITSEG ! this is done in bootsect already, but...mov ds,ax   !ds 置成#INITSEG(0x9000)。!这已经在bootsect 程序中设置过,但是现在是setup 程序,Linus 觉得需要再重新设置一下。mov ah,#0x03 ! read cursor pos! BIOS 中断0x10 的读光标功能号 ah = 0x03,输入:bh = 页号,返回:ch = 扫描开始线,cl = 扫描结束线,! dh = 行号(0x00 是顶端),dl = 列号(0x00 是左边)。xor bh,bhint 0x10 ! save it in known place, con_init fetchesmov [0],dx ! it from 0x90000.dx0x90000! 上两句是说将光标位置信息存放在0x90000 处,控制台初始化时会来取。! Get memory size (extended mem, kB) ! 下面3 句取扩展内存的大小值(KB)。! 是调用中断0x15,功能号ah = 0x88! 返回:ax = 从0x100000(1M)处开始的扩展内存大小(KB)。! 若出错则CF 置位,ax = 出错码。mov ah,#0x88int 0x15    ! BIOS中断int 0x15,用于获得物理内存的大小,放到axmov [2],ax ! 将扩展内存数值存在0x90002 处(1 个字)。! ax赋值给[2],[2]是间接寻址,[2]前有默认的段寄存器,存的是0x9000! [2]是将0x9000左移4位加2,即0x90002,存的数值用于扩展内存的大小! 操作系统要管理内存,要知道内存有多大……! now we want to move to protected mode ... ! 从这里开始我们要保护模式方面的工作了。cli ! no interrupts allowed ! ! 此时不允许中断。! first we move the system to it's rightful place! 首先我们将system 模块移到正确的位置。! bootsect 引导程序是将system 模块读入到从0x10000(64k,SYSSEG=0x1000)开始的位置。由于当时假设! system 模块最大长度不会超过0x80000(512k),也即其末端不会超过内存地址0x90000,! 所以bootsect 会将自己移动到0x90000 开始的地方,并把setup 加载到它的后面。! 下面这段程序的用途是再把整个system 模块移动到0x00000 位置,即把从0x10000 到0x8ffff! 的内存数据块(512k),整块地向内存低端移动了0x10000(64k)的位置。mov ax,#0x0000cld      ! 'direction'=0, movs moves forward! 移动do_move:mov es,ax   ! destination segment ! es:di 目的地址(初始为0x0000:0x0)add ax,#0x1000cmp ax,#0x9000  ! 已经把从0x8000 段开始的64k 代码移动完jz end_movemov ds,ax   ! source segmentsub di,di   ! di 减为0ds:si 源地址(初始为0x1000:0x0)sub si,si    ! si 减为0 mov cx,#0x8000  ! 移动0x8000 字(64k 字节)。repmovswjmp do_move! 将操作系统移动到0x0000(es:di)处,地址0x0000-0x80000用于放操作系统! 之前的0x7c00要腾出空间给操作系统

内存地址:0x90000,长度:2,名称:光标位置
内存地址:0x90002,长度:2,名称:扩展内存数
内存地址:0x9000C,长度2,名称:显卡参数
内存地址:0x901FC,长度:2,名称:根设备号

下边要进入保护模式了

! then we load the segment descriptors! 此后,我们加载段描述符。! 从这里开始会遇到32 位保护模式的操作,因此需要Intel 32 位保护模式编程方面的知识了,! 有关这方面的信息请查阅列表后的简单介绍或附录中的详细说明。这里仅作概要说明。!! lidt 指令用于加载中断描述符表(idt)寄存器,它的操作数是6 个字节,0-1 字节是描述符表的! 长度值(字节);2-5 字节是描述符表的32 位线性基地址(首地址),其形式参见下面! 219-220 行和223-224 行的说明。中断描述符表中的每一个表项(8 字节)指出发生中断时! 需要调用的代码的信息,与中断向量有些相似,但要包含更多的信息。!! lgdt 指令用于加载全局描述符表(gdt)寄存器,其操作数格式与lidt 指令的相同。全局描述符! 表中的每个描述符项(8 字节)描述了保护模式下数据和代码段(块)的信息。其中包括段的! 最大长度限制(16 位)、段的线性基址(32 位)、段的特权级、段是否在内存、读写许可以及! 其它一些保护模式运行的标志。参见后面205-216 行。!end_move:mov ax,#SETUPSEG ! right, forgot this at first. didn't work :-)mov ds,ax ! ds 指向本程序(setup)段。lidt idt_48 ! load idt with 0,0IDT 是保护模式下的中断函数表! 加载中断描述符表(idt)寄存器,idt_486 字节操作数的位置!2 字节表示idt 表的限长,后4 字节表示idt 表所处的基地址。lgdt gdt_48 ! load gdt with whatever appropriateGDT是保护模式下的寻址表! 初始化表,加载全局描述符表(gdt)寄存器,gdt_486 字节操作数的位置! that was painless, now we enable A20! 以上的操作很简单,现在我们开启A20 地址线。参见程序列表后有关A20 信号线的说明。! 8042是键盘控制器,其输出端口P2用来控制A20地址线call empty_8042  ! 等待输入缓冲器空。只有当输入缓冲器为空时才可以对其进行写命令。mov al,#0xD1    ! command write ! 0xD1 命令码-表示要写数据到8042P2 端口out #0x64,al    ! P2 端口的位1 用于A20 线的选通。call empty_8042     ! 等待输入缓冲器空,看命令是否被接受。mov al,#0xDF    ! A20 on ! 选通A20 地址线的参数。out #0x60,al    ! 数据要写到0x60 口。call empty_8042      ! 输入缓冲器为空,则表示A20 线已经选通。! well, that went ok, I hope. Now we have to reprogram the interrupts :-(! we put them right after the intel-reserved hardware interrupts, at! int 0x20-0x2F. There they won't mess up anything. Sadly IBM really! messed this up with the original PC, and they haven't been able to! rectify it afterwards. Thus the bios puts interrupts at 0x08-0x0f,! which is used for the internal hardware interrupts as well. We just! have to reprogram the 8259's, and it isn't fun.!! 希望以上一切正常。现在我们必须重新对中断进行编程??!! 我们将它们放在正好处于intel 保留的硬件中断后面,在int 0x20-0x2F!! 在那里它们不会引起冲突。不幸的是IBM 在原PC 机中搞糟了,以后也没有纠正过来。!! PC 机的bios 将中断放在了0x08-0x0f,这些中断也被用于内部硬件中断。!! 所以我们就必须重新对8259 中断控制器进行编程,这一点都没劲。……! well, that certainly wasn't fun :-(. Hopefully it works, and we don't! need no steenking BIOS anyway (except for the initial loading :-).! The BIOS-routine wants lots of unnecessary data, and it's less! "interesting" anyway. This is how REAL programmers do it.!! Well, now's the time to actually move into protected mode. To make! things as simple as possible, we do no register set-up or anything,! we let the gnu-compiled 32-bit programs do that. We just jump to! absolute address 0x00000, in 32-bit protected mode.!! 哼,上面这段当然没劲,希望这样能工作,而且我们也不再需要乏味的BIOS 了(除了!! 初始的加载。BIOS 子程序要求很多不必要的数据,而且它一点都没趣。那是“真正”的!! 程序员所做的事。! 这里设置进入32 位保护模式运行。首先加载机器状态字(lmsw - Load Machine Status Word),也称! 控制寄存器CR0,其比特位01 将导致CPU 工作在保护模式。mov ax,#0x0001  ! protected mode (PE) bit ! 保护模式比特位(PE)。lmsw ax     ! This is it! ! 就这样加载机器状态字!jmpi 0,8    ! jmp offset 0 of segment 8 (cs) ! 跳转至cs8,偏移0 处。! 跳转至0处,即跳转到system! jmpi 0,80 赋给IP,将8赋给CScs=8用来插gdt,按照之前的寻址方式,段寄存器=CS<<4+IP=0x80,! 这种寻址方式,CSIP都是16位寄存器,CS<<4+IP,最多是20位地址,最大值的1M! 也就是说 计算机的寻址空间是1M! 现在的操作系统是4G的,16->32位,1M->4G,32位也叫保护模式! 16位与32位的本质区别是CPU的解释程序不一样! 我们已经将system 模块移动到0x00000 开始的地方,所以这里的偏移地址是0。这里的段! 值的8 已经是保护模式下的段选择符了,用于选择描述符表和描述符表项以及所要求的特权级。! 段选择符长度为16 位(2 字节);位0-1 表示请求的特权级0-3linux 操作系统只! 用到两级:0 级(系统级)和3 级(用户级);位2 用于选择全局描述符表(0)还是局部描! 述符表(1);位3-15 是描述符表项的索引,指出选择第几项描述符。所以段选择符! 8(0b0000,0000,0000,1000)表示请求特权级0、使用全局描述符表中的第1 项,该项指出! 代码的基地址是0,因此这里的跳转指令就会去执行system 中的代码。! This routine checks that the keyboard command queue is empty! No timeout is used - if this hangs there is something wrong with! the machine, and we probably couldn't proceed anyway.! 下面这个子程序检查键盘命令队列是否为空。这里不使用超时方法 - 如果这里死机,! 则说明PC 机有问题,我们就没有办法再处理下去了。! 只有当输入缓冲器为空时(状态寄存器位2 = 0)才可以对其进行写命令。empty_8042:.word 0x00eb,0x00eb     ! 这是两个跳转指令的机器码(跳转到下一句),相当于延时空操作。in al,#0x64     ! 8042 status port !AT 键盘控制器状态寄存器。test al,#2  ! is input buffer full? ! 测试位2,输入缓冲器满?jnz empty_8042 ! yes - loopretgdt: ! 全局描述符表开始处。描述符表由多个8 字节长的描述符项组成。! 这里给出了3 个描述符项。第1 项无用(206 行),但须存在。第2 项是系统代码段! 描述符(208-211 行),第3 项是系统数据段描述符(213-216 行)。每个描述符的具体! 含义参见列表后说明。.word 0,0,0,0 ! dummy !0个表项,从0开始寻址。第1 个描述符,不用。! 这里在gdt 表中的偏移量为0x08,当加载代码段寄存器(段选择符)时,使用的是这个偏移值。以8个字节为单位寻址! 第一个表项,一个word16位,共4个,表一个项是16*4位.word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb).word 0x0000 ! base address=0.word 0x9A00 ! code read/exec.word 0x00C0 ! granularity=4096, 386! 这里在gdt 表中的偏移量是0x10,当加载数据段寄存器(如ds 等)时,使用的是这个偏移值。.word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb).word 0x0000 ! base address=0.word 0x9200 ! data read/write.word 0x00C0 ! granularity=4096, 386

这里写图片描述

cr0的最后一位:
置0是16位模式,即 实模式
置1是32位模式,即保护模式

保护模式和实模式下的地址翻译与中断处理

  • 实模式下,地址是 CS<<4+IP
  • 保护模式下,地址是 根据CS查GDT,查到的值+IP,中断处理的int n,中断函数是查IDT表的n对应的数据

GDT:Global Descriptor Table 全局描述符表,由硬件实现,快。GDT的内容存放,由硬件决定
CS 是selector 选择子,存放的是索引,GDT的index(描述符索引)是 段的描述符 在描述符表的位置,该位置的值 与 IP一起,组成了32位的地址

这里写图片描述

2.进入system

system模块的第一部分是 head.s

2.1 为什么system模块的第一部分是head.s

这是由Makefile决定的,Makefile:

disk: Image # 表示disk 这个目标要由Image 产生。dd bs=8192 if=Image of=/dev/PS0 # dd 为UNIX 标准命令:复制一个文件,根据选项进行转换和格式化# bs=表示一次读/写的字节数。# if=表示输入的文件,of=表示输出到的文件。# 这里/dev/PS0 是指第一个软盘驱动器(设备文件)。Image: boot/bootsect boot/setup tools/system tools/build # 说明目标(Image 文件)是由# 冒号后面的4 个元素产生,分别是boot/bootsect 和setup 文件、tools/system 和build 文件。# boot/bootsect依赖的是bootsect.s# boot/setup依赖的是setup.s……# 表示tools/system 文件要由分号右边所列的元素生成。# 其中 boot/head.o 依赖于head.s# init/main.o依赖于 main.c# DRIVERS置驱动tools/system: boot/head.o init/main.o \$(ARCHIVES) $(DRIVERS) $(MATH) $(LIBS) $(LD) $(LDFLAGS) boot/head.o init/main.o \# $(LD) 是链接,将 boot/head.o init/main.o …… 连接到一起,就有了system  ……-o tools/system > System.map # 生成system 的命令。# 最后的 > System.map 表示gld 需要将连接映象重定向存放在System.map 文件中。

2.2 head.s

setup是进入保护模式,head是进入之后的初始化

这里写图片描述

movl $0x10,%eax # 将0x10的值赋给eax,这里是32位汇编,与之前的16位汇编不同

/** linux/boot/head.s** (C) 1991 Linus Torvalds*//** head.s contains the 32-bit startup code.** NOTE!!! Startup happens at absolute address 0x00000000, which is also where* the page directory will exist. The startup code will be overwritten by* the page directory.*//** head.s 含有32 位启动代码。* 注意!!! 32 位启动代码是从绝对地址0x00000000 开始的,这里也同样是页目录将存在的地方,* 因此这里的启动代码将被页目录覆盖掉。*/.text.globl _idt,_gdt,_pg_dir,_tmp_floppy_area_pg_dir: # 页目录将会存放在这里。startup_32: # 18-22 行设置各个数据段寄存器。movl $0x10,%eax # 对于GNU 汇编来说,每个直接数要以'$'开始,否则是表示地址。# 每个寄存器名都要以'%'开头,eax 表示是32 位的ax 寄存器。# 再次注意!!! 这里已经处于32 位运行模式,因此这里的$0x10 并不是把地址0x10 装入各个# 段寄存器,它现在其实是全局段描述符表中的偏移值,或者更正确地说是一个描述符表项# 的选择符。这里$0x10 的含义是请求特权级0(位0-1=0)、选择全局描述符表(位2=0)、# 选择表中第2 项(位3-15=2)。它正好指在当前的Linux 操作系统中,gas 和gld 已经分别更名为as 和ld。# 向表中的数据段描述符项。# 下面代码的含义是:置ds,es,fs,gs 中的选择符为setup.s 中构造的数据段(全局段描述符表# 的第2 项)=0x10,并将堆栈放置在数据段中的_stack_start 数组内,然后使用新的中断描述# 符表和全局段描述表.新的全局段描述表中初始内容与setup.s 中的完全一样。# 指向gdt的0x10项(数据段)mov %ax,%dsmov %ax,%esmov %ax,%fsmov %ax,%gslss _stack_start,%esp # 表示_stack_start??ss:esp,设置系统堆栈。# stack_start 定义在kernel/sched.c,69 行。call setup_idt # 调用设置中断描述符表子程序。call setup_gdt # 调用设置全局描述符表子程序。movl $0x10,%eax # reload all the segment registersmov %ax,%ds # after changing gdt. CS was alreadymov %ax,%es # reloaded in 'setup_gdt'mov %ax,%fs # 因为修改了gdt,所以需要重新装载所有的段寄存器。mov %ax,%gs # CS 代码段寄存器已经在setup_gdt 中重新加载过了。lss _stack_start,%esp# 32-36 行用于测试A20 地址线是否已经开启。采用的方法是向内存地址0x000000 处写入任意# 一个数值,然后看内存地址0x100000(1M)处是否也是这个数值。如果一直相同的话,就一直# 比较下去,也即死循环、死机。表示地址A20 线没有选通,结果内核就不能使用1M 以上内存。xorl %eax,%eax1: incl %eax # check that A20 really IS enabledmovl %eax,0x000000 # loop forever if it isn'tcmpl %eax,0x100000je 1b # '1b'表示向后(backward)跳转到标号1 去(33 行)。若是'5f'则表示向前(forward)跳转到标号5 去。# 0地址处和1M地址处相同(A20没开启),就死循环/** NOTE! 486 should set bit 16, to check for write-protect in supervisor* mode. Then it would be unnecessary with the "verify_area()"-calls.* 486 users probably want to set the NE (#5) bit also, so as to use* int 16 for math errors.*//** 注意! 在下面这段程序中,486 应该将位16 置位,以检查在超级用户模式下的写保护,* 此后"verify_area()"调用中就不需要了。486 的用户通常也会想将NE(#5)置位,以便* 对数学协处理器的出错使用int 16*/# 下面这段程序(43-65)用于检查数学协处理器芯片是否存在。方法是修改控制寄存器CR0,在# 假设存在协处理器的情况下执行一个协处理器指令,如果出错的话则说明协处理器芯片不存在,# 需要设置CR0 中的协处理器仿真位EM(位2),并复位协处理器存在标志MP(位1)。movl %cr0,%eax # check math chipandl $0x80000011,%eax # Save PG,PE,ET/* "orl $0x10020,%eax" here for 486 might be good */orl $2,%eax # set MPmovl %eax,%cr0call check_x87jmp after_page_tables # 页表……/** setup_idt** sets up a idt with 256 entries pointing to* ignore_int, interrupt gates. It then loads* idt. Everything that wants to install itself* in the idt-table may do so themselves. Interrupts* are enabled elsewhere, when we can be relatively* sure everything is ok. This routine will be over-* written by the page tables.*//** 下面这段是设置中断描述符表子程序 setup_idt** 将中断描述符表idt 设置成具有256 个项,并都指向ignore_int 中断门。然后加载中断* 描述符表寄存器(用lidt 指令)。真正实用的中断门以后再安装。当我们在其它地方认为一切* 都正常时再开启中断。该子程序将会被页表覆盖掉。*/# 中断描述符表中的项虽然也是8 字节组成,但其格式与全局表中的不同,被称为门描述符# (Gate Descriptor)。它的0-1,6-7 字节是偏移量,2-3 字节是选择符,4-5 字节是一些标志。setup_idt:lea ignore_int,%edx # 将ignore_int 的有效地址(偏移值)值??edx 寄存器movl $0x00080000,%eax # 将选择符0x0008 置入eax 的高16 位中。movw %dx,%ax /* selector = 0x0008 = cs */# 偏移值的低16 位置入eax 的低16 位中。此时eax 含有#门描述符低4 字节的值。movw $0x8E00,%dx /* interrupt gate - dpl=0, present */# 此时edx 含有门描述符高4 字节的值。lea _idt,%edi # _idt 是中断描述符表的地址。mov $256,%ecxrp_sidt:movl %eax,(%edi) # 将哑中断门描述符存入表中。movl %edx,4(%edi)addl $8,%edi # edi 指向表中下一项。dec %ecxjne rp_sidtlidt idt_descr # 加载中断描述符表寄存器值。ret……# 下面这几个入栈操作(pushl)用于为调用/init/main.c 程序和返回作准备。# 前面3 个入栈指令不知道作什么用的,也许是Linus 用于在调试时能看清机器码用的?。# 139 行的入栈操作是模拟调用main.c 程序时首先将返回地址入栈的操作,所以如果# main.c 程序真的退出时,就会返回到这里的标号L6 处继续执行下去,也即死循环。# 140 行将main.c 的地址压入堆栈,这样,在设置分页处理(setup_paging)结束后# 执行'ret'返回指令时就会将main.c 程序的地址弹出堆栈,并去执行main.c 程序去了。after_page_tables:pushl $0 # These are the parameters to main :-)pushl $0 # 这些是调用main 程序的参数(指init/main.c)。pushl $0pushl $L6 # return address for main, if it decides to.pushl $_main # '_main'是编译程序对main 的内部表示方法。jmp setup_paging # 跳转到 设置页表L6:jmp L6 # main should never return here, but# just in case, we know what happens.……

setup_paging执行ret后,会执行main函数
进入main后的栈位 0,0,0,L6
main函数的三个参数是0,0,0
main函数返回时进入L6,死循环

head.s 到main.c,从汇编跳到c语言。C语言执行时,最终也转换为汇编执行,所以 C语言的 函数间跳转,也是通过汇编实现的,与汇编跳转到C语言 没有本质区别。

C语言执行函数func时(假设func有3个参数 p1,p2,p3)
执行时,将 返回地址,参数压栈。然后 跳转到该函数处,执行该函数,
当函数执行完,遇到 结束的 } 汇编将其转为ret,从栈中弹出该 返回地址

after_page_tables中,pushl 压栈,栈如下:0 0 0 L6 _main
jmp setup_paging 跳转到 setup_paging,执行setup_paging 函数,执行完遇到ret 跳出该函数,将栈中的 _main 返回,0 0 0 是main函数的参数,返回到 L6,main函数是一个死循环

3. main

3.1 init/main.c

void main (void)        /* This really IS void, no error here. */{               /* The startup routine assumes (well, ...) this */  /* 这里确实是void,并没错。在startup 程序(head.s)中就是这样假设的。 */……  // 以下是内核进行所有方面的初始化工作。阅读时最好跟着调用的程序深入进去看,实在看  // 不下去了,就先放一放,看下一个初始化调用 -- 这是经验之谈?。  mem_init (main_memory_start, memory_end);  trap_init ();         // 陷阱门(硬件中断向量)初始化。(kernel/traps.c,181 行)  blk_dev_init ();      // 块设备初始化。 (kernel/blk_dev/ll_rw_blk.c,157 行)  chr_dev_init ();      // 字符设备初始化。 (kernel/chr_dev/tty_io.c,347 行)  tty_init ();          // tty 初始化。 (kernel/chr_dev/tty_io.c,105 行)  time_init ();         // 设置开机启动时间??startup_time(见76 行)。  sched_init ();        // 调度程序初始化(加载了任务0 的tr, ldtr) (kernel/sched.c,385)  buffer_init (buffer_memory_end);  // 缓冲管理初始化,建内存链表等。(fs/buffer.c,348)  hd_init ();           // 硬盘初始化。 (kernel/blk_dev/hd.c,343 行)  floppy_init ();       // 软驱初始化。 (kernel/blk_dev/floppy.c,457 行)  sti ();           // 所有初始化工作都做完了,开启中断。  // 下面过程通过在堆栈中设置的参数,利用中断返回指令切换到任务0。  move_to_user_mode ();     // 移到用户模式。 (include/asm/system.h,第1 行)  if (!fork ())    {               /* we count on this going ok 保证main一直不会退出*/      init ();    }  /*   * NOTE!! For any other task 'pause()' would mean we have to get a   * signal to awaken, but task0 is the sole exception (see 'schedule()')   * as task 0 gets activated at every idle moment (when no other tasks   * can run). For task0 'pause()' just means we go check if some other   * task can run, and if not we return here.   */  /* 注意!! 对于任何其它的任务,'pause()'将意味着我们必须等待收到一个信号才会返   * 回就绪运行态,但任务0(task0)是唯一的意外情况(参见'schedule()'),因为任务0 在   * 任何空闲时间里都会被激活(当没有其它任务在运行时),因此对于任务0'pause()'仅意味着   * 我们返回来查看是否有其它任务可以运行,如果没有的话我们就回到这里,一直循环执行'pause()'。   */  for (;;)    pause ();}

main的工作就是 xx_init:内存、中断、设备、时钟、CPU等的初始化

3.2 mm/memory.c

用数据结构+算法 管理硬件
初始化了mem_map数组

//// 物理内存初始化。// 参数:start_mem - 可用作分页处理的物理内存起始位置(已去除RAMDISK 所占内存空间等)。// end_mem - 实际物理内存最大地址,决定了mem_map多长,有多大内存。// 在该版的linux 内核中,最多能使用16Mb 的内存,大于16Mb 的内存将不于考虑,弃置不用。// 0 - 1Mb 内存空间用于内核系统(其实是0-640Kb)。void mem_init (long start_mem, long end_mem){  int i;  HIGH_MEMORY = end_mem;    // 设置内存最高端。  for (i = 0; i < PAGING_PAGES; i++)    // 首先置所有页面为已占用(USED=100)状态,    mem_map[i] = USED;      // 初始化mem_map数组,即将页面映射数组全置成USED。  i = MAP_NR (start_mem);   // 然后计算可使用起始内存的页面号。  end_mem -= start_mem;     // 再计算可分页处理的内存块大小。  end_mem >>= 12;       // 右移12位,即减掉4k,是一页。从而计算出可用于分页处理的页面数。  while (end_mem-- > 0)     // 最后将这些可用页面对应的页面映射数组清零。        mem_map[i++] = 0;   // 将 mem_map 数组元素值 置0,1 吊事该内存使用了,0表示没用}

4.操作系统的启动过程

  1. boot:将操作系统从磁盘读入
  2. setup:获得一些参数,启动保护模式
  3. head:初始化GDT表,页表,跳到main
  4. main:执行好多init,进行初始化,比如 mem_init:内存初始化

操作系统开机:读入曹组哦系统,完成初始化

0 0
原创粉丝点击