Linux 2.4 内核说明文档(引导篇)

发表于:2007-05-26来源:作者:点击数: 标签:
[list=] 本文档是《Linux2.4内核说明文档》中的第一部分。以下是整个文档大致目录: 1,启动 2,进程和中断管理([url=http://bbs.chinaunix.net/forum/viewtopic.php?t=571760]http://bbs.chinaunix.net/forum/viewtopic.php?t=571760[/url]) 3,虚拟文件

[list=]
本文档是《Linux2.4 内核说明文档》中的第一部分。以下是整个文档大致目录: 
1,启动 
2,进程和中断管理 ([url=http://bbs.chinaunix.net/forum/viewtopic.php?t=571760]http://bbs.chinaunix.net/forum/viewtopic.php?t=571760[/url])
3,虚拟文件系统 
4,Linux 页缓冲 
5,IPC机制 

本篇文档目录:
1.1. 创建Linux内核镜像
1.2. 引导:概述
1.3. 引导:BOIS POST
1.4. 引导:bootsector和setup
1.5. 采用LILO引导器
1.6. 高级初始化
1.7. SMP机在x86系统上引导
1.8. 释放初始化数据和代码
1.9. 处理内核命令

以下是正文:

1. 引导
1.1. 创建Linux内核镜像
本部分解释了编译内核时每个步骤以及每个步骤的输出。这个创建过程依赖于不同的体系结构,这里强调一下我们仅考虑创建一个Linux x86的内核。当用户输入“make zImage”或者“make bzImage”时,输出的可启动内核镜像就分别存放为arch/i386/boot/zImage或者arch/i386/boot/bzImage。下面来看看这个镜像是怎么创建的:
1) 首先C和汇编源文件被编译成ELF中间文件(.o),其中一部分按照逻辑分组打包成压缩文件(.a)。
2) 调用ld指令将以上的.o和.a文件被链接成一个静态的80386可执行文件vmlinux。
3) 接着调用nm vmlinux 指令剔除不相关和不感兴趣的符号并创建系统关系图。
4) 进入arch/i386/boot目录。
5) Bootsect.S文件按照目标是bzImage(zImage)在定义(不定义) –D_BIG_KERBEL_ 宏 下进行预处理,结果分别存为bbootsect.s(bootsect.s)。
6) Bbootsect.s文件被编译并转换成“raw binary”格式的bbootsect文件(bootsect.s 被转换成“raw”格式文件bootsect)。
7) setup.s(setup.s 包含了video.s文件)被预处理成bzImage需要的bsetup.s或者zImage需要的setup.s文件。这个过程和bootsector一样,bzImage镜像需要定义.D__BIG_KERNEL__宏,结果被转换成“raw binary”格式的bsetup,zImage镜像则被转换成“raw”格式的setup。
8) 进入arch/i386/boot/compressed目录,移出/usr/src/linux/vmlinux文件中ELF标识节.note和.comment ,并将其转换成raw binary格式存放到临时文件$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的标号.Ttext 0x1000和bvmlinux的标号.Ttext 0x100000的不同,这是由于bzImage压缩装载器是从高位装载的。
13) 将bvmlinux转换成“raw binary”文件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,这个规则如下:
0x4000 bytes >= 512 + setup_sects*512 + 运行bootsector/setup所需堆栈空间
在后面将说明是那个部分造成了这种限制。
BzImage文件大小的上限采用LILO启动时为2.5M,采用冷启动如软盘或者光盘等则为1048560字节。
注意,tools/build工具检验了boot段的大小、内核镜像的大小和setup的低范围地址,并没有检测setup的高范围地址。因此,在setup.S文件末尾的“.space”节增加一个大的地址数值就会很容易创建一个无法使用的内核。

1.2. 引导:概述
启动过程是和体系结构相关的,这里我们仅关注IBM PC/IA32体系。由于旧有设计以及向前兼容,PC机采用了以前流行的风格启动操作系统。这个过程可以被分为一下六个逻辑步骤:
1) BOIS选择启动设备。
2) 从启动设备装载bootsector。
3) Bootsector装载setup,解压缩程序和内核镜像。
4) 在保护模式下解压内核。
5) 汇编代码执行低级初始化。
6) 执行上层初始化。

1.3. 引导:BOIS POST
1) 电源启动时钟发生器并在总线上产生一个#POWERGOOD的中断。
2) 产生CPU的RESET中断(此时CPU处于8086工作模式)。
3) %ds=%es=%fs=%gs=%ss=0, %cs=0xFFFF0000,%eip = 0x0000FFF0 (ROM BIOS POST code). 
4) 在中断无效状态下执行所有POST检查。
5) 在地址0初始化中断向量表IVT。
6) 0x19中断以启动设备号为参数调用BIOS启动装载程序。这个程序从0扇面1扇区物理位置0x7C00开始装载。

1.4. 引导:bootsector和setup
用来启动内核的bootsector可以是以下几种:
 Linux bootsector
 LILO等bootsector
 无bootsector
以下详细解释linux bootsector。下面一些代码,负责初始化用作段变量的宏定义。
29 SETUPSECS = 4 /* default nr of setup.sectors */ 
30 BOOTSEG = 0x07C0 /* original address of boot.sector */ 
31 INITSEG = DEF_INITSEG /* we move boot here . out of the way */ 
32 SETUPSEG = DEF_SETUPSEG /* setup starts here */ 
33 SYSSEG = DEF_SYSSEG /* system loaded at 0x10000 (65536) */ 
34 SYSSIZE = DEF_SYSSIZE /* system size: # of 16.byte clicks */ 
(左边的数值是代码的行号) 在文件include/asm/boot.h中定义了DEF_INITSEG, DEF_SETUPSEG, DEF_SYSSEG和DEF_SYSSIZE的值。
/* Don't touch these, unless you really know what you're doing. */ 
#define DEF_INITSEG 0x9000 
#define DEF_SYSSEG 0x1000 
#define DEF_SETUPSEG 0x9020 
#define DEF_SYSSIZE 0x7F00 
以下来看看bootsect.S的源代码:
54 movw $BOOTSEG, %ax 
55 movw %ax, %ds 
56 movw $INITSEG, %ax 
57 movw %ax, %es 
58 movw $256, %cx 
59 subw %si, %si 
60 subw %di, %di 
61 cld 
62 rep 
63 movsw 
64 ljmp $INITSEG, $go 
65 # bde . changed 0xff00 to 0x4000 to use debugger at 0x6400 up (bde). We 
66 # wouldn't have to worry about this if we checked the top of memory. Also 
67 # my BIOS can be configured to put the wini drive tables in high memory 
68 # instead of in the vector table. The old stack might have clobbered the 
69 # drive table. 
70 go: movw $0x4000.12, %di # 0x4000 is an arbitrary value >= 
71 # length of bootsect + length of 
72 # setup + room for stack; 
73 # 12 is disk parm size. 
74 movw %ax, %ds # ax and es already contain INITSEG 
75 movw %ax, %ss 
76 movw %di, %sp # put stack at INITSEG:0x4000.12. 
代码54行~63行将bootsector从地址0x7C00移动到0x90000,由以下过程完成:
1) set %ds:%si to $BOOTSEG:0 (0x7C0:0 = 0x7C00) 
2) set %es:%di to $INITSEG:0 (0x9000:0 = 0x90000) 
3) set the number of 16bit words in %cx (256 words = 512 bytes = 1 sector) 
4) clear DF (direction) flag in EFLAGS to auto.increment addresses (cld) 
5) go ahead and copy 512 bytes (rep movsw) 
The reason this code does not use rep movsd is intentional (hint . .code16). 
64行跳转到标号go:(一个最新创建的bootsector的拷贝,也就是在0x9000段)。这和接下来的三段指令(64~76行)在$INITSEG:0x4000.0xC 段初始化一个堆栈,也就是指令%ss = $INITSEG (0x9000) 和 %sp = 0x3FF4 (0x4000.0xC)。这就是我们前面提到的setup大小限制的来历(见1.1节)。
77~103行代码建立了第一个磁盘的参数表,以便允许多扇区读操作。
77 # Many BIOS's default disk parameter tables will not recognise 
78 # multi.sector reads beyond the maximum sector number specified 
79 # in the default diskette parameter tables . this may mean 7 
80 # sectors in some cases. 
81 # 
82 # Since single sector reads are slow and out of the question, 
83 # we must take care of this by creating new parameter tables 
84 # (for the first disk) in RAM. We will set the maximum sector 
85 # count to 36 . the most we will encounter on an ED 2.88. 
86 # 
87 # High doesn't hurt. Low does. 
88 # 
89 # Segments are as follows: ds = es = ss = cs . INITSEG, fs = 0, 
90 # and gs is unused. 
91 movw %cx, %fs # set fs to 0 
92 movw $0x78, %bx # fs:bx is parameter table address 
93 pushw %ds 
94 ldsw %fs:(%bx), %si # ds:si is source 
95 movb $6, %cl # copy 12 bytes 
96 pushw %di # di = 0x4000.12. 
97 rep # don't need cld .> done on line 66 
98 movsw 
99 popw %di 
100 popw %ds 
101 movb $36, 0x4(%di) # patch sector count 
102 movw %di, %fs:(%bx) 
103 movw %es, %fs:2(%bx) 
通过0x13BOIS服务0号函数重置软盘管理器,并且在bootsector完成后立即载入setup部分。也就是说在物理地址0x90200 ($INITSEG:0x200)处再次调用0x13BOIS服务2号函数。这个过程发生在107~124行。
107 load_setup: 
108 xorb %ah, %ah # reset FDC 
109 xorb %dl, %dl 
110 int $0x13 
111 xorw %dx, %dx # drive 0, head 0 
112 movb $0x02, %cl # sector 2, track 0 
113 movw $0x0200, %bx # address = 512, in INITSEG 
114 movb $0x02, %ah # service 2, "read sector(s)" 
115 movb setup_sects, %al # (assume all on head 0, track 0) 
116 int $0x13 # read it 
117 jnc ok_load_setup # ok . continue 
118 pushw %ax # dump error code 
119 call print_nl 
120 movw %sp, %bp 
121 call print_hex 
122 popw %ax 
123 jmp load_setup 
124 ok_load_setup: 
如果由于某些原因出错,例如无法使用的软盘或者在运行过程中有人弹出了磁盘等,装载过程将输出错误代码并且无限循环尝试本过程。除非重试成功(这通常不会发生,如果出现其他错误后果将更严重),唯一退出这个循环的办法就是重新启动机器。
如果成功装载配置代码部分,流程将跳转到ok_load_setup标签。
紧接着,启动程序就在物理地址0x10000装载压缩后的内核。这样做是为了保护低位(0~64K)内存的固件数据区。在内核装载后,启动程序跳转到地址$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引导器
在原始的linux bootsector上采用专门的引导器(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”。一个较好的例子就是“f00f bug”,它仅在内核编译为686以前版本并再此环境下工作时产生。
24) 设置一个标记,标识调度表在“next opportunity”时调用,如果提供了“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节的相对入口偏移量。Rogier Wolff 提议引进一种分等级(“优先权”)的基础下部组织,模块通过它让连接器知道在何种关联指令下这些模块可以被链接,但迄今为止都没有一个内核可接收的具备一流风格的可用的实现补丁。因此,确定你的连接指令是正确的。如果像上面的例子,倘若在同一个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文件提供的Intel MP规范EIP代码定义;然后,它为该CPU产生让引导程序执行trampoline.S 代码的STARTUP IPI。
引导CPU在低位内存为每个CPU创建一份trampoline代码的拷贝,这个启动程序在自身写入一个可供引导程序判断是否正在执行trampoline代码的随机数。Intel MP 标准限定了trampoline代码必须在低位内存。Trampoline代码仅仅将%bx登记为1,进入到保护模式,并跳转到arch/i386/kernel/head.S的主入口startup_32。
现在,引导程序开始执行head.S并发觉这并不是引导过程,于是它跳过清楚BSS的代码,进入到当前CPU的空任务的入口函数initialize_secondary()并重新调用引导过程已经初始化成功的init_tasks[cpu]。
注意,虽然init_task可以被共享但是每个空任务都有自己的时间分配系统,这就是为何init_tss[NR_CPUS]是一个队列。

1.8. 释放初始化数据和代码
当操作系统完成对自身的初始化,大多数的代码和数据结构将不再需要。多数的操作系统(BSD,FreeBSD等)不会释放这些不需要的数据,因而浪费了宝贵的物理内核内存。他们的理由是“相关代码在多种子系统中都有关联,所以释放不是切实可行的”。当然,Linux系统不能以这些为托词,因为在linux下,如果某事在理论上是可行的,那么它多半已经被实现了或者某人正在为之而努力。
那么,像我在前面所说的Linux内核仅能被编译成ELF二进制格式,现在我们来找出这个原因(或者是其中之一的原因)。这个原因和linux提供的两个用于清楚初始化代码和数据的宏有关系:
 初始化代码的__init宏
 初始化数据的__initdata宏
在include/linux/init.h中,gclearcase/" target="_blank" >cc特殊分类符定义了他们的含义。
#ifndef MODULE 
#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_arch-> parse_mem_cmdline)从内存页拷贝256字节到saved_command_line,这将显示到/proc/cmdline。如果出现“mem=”项也以同样流程处理,并为VM参数做适量调整。
4) 我们回到parse_options函数(由start_kernel调用),它处理一些内核参数(当前的“init=”以及init所需的环境变量)并将每个命令提交到checksetup函数。
5) Checksetup函数在.setup.init节中搜索并调用对应的函数,传递命令。注意经由__setup注册的函数返回值0的使用,这有可能出现提交“变量=值”命令,而不是一个函数在“值”时无效另一个无效。Jeff Garzik评论说“这样做的黑客在快马加鞭”为什么?因为这明显就是ld旧有的细节。也就是说一个命令连接内核时让函数A先于B调用,而另一个则是相反的命令,结果就依赖于这条命令。
那么怎样写处理引导命令的程序呢?我们使用定义在include/linux/init.h里的__setup()宏:
/* 
* Used for kernel command line parameter setup 
*/ 
struct kernel_param { 
const char *str; 

int (*setup_func)(char *); 
}; 
extern struct kernel_param __setup_start, __setup_end; 
#ifndef MODULE 
#define __setup(str, fn) \ 
static char __setup_str_##fn[] __initdata = str; \ 
static struct kernel_param __setup_##fn __initsetup = \ 
{ __setup_str_##fn, fn } 
#else 
#define __setup(str,func) /* nothing */ 
endif 
你可以在自己的代码里仿效这个做法(下面是逻辑总线HBA驱动的示例)。
static int __init 
BusLogic_Setup(char *str) 

int ints[3]; 
(void)get_options(str, ARRAY_SIZE(ints), ints); 
if (ints[0] != 0) { 
BusLogic_Error("BusLogic: Obsolete Command Line Entry " "Format Ignored\n", NULL); 
return 0; 

if (str == NULL || *str == '\0') 
return 0; 
return BusLogic_ParseDriverOptions(str); 
}
__setup("BusLogic=", BusLogic_Setup); 
注意__setup()不会对模块进行任何操作,所以这段代码希望处理引导命令并在模块初始化过程中,模块化连接或者静态连接时都能调用自身的处理函数。这也同样意味着可以编译一个仅处理以模块化编译的内核的参数,而不处理静态编译或其他情况。

[/list]

 xuediao 回复于:2005-06-07 16:35:09
关于linux 2.6.11内核文件IO的系统调用实现的帖子:
1. [url=http://bbs.chinaunix.net/forum/viewtopic.php?t=544401]open & creat [/url] 描述了open函数和creat函数的实现
2. [url=http://bbs.chinaunix.net/forum/viewtopic.php?t=546490]read & write [/url] 描述了read 函数和write函数的实现
3. [url=http://bbs.chinaunix.net/forum/viewtopic.php?t=549408]flseek & close [/url] 描述了fleek函数和close函数的实现

 xuediao 回复于:2005-06-08 14:12:32
这是zImage内核装载器通用的内存示意图:

+----------------0A0000----------------+
|
|  BIOS预留空间,不能使用, 
|  为BIOS EBDA保留空间.
|
+----------------09A000----------------+
|
|  栈/堆/命令行,内核实模式代码使用.
|
+----------------098000----------------+
|
|  内核 setup,内核实模式代码使用.
|
+----------------090200----------------+
|
|  内核bootsector,
|  内核boot sector代码使用.
|
+----------------090000----------------+
|
|  保护模式内核,
|  大小为内核文件体积.
|
+----------------010000----------------+
|
|  引导装载器,
|  Bootsector 入口点0000:7C00
|
+----------------001000----------------+
|
|  MBR/BIOS预留空间
|
+----------------000800----------------+
|
|  MBR常用空间 
|
+----------------000600----------------+ 
|
|  BIOS使用空间  
|
+----------------000000----------------+

 xuediao 回复于:2005-06-13 09:31:51
前面的正文说明了引导过程,这里附录一下引导过程所需的协议。

当前存在4个 Linux/i386的引导协议:

旧版本内核: 仅支持zImage/Image. 一些非常老的内核甚至不会支持引导命令。

2.00协议: (内核 1.3.73)增加了对bzImage和initrd的支持, 并在引导装载器和内核之间提供了标准通信方式。虽然传统的setup内存区域依然假定是可写的,但 setup.S 在装载时确是可重定位的。

2.01协议: (内核1.3.76) 增加了堆溢出警告。

2.02协议: (内核2.4.0-test3-pre3) 新增命令行协议。降低了常规内存的上限,并规定setup内存区域不能被覆盖,这使得使用EBDA从SMM或者32位BIOS入口引导系统更加安全。 zImage同样支持,但不再重视。

2.03协议: (内核 2.4.18-pre1) 明确定义引导装载器中可用的initrd地址上限。

 slackwaresz 回复于:2005-06-13 11:37:44
3q~

 xuediao 回复于:2005-06-14 09:42:19
关于内核头的内容还有较多的详细解说,由于时间太紧,只能一天来点,各位见谅  :lol:

 xuediao 回复于:2005-06-14 09:42:19
实模式的内核头

   装载内核首先需要装载实模式代码(即是bootsector和setup代码),然后在0x01f1处检查内核头。尽管装载器可能仅选择装载前两个代码节(大约1k),然而实模式代码可以达到32k的长度。

这个内核头如下:

偏移量/长度 支持协议 名字 含义


01F1/1 ALL setup_sects setup内部节长度,为了向后兼容,如果值为0,则代表真是值为4。
01F2/2 ALL root_flags 如果设置此标识,则以只读方式挂载root。
01F4/2 ALL syssize 不能设置,仅供bootsect.S使用。
01F6/2 ALL swap_dev 不能设置,已经废弃的项。
01F8/2 ALL ram_size 不能设置,仅供bootsect.S使用。
01FA/2 ALL vid_mode 视频模式选现。
01FC/2 ALL root_dev 默认根设备号。
01FE/2 ALL boot_flag 0xAA55唯一号。
0200/2 2.00+ jump Jump指令。
0202/4 2.00+ header 唯一签名"HdrS"。
0206/2 2.00+ version 支持的引导协议版本号。
0208/4 2.00+ realmode_swtch 引导装载器钩子程序标识。
020C/2 2.00+ start_sys 装载的首段地址(0x1000) (已经废弃)。
020E/2 2.00+ kernel_version 内核版本字符串指针。
0210/1 2.00+ type_of_loader 引导装载器标识。
0211/1 2.00+ loadflags 引导协议选项。
0212/2 2.00+ setup_move_size 移动到高位内存的偏移量。
0214/4 2.00+ code32_start 引导装载器钩子。
0218/4 2.00+ ramdisk_image initrd装载地址
021C/4 2.00+ ramdisk_size initrd长度
0220/4 2.00+ bootsect_kludge 不能设置,仅供bootsect.S使用。
0224/2 2.01+ heap_end_ptr setup后的空余内存首指针。
0226/2 N/A pad1 没有使用。
0228/4 2.02+ cmd_line_ptr 内核命令行32位指针。
022C/4 2.03+ initrd_addr_max initrd地址上限

 amthe 回复于:2005-06-14 14:57:35
先顶在看。

 xuediao 回复于:2005-06-15 14:20:22
type_of_loader特别说明:

type_of_loader:
如果引导装载器被指定了一个id,则进入到0xTV地址,这里T代表引导装载器的标号,V代表版本号。否则,跳转到0xFF。

id号对应得引导装载器类型列表:
0  LILO
1  Loadlin
2  bootsect-loader
3  SYSLINUX
4  EtherBoot
5  ELILO
7  GRuB
8  U-BOOT

 xuediao 回复于:2005-06-15 14:22:41
回帖的兄弟太少,不知道大伙有啥意见,唉 BBS 啊

 st690714 回复于:2005-06-15 16:55:49
这么看太累,最好有个连接能下载到文档

 xuediao 回复于:2005-06-15 17:26:58
由于暂时找不到地方放这个文档,所以暂时不能提供地方下载。

 xuediao 回复于:2005-06-16 10:30:27
loadflags, heap_end_ptr:
如果协议版本是2.01或者更高,则条装到heap_end_ptr 地址,设置0x80位装载标识CAN_USE_HEAP。

  setup_move_size: 
如果采用2.00或者2.01协议,如果内核没有装载到0x90000,则稍后将内核移动到该地址。如果你需要在内核中增加更多的东西,则设置这个字段。

 xuediao 回复于:2005-06-20 10:53:52
cmd_line_ptr 特别说明
 

 cmd_line_ptr:
如果协议版本是2.02或者更高,这就是一个32位的指向内核命令行的指针地址.内核命令行可能存放到setup后到0xA0000地址之间的任何位置。当引导装载器不支持命令行时,这个字段可指向一个空串。如果这个字段为零,则内核会人定引导装载器不支持2.02以上协议。

 popmouse 回复于:2005-06-21 10:41:07
有没有更多的?继续啊!!!

 xuediao 回复于:2005-06-22 11:10:47
ramdisk_max 说明:


 ramdisk_max:
initrd内容可能占用得最大地址。对于引导协议2.02或者更早版本,这个字段并没有提供,最大得地址为0x37FFFFFF。这个地址是安全可用的最高可用地址,所以当ramdisk数值表示了131072字节长,那么这个字段就是0x37FFFFFF,这样就可以在地址0x37FFFFFF开始ramdisk 。

 lifengliu 回复于:2005-06-24 14:08:34
非常好,一直都想看到这样的内核相关的文档。 那位朋友提供一个地方下载吧,这样更能推广

 xuediao 回复于:2005-06-29 09:47:58
关于第一章 引导 部分的内容就基本发完了,下面是第二章 进程和中断管理,请大家继续支持!

 SybaseLU 回复于:2005-06-29 18:05:12
很奇怪为什么这么多的人讨论这些没有实际有意义的东西,而且一模一样。如果这里放到嵌入式来讲,有点意义。真正的对于不同体系结构的CPU的bootloader并不需要中国人来写,网上有许多针对不同的MCU的现成的bootloader。不要随便看到一片文章,就放到这里讨论,最好是自己去做,去实践,把自己实际用到的东西和遇到的困难拿来讨论。提点意见。

 UNIX-COW 回复于:2005-06-29 22:52:51
[quote:54d15f9995="SybaseLU"]很奇怪为什么这么多的人讨论这些没有实际有意义的东西,而且一模一样。如果这里放到嵌入式来讲,有点意义。真正的对于不同体系结构的CPU的bootloader并不需要中国人来写,网上有许多针对不同的MCU的现成的bootloader..........[/quote:54d15f9995]


ni de kou qi hao da a  :em03:

 xuediao 回复于:2005-07-01 10:55:53
[quote:26ba1903b2="SybaseLU"]很奇怪为什么这么多的人讨论这些没有实际有意义的东西,而且一模一样。如果这里放到嵌入式来讲,有点意义。真正的对于不同体系结构的CPU的bootloader并不需要中国人来写,网上有许多针对不同的MCU的现成的bootloader..........[/quote:26ba1903b2]

也许你是一个牛人,不过坦白的讲,我还不是,我相信现在中国有很多程序员都不是。发这样的东西,仅是为了与大家探讨学习,积累大家的经验看法,共同进步,以便能够从一个不牛的人快一点变成一个牛人;发这些东西,还有个想法,就是和大家共享这么一点微不足道的经验,以使更多的于我后来之人无须从零开始,少走弯路。

一花独放不是春,百花齐放春满园。如果你也有心,也和大家一道来分享,我想会有学多获益的人在心里感谢你的。

另:对于一模一样的描绘,我不敢苟同。我的帖子,都是我自己一字一句敲出来的。

 megavolt 回复于:2005-07-02 08:16:49
[quote:18a991104b="SybaseLU"]很奇怪为什么这么多的人讨论这些没有实际有意义的东西,而且一模一样。如果这里放到嵌入式来讲,有点意义。真正的对于不同体系结构的CPU的bootloader并不需要中国人来写,网上有许多针对不同的MCU的现成的bootloader..........[/quote:18a991104b]

窃以为了解学习一下还是很有好处的,就像计算机的一些理论课程,看似无用其实很有用,个中滋味各位看官应该是深有体会的罢。

 hongfengyue 回复于:2005-07-04 09:27:26
支持

 lifengliu 回复于:2005-07-06 09:51:45
好帖,顶!!

顶起来,让大家看看!

 jinbsd 回复于:2005-07-06 11:36:30
这个文档意义不大,惟有目的就是搞两个精华贴.
不知道楼主为什么贴这个?

你写的内容不准确,而且你自己可能还有一些没搞明白是怎么一回事.

没有贬低楼主的意思,只是就事论事.

 xuediao 回复于:2005-07-06 13:13:04
[quote:39f390d1c2="jinbsd"]这个文档意义不大,惟有目的就是搞两个精华贴.
不知道楼主为什么贴这个?

你写的内容不准确,而且你自己可能还有一些没搞明白是怎么一回事.

没有贬低楼主的意思,只是就事论事.[/quote:39f390d1c2]

说得很是道理,有些我确实没有搞定,如果能够指出来,我将是感激不尽!我一直的工作都是分析linux内核,能够找到探讨的门路不多,也不知道自己到了何种状态,有幸能够获取指教,我是非常的感激。

这篇文档主要是2.4的引导方面的说明,大多是参考的国外的文档写的,我回复的帖子基本上都是关于bootloader 通用的内存数据结构分布的解释说明。现在使用的bootloader在此基础上都有改动,有个朋友曾谈到这个问题,不过最后也没有给出适当的例子说明,很是遗憾。

对我而言,认知的提高远比“精华帖”的称号要有用得多。

最后,欢迎各位给予指教:
msn( xuediao_feng@hotmail.com)

 零二年的夏天 回复于:2005-07-06 22:35:08
欢迎大家提供这样的文档,这才是本版需要的内容。

也希望大家可以积极讨论。

 law7890 回复于:2005-07-07 16:25:45
好.顶一下!~~~~

 jinbsd 回复于:2005-07-09 17:55:22
我不用MSN楼主可否换个联络方式。

E-MAIL,你多常时间查看一次?》

 comtebox 回复于:2005-07-10 14:04:08
谢谢~~这个帖子很有用啊~!!

 xuediao 回复于:2005-07-11 16:13:46
[quote:c72b6f9e63="jinbsd"]我不用MSN楼主可否换个联络方式。

E-MAIL,你多常时间查看一次?》[/quote:c72b6f9e63]

还有email: xuediao@eyou.com

一般来说,我每天都会看邮件的 ^_^

恳请各位指教,不甚感激!

 我菜我怕谁 回复于:2005-07-12 08:11:49
希望能和楼主多多学习,探讨.帮偶提高水平.呵呵
xue_hu2001@eyou.com

原文转自:http://www.ltesting.net