Linux的启动过程分析资料.docx

上传人:b****1 文档编号:14353694 上传时间:2023-06-22 格式:DOCX 页数:12 大小:133.66KB
下载 相关 举报
Linux的启动过程分析资料.docx_第1页
第1页 / 共12页
Linux的启动过程分析资料.docx_第2页
第2页 / 共12页
Linux的启动过程分析资料.docx_第3页
第3页 / 共12页
Linux的启动过程分析资料.docx_第4页
第4页 / 共12页
Linux的启动过程分析资料.docx_第5页
第5页 / 共12页
Linux的启动过程分析资料.docx_第6页
第6页 / 共12页
Linux的启动过程分析资料.docx_第7页
第7页 / 共12页
Linux的启动过程分析资料.docx_第8页
第8页 / 共12页
Linux的启动过程分析资料.docx_第9页
第9页 / 共12页
Linux的启动过程分析资料.docx_第10页
第10页 / 共12页
Linux的启动过程分析资料.docx_第11页
第11页 / 共12页
Linux的启动过程分析资料.docx_第12页
第12页 / 共12页
亲,该文档总共12页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

Linux的启动过程分析资料.docx

《Linux的启动过程分析资料.docx》由会员分享,可在线阅读,更多相关《Linux的启动过程分析资料.docx(12页珍藏版)》请在冰点文库上搜索。

Linux的启动过程分析资料.docx

Linux的启动过程分析资料

 

嵌入式系统期中大作业

题目:

Linux的启动过程分析

 

学院:

信息工程

专业:

网络工程

姓名:

梁法成

学号:

201324070128

2016.4.15

目录

1.从开机加电到执行main函数之前的过程2

1.1 启动BIOS,准备实模式下的中断向量表和中断服务程序2

1.1.1 BIOS的启动原理2

1.1.2 BIOS在内存中加载中断向量表和中断服务程序2

1.2 加载操作系统内核程序并为保护模式做准备3

1.2.1 加载第一部分内核代码——引导程序(bootsect)3

1.2.2 加载第二部分内核代码——setup4

1.2.3.加载第三部分内核代码——system模块5

1.3 开始向32位模式转变,为main函数的调用做准备6

1.3.1 关中断并将system移动到内存地址起始位置0x000006

1.3.2 设置中断描述符表和全局描述符表7

1.3.3 打开A20,实现32位寻址7

1.3.4 为保护模式下执行head.s做准备7

1.3.5 head.s开始执行7

2.从main到怠速10

2.1设备环境初始化及激活进程010

2.1.1与建立人机交互界面相关的外设的中断服务程序挂接10

2.1.2 初始化进程010

2.1.3 初始化硬盘11

2.1.4 开启中断11

2.2进程1的创建及执行12

2.2.1进程0创建进程112

2.2.2内核第一次做进程调度12

2.2.3 轮转到进程1执行13

2.3进程2的创建及执行13

2.3.1、打开终端设备文件及复制文件句柄13

2.3.2、进程1创建进程2并切换到进程213

2.3.3、加载shell程序14

2.3.4、实现系统怠速14

 

1.从开机加电到执行main函数之前的过程

从开机到main函数的执行分三步完成,目的是实现从启动盘加载操作系统程序,完成执行main函数所需要的准备工作。

第一步,启动BIOS,准备实模式下的中断向量表和中断服务程序;第二步,从启动盘加载操作系统程序到内存,加载操作系统程序的工作就是利用第一步中准备的中断服务程序实现的;第三步,为执行32位的main函数做过渡工作。

本章将详细分析这三步在计算机中是如何完成的,以及每一步在内存中都做了些什么。

1.1 启动BIOS,准备实模式下的中断向量表和中断服务程序

问题:

在RAM中什么程序也没有的时候,谁来完成加载软盘中操作系统的任务呢?

答案是:

BIOS。

1.1.1 BIOS的启动原理

从硬件角度看,Intel80x86系列的CPU可以分别在16位实模式和32位保护模式下运行。

为了兼容,也为了解决最开始的启动问题,Intel将所有80x86系列的CPU,包括最新型号的CPU的硬件都设计为加电即进入16位实模式状态运行。

同时,还有一点非常关键的是,将CPU硬件逻辑设计为加电瞬间强行将CS的值置为0xF000、IP的值置为0xFFF0,这样CS:

IP就指向0xFFFF0这个地址位置。

1.1.2 BIOS在内存中加载中断向量表和中断服务程序

BIOS程序在内存最开始的位置(0x00000)用1KB的内存空间(0x00000~0x003FF)构建中断向量表,在紧挨着它的位置用256字节的内存空间构建BIOS数据区(0x00400~0x004FF),并在大约57KB以后的位置(0x0E05B)加载了8KB左右的与中断向量表相应的若干中断服务程序。

中断向量表中有256个中断向量,每个中断向量占4字节,其中两个字节是CS的值,两个字节是IP的值。

每个中断向量都指向一个具体的中断服务程序。

1.2 加载操作系统内核程序并为保护模式做准备

从现在开始,就要执行真正的boot操作了,即把软盘中的操作系统程序加载至内存。

对于Linux0.11操作系统而言,计算机将分三批逐次加载操作系统的内核代码。

第一批由BIOS中断int0x19把第一扇区bootsect的内容加载到内存;第二批、第三批在bootsect的指挥下,分别把其后的4个扇区和随后的240个扇区的内容加载至内存。

1.2.1 加载第一部分内核代码——引导程序(bootsect)

经过执行一系列BIOS代码之后,计算机完成了自检等操作(这些和我们讲的启动操作系统没有直接的关系,读者不必关心)。

由于我们把软盘设置为启动设备,计算机硬件体系结构的设计与BIOS联手操作,会让CPU接收到一个int0x19中断。

CPU接收到这个中断后,会立即在中断向量表中找到int0x19中断向量。

接下来,中断向量把CPU指向0x0E6F2,这个位置就是int0x19相对应的中断服务程序的入口地址。

这个中断服务程序的作用就是把软盘第一扇区中的程序(512B)加载到内存中的指定位置。

这个中断服务程序的功能是BIOS事先设计好的,代码是固定的,与Linux操作系统无关。

无论Linux0.11的内核是如何设计的,这段BIOS程序所要做的就是“找到软盘”并“加载第一扇区”,其余的它什么都不知道,也不必知道。

按照这个简单、“生硬”的规则,int0x19中断向量所指向的中断服务程序,即启动加载服务程序,将软驱0号磁头对应盘面的0磁道1扇区的内容复制至内存0x07C00处。

1.2.2 加载第二部分内核代码——setup

bootsect对内存的规划BIOS已经把bootsect也就是引导程序载入内存了,现在它的作用就是把第二批和第三批程序陆续加载到内存中。

为了把第二批和第三批程序加载到内存中的适当位置,bootsect首先做的工作就是规划内存。

通常,我们是用高级语言编写应用程序的,这些程序是在操作系统的平台上运行的。

我们只管写高级语言的代码、数据。

至于这些代码、数据在运行的时候放在内存的什么地方,是否会相互覆盖,我们都不用操心,因为操作系统和高级语言的编译器替我们做了大量的看护工作,确保不会出错。

现在我们讨论的是,操作系统本身使用的是汇编语言,没有高级语言编译器替操作系统提供保障,只有靠操作系统的设计者把内存的安排想清楚,确保无论操作系统如何运行,都不会出现代码与代码、数据与数据、代码与数据之间相互覆盖的情况。

为了更准确地理解操作系统的运行机制,我们必须清楚操作系统的设计者是如何规划内存的。

在实模式状态下,寻址的最大范围是1MB。

这些源代码的作用就是对后续操作所涉及的内存位置进行设置,包括将要加载的setup程序的扇区数(SETUPLEN)以及被加载到的位置(SETUPSEG);启动扇区被BIOS加载的位置(BOOTSEG)及将要移动到的新位置(INITSEG);内核(ker-nel)被加载的位置(SYSSEG)、内核的末尾位置(ENDSEG)及根文件系统设备号(ROOT_DEV)。

设置这些位置就是为了确保将要载入内存的代码与已经载入内存的代码及数据各在其位,互不覆盖,并且各自有够用的内存空间。

下面,bootsect程序要执行它的第二步工作:

将setup程序加载到内存中。

加载setup这个程序,要借助BIOS提供的int0x13中断向量所指向的中断服务程序(也就是磁盘服务程序)来完成。

执行int0x13指令,产生0x13中断,通过中断向量表找到这个中断服务程序,将软盘第二扇区开始的4个扇区,即setup.s对应的程序加载至内存的SE-TUPSEG(0x90200)处。

复制后的boot-sect的起始位置是0x90000,占用512字节的内存空间。

不难看出0x90200紧挨着bootsect的尾端,所以bootsect和setup是连在一起的。

现在,操作系统已经从软盘中加载了5个扇区的代码。

等bootsect执行完毕后,setup这个程序就要开始工作了。

1.2.3.加载第三部分内核代码——system模块

接下来,bootsect程序要执行第三批程序的载入工作,即将系统模块载入内存。

bootsect借着BIOS中断int0x13,将240个扇区的system模块加载进内存。

加载工作主要是由bootsect调用read_it子程序完成的。

到此为止,第三批程序已经加载完毕,整个操作系统的代码已全部加载至内存。

bootsect的主体工作已经做完了。

下面要通过执行“jmpi0,SETUPSEG”这行语句跳转至0x90200处,就是前面讲过的第二批程序——setup程序加载的位置。

CS:

IP指向setup程序的第一条指令,意味着由setup程序接着bootsect程序继续执行。

setup程序现在开始执行。

它做的第一件事情就是利用BIOS提供的中断服务程序从设备上提取内核运行所需的机器系统数据,其中包括光标位置、显示页面等数据,这些机器系统数据被加载到内存的0x90000~0x901FC位置。

图1-15标出了其内容及准确的位置。

这些数据将在以后main函数执行时发挥重要作用。

到此为止,操作系统内核程序的加载工作已经完成。

接下来的操作对Linux0.11而言具有战略意义。

系统通过已经加载到内存中的代码,将实现从实模式到保护模式的转变,使Linux0.11真正成为“现代”操作系统。

 

1.3 开始向32位模式转变,为main函数的调用做准备

接下来,操作系统要使计算机在32位保护模式下工作。

这期间要做大量的重建工作,并且持续工作到操作系统的main函数的执行过程中。

在本节中,操作系统执行的操作包括打开32位的寻址空间、打开保护模式、建立保护模式下的中断响应机制等与保护模式配套的相关工作、建立内存的分页机制,最后做好调用main函数的准备。

1.3.1 关中断并将system移动到内存地址起始位置0x00000

如图所示,这个准备工作先要关闭中断,即将CPU的标志寄存器(EFLAGS)中的中断允许标志(IF)置0。

这意味着,程序在接下来的执行过程中,无论是否发生中断,系统都不再对此中断进行响应,直到main函数中能够适应保护模式的中断服务体系被重建完毕才会打开中断,而那时候响应中断的服务程序将不再是BIOS提供的中断服务程序,取而代之的是由系统自身提供的中断服务程序。

下面,setup程序做了一个影响深远的动作:

将位于0x10000的内核程序复制至内存地址起始位置0x00000处!

这样做能取得“一箭三雕”的效果:

1)废除BIOS的中断向量表,等同于废除了BIOS提供的实模式下的中断服务程序。

2)收回刚刚结束使用寿命的程序所占内存空间。

3)让内核代码占据内存物理地址最开始的、天然的、有利的位置。

1.3.2 设置中断描述符表和全局描述符表

setup程序继续为保护模式做准备。

此时要通过setup程序自身提供的数据信息对中断描述符表寄存器(IDTR)和全局描述符表寄存器(GDTR)进行初始化设置。

GDT(GlobalDescriptorTable,全局描述符表),在系统中唯一的存放段寄存器内容(段描述符)的数组,配合程序进行保护模式下的段寻址。

它在操作系统的进程切换中具有重要意义,可理解为所有进程的总目录表,其中存放每一个任务(task)局部描述符表(LDT,LocalDescriptorTable)地址和任务状态段(TSS,TaskStructureSegment)地址,完成进程中各段的寻址、现场保护与现场恢复。

IDT(InterruptDescriptorTable,中断描述符表),保存保护模式下所有中断服务程序的入口地址,类似于实模式下的中断向量表。

1.3.3 打开A20,实现32位寻址

下面是标志性的动作——打开A20!

打开A20,意味着CPU可以进行32位寻址,最大寻址空间为4GB。

打开A20现在看来,Linux0.11还显得有些稚嫩,最大只能支持16MB的物理内存,但是其线性寻址空间已经是不折不扣的4GB。

1.3.4 为保护模式下执行head.s做准备

setup程序将CPU工作方式设为保护模式。

将CR0寄存器第0位(PE)置1,即设定处理器工作方式为保护模式。

1.3.5 head.s开始执行

head程序除了做一些调用main的准备工作之外,还做了一件对内核程序在内存中的布局及内核程序的正常运行有重大意义的事,就是用程序自身的代码在程序自身所在的内存空间创建了内核分页机制,即在0x000000的位置创建了页目录表、页表、缓冲区、GDT、IDT,并将head程序已经执行过的代码所占内存空间覆盖。

这意味着head程序自己将自己废弃,main函数即将开始执行。

这些工作完成后,内存中的布局如图所示。

可以看出,只有184字节的剩余代码。

由此可见,在设计head程序和system模块时,其计算是非常精确的,对head.s的代码量的控制非常到位。

 

开机时的16位实模式与main函数执行需要的32位保护模式之间有很大的差距,这个差距谁来填补?

head.s做的就是这项工作。

这期间,head程序打开A20,打开pe、pg,废弃旧的、16位的中断响应机制,建立新的32位的IDT……这些工作都做完了,计算机已经处在32位的保护模式状态了,调用32位main函数的一切条件已经准备完毕,这时顺理成章地调用main函数。

后面的操作就可以用32位编译的main函数完成。

至此,Linux0.11内核启动的一个重要阶段已经完成,接下来就要进入main函数对应的代码了。

特别需要提示的是,此时仍处在关闭中断的状态!

 

2.从main到怠速

系统达到怠速状态前所做的一切准备工作的核心目的就是让用户程序能够以“进程”的方式正常运行。

能够实现这一目的的标准包括三方面的内容:

1)用户程序能够在主机上进行运算;

2)能够与外设进行交互;

3)能够让用户以它为媒介进行人机交互。

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

2.1.1与建立人机交互界面相关的外设的中断服务程序挂接

Linus在操作系统源代码中本来设计了chr_dev_init()函数,明显是要用这个函数初始化字符设备,但我们可以看到这是一个空函数。

Linus又设计了tty_init()函数,内容就是初始化字符设备。

有人解释tty是teletype。

字符设备的初始化为进程与串行口(可以通信、连接鼠标……)、显示器以及键盘进行I/O通信准备工作环境,主要是对串行口、显示器、键盘进行初始化设置,以及与此相关的中断服务程序与IDT挂接。

2.1.2 初始化进程0

进程0是Linux操作系统中运行的第一个进程,也是Linux操作系统父子进程创建机制的第一个父进程。

下面讲解的内容对进程0能够在主机中正常运算的影响最为重要和深远,主要包含如下三方面的内容。

1)系统先初始化进程0。

进程0管理结构task_struct的母本(init_task={INIT_TASK,})已经在代码设计阶段事先设计好了,但这并不代表进程0已经可用了,还要将进程0的task_struct中的LDT、TSS与GDT相挂接,并对GDT、task[64]以及与进程调度相关的寄存器进行初始化设置。

2)Linux0.11作为一个现代操作系统,其最重要的标志就是能够支持多进程轮流执行,这要求进程具备参与多进程轮询的能力。

系统这里对时钟中断进行设置,以便在进程0运行后,为进程0以及后续由它直接、间接创建出来的进程能够参与轮转奠定基础。

3)进程0要具备处理系统调用的能力。

每个进程在运算时都可能需要与内核进行交互,而交互的端口就是系统调用程序。

系统通过函数set_system_gate将system_call与IDT相挂接,这样进程0就具备了处理系统调用的能力了。

这个system_call就是系统调用的总入口。

进程0只有具备了以上三种能力才能保证将来在主机中正常地运行,并将这些能力遗传给后续建立的进程。

2.1.3 初始化硬盘

硬盘的初始化为进程与硬盘这种块设备进行I/O通信建立了环境基础。

在hd_init()函数中,将硬盘请求项服务程序do_hd_request()与blk_dev控制结构相挂接,硬盘与请求项的交互工作将由do_hd_request()函数来处理,然后将硬盘中断服务程序hd_interrupt()与IDT相挂接,最后,复位主8259Aint2的屏蔽位,允许从片发出中断请求信号,复位硬盘的中断请求屏蔽位(在从片上),允许硬盘控制器发送中断请求信号。

2.1.4 开启中断

现在,系统中所有中断服务程序都已经和IDT正常挂接。

这意味着中断服务体系已经构建完毕,系统可以在32位保护模式下处理中断,重要意义之一是可以使用系统调用。

可以开启中断了!

2.2进程1的创建及执行

现在,计算机中已经有了一个名副其实的、3特权级的进程——进程0。

下面我们要详细讲解进程0做的第一项工作——创建进程1。

2.2.1进程0创建进程1

进程0作为父进程调用fork函数创建第一个子进程——进程1。

执行sys_fork(),在task[64]中为进程1申请一个空闲位置并获取进程号。

进程0将在copy_process()函数中做非常重要的、体现父子进程机制的工作:

(1)为进程1创建tast_struct,将进程0的tast_struct内容复制给进程1;

(2)为进程1的tast_struct、tss做个性化设置;

(3)为进程1创建第一个页表,将进程0的页表内容赋给这个页表;

(4)进程1共享进程0的文件;

(5)设置进程1的GDT项;

(6)最后将进程1设置为就绪状态,使其可以参与进程间的轮转

2.2.2内核第一次做进程调度

产生进程切换的两种情况:

(1)允许进程运行的时间结束;

(2)进程的运行停止。

进入sys_pause()函数后,将进程0设置为可中断等待状态,然后调用schedule()函数进行进程切换。

2.2.3 轮转到进程1执行

进程1为安装硬盘文件系统做准备:

(1)根据机器系统数据设置硬盘参数;

(2)读取硬盘引导块;

(3)从引导块中获取信息。

进程1格式化虚拟盘并更换根设备为虚拟盘:

格式化虚拟盘使之成为块设备

进程1在根设备上加载根文件系统:

操作系统的文件系统大致分为两类:

一部分在操作系统内核中,另一部分在硬盘、软盘、虚拟盘中。

2.3进程2的创建及执行

现在已经有了进程0、进程1。

下面主要讲进程1如何创建进程2的过程,以及进程2的执行,最终shell进程开始执行,整个boot工作完成,实现系统怠速。

2.3.1、打开终端设备文件及复制文件句柄

(1)打开标准输入设备文件

(2)打开标准输出、标准错误输出设备文件:

复制文件句柄的方法实现

2.3.2、进程1创建进程2并切换到进程2

进程1调用fork()函数,创建进程2,schedual()函数切换到进程2。

进程2的加载是从硬盘中加载。

2.3.3、加载shell程序

(1)关闭标准输入设备文件,打开rc文件

(2)检测shell文件

(3)为shell程序的执行做准备

(4)执行shell程序

2.3.4、实现系统怠速

(1)创建update进程:

主要任务是将缓冲区中的数据同步到外设(软盘、硬盘等)上,为了提高效率。

(2)切换到shell程序执行

(3)重建shell:

实现系统怠速后,操作系统用户将通过shell进程提供的平台与计算机进行交互。

 

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 表格模板 > 合同协议

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2