电脑从开机加电到操作系统main函数之前执行的过程

总的来说在操作系统加电启动之后到main函数执行之前操作系统经历了以下3个大步骤

1.启动BIOS。这个时候位于实模式下,加载中断向量和中断服务程序

2.加载操作系统内核并为保护模式做准备。这个时候操作系统一共加载了3部分代码:引导程序bootsect,内核代码setup,内核代码system模块

3.从实模式转换为32位保护模式。这个过程要做大量重建工作,并且持续工作到操作系统main函数的执行过程。细说的话,主要包括打开32位寻址空间,打开保护模式,建立保护模式下的中断相应机制与保护模式配套的相关工作,建立内存分页机制。

名词说明:

实模式:20位的存储器地址空间,可以直接软件访问BIOS及周边硬件,没有硬件支持的分页机制和实时多任务概念。从80286开始,所有的80x86CPU开机状态都是实模式。

中断向量表:实模式下用于记录所有中断号对应的中断服务程序的内存地址。

中断服务程序:用于指示中断发生后该怎么办的程序。

SS:栈基址寄存器,SP:栈顶指针,两者一起构成栈在内存中的位置。压栈方式为高地址向地地址。

根文件系统设备:linux 0.11使用Minux OS的文件系统管理方式,要求系统必须存在一个根文件,其他文件系统挂在其上。因此Linux 0.11在启动时需要两部分数据,即系统内核镜像和根文件系统。

GDT(全局描述符表):在系统中唯一的存放段寄存器内容的数组,配合程序进行保护模式下的段寻址。其可理解为所有进程的总目录表,其中存放每一个task局部描述符表(LDT)地址和任务状态段(TSS)地址,完成进程中各段的寻址,现场保护与现场恢复。

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

GDTR,IDTR:分别为以上描述符表的基地址寄存器

CR0寄存器:0号32为控制寄存器,存放系统控制标志。第0位为PE标志,置1时CPU工作在保护模式下,置0时为实模式。

设置段寄存器指令:该组指令的功能是把内存单元的一个“低字”传送给指令的指定16寄存器,然后把“高字”传给相应的段寄存器。命令格式:LDS/LES/LFS/LGS/LSS Mem, Reg

说明:我学的是linux 0.11内核,目前有的内核版本是3.6,虽然差好多,但是适合学习。

注意:开始阶段的BIOS与操作系统无关

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

电脑启动后,CPU逻辑电路被设计为只能运行内存中的程序,没有能力直接运行存在于软盘或硬盘中的操作系统,如果想要运行,必须要加载到内存(RAM)中。

BIOS是如何启动的:

CPU硬件逻辑设计为在加电瞬间强行将CS值置为0XF000,IP为0XFFF0,这样CS:IP就指向0XFFFF0这个位置,这个位置正是BIOS程序的入口地址。

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

BIOS程序被固化在计算机主机板上的一块很小的ROM芯片里。现在CS:IP已经指向了0XFFFF0这个位置,意味着BIOS开始启动。随着BIOS程序的执行,屏幕上会显示显卡的信息,内存的信息……说明BIOS程序在检测显卡,内存……期间,有一项对启动操作系统至关重要的工作,那就是BIOS在内存中建立中断向量表和中断服务程序。

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

中断向量表有256个中断向量,每个中断向量占4个字节,其中两个字节是CS值,两个字节是IP值。每个中断向量都指向一个具体的中断服务程序。

2.加载操作系统内核并为保护模式做准备

现在开始计算机要分三批逐次加载OS的内核代码。分别是引导程序bootsect,内核代码setup,内核代码system模块

加载引导程序bootsect

首先对CPU发送int 0x19中断,使CPU运行int 0x19中断对应的中断服务程序,这个中断服务程序的作用就是把软盘第一个扇区的程序加载到内存的指定位置。总结来说就是:找到软盘并加载第一扇区。这个第一扇区的内容就是bootsect,第一扇区称为启动扇区。

至此,第一批代码bootsect已经加载进内存了,下面的工作就是执行bootsect把软盘的第二,三批代码加载入内存。

加载第二部分程序setup

bootsect首先对内存进行规划:包括之后将要加载的setup程序的扇区数,被加载到的位置,启动扇区被BIOS加载的位置等等。总之就是规划好之后内存分布,方便之后使用。

bootsect接着复制自身代码。从内存0x07c00复制到内存0x90000处。这个时候说明OS开始根据自身的需求安排内存了。

最后,bootsect将setup加载至内存。其中加载过程需要借助BIOS提供的int 0x13中断向量指向的中断服务程序来完成。该程序将软盘第二个扇区开始的4个扇区,即setup.s对应的程序加载至内存的SETUPSEG(0x90200)处。

加载第三部内核代码--system模块

加载第三批代码仍然用int 0x13中断向量。

这次加载的扇区数是240个,为了让用户觉得不是机器故障,Linus在屏幕上设计了打出一行字符串:"Loading system ... "

加载完毕后再确定一下根设备号。

3.从实模式转换为32位保护模式

至此,3部分代码已经加载完毕,开始转变进入保护模式。

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

关中断是为了不响应外部中断,直到保护模式下的中断服务体系被构建完毕才会打开中断。

回顾一下,0x00000这个位置原来存放着由BIOS建立的中断向量表级BIOS数据区。这个复制会覆盖原区域。这样的好处是:

a)废除BIOS的中断向量表。

b)内存空间回收

c)让内核代码占据内存物理地址最开始的有利位置

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

设置并初始化这两个表

打开A20,实现32位寻址

A20为第21根地址线,实模式下启用前20根(A0~A19),保护模式将开启A20~A31,标志32位寻址。

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

为了建立保护模式下的中断机制,setup程序将对可编程中断控制器8259A进行重新编程,重新映射中断号。

接着执行代码:

...

mov ax,#0x0001   ! protected mode bit

lmsw ax

jmpi 0,8                 ! jmp offset 0 of segment 8 (cs)

...

其中8代表1000,最后两位表示内核特权级,11则表示用户权级;第三位0表示GDT,1则表示LDT;1表示选择表中的第1项(从0开始),这个用来确定为代码段(cs)的段基址和段限长等信息。示意图如下:

head.s开始执行

head.s的加载方式与之前的bootsect,setup有所不同,大致过程是:先将head.s汇编成目标代码,将用C语言编写的内核程序编译成目标代码,然后链接成system模块。也就是在内存既有内核程序,又有head程序。两者是紧挨着的。

head程序除了做一些调用main之外的工作之外,还主要用程序的自身代码在程序自身所在的内存空间创建了内存分页机制。

head程序将L6标号和main函数入口地址压栈,栈顶为main函数地址,目的是使head程序执行完后通过ret指令就可以直接执行main函数。正常来说main函数是不应该退出的,但如果main函数异常退出,就会返回这里标号L6处继续执行,防止系统崩溃。

这些压栈操作完成后,head执行setup_paging:去执行,开始创建分页机制。先要将页目录表和4个页表放在物理内存的起始位置。

4个页表分别映射0x0000到0xFFF000的所有页面,一个页面大小为4KB

head程序设置完的内存分布示意图

至此,开机到main函数执行之前的过程结束。

参考:Linux内核设计的艺术

时间: 2024-10-21 21:25:35

电脑从开机加电到操作系统main函数之前执行的过程的相关文章

linux系统启动过程具体解释-开机加电后发生了什么 --linux内核剖析(零)

本文參考了例如以下文章 深入理解linux启动过程 mbr (主引导记录(Master Boot Record)) 电脑从开机加电到操作系统main函数之前执行的过程 详细解释linux系统的启动过程及系统初始化 linux系统的启动流程 关于linux系统的启动流程我们能够按步进行划分为例如以下: BIOS POST自检 BIOS(Boot Sequence) 引导操作系统 载入对应引导上的MBR(bootloader) 主引导设置载入其BootLoader 载入操作系统 启动BIOS,准备实

《Linux内核设计的艺术》学习笔记(一)从开机加电到执行main函数之前的过程

分享一个最近丢了手机心塞到爆炸的我,现在穷的只剩下满脑子的智慧了,好了,我要开始学习了. 首先,搭建一个linux0.11的系统环境,贴出结果图. 从开机加电到执行main函数之前的过程. 1. 启动BIOS,准备实模式下的中断向量表和中断服务程序; 2. 从启动盘加载操作系统程序到内存,加载操作系统程序的工作就是利用第一步中断服务程序实现的; 3. 为执行32位的main函数做过渡工作. 启动BIOS,准备实模式下的中断向量表和中断服务程序 cpu的硬件设计为加电即进入16位实模式下状态运行,

【Linux内核】从开机加电到main函数执行(1)

从开机加电到main函数执行(1) 启动BIOS,准备中断 BIOS的任务是将硬盘中的操作系统加载到内存中. BIOS加载中断处理程序 BIOS的启动由硬件完成.8086系列在加电时进入16位实模式,将CS置为0xFFFF,IP置为0x0000, CS:IP指向0xFFFF0,指向了BIOS对应的地址. CS是代码段寄存器,IP是指令指针寄存器,两者组合形成的地址是要执行的指令的内存地址,在实模式下是绝对地址 如果这个位置没有可执行代码会就此死机.有代码的话就会执行.BIOS会执行自检程序,检查

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

1.启动BIOS,准备实模式下中断向量表和中断服务程序 在按下电源按钮的瞬间,CPU硬件逻辑强制将CS:IP设置为0xFFFF:0x0000,指向内存地址的0xFFFF0位置,此位置属于BIOS的地址范围.关于硬件如何指向BIOS区,这是一个纯硬件动作,在RAM实地址空间中,属于BIOS地址空间部分为空,硬件只要见到CPU发出的地址属于BIOS地址范围,直接从硬件层次将访问重定向到BIOS的ROM区中.这也就是为什么RAM中存在空洞的原因. BIOS程序在内存最开始的位置(0x00000)用1K

多玩YY语音的面试题:C++中如何在main()函数之前执行操作?

第一反应main()函数是所有函数执行的开始.但是问题是main()函数执行之前如何执行呢? 联想到MFC里面的 C**App类的theApp对象,其执行顺序就在main函数之前.道理相通,顺理推下,能够想到:如果在main函数之前声明一个类的全局的对象.那么其执行顺序,根据全局对象的生存期和作用域,肯定先于main函数. 示例如下: class simpleClass { public: simpleClass( ) { cout << "simpleClass construct

windows平台中让函数在main函数之前执行的方法

1.将要执行的代码写到类的构造函数中,并定义对应的全局变量2.将要执行的代码写到TLS回调函数中在c/c++中,我们都知道main函数是程序开始执行的地方,但是在进行反调试的时候,很多时候都需要调试检测函数在main函数之前执行. 1.将要执行的代码写到类的构造函数中,并定义对应的全局变量在windows平台中,执行我们手写的main函数之前,系统会执行一段CRTstartup代码,对系统的堆栈.全局变量.命令行参数.环境变量等进行初始化操作.该方法就是利用windows在执行main函数之前先

全局对象的构造函数会在main 函数之前执行

#include <iostream> using namespace std; class A { public: A() { cout << "Generator A" << endl; } } a = A(); int main() { cout << "Hello World" << endl; } 全局对象的构造会在main函数之前执行.

java main函数不执行?

今天脑袋短路,对于这个问题纠结了好久.这个问题具体是这样的: public class test { public static void main(String[] args) { test2 t = new test2(); System.out.println("" + t.i); t.meth(); } } public class test2 { public Integer i=5; test2(){ System.out.println("构造函数")

在main函数之外执行函数的情况

1.onexit函数 #include "stdafx.h" #include "iostream" #include <stdlib.h> using namespace std; int func() //必须为int返回值 { cout<<"This is after main function"<<endl; system("pause"); return 0; } int main