linux 系统启动 2

我在以前也写过一篇关于linux启动的一篇文章,但是当时对启动过程了解不是很到位,在这里我将对linux的启动重新进行记录一下。

本文我将围绕介绍linux内核是怎么被加载到内存中并执行。由于系统引导程序是依赖于计算机的体系结构的,这里只讨论80X86体系结构的计算机。

史前时代:BIOS

计算机在加电的时候是毫无用处的,因为在它所有的RAM中都是随机的数据,没有操作系统在运行。为了启动系统,一个特殊的硬件电路发出一个RESET的逻辑值到CPU的一个引脚上,并且在这之后像cs、eip等寄存器都被设置为固定值,并且会执行0xffffffff0物理地址的代码,而这块地址就是有名的ROM,而ROM中存放的程序呢?也就是我们常说的BIOS。由于它一般由底层的中断驱动程序模块,所以全部操作系统在booting的时候,操纵它来控制硬件设备。
在系统引导linux强制使用BIOS,那么BIOS的引导程序的本质操作有下面几种:
  1. 执行一系列的测试,确定有那些硬件,以及他们运行是否正确。这个阶段被称为POST(power-on self-test)。
  2.  初始化硬件设备
  3.  查找操作系统用以启动。事实上,依照BIOS的设置,这个程序会到硬盘或者CD_ROM等中的第一个区域,也就是常数的boot sector,对于这点你可以看我以前的博客
  4.  当找到一个可用的系统,那么将copy这个first sector的内容到RAM中,而后重指定地址开始执行代码,则开始载入系统。

远古时代: the boot loader

the boot loader是被BIOS调用的以加载操作系统kernel进入RAM的程序。
first sector的指令已经被加载到RAM中并且执行了,而这些执行的指令作用是加载包含kernel镜像的sector到RAM。
对于从硬盘启动对于不同实现有一些区别。在硬盘的first sector就是大名鼎鼎的MBR,它包含了分区表和一个小程序,它指定的是那一个分区的first sector包含的系统启动。但是对于window来说呢,有一些不同,他在这里指定了一个操作系统被启动,它是通过设置在一个硬盘启动分区设置一个flag,用MBR中程序去查找这个flag后启动,所以用户没有选择操作系统的权利。在后面我们将看见linux很灵活,因为它替换了MBR中的程序,允许我们选择操作系统。

从硬盘启动linux
对于boot loader有很多种,比较著名的是LInux LOader(LILO)和GRand Unified Bootloader(GRUB).GRUB比FIFO更加高级一些,因为它能读取不同文件系统的硬盘,并且启动程序。
对于LILO既可以装在MBR或者每一个硬盘分区的boot sector上面,但是结果都是在loader执行后,由用户选择启动的操作系统。事实上LILO太大了,一般都是存放一部分到MBR或者是分区boot sector中。当执行完前部分后,再调用后面的部分。
对于linux启动,LILO将执行下面过程:
  1.  执行一个BIOS程序现实登陆信息,比如说“loading....”
  2.  执行一个BIOS程序加载kernel所在分区以进行初始化
  3.  执行一个BIOS程序去加载剩下的kernel镜像到RAM中,依据不同的情况从低地址或高地址开始加载。
  4.  调到setup()函数。

中世纪时代:setup()函数

setup函数是汇编语言,并且它是由linker的时候放在相对位置固定的kernel镜像中。
setup函数是初始化硬件设备(为了加强健壮性和可移植性,linux并不依赖BIOS的POST),并且创建环境来执行kernel的程序。setup函数一般有下面集中功能:
  1. 调用一个BIOS例程,建立一个描述系统物理内存的表
  2.  设置键盘重复间隔和频率
  3.  初始化视频适配卡
  4.  再次初始化硬盘并且决定硬盘的参数
  5.  检查IBM MCA总线
  6.  检查PS/2指向的设备
  7.  检查BIOS支持的高级电源管理
  8.  如果BIOS支持强化硬盘设备服务(EDD),那么调用相应的BIOS程序建立一张表描述硬盘使用情况。
  9.  如果kernel镜像加载是以低地址加载进入RAM中,那么setup函数被移动到低地址开始地方,负责不动。只是因为kernel是为了能够放到指定硬盘位子和为了加快启动,都是压缩过的,那么在解压缩的时候,会在加载kernel的后面的空间中使用一个缓冲区,那么如果如果setup函数在kernel内核后面那么就会被覆盖了
  10.  设置A20cpu引脚为8024键盘控制器
  11.  建立一个临时的中断描述表(IDT)和临时全局描述表(GDT)
  12.  如果需要重置浮点单元
  13.  重新编写可编程终端控制器(PIC)用以标识全部终端除了IRQ2.
  14.  切换CPU到重新编写可编程终端控制器(PIC)用以标识全部终端除了IRQ2.
  15.  切换CPU从实际模式到保护模式
  16.  跳转到startup_32汇编函数中去

文艺复兴:startup_32()函数



其实这里有2个startup_32()函数,第一个函数执行下面这些操作:
  1. 初始化段寄存器和临时栈
  2.  清空eflags寄存器
  3.  将kernel指定的未初始化_edata和_end变量,全部设置为0
  4.  调用decompress_kernel()函数去解压缩kernel镜像。依据高地址装入和低地址装入不同,把解压后的kernel镜像开始放在不同的地方,但是最终都定位到指定的地方
  5.  将程序跳转到上面指定的解压后kerne存放位置
而这个解压后的kernel文件的开始,则就是前面我提到的第二个start_up32()函数。它的作用是建立linux的第一个进程(process 0)的环境。那么这个函数执行以下操作:
  1. 设置段寄存器为最终值
  2.  设置kernel的bss段为0
  3.  新建一个临时页表,其中包含swapper_pg_dir和pg0,用以映射线性物理地址
  4.  在cr3寄存器中保存页全局目录(PGD)
  5.  为0号进程设置内核态堆栈
  6.  再次清空eflags寄存器
  7.  调用setup_idt()函数用无中断处理函数去设置IDT
  8.  把从BIOS中获得的系统参数和传递给操作系统的参数放入第一个页框中
  9.  识别处理器型号
  10.  用GDT和IDT标来填充gdtr和idtr寄存器
  11.  跳到start_kernel()函数。

现代:start_kernel()函数

start_kernel()函数完成对linux kernel的初始化,基本上所有的模块的初始化都是这个函数完成,在这里只列举了我们关心的:
  1. 通过调用sched_init()函数来初始化调度程序
  2. 通过调用build_all_zonelists()函数初始化内存区域
  3. 调用page_alloc_init()函数出书画伙伴系统分配程序
  4. 调用trap_init()来初始化IDT
  5. 调用softirq_init()函数初始化TASKLET_SOFTIRQ和HI_SOFTIRQ
  6. 调用time_init()初始化系统时间
  7. 调用calibrate_delay()函数决定cpu时钟速度
  8. 通过调用kernel_thread()函数创建进程1.
当进程1也就是我们常说的init进程,新建后,将执行的操作请参考我前面写的l linux 启动

先写这篇博客,来开启我对<<深入理解linux内核>>这本书的研究。。。

参考资料:
<<深入理解linux内核>>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值