已阅读5页,还剩5页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
本篇文档目录: 1.1. 创建Linux内核镜像 1.2. 引导:概述 1.3. 引导:BOISPOST 1.4. 引导:bootsector和setup 1.5. 采用LILO引导器 1.6. 高级初始化 1.7. SMP机在x86系统上引导 1.8. 释放初始化数据和代码 1.9. 处理内核命令 以下是正文: 1. 引导 1.1. 创建Linux内核镜像 本部分解释了编译内核时每个步骤以及每个步骤的输出。这个创建过程依赖于不同的体系结构,这里强调一下我们仅考虑创建一个Linuxx86的内 核。当用户输入“makezImage”或者“makebzImage”时,输出的可启动内核镜像就分别存放为 arch/i386/boot/zImage或者arch/i386/boot/bzImage。下面来看看这个镜像是怎么创建的: 1) 首先C和汇编源文件被编译成ELF中间文件(.o),其中一部分按照逻辑分组打包成压缩文件(.a)。 2) 调用ld指令将以上的.o和.a文件被链接成一个静态的80386可执行文件vmlinux。 3) 接着调用nmvmlinux指令剔除不相关和不感兴趣的符号并创建系统关系图。 4) 进入arch/i386/boot目录。 5) Bootsect.S文件按照目标是bzImage(zImage)在定义(不定义)D_BIG_KERBEL_宏下进行预处理,结果分别存为bbootsect.s(bootsect.s)。 6) Bbootsect.s文件被编译并转换成“rawbinary”格式的bbootsect文件(bootsect.s被转换成“raw”格式文件bootsect)。 7) setup.s(setup.s包含了video.s文件)被预处理成bzImage需要的bsetup.s或者zImage需要的setup.s文 件。这个过程和bootsector一样,bzImage镜像需要定义.D_BIG_KERNEL_宏,结果被转换成“rawbinary”格式的 bsetup,zImage镜像则被转换成“raw”格式的setup。 8) 进入arch/i386/boot/compressed目录,移出/usr/src/linux/vmlinux文件中ELF标识节.note和.comment,并将其转换成rawbinary格式存放到临时文件$tmppiggy。 9) 将$tmppiggy压缩成$tmppiggy.gz 10) .将$tmppiggy.gz链接成重定向文件piggy.o 11) 编译压缩程序head.S和misc.c文件 12) 将head.o、misc.o和piggy.o链接成bvmlinux(或者vmlinux),注意vmlinux的标号.Ttext0x1000和 bvmlinux的标号.Ttext0x100000的不同,这是由于bzImage压缩装载器是从高位装载的。 13) 将bvmlinux转换成“rawbinary”文件bvmlinux.out,移出ELF节.note和.comment。 14) 回到arch/i386/boot目录并调用tools/build将bbootsect、bsetup和压缩后的bvmlinux链接成bzImage。这个过程将向bootsector末尾添加重要的变量例如setup_sects和root_dev。 bootsector的大小总是512字节,setup的大小必须大于4个分节且受限于12K,这个规则如下: 0x4000bytes=512+setup_sects*512+运行bootsector/setup所需堆栈空间 在后面将说明是那个部分造成了这种限制。 BzImage文件大小的上限采用LILO启动时为2.5M,采用冷启动如软盘或者光盘等则为1048560字节。 注意,tools/build工具检验了boot段的大小、内核镜像的大小和setup的低范围地址,并没有检测setup的高范围地址。因此,在setup.S文件末尾的“.space”节增加一个大的地址数值就会很容易创建一个无法使用的内核。 1.2. 引导:概述 启动过程是和体系结构相关的,这里我们仅关注IBMPC/IA32体系。由于旧有设计以及向前兼容,PC机采用了以前流行的风格启动操作系统。这个过程可以被分为一下六个逻辑步骤: 1) BOIS选择启动设备。 2) 从启动设备装载bootsector。 3) Bootsector装载setup,解压缩程序和内核镜像。 4) 在保护模式下解压内核。 5) 汇编代码执行低级初始化。 6) 执行上层初始化。 1.3. 引导:BOISPOST 1) 电源启动时钟发生器并在总线上产生一个#POWERGOOD的中断。 2) 产生CPU的RESET中断(此时CPU处于8086工作模式)。 3) %ds=%es=%fs=%gs=%ss=0,%cs=0xFFFF0000,%eip=0x0000FFF0(ROMBIOSPOSTcode). 4) 在中断无效状态下执行所有POST检查。 5) 在地址0初始化中断向量表IVT。 6) 0x19中断以启动设备号为参数调用BIOS启动装载程序。这个程序从0扇面1扇区物理位置0x7C00开始装载。 1.4. 引导:bootsector和setup 用来启动内核的bootsector可以是以下几种: l Linuxbootsector l LILO等bootsector l 无bootsector 以下详细解释linuxbootsector。下面一些代码,负责初始化用作段变量的宏定义。 29SETUPSECS=4/*defaultnrofsetup.sectors*/ 30BOOTSEG=0x07C0/*originaladdressofboot.sector*/ 31INITSEG=DEF_INITSEG/*wemoveboothere.outoftheway*/ 32SETUPSEG=DEF_SETUPSEG/*setupstartshere*/ 33SYSSEG=DEF_SYSSEG/*systemloadedat0x10000(65536)*/ 34SYSSIZE=DEF_SYSSIZE/*systemsize:#of16.byteclicks*/ (左边的数值是代码的行号)在文件include/asm/boot.h中定义了DEF_INITSEG,DEF_SETUPSEG,DEF_SYSSEG和DEF_SYSSIZE的值。 /*Donttouchthese,unlessyoureallyknowwhatyouredoing.*/ #defineDEF_INITSEG0x9000 #defineDEF_SYSSEG0x1000 #defineDEF_SETUPSEG0x9020 #defineDEF_SYSSIZE0x7F00 以下来看看bootsect.S的源代码: 54movw$BOOTSEG,%ax 55movw%ax,%ds 56movw$INITSEG,%ax 57movw%ax,%es 58movw$256,%cx 59subw%si,%si 60subw%di,%di 61cld 62rep 63movsw 64ljmp$INITSEG,$go 65#bde.changed0xff00to0x4000tousedebuggerat0x6400up(bde).We 66#wouldnthavetoworryaboutthisifwecheckedthetopofmemory.Also 67#myBIOScanbeconfiguredtoputthewinidrivetablesinhighmemory 68#insteadofinthevectortable.Theoldstackmighthaveclobberedthe 69#drivetable. 70go:movw$0x4000.12,%di#0x4000isanarbitraryvalue= 71#lengthofbootsect+lengthof 72#setup+roomforstack; 73#12isdiskparmsize. 74movw%ax,%ds#axandesalreadycontainINITSEG 75movw%ax,%ss 76movw%di,%sp#putstackatINITSEG:0x4000.12. 代码54行63行将bootsector从地址0x7C00移动到0x90000,由以下过程完成: 1) set%ds:%sito$BOOTSEG:0(0x7C0:0=0x7C00) 2) set%es:%dito$INITSEG:0(0x9000:0=0x90000) 3) setthenumberof16bitwordsin%cx(256words=512bytes=1sector) 4) clearDF(direction)flaginEFLAGStoauto.incrementaddresses(cld) 5) goaheadandcopy512bytes(repmovsw) Thereasonthiscodedoesnotuserepmovsdisintentional(hint.code16). 64行跳转到标号go:(一个最新创建的bootsector的拷贝,也就是在0x9000段)。这和接下来的三段指令(6476行)在$ INITSEG:0x4000.0xC段初始化一个堆栈,也就是指令%ss=$INITSEG(0x9000)和%sp=0x3FF4 (0x4000.0xC)。这就是我们前面提到的setup大小限制的来历(见1.1节)。 77103行代码建立了第一个磁盘的参数表,以便允许多扇区读操作。 77#ManyBIOSsdefaultdiskparametertableswillnotrecognise 78#multi.sectorreadsbeyondthemaximumsectornumberspecified 79#inthedefaultdisketteparametertables.thismaymean7 80#sectorsinsomecases. 81# 82#Sincesinglesectorreadsareslowandoutofthequestion, 83#wemusttakecareofthisbycreatingnewparametertables 84#(forthefirstdisk)inRAM.Wewillsetthemaximumsector 85#countto36.themostwewillencounteronanED2.88. 86# 87#Highdoesnthurt.Lowdoes. 88# 89#Segmentsareasfollows:ds=es=ss=cs.INITSEG,fs=0, 90#andgsisunused. 91movw%cx,%fs#setfsto0 92movw$0x78,%bx#fs:bxisparametertableaddress 93pushw%ds 94ldsw%fs:(%bx),%si#ds:siissource 95movb$6,%cl#copy12bytes 96pushw%di#di=0x4000.12. 97rep#dontneedcld.doneonline66 98movsw 99popw%di 100popw%ds 101movb$36,0x4(%di)#patchsectorcount 102movw%di,%fs:(%bx) 103movw%es,%fs:2(%bx) 通过0x13BOIS服务0号函数重置软盘管理器,并且在bootsector完成后立即载入setup部分。也就是说在物理地址0x90200($INITSEG:0x200)处再次调用0x13BOIS服务2号函数。这个过程发生在107124行。 107load_setup: 108xorb%ah,%ah#resetFDC 109xorb%dl,%dl 110int$0x13 111xorw%dx,%dx#drive0,head0 112movb$0x02,%cl#sector2,track0 113movw$0x0200,%bx#address=512,inINITSEG 114movb$0x02,%ah#service2,readsector(s) 115movbsetup_sects,%al#(assumeallonhead0,track0) 116int$0x13#readit 117jncok_load_setup#ok.continue 118pushw%ax#dumperrorcode 119callprint_nl 120movw%sp,%bp 121callprint_hex 122popw%ax 123jmpload_setup 124ok_load_setup: 如果由于某些原因出错,例如无法使用的软盘或者在运行过程中有人弹出了磁盘等,装载过程将输出错误代码并且无限循环尝试本过程。除非重试成功(这通常不会发生,如果出现其他错误后果将更严重),唯一退出这个循环的办法就是重新启动机器。 如果成功装载配置代码部分,流程将跳转到ok_load_setup标签。 紧接着,启动程序就在物理地址0x10000装载压缩后的内核。这样做是为了保护低位(064K)内存的固件数据区。在内核装载后,启动程序跳 转到地址$SETUPSEG:0。一旦这些固件数据不再需要的时候,它们会被从0x10000移动到0x1000地址的完整内核镜像覆盖。这个过程由 setup.S完成,它主要设置保护模式下的状态,并跳转到压缩内核的起始物理地址0x1000,也就是 arch/386/boot/compressed/head.S,misc.c文件。它设置堆栈,调用decompress_kernel()解压 缩内核到0x100000并跳转到该地址。 注意以前的装载器(如老版本的LILO)仅能装载setup的前4节,这就是setup里面有必要时候装载自身重置的代码的原因。同样,setup代码必须注意多种不同类型版本的装载器与zImage/bzImage的结合,它也因此非常复杂。 让我们分析一下bootsector代码里允许装载大内核(即bzImage)的组装部分(kludge)。首先setup部分像往常一样装载到 地址0x90200,但是采用调用BIOS服务将数据从低位内存移动到高位内存的辅助程序,内核一次可装载64K。这个辅助程序在bootsect.S中 的bootsect_kludge曾提到,并在setup.S中定义为bootsect_helper。Setup.S中的 bootsect_kludge标签段包含了setup段的代码以及其中bootsect_helper代码的偏移量,这样bootsector可以调用 lcall指令跳转到bootsect_helper。bootsect_helper包含在setup.S文件里的原因很简单,因为 bootsect.S没有剩余的空间了。这个程序调用0x15号BIOS服务以便移动到高位内存并复位%es,使其总是指向0x10000。这保证了 bootsect.S里的代码在从磁盘拷贝数据时不会溢出。 1.5. 采用LILO引导器 在原始的linuxbootsector上采用专门的引导器(LILO)有很多好处: 1) 可在多个操作系统或多个内核之间选择。 2) 可以输入内核指令参数。 3) 可以装载更大(2.5M甚至1M)的内核文件。 老版本的LILO不能装载bzImage内核,新版本采用了bootsect+setup这样的技术,通过BIOS服务将数据从低位内存拷贝到高 位。一些人在为是否移出对zImage支持而争论,这个主要原因是在已损坏的BIOS服务时,zImage可以正常装载而bzImage却不能。 LILO所做的最后一件事情就是跳转到setup.S,然后就是正常的处理过程。 1.6. 高级初始化 对于“高级初始化”我们认为这不是直接和引导过程相关,即使它的部分实现代码也是用汇编语言编写的。也就是arch/i386/kernel/head.S文件,它是未压缩内核的最初部分。整个过程如以下部分: 1) 初始化段数值(%ds=%es=%fs=%gs=_KERNEL_DS=0x18)。 2) 初始化内存页表。 3) 设置%cr0的PG位,使内存分页机制有效。 4) 将BSS清零(在SMP机上,仅第一个CPU会执行此操作)。 5) 拷贝内核引导指令的前2k。 6) 利用EFLAGS检测CPU类型,如果可能,还有cpuid,以便探测386或者更高型号。 7) 第一个CPU调用start_kernel函数,如果ready等于1,其他CPU则调用arch/i386/kernel/smpboot.c文件的:initialize_secondary()函数,这个函数重新装载esp/eip且不再返回。 init/main.c文件的start_kernel函数是用c语言编写的,并完成以下工作: 1) 获得一个全局的内核锁(这在单CPU完成初始化的时候需要)。 2) 执行细节配置(内存页布局分析,再次拷贝引导命令等)。 3) 输出带有版本号的内核标语,编译器通常将这些创建到内核保存消息的ring缓冲中。版本号来自于init/version.c的linux_banner变量,在使用cat/proc/version时也会显示同样的字符串。 4) 初始化trap。 5) 初始化IRQ。 6) 初始化调度所需数据。 7) 初始化时钟数据。 8) 初始化软中断子系统。 9) 处理引导命令。 10) 初始化控制台。 11) 如果模块支持功能被编译到内核,初始化动态模块装载器。 12) 如果支持“profile”命令,初始化配置文件缓冲区。 13) 调用kmem_cache_init()函数,初始化大多数的块分配算法。 14) 中断有效。 15) 为当前CPU计算BogoMips值。 16) 调用mem_init()函数计算max_mapnr、totalram_pages和high_memory,并输出“Memory”。 17) 调用kmem_cache_sizes_init()函数完成块分配算法初始化。 18) 初始化procfs使用的数据结构。 19) 调用for_init(),创建uid_cache进程,基于内存可用总量初始化max_threads,并将init_task结构的RLIMIT_NPROC设置为max_threads的1/2。 20) 创建VFS、VM、高速缓冲等所需的多种块缓冲器。 21) 如果IPC支持功能被编译到内核,初始化IPC子系统,注意系统V的shm,它包含了挂载一个shmfs文件系统的内部实例。 22) 如果配额功能被编译到内核,则为其创建并初始化一个特殊的块缓冲。 23) 执行bug检查,可能在任何时候激活processor/bus/etc工作区下的bugs,比较各种结构以发现“ia64没有bug”或者“ia32 有相当多的bug”。一个较好的例子就是“f00fbug”,它仅在内核编译为686以前版本并再此环境下工作时产生。 24) 设置一个标记,标识调度表在“nextopportunity”时调用,如果提供了“init”引导命令,则执行execute_command函数 以创建一个内核线程init,否则尝试执行/sbin/init,/etc/init,/bin/init,/bin/sh,如果所有的过程都失 败了,则提示使用“init=”参数。 25) .进入到idle循环,这是一个pid为0的idle进程。 注意,init内核进程调用了do_basic_setup函数,这个函数依次调用do_initcalls函数遍历由_initcall或者 module_init宏注册的函数列表,并调用他们。这些函数不是相互倚赖,就是他们的依赖关系已经在Makefile文件里面固定了。这意味着依靠目 录树的位置和Makefile的结构,调用初始化函数的指令是可以更改的。有时,这很重要。因为你可以想像两个子系统A和B,其中B依赖于A的初始化晚完 成。如果A被静态编译,并且B是一个模块,则在A准备号所有必须环境后调用B实体的入口;如果A是一个模块,B也同样必须是一个模块,这样才没有问题。但 如果A和B都被静态编译到内核时呢?调用他们的指令依赖于内核的.initcall.init节的相对入口偏移量。RogierWolff提议引进一 种分等级(“优先权”)的基础下部组织,模块通过它让连接器知道在何种关联指令下这些模块可以被链接,但迄今为止都没有一个内核可接收的具备一流风格的可 用的实现补丁。因此,确定你的连接指令是正确的。如果像上面的例子,倘若在同一个Makefile中相继列出A和B,一旦在静态编译时他们能够正常工作, 他们将会一直如此。如果他们不能工作,更改列举有他们目标文件的指令。 另外一件事是linux的通过“init=”引导命令的含义执行多选择性的初始化程序的能力。这个在意外覆盖/sbin/init或者手工调试初始化脚本和/etc/inittab,并在某时执行其中一个时非常有用。 1.7. SMP机在x86系统上引导 SMP机的引导过程在到达start_kernel入口前和普通顺序一样执行bootsector,setup等等,然后调用smp_init和 smp_boot_cpus(位于src/i386/kernel/smpboot.c)函数。smp_boot_cpus函数依次遍历每个apicid 直到NR_CPUS数,并针对每个apicid调用do_boot_cpu函数。do_boot_cpu函数的功能就是为目标CPU创建一个空闲任务并记 录到一个众所周知的位置,该位置由trampoline.S文件提供的IntelMP规范EIP代码定义;然后,它为该CPU产生让引导程序执行 trampoline.S代码的STARTUPIPI。 引导CPU在低位内存为每个CPU创建一份trampoline代码的拷贝,这个启动程序在自身写入一个可供引导程序判断是否正在执行 trampoline代码的随机数。IntelMP标准限定了trampoline代码必须在低位内存。Trampoline代码仅仅将%bx登记为 1,进入到保护模式,并跳转到arch/i386/kernel/head.S的主入口startup_32。 现在,引导程序开始执行head.S并发觉这并不是引导过程,于是它跳过清楚BSS的代码,进入到当前CPU的空任务的入口函数initialize_secondary()并重新调用引导过程已经初始化成功的init_taskscpu。 注意,虽然init_task可以被共享但是每个空任务都有自己的时间分配系统,这就是为何init_tssNR_CPUS是一个队列。 1.8. 释放初始化数据和代码 当操作系统完成对自身的初始化,大多数的代码和数据结构将不再需要。多数的操作系统(BSD,FreeBSD等)不会释放这些不需要的数据,因而 浪费了宝贵的物理内核内存。他们的理由是“相关代码在多种子系统中都有关联,所以释放不是切实可行的”。当然,Linux系统不能以这些为托词,因为在 linux下,如果某事在理论上是可行的,那么它多半已经被实现了或者某人正在为之而努力。 那么,像我在前面所说的Linux内核仅能被编译成ELF二进制格式,现在我们来找出这个原因(或者是其中之一的原因)。这个原因和linux提供的两个用于清楚初始化代码和数据的宏有关系: l 初始化代码的_init宏 l 初始化数据的_initdata宏 在include/linux/init.h中,gcc特殊分类符定义了他们的含义。 #ifndefMODULE #define_init _attribute_(_section_(.text.init) #define_initdata _attribute_(_section_(.data.init) #else #define_init #define_initdata #endif 这意味着如果代码是静态编译到内核的,那么它就放在执行的ELF节.text和.init,这两个节都在arch/i386/vmlinux.lds文件的连接关联图中都有定义;否则这两个宏就没有任何含义。 引导时将出现init内核任务调用free_initmem函数释放从地址_init_begin到地址_init_end的所有页。在一个典型系统上(如工作终端),这个结果将释放260k左右的内存。 静态编译情况时,所有经由module_init注册的函数被放置到同样会被释放的.initcall.init这里。当设计一个子系统时,当今 Linux的趋势是从旧有版本提供一个init/exit入口点,这样将来有争议的子系统在需要时可以组件化。以pipefs为例,见fs/pipe.c 文件。即使给定的子系统不会是一个模块(如fs/buffer.c中的bdflush),通过module_init宏它依然正常精简地实现了初始化功 能,并在函数正确调用时正常运行。 还有两个宏_exit和_exitdata也以类似的风格工作,但他们和模块支持有更加直接的关联,因此在稍后将详加讨论。 1.9. 处理内核命令 让我们回想在引导期间向内核传递控制命令会发生什么: 1) LILO通过BIOS键盘服务接收命令,保存到物理内存明显的位置,这就像发出一个信号说这里有一条可用的命令。 2) arch/i386/kernel/head.S拷贝命令的前2k到内存页。 3) arch/i386/kernel/setup.c文件的parse_mem_cmdline函数(start_kernel setup_archparse_mem_cmdline)从内存页拷贝256字节到saved_command_line,这将显示到 /proc/cmdline。如果出现“mem”项也以同样流程处理,并为VM参数做适量调整。 4) 我们回到parse_options函数(由start_kernel调用),它处理一些内核参数(当前的“init”以及init所需的环境变量)并将每个命令提交到checksetup函数。 5) Checksetup函数在.setup.init节中搜索并调用对应的函数,传递命令。注意经由_setup注册的函数返回值0的使用,这有可能出现 ”为什么?因为这明显就是ldJ提交“变量=值”命令,而不是一个函数在“值”时无效另一个无效。JeffGarzik评论说“这样做的黑客在快马加鞭 旧有的细节。也就是说一
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 高级殡葬礼仪接待服务中客户满意度提升策略
- 面试技巧与人事主管的职责
- 纪念馆安全审计计划
- 项目主管项目质量管理总结与提升计划
- 亲子早教老师高级课程设计与家长沟通服务提升方案
- 竞品情报收集与分析工作计划
- 中央空调系统设备手册及操作规程
- 线上渠道销售业绩提升计划与数据分析报告
- 人力资源部招聘流程优化及人才引进计划
- 上海公务员面试成功案例
- 终止合同及保密协议书
- 电力企业安全教育培训管理制度
- 施工现场安全事故应急预案
- 2025年中级消防设施操作员《理论知识》题库必做200题(含答案)
- 2025年税务师考试《税法一》冲刺试卷(含答案)
- 2025版《煤矿安全规程》题库
- 大学生职业生涯规划书课件
- 2025云南省交通投资建设集团有限公司下属云南省交通科学研究院有限公司管理人员招聘16人考试参考试题及答案解析
- DB23T 3045-2021 森林山地木栈道建设技术规程
- 医疗健康体检服务投标书标准范本
- 企业培训课程评估及反馈工具
评论
0/150
提交评论