03LINUX内核引导启动程序

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。


CPU 在实模式运行方式时,段寄存器用来放置一个内存段地址 (比如0x9000),而此时在该段内可以寻址64KB 的内存。但 当进入保护模式运行方式时,此时段寄存器中放置的并不是内 存中的某个地址值,而是指定描述符表中某个描述符项相对于 该描述符表基址的一个偏移量。在这个8 字节的描述符中含有 该段线性地址的‘段’基址和段的长度,以及其它一些描述该 段特征的比特位。因此此时所寻址的内存位置是这个段基址加 上当前执行代码指针eip 的值。而当前描述符表的基地址则保 存在描述符表寄存器中,如全局描述符表寄存器gdtr、中断门 描述符表寄存器idtr,加载这些表寄存器须使用专用指令lgdt 或 lidt。当然,此时所寻址的实际物理内存地址,还需要经过内 存页面处理管理机制进行变换后才能得到。 简而言之,32 位保护模式下的内存寻址需要经过描述符表中的 描述符和内存页管理来确定。

功能描述
百度文库
setup.s 程序


setup 程序的作用主要是利用ROM BIOS 中断读取机器系统 数据,并将这些数据保存到0x90000 开始的位置(覆盖掉了 bootsect 程序所在的地方)。内核相关程序使用这些参数将。 然后setup 程序将system 模块从0x10000-0x8ffff(当时认为 内核系统模块system 的长度不会超过此值:512KB)整块向 下移动到内存绝对地址0x00000 处。接下来加载中断描述符 表寄存器(idtr)和全局描述符表寄存器(gdtr),开启A20 地址 线,重新设置两个中断控制芯片8259A,将硬件中断号重新 设置为0x20 - 0x2f。最后设置CPU 的控制寄存器CR0(也称 机器状态字),从而进入32 位保护模式运行,并跳转到位 于system模块最前面部分的head.s 程序继续运行。 为了能让head.s 在32位保护模式下运行,在本程序中临时设 置了中断描述符表(idt)和全局描述符表(gdt),并在gdt 中设 置了当前内核代码段的描述符和数据段的描述符。在下面 的head.s 程序中会根据内核的需要重新设置这些描述符表。
head.s 程序

这段程序实际上处于内存绝对地址0 处开始的地方。首先是加 载各个数据段寄存器,重新设置中断描述符表idt,共256 项, 并使各个表项均指向一个只报错误的哑中断程序。然后重新设 置全局描述符表gdt。接着使用物理地址0 与1M 开始处的内容 相比较的方法,检测A20 地址线是否已真的开启(如果没有开 启,则在访问高于1Mb 物理内存地址时CPU 实际只会访问 (IP MOD 1Mb)地址处的内容),如果检测下来发现没有开 启,则进入死循环。然后程序测试PC 机是否含有数学协处理 器芯片(80287、80387 或其兼容芯片),并在控制寄存器 CR0 中设置相应的标志位。接着设置管理内存的分页处理机 制,将页目录表放在绝对物理地址0 开始处(也是本程序所处 的物理内存位置,因此这段程序将被覆盖掉),紧随后面放置 共可寻址16MB 内存的4 个页表,并分别设置它们的表项。最 后利用返回指令将预先放置在堆栈中的/init/main.c 程序的入口 地址弹出,去运行main()程序。
LINUX内核引导程序
武汉大学计算机学院 郑鹏 Email:pzheng51@163.com
LINUX系统初始化1

1.当PC启动时,Intel系列的CPU首先进入的 是实模式,并开始执行位于地址0xFFFF0处 的代码(只用于实模式高地址位忽略),也就 是ROM-BIOS起始位置的代码。BIOS先进行 一系列的系统自检,然后初始化位于地址0的 中断向量表。最后BIOS将启动盘的第一个扇 区,也就是bootsect.S,装入到0x7C00(31K)处, 并开始执行此处的代码。
LINUX系统初始化6

启动引导时内核在内存中的位置和移动后的位置情况
bootsect.s 程序

功能描述

bootsect.s 代码是磁盘引导块程序,驻留在磁盘的第一个扇 区中(引导扇区,0 磁道(柱面),0 磁头,第1 个扇区)。在PC 机加电ROM BIOS 自检后,引导扇区由BIOS 加载到内存 0x7C00 处,然后将自己移动到内存0x90000 处。该程序的 主要作用是首先将setup 模块(由setup.s 编译成)从磁盘加 载到内存,紧接着bootsect 的后面位置(0x90200),然后利 用BIOS 中断0x13取磁盘参数表中当前启动引导盘的参数, 接着在屏幕上显示“Loading system...”字符串。再者将 system 模块从磁盘上加载到内存0x10000 开始的地方。随后 确定根文件系统的设备号,若没有指定,则根据所保存的 引导盘的每磁道扇区数判别出盘的类型和种类(是1.44M A 盘?)并保存其设备号于root_dev(引导块的0x508 地址处), 最后长跳转到setup 程序的开始处(0x90200)执行setup 程 序。
Intel 32 位保护运行机制

中断描述符表IDT 的结构与GDT 类似,在 Linux 内核中它正好位于GDT 表的后面。 共含有256 项8字节的描述符。但每个描述 符项的格式与GDT 的不同,其中存放着相 应中断过程的偏移值(0-1,6-7 字节)、 所处段的选择符值(2-3 字节)和一些标志 (4-5 字节)。
Intel 32 位保护运行机制

Linux 内核中所使用的描述符表在内存中的示意图
系统运行

初始化进程开始执行/etc/init、/bin/init 或 /sbin/init中的一个之后,系统内核就不再对程 序进行直接控制了。之后系统内核的作用主要 是给进程提供系统调用,以及提供异步中断事 件的处理。多任务机制已经建立起来,并开始 处理多个用户的登录和fork()创建的进程。
LINUX系统初始化2


2.当bootsect.S开始运行时,将自己装入到绝对地址 0x90000 (576K)处,再将其后的2k字节代码 (boot/setup.s)装入到地址0x90200处,最后将核心的其 余部分(system 模块)装入到0x10000。当系统装入 时,会显示Loading...信息。 装入完成后,控制转向另一个实模式下的汇编语言代 码boot/setup.S。 因为当时system 模块的长度不会超 过0x80000 字节大小(即512KB),所以它不会覆盖 在0x90000处开始的bootsect 和setup 模块。随后将 system 模块移动到内存起始处,这样system 模块中代 码的地址也即等于实际的物理地址。便于对内核代码 和数据的操作。
内核提供的各种系统调用


进程的基本概念和系统的基本数据结构 从系统内核的角度看来,一个进程仅仅是进程控制 表(process table)中的一项。进程控制表中的每一 项都是一个task_struct 结构,而task_struct 结构本身 是在include/linux/sched.h中定义的。在task_struct结构 中存储各种低级和高级的信息,包括从一些硬件设 备的寄存器拷贝到进程的工作目录的链接点。 进程控制表既是一个数组,又是一个双向链表,同 时又是一个树。其物理实现是一个包括多个指针的 静态数组。此数组的长度保存在include/linux/tasks.h 定义的常量NR_TASKS中,其缺省值为128,数组中 的结构则保存在系统预留的内存页中。链表是由 next_task 和prev_task两个指针实现的,而树的实现 则比较复杂。
LINUX系统初始化3

3.Setup部分首先设置一些系统的硬件设备, 然后将核心从0x10000处移至0x1000处。这 时系统转入保护模式,开始执行位于 0x1000处的代码。
LINUX系统初始化4


4.接下来是内核的解压缩。0x1000处的代码来自于文 件zBoot/head.S,它用来初始化寄存器和调用 decompress_kernel()程序。decompress_kernel()程序由 zBoot/inflate.c, zBoot/unzip.c 和zBoot/misc.c组成。解 压缩后的数据被装入到了0x100000处,这也是Linux 不能在内存小于2M的环境下运行的主要原因。 5.解压后的代码在0x1010000处开始执行,紧接着所 有的32位的设置都将完成: IDT、GDT和LDT将被装 入,处理器初始化完毕,设置好内存页面,最终调 用start_kernel过程。这大概是整个内核中最为复杂的 部分。
内核提供的各种系统调用


进程的基本概念和系统的基本数据结构 系统启动后,内核通常作为某一个进程的代表。一 个指向task_struct的全局指针变量current用来记录正 在运行的进程。变量current只能由kernel/sched.c中 的进程调度改变。当系统需要查看所有的进程时, 则调用for_each_task,这将比系统搜索数组的速度 要快得多。 某一个进程只能运行在用户方式(user mode)或内 核方式(kernel mode)下。用户程序运行在用户方 式下,而系统调用运行在内核方式下。在这两种方 式下所用的堆栈不一样:用户方式下用的是一般的 堆栈,而内核方式下用的是固定大小的堆栈(一般 为一个内存页的大小)。
head.s 程序

head.s 程序执行结束后,已经正式完成了 内存页目录和页表的设置,并重新设置了 内核实际使用的中断描述符表idt 和全局描 述符表gdt。另外还为软盘驱动程序开辟了 1KB 字节的缓冲区。
head.s 程序

system 模块在内存中的映像示意图
Intel 32 位保护运行机制
LINUX系统初始化5

6.start_kernel()程序用于初始化系统内核的各个 部分,包括:



设置内存边界,调用paging_init()初始化内存页面。 初始化陷阱,中断通道和调度。 对命令行进行语法分析。 初始化设备驱动程序和磁盘缓冲区。 校对延迟循环。

7.最后,系统核心转向move_to_user_mode(), 以便创建初始化进程(init)。此后,进程0开 始进入无限循环。
setup.s 程序


在setup.s 程序执行结束后,系统模块system 被移动 到物理地址0x0000 开始处,而从0x90000 处则存放 了内核将会使用的一些系统基本参数。 此时临时全局表中有三个描述符,第一个是 (NULL)不用,另外两个分别是代码段描述符和 数据段描述符。它们都指向系统模块的起始处,也 即物理地址0x0000 处。这样当setup.s 中执行最后一 条指令 'jmp 0,8 '(第193 行)时,就会跳到head.s 程序开始处继续执行下去。这条指令中的'8'是段选 择符,用来指定所需使用的描述符项,此处是指gdt 中的代码段描述符。'0'是描述符项指定的代码段中 的偏移值。
Intel 32 位保护运行机制

针对不同的使用方面,描述符表分为三种:



全局描述符表(GDT) 中断描述符表(IDT) 局部描述符表(LDT)

当CPU 运行在保护模式下,某一时刻GDT 和IDT 分 别只能有一个,分别由寄存器GDTR和IDTR 指定它 们的表基址。局部表可以有0-8191 个,其基址由当 前LDTR 寄存器的内容指定,是使用GDT 中某个描 述符来加载的,也即LDT 也是由GDT 中的描述符来 指定。但是在某一时刻同样也只有其中的一个被认 为是活动的。一般对于每个任务(进程)使用一个 LDT。在运行时,程序可以使用GDT 中的描述符以 及当前任务的LDT 中的描述符。

setup.s 程序结束后内存中程序示意图
setup.s 程序
head.s 程序

head.s 程序在被编译后,会被连接成system 模块的最前面开始部分,这也就是为什么称 其为头部(head)程序的原因。从这里开始, 内核完全都是在保护模式下运行了。head.s 汇编程序与前面的语法格式不同,它采用的 是AT&T 的汇编语言格式,并且需要使用 GNU 的gas 和gld2进行编译连接。因此请注 意代码中赋值的方向是从左到右。
相关文档
最新文档