Chinaunix首页 | 论坛 | 博客
  • 博客访问: 5488836
  • 博文数量: 922
  • 博客积分: 19333
  • 博客等级: 上将
  • 技术积分: 11226
  • 用 户 组: 普通用户
  • 注册时间: 2007-03-27 14:33
文章分类

全部博文(922)

文章存档

2023年(1)

2020年(2)

2019年(1)

2017年(1)

2016年(3)

2015年(10)

2014年(17)

2013年(49)

2012年(291)

2011年(266)

2010年(95)

2009年(54)

2008年(132)

分类: LINUX

2013-05-24 11:20:48

原文地址:Linux内核启动分析 作者:fuchao2012

0 引言

    从嵌入式系统到超级服务站,Linux 已获得广泛的应用。Linux 是一个完整通用的Unix 类分布式操作系统,它的结构紧凑、功能强、效率高、可移植性好且在Internet 上可自由取用。
    Linux 和Unix 操作系统一样,操作系统的主要功能集中在内核,内核中包含进程管理、文件管理、设备管理和网络管理等部分。本文深入探讨Linux 操作系统内核的启动过程,希望对Linux 学习和使用者有所启迪。

1 内核结构及平台相关性

    本文分析的内核版本为2.6.9。当我们使用tar 命令将linux-2.6.9.tar.bz2 解开时,内核源代码被放到了linux-2.6.9/目录中。Linux 内核各功能文件分别存放在linux-2.6.9/ 目录下的相应子目录中。
    Linux 操作系统可以工作在多种不同硬件平台上,如80x86CPU 系列(80386 以上)、SUN sparc64 和arm26 等。为了让Linux 体现优良的可移植性,Linux 内核代码针对不同的硬件平台包含有对应的启动和初始化程序。这些程序处于arch/子目录中。用户完全可以根据自己的需要,从内核代码中各取所需,即时编译和更换系统内核,这也是Linux 操作系统获得世界各地网络爱好者普遍支持的主要原因。鉴于绝大部分Linux 应用于Intel 80x86 系列平台,所以本文也仅限对Linux在80x86 系列平台的启动过程进行分析。本文所要探讨的启动程序位于arch/i386/boot/ 目录,系统的启动过程主要由bootsect.s、setup.s 和head.s 等3 个汇编程序完成。

2 系统启动流程

    当PC 的电源打开后,80x86 结构的CPU将自动进入实模式,并从地址0XFFFF0 开始自动执行程序代码,这个地址通常是ROM-BIOS 中的地址。PC 机的BIOS 将执行某些系统的检测,如测试内存等,并在物理地址0 处开始初始化中断向量。此后,它将可启动设备的第1 个扇区(磁盘引导扇区,512 字节)读入内存绝对地址0x7C00 处,并把控制权交给这段代码。Linux的这段代码(boot/bootsect.s)是用8086 汇编语言编写,它首先把自己举到内存地址0x90000,并跳转到这里执行。然后把启动设备中后2KB代码(boot/setup.s)读入到内存0x90200 处,而内核的其它部分(system模块)则被读入到从地址0x10000 开始处。随后setup.s 将把system 模块移动到内存起始处,这样system 模块中代码的地址就等于实际的物理地址,便于对内核代码的操作。
    图1 展示了内核启动时引导程序在内存中的动态位置。从图1 可看出,Linux 内核编译后生成的内核映像(system模块)最大只能为512 KB,超出这个大小将无法装入。这虽可以满足一般内核要求,但在特殊情况下内核也会大于512 K,这时可用两种办法:①把内核映像压缩(ZIP 格式),系统进入操作模式后再展开;②把某些非基本的具有独立功能的部分编译成模块,在系统启动后或启动过程中插入。

    待系统模块(system)头部运行结束,所有32 位运行方式的设置启动都完成,IDT、GDT 以及LDT 被加载,处理器和协处理器被确认,分页工作也设置好了。最终调用init/main.c 中的main()程序进入系统的初始化过程,至此系统获得正常启动。

3 启动过程分析

    主要对bootsect.s、setup.s 和head.s 的工作机理做了较为详细的阐述。

3.1 bootsect 模块分析

    bootsect.s 代码是磁盘引导块程序,驻留在引导盘的引导扇区(0 磁道,0 磁头,第1 扇区)。在PC 加电ROM BIOS 自检后,bootsect.s 由BIOS 自动加载到内存0x7C00 处,然后将自己移到内存0x90000 处。图2 代码显示了bootsect.s 的移动过程,其中#BOOTSEG 为0x7C00,#INITSEG 为0x9000。接下来,程序利用BIOS中断,INT 0x13 将setup 模块从磁盘第2 个扇区开始读到0x90200 开始处,共读4 个扇区。如果读出错误,则CF标志置位,程序复位驱动器,并重试。加载setup 模块后,程序利用中断取磁盘驱动器参数,并将中断返回的每磁道扇区数保存在变量sectors 中。然后程序将system 模块加载到内存0x10000 处。加载system 模块期间,显示“Loading system?”信息。为了提高加载速度,只要可能,就每次加载整条磁道的数据。从磁盘读取一次数据后,程序就比较当前所读段是否就是系统数据末端所处的段(#ENDSEG),如果不是,就跳转至ok1_read 标号处继续读数据。最后,程序向软驱控制卡的驱动端口0x3f2 写0,关闭软驱电动机。
程序运行
jmpi 0,SETUPSEG
跳转到0x9020:0000 处,CPU开始执行setup 模块。

3.2 setup 模块分析

    setup.s首先利用ROMBIOS中断读取机器系统参数(光标位置、扩展内存数、硬盘参数表等),并将这些数据保存到内存0x90000 开始的位置(覆盖掉了bootsect 程序)。这些参数将被内核中相关程序使用,例如设备驱动程序集中的ttyio.c。随后系统进入保护模式运行。CPU 在实模式下运行,寻址一个内存地址主要是使用段基址和段内偏移值,段值被存放在段寄存器中;而在保护模式运行方式下,段寄存器中存放的是一个描述表中某项的索引值。索引值指定的描述符项中含有需要寻址的内存段的基地址、段的最大长度值和段的访问级别等信息。和实模式下的寻址相比,段寄存器值换成了段描述符项索引。
    接下来,程序关闭中断,将system模块整体向内存低端移动0x1000。每次移动0x8000 字,循环执行8 次。然后程序执行lidt idt_48 加载中断描述符表(idt)寄存器;执行lgdt gdt_48 加载全局描述符表(gdt)寄存器。此时中断描述符表中只有一个空项(值全为0)。全局描述符表中有3 个描述符项:第1 项无用,但必须存在;第2 项(索引值0x08)是系统代码段描述符,所定义的段基址为0,段中代码可被读和执行,段长为8 M;第3项(索引值0x10) 是系统数据段描述符,所定义的段的基址为0,段中数据可读和可写,段限长为8M。
    最后,程序重置协处理器,对8259 中断控制芯片编程,完成进入保护虚地址模式的所有准备工作。通过设置机器状态字MSW(第0 号控制寄存器CR0 的低16 位)中的PE 位使CPU进入保护模式,开始运行system模块中的head.s(指令jmpi 0,8)。注意,CPU 已在保护模式下运行,CS 置8 表示请求特权级0,使用全局描述符表中的第1 项(索引值0x08)。图3 显示了setup.s 结束后内存分布。

3.3 head 模块分析

    全局描述中的第1 项表明CPU 开始执行内存基址为0x0000 处的代码,这里存放的是system 模块的头部,即为32位汇编程序kernel/head.s。head.s 采用的是AT&T 汇编语言格式,并且需要使用GNU 的gas 和gld 进行编译连接。
    程序首先将%ds、%es、%fs 和%gs 置立即数0x10,这里的0x10 并不是把地址0x10 装入各个段寄存器,它是全局描述符表中的偏移值。然后程序设置系统新的中断描述符表(lidt idt_
drscr),新的idt 处于head.s 尾,共256 项(每项8B),并都指向ignore_int 哑中断门,真正实用的中断门以后再安装。中断门描述符的格式及程序进入中断服务例程如图4 所示。

    程序重新设置系统全局描述符表(lgdt gdt_descr),新的GDT在head.s 末端,共256 项。前4 项分别是空项(不用)、代码段描述符、数据段描述符和系统段描述符,其中系统段描述符Linux没有派用处。后面还预留了252 项的空间,用于放置所创建任务的局部描述符(LDT)和对应的任务状态段TSS 的描述符。

    然后程序设置页表并启动分页机制,Linux 在386 系列平台使用2 级页表,即页表目录和页表。从基地址0x1000 处开始填充页表,0x0000 处将存放页表目录。当前代码的物理基地址已变为0x1000(指令.org 0x1000),程序连续分配4 个页表,每个页表大小为4 K。页表项为4 个字节,包含页框地址、内存页信息(是否在内存、是否修改过、普通用户还是超级用户等)。程序紧接对5 页内存(1 页目录+4 页页表) 清零,开始设置页目录中的项。“$pg0+7”表示0x00001007 是页目录表中的第1 项。则第1 个页表所在的地址为:0x00001007&0xfffff000;第1 个页表的属性标志为:0x00001007&00000fff,表示该页存在,用户可读写。xor %eax,%eax;movl %eax,%cr3 设置页目录基址寄存器cr3
Movl %cr0,%eax;
orl $0x80000000,%eax;
movl 
%eax,%cr0
启动控制寄存器cr0 的分页标志(PG)位。
    至此386 系列CPU 的分段机制和分页机制均已启动,内核也置于最终的适当位置,与平台有关的启动过程基本完成。系统内核将进入与平台无关的内核数据结构初始化工作。采用的方法是,在head.s 中将main.c 程序的地址压入堆栈。这样,在设置分页处理(setup_paging)结束后执行“ret”返回指令时就会将main.c 程序的地址弹出堆栈,并去执行main.c 程序。main.c 调用所有设置过程,如为设备申请I/O 空间、其它页表的初始化、缓冲区初始化、网络初始化和设置多任务等,然后系统转移到用户模式,创建并启动初始进程init。这些初始化设置过程都与Linux 的设备管理、文件管理、网络管理和CPU管理和内存管理有关。这样Linux 操作系统完全启动。

4 结束语

    Linux 是当前流行的一种操作系统,Linux 有许多不同的版本,比较流行的有Red Hat、Slackware 和Debian,各版本的Linux 之间的区别不大,其内核都差不多。本文就2.6.9 内核版本对Linux 的启动过程进行了详细的分析,希望Linux 使用者能从中得到一些启迪。
阅读(1027) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~