Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1721767
  • 博文数量: 199
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 6186
  • 用 户 组: 普通用户
  • 注册时间: 2012-10-30 11:01
个人简介

Linuxer.

文章存档

2015年(4)

2014年(28)

2013年(167)

分类: LINUX

2014-01-02 17:05:28

前言:
本文是对早期内核的引导启动过程做的分析笔记,这样可以更好的了解内核的启动过程。而现代大部分PC都是靠grub等引导工具引导启动的。x86架构下linux系统引导启动过程,大致分为以下几个阶段:
 
一.BIOS启动引导阶段
(1)当PC的电源打开后,80x86结构的cpu将自动进入实模式,并从地址0xFFFF0开始自动执行程序代码,这个地址通常是BIOS的地址。
(2)BIOS的首先进行POST(Power-On Self Test,加电后自检),检测系统中一些关键设备是否存在和能否正常工作,例如内存和显卡等设备。此时显卡还没有初始化,如果发现了一些致命错误,例如没有找到内存或者内存有问题(此时只会检查640K常规内存),BIOS会直接控制喇叭发声来报告错误,声音的长短和次数代表了错误的类型。
(3)然后物理地址0处开始初始化中断向量(注意:这个BIOS的中断向量很重要,后边的很多和硬盘等的交互都是通过此中断向量完成的)。
(4)此后,BIOS将启动设备的第一个扇区(第0磁道第一个扇区被称为MBR,也就是Master Boot Record,即主引导记录,它的大小是512字节,里面存放了预启动信息、分区表信息)读入内存绝对地址0x7C00处,并跳转到这个地方。其实被复制到物理内存0x7C00处的内容就是Boot Loader,对于较早的内核不靠grub启动的,它就是bootsect.S程序,而对于现在PC多数使用grub引导启动的,就是lilo或者grub了。

//我们分析的是不依靠grub启动的较老的内核
二.bootsect.S程序
bootsect.S代码是磁盘引导块程序,驻留在磁盘第一个扇区中(即引导扇区,0磁道,0磁头,扇区大小为512B),相当于后来的grub程序。
1.该程序的主要作用:
(1)bootsect代码执行期间,首先它会将自己移动到内存绝对地址0X90000开始处并继续执行。
(2)把磁盘的第2个扇区开始的4个扇区的setup模块(即setup.S程序)加载到紧跟着bootsect后面位置处,即0x90200地址。由此也可以看出setup最大为4个扇区,2KB。
(3)然后利用BIOS中断0x13取磁盘参数表中当前启动引导磁盘的参数,在屏幕上显示"Loading system..."字符串。
(4)然后再把setup模块后边的system模块(即内核)加载到内存0x10000开始的地方。
(5)确定根文件系统的设备号,并保存于root_dev中(引导块的508地址处)。
(6)最后长跳转到setup程序处(0x90200地址)去执行setup程序。

2.部分代码解析
(1)bootsect程序将自己拷贝到0x9000地址处
start:
 mov ax,#BOOTSEG  //BOOTSEG=0x7c00,赋值ax寄存器为0x7c00
 mov ds,ax     //将ds段寄存器置为0x7c00
 mov ax,#INITSEG  //INITSEG=0x9000
 mov es,ax     //将es段寄存器置为0x9000
 mov cx,#256    //设置移动的计数值为256字(下边是movw),即512B,正好是一个扇区大小,也是bootset程序大小
 sub si,si     //清空si寄存器
 sub di,di     //清空di寄存器
 rep        //重复执行并递减cx的值,直到cx=0
 movsw       //即movs指令,源地址:ds:si---->目标地址:es:di
 jmpi go,INITSEG  //段间跳转,标号go是段内偏移地址

(2)读setup模块到0x90200地址处
load_setup:
 xor dx,dx           //输入参数:设置驱动器号是0,磁头号是0
 mov cx,#0x0002          //输入参数:高位00指定磁道号为0,低位02指定是开始扇区号,即第二个扇区
 mov bx,#0x0200          //输入参数:bx指定数据缓冲区es:bx,es已经设置为0x9000,所以数据缓冲区地址就是0x90200地址处
 mov ax,#0x0200+SETUPLEN //输入参数:高位的02为子命令号,表示读磁盘扇区到内存。低位的04表示需要读出的扇区数量
 int 0x13                //利用BIOS中断INT 0x13读扇区内容到指定内存0x90200地址中
 jnc ok_load_setup       //读取OK,跳转到ok_load_setup!
 
(3)取当前引导磁盘参数(就是取每磁道扇区数)
ok_load_setup:
 xor dl,dl         //输入参数dl表示驱动器号为0
 mov ah,#0x08       //BIOS 0x13中断的子命令08,表示读取磁盘驱动器参数
 int 0x13      //利用BIOS 0x13中断读磁盘驱动器参数
 xor ch,ch          //ch包含返回的最大磁盘号的低8位,cl返回每磁道扇区数,ch在这清0,那么cx值就为每磁道扇区数(即cl的值)
 seg cs            //表示下一条的语句在cs段寄存器指定的段中
 mov sectors,cx   //每磁道扇区数保存到sectors
 mov ax,#INITSEG   //INITSEG=0x9000
 mov es,ax          //改变es段寄存器为0x9000

(4)把system模块加载到内存0x10000地址处
 mov ax,#SYSSEG   //SYSSEG=0x10000
 mov es,ax          //将es段寄存器设置为0x10000,即存放system的段地址
 call read_it       //读磁盘上的system模块,es为输入参数
 call kill_motor    //关闭驱动器马达
 call print_nl      //光标回车换行

(5)确定根文件系统的设备号
 seg cs             //下一条指令在cs段寄存器指定的段内执行
 mov ax,root_dev    //将定义在引导扇区508,509字节处的root_dev赋值给ax寄存器
 or ax,ax           //将ax寄存器内容或操作,结果保存到ax寄存器中
 jne root_defined   //若或后ax值不为0,则跳转,即root_dev值已经初始化过。为0表示未初始化,往下执行
 seg cs             //下一条指令在cs段寄存器指定的段内执行
 mov bx,sectors     //每磁道扇区数赋值给bx寄存器
 mov ax,#0x0208     //代表/dev/ps0,即1.2M软驱
 cmp bx,#15         //判断每磁道扇区数是否为15个
 je root_defined    //若是15个,则ax中的值就是引导驱动器的设备号
 mov ax,#0x021c     //代表/dev/Ps0,即1.44M软驱
 cmp bx,#18         //判断每磁道扇区数是否为18个
 je root_defined    //若是18个,则ax中的值就是引导驱动器的设备号
undef_root:          //若既不是15个也不是18个,则死循环,即死机
 jmp undef_root
root_defined:
 seg cs
 mov root_dev,ax    //将ax中保存的设备号赋值给变量root_dev
 
(6)长跳转到setup程序处(0x90200地址)
 jmpi 0,SETUPSEG    //长跳转到setup程序处(0x9020:0000)去执行
 
三、setup.S程序
setup.S程序是一个操作系统加载程序。仍在实模式下运行,主要是设置系统参数,为进入保护模式做准备。
1.它的主要作用是:
(1)利用BIOS中断读取机器系统数据,并将这些数据保存到0x90000开始的位置(即会覆盖掉bootsect程序所在的地方)。
(2)然后将system模块从0x10000-0x8FFFF (当时认为内核模块长度不会大于512K)整块向下移动到内存绝对地址0x00000处。
(3)加载中断描述符表寄存器(IDTR)和全局描述符表寄存器(GDTR),开启A20地址线。
(4)重新设置两个中断控制芯片8259A,将硬件中断号设置为0x20-0x2F.
(5)设置CPU的控制寄存器CR0,从而进入32位保护模式运行。
(6)跳转到位于system模块的最前面部分的head.S程序继续运行。

2.部分代码解析
(1)利用BIOS中断读取机器系统数据
 mov ax,#INITSEG    //INITSEG=0x90000
 mov ds,ax          //将ds段寄存器设置为0x90000
 
 mov ah,#0x88       //利用BIOS中断15,功能号为0x88,读取系统可扩展内存的大小
 int 0x15
 mov [2],ax         //并将读到的系统可扩展内存的大小0x90002处
 
还有读取显示参数、光标位置、显示模式,取硬盘参数表等分别保存在固定的位置,都是通过BIOS中断实现的,所以不在列出代码。
 
(2)将system模块整块向下移动到地址0x00000处
 mov ax,#0x0000    //
 cld               //清除操作方向标志位
do_move:
 mov es,ax         //将es段寄存器设置为0x0000,es:di为目的地址
 add ax,#0x1000    //
 cmp ax,#0x9000    //是否已经移动完
 jz end_move       //是则跳转
 mov ds,ax         //源地址:ds:si(初始为:0x1000:0x0)
 sub di,di
 sub si,si
 mov cx #0x8000    //移动0x8000字,即(从0x10000-0x8ffff)
 rep        //重复执行并递减cx的值,直到cx=
 movsw             //移动源地址:ds:si---->目标地址:es:di
 jmp do_move
(注意:这里是否把BIOS中断覆盖掉????)
(3)加载IDTR、GDTR
end_move:
 mov ax,#SETUPSEG  //SETUPSEG=0x90200
 mov ds,ax         //ds指向本程序setup段
 ligt idt_48       //加载IDT寄存器,是一个长度为0的空表,idt在线性空间的32为基地址也设置为0,限长也为0。
 lgdt gdt_48       //加载GDT寄存器,设置了3个描述符项,第一项为空,第2项为内核代码段描述符,第3项为内核数据段描述符,并且GDT设置的地址为0x90200+gdt,即在setup程序后边
 
(4)设置8259A
8259 芯片主片端口为0x20-0x21, 从片端口为0xA0-0xA1.

(5)设置控制寄存器CR0
 mov ax,#0x0001   //保护模式位PE,即CR0的第0位置1导致cpu切换到保护模式,并且运行在特权级0中
 lmsw ax          //加载机器状态字,进入保护模式
 
(6)跳转到head.S程序
 jmpi 0,8         //跳转到cs段偏移0处,即head.S程序。段值8已经是保护模式下的段选择符了,即表示请求特权级0,使用全局描述符表GDT中第二个段描述符,即内核代码段描述符。该描述符指出代码基地址为0,即跳转到了system中的代码。
 
四、head.S程序
head.S程序在被编译成目标文件后会与内核其他程序一起被链接成system模块,位于system模块的最前面部分。
1.它的主要作用是:
(1)加载各个数据段寄存器。
(2)重新设置中断描述符表IDT,共256项。
(3)重新设置了全局描述符表GDT,只是把段限长设置为16MB。
(4)监测A20地址线是否已经开启。
(5)接着设置管理内存的分页处理机制。
(6)转去执行/init/main.c程序的main()函数。
2.部分代码解析:
(1)加载各个数据段寄存器
startup_32:            //这里已经在处于32位运行模式
 movl $0x10,%eax      //选择内核数据段描述符,并用该描述符设置各个数据段寄存器
 mov %ax,%ds
 mov %ax,%es
 mov %ax,%fs
 mov %ax,%gs
 lss _stack_start,%esp //设置系统堆栈
 
 call setup_idt        //重新设置IDT
 call setup_gdt        //重新设置GDT
 movl $0x10,%eax       //由于GDT重新设置过,内核数据段描述符的限长改为了16M,所以要重
 mov %ax,%ds           //新各个数据段寄存器加载内核数据段描述符
 mov %ax,%es
 mov %ax,%fs
 mov %ax,%gs
(2) 重新设置IDT
setup_idt:   
 lea ignore_int,%edx    //将ignore_int的有效地址值存入edx寄存器
 movl $0x00080000,%eax  //将段选择符0x0008放入eax的高16位中
 movw %dx,%ax           //ignore_int的有效地址值的低16位放入eax的低16位中
 movw $0x8E00,%dx       //设置edx寄存器的低16位为8E00,即表示特权级0,内核代码段存在于内存中
 lea _idt,%edi      //设置中断描述符表的地址给目标寄存器edi
 mov $256,%ecx          //共256项
rp_sidt:
 movl %eax,(%edi)       //低4字节存入中断描述符表中
 movl %edx,4(%edi)      //低4字节存入中断描述符表中
 addl $8,%edi           //指向下一个中断描述符表
 dec %ecx               //256依次递减
 jne rp_sidt            //没到256项就回去接着设置
 lidt idt_descr         //设置完之后,加载中断描述符表寄存器
 ret
(3)重新设置GDT
setup_dgt:
 lgdt gdt_descr         //加载全局描述符表寄存器
 ret

gdt_descr:
 .word 256*8-1
 .long _gdt
 
_gdt:
 .quad 0x0000000000000000   //空的全局描述符表项
 .quad 0x00c09a0000000fff   //0x08,内核代码段限长16M
 .quad 0x00c0920000000fff   //0x10,内核数据段限长16M
 .quad 0x0000000000000000   //空的全局描述符表项
 .fill 252,8,0              //预留空间
 
(4)设置管理内存的分页处理机制
setup_paging:
 movl $1024*5,%ecx       //对5页内存(1页目录,4页页表)清零
 xorl %eax,%eax          //eax内容清空
 xorl %edi,%edi          //页目录从0x0000开始
 cld;rep;stosl           //eax内容存到es:edi所指的内存处,且edi增4
 
 //以下四项为设置页目录表中的项,_pg_dir地址为0x0000,即页目录地址           
 movl $pg0+7,_pg_dir     //pg0在前边被指定为0x1000,所以第一个页表项设定为0x00001007
 movl $pg1+7,_pg_dir+4   //设置页目录中的第二个页表项
 movl $pg2+7,_pg_dir+8   //设置页目录中的第三个页表项
 movl $pg3+7,_pg_dir+12  //设置页目录中的第四个页表项
 
 //填写4个页表中的页内容
 movl $pg3+4092,%edi     //edi设定为最后一页页表的最后一项
 movl $0xfff007,%eax     //最后一项对应的物理内存是0x0xfff000,属性标志为0x07,表示页存在、用户可读写
 std                     //方向位置位
1:stosl                   //eax内容存到es:edi所指的内存处,且edi增4
 subl $0x1000,%eax       //物理地址减4K,处理下一页。因为页表中的每一项都是代表一页的。
 jge 1b                  //若小于0表示都已填写页表ok
 
 xorl %eax,%eax          //页目录地址在0x0000,所以这里对eax寄存器清空
 movl %eax,%cr3          //将页目录表地址存入CR3中
 
 movl %cr0,%eax          //
 orl $ox80000000,%eax    //开启PG标志
 movl %eax,%cr0          //表示启动使用分页处理
 ret                     //
 
(5)转去执行/init/main.c程序的main函数
//现在新的版本的内核初始化程序改名字为start_kernel()
after_page_tables:
 push $0                 //设置main函数调用参数envp=0
 push $0                 //设置main函数调用参数argv=0
 push $0                 //设置main函数调用参数argc=0
 push $L6
 push $_main             //编译程序对main函数的内部表示方法
 jmp setup_paging        //设置分页结束后,执行ret会将main地址弹出,转去执行main函数
 jmp L6                  //不会跳转到这里

到此为止,引导启动程序结束,开始转去执行内核初始化程序。 
 
 

 

阅读(4175) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~