Chinaunix首页 | 论坛 | 博客
  • 博客访问: 598474
  • 博文数量: 5
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 56
  • 用 户 组: 普通用户
  • 注册时间: 2019-06-26 09:19
文章分类
文章存档

2019年(5)

我的朋友

分类: LINUX

2019-08-14 12:13:59

首先一个操作系统的内核的启动由几部分完成:
1. 引导内核: 首先要在第一个扇区编写小于512字节的启动代码,或者使用现存的bootloader,如: LILO, GRUB等。
2. 加载内核: 将内核放在物理内存的某个物理地址上。
3. 执行内核: 指令指针转移到内核中与平台相关(如: x86, ARM)的机器码的位置,并初始化基本的功能,平台相关的代码大多用汇编编写。随后转移到平台无关的代码,完成系统的初始化,最后切换到正常的运转模式(如: IA-32下一般需要激活分页功能,并且打开保护模式的标志位跳转到保护模式)。

首先看第一阶段linux内核的实现过程(以下将linux2.6.11内核统称为linux内核,各版本代码不一致):
看一下当前根目录下的Makefile,有以下语句:

点击(此处)折叠或打开

  1. include $(srctree)/arch/$(ARCH)/Makefile

$(srctree)指的就是当前的根目录,ARCH可以由我们在编译内核的时候指定,如果不指定默认为当前系统的体系结构:

点击(此处)折叠或打开

  1. SUBARCH := $(shell uname -m | sed -e s/i.86/i386/ -e s/sun4u/sparc64/ \
  2. -e s/arm.*/arm/ -e s/sa110/arm/ \
  3. -e s/s390x/s390/ -e s/parisc64/parisc/ )
由SUBARCH这个变量来判断当前机器的体系结构。
为了方便默认为i386,具体数值可以把命令Copy一下自己在shell执行一下。


来看一下bootsect.S,该代码是较早的linux版本的引导代码。和基本的引导代码一样,首先跳转到0x7c00,随后执行代码,添加0xaa55的标记,最后让代码填充到512字节。
但是在目前的版本抛弃了该执行过程,看代码(arch/$(ARCH)/boot/bootsect.S):

点击(此处)折叠或打开

  1. movw    $bugger_off_msg, %si

  2. msg_loop:
  3.     lodsb
  4.     andb    %al, %al
  5.     jz    die
  6.     movb    $0xe, %ah
  7.     movw    $7, %bx
  8.     int    $0x10
  9.     jmp    msg_loop

  10. die:
  11.     # Allow the user to press a key, then reboot
  12.     xorw    %ax, %ax
  13.     int    $0x16
  14.     int    $0x19

  15.     # int 0x19 should never return. In case it does anyway,
  16.     # invoke the BIOS reset code...
  17.     ljmp    $0xf000,$0xfff0


  18. bugger_off_msg:
  19.     .ascii    "Direct booting from floppy is no longer supported.\r\n"
  20.     .ascii    "Please use a boot loader program instead.\r\n"
  21.     .ascii    "\n"
  22.     .ascii    "Remove disk and press any key to reboot . . .\r\n"
  23.     .byte    0
上述代码执行一个循环显示bugger_off_msg告诉用户软盘启动不是长期支持的,请使用bootloader。die函数使用BIOS中断(可以在书中查找,《intel汇编语言程序设计》。或者查看标准的Phoenix BIOS,如果没有该文献和书籍,可以联系我) 等待一个按键重新启动。 
很明显Linux内核使用bootloader启动。

继续分析代码文件(arch/$(ARCH)/boot/setup.S[使用LILO的启动代码文件]): 

点击(此处)折叠或打开

  1. /* Signature words to ensure LILO loaded us right */
  2. #define SIG1    0xAA55
  3. #define SIG2    0x5A5A

  4. INITSEG = DEF_INITSEG        # 0x9000, we move boot here, out of the way
  5. SYSSEG = DEF_SYSSEG        # 0x1000, system loaded at 0x10000 (65536).
  6. SETUPSEG = DEF_SETUPSEG        # 0x9020, this is the current segment
  7.                 # ... and the former contents of CS
注释写到用特征代码来确定LILO加载是正确的,说明SIG1和SIG2可能用在文件尾部来判断是否加载成功,与软盘启动0xaa55同样道理。(如果有LILO源代码或者文档请发给我,感谢!)。  
其他的代码就是一些启动代码和从磁盘读取内核的代码,例如: 使能a20地址线,读硬盘,加载GDT,LDT,开启分页或者PAE等功能然后跳转到保护模式进入32位代码等等。
本文不叙述(太多了!), 其他文章进行详细分析0-0。
略过其他代码,但还是要看一段:

点击(此处)折叠或打开

  1. .byte 0x66, 0xea            # prefix + jmpi-opcode
  2. code32:    .long    0x1000                # will be set to 0x100000
  3.                         # for big kernels
0x66,0xea代表jmpi指令和jmpi指令前缀,加0x66指令前缀是因为我们没有重载CS寄存器,所以当前还处于实模式,没有办法跳转到0x100000的地址位置,使用0x66后,CPU将会给我们一个48位的远指针。后面跟着0x1000也就表示这段代码实际表示:
jmpi 0x1000或者0x100000, __BOOT_CS,code32标签的地址定义为如下代码:

点击(此处)折叠或打开

  1. code32_start:
  2. # start address for 32-bit code.
  3. #ifndef __BIG_KERNEL__
  4.         .long    0x1000        # 0x1000 = default for zImage
  5. #else
  6.         .long    0x100000    # 0x100000 = default for big kernel
  7. #endif
也就是说如果定义了__BIG_KERNEL__(bzImage)就使用0x100000作为跳转地址,否则就使用0x1000(zImage)。
跳转到startup_32之后做一些初始化工作: 清理BSS等。随后解压内核调用decompress_kernel函数(boot/compress/misc.c),并且跳转到解压后的内核代码位置(arch/$(ARCH)/kernel/head.S)该代码做一些最后的初始化功能:
1. 激活分页
2. 清理bss(用0字节填充__bss_start和_bss_end之间的内容)
3. 初始化中断描述符表
4. 检测处理器类型

最后调用start_kernel函数。内核就真正的开始启动了。

EMAIL回复会比较及时
深入Linux内核架构(中文版)                    Wolfgang Manuerer著    郭旭译
阅读(4524) | 评论(0) | 转发(0) |
1

上一篇:正则表达式到有限状态机

下一篇:没有了

给主人留下些什么吧!~~