Chinaunix首页 | 论坛 | 博客
  • 博客访问: 121130
  • 博文数量: 31
  • 博客积分: 2010
  • 博客等级: 大尉
  • 技术积分: 361
  • 用 户 组: 普通用户
  • 注册时间: 2008-03-11 15:38
文章分类

全部博文(31)

文章存档

2008年(31)

我的朋友

分类: LINUX

2008-04-14 22:59:04

     今天学习了Linux的启动过程,先大致了解一下Linux的流程,再细说具体的工作。

一、概述

    Linux的启动过程大致是这样的:加电后,CPU自动进入实模式,从0xffff0处的
BIOS开始执行,BIOS先检测硬件,再在内存物理地址0开始初始化中断向量,然后从磁盘的第一个扇区把系统引导程序bootsect.s读入内存0x7c00处,并跳到此处开始运行bootsect,它首先将自己移动到0x90000处,并跳至此处运行,后来把磁盘上在后面的setup.s读入0x90200处,并将内核从磁盘读到0x10000处,最后跳去执行setup.s,setup.s把内核移到到物理地址的开始位置,在设置保护模式下的环境后跳到物理地址起始位置在保护模式下执行内核的头:head.s。head.s设置内核执行的环境,最后跳到main执行初始化程序。

    从以上可以看出,Linux的启动过程是由3个汇编程序完成的,分别是bootsect.s,setup.s和head.s,其中bootsetc.s和setup.s是由as86语法写的,属于系统引导程序,而head.s是按照GNU as语法写的,是操作系统内核的一部分。从功能上,总的来说,BIOS是加载系统引导程序,bootsect.s加载系统内核,setup.s是获取系统信息并为运行内核做准备,head.s则是设置内核运行环境。下面就分别说一下这三个汇编程序具体干了什么。

二、Bootsect.s

   1)复制自身到0x90000处并从此处执行:由于移动幅度超过了段的界限,所以在移动后需要重新设置各个段的寄存器。

   2)加载setup.s:利用BIOS提供的13号中断(读磁盘数据到内存)将setup.s复制到内存0x90200处。

   3)加载内核模块system:先将system模块从磁盘加载到内存0x10000处,再关闭驱动器马达。在这里,我想说一下system具体的复制过程的子程序read_it:首先利用BIOS的13号中断获取磁盘的信息(磁道扇区数),再循环调用read_track子程序(读指定数目扇区到内存指定位置)完成。由于system模块大于段长64KB同时也占用磁盘的多个磁道,所以在复制过程中,要不断调整磁道和段地址:每次循环都先尝试读当前磁道的剩余扇区到内存中,如果判断读入后超过段长,则重新计算本次读入扇区数,执行一次读入,并在读入后调整段地址,开始新一轮的读入;如果判断读入后没有超过段长,就执行一次读入(call read_track),然后移到下一磁头或者磁道,开始新一轮的读入。

  4)最后,保存文件系统信息,跳到setup.s。

三、Setup.s

  1)读取系统信息:BIOS中的系统信息;坐标信息;扩展内存大小;显卡信息;硬盘信息。

  2)将system模块从0x10000处移到0x00000处。在这里开始我有些糊涂:费这事儿干嘛!把system先移到0x10000,再移到0x00000。后来想起来了,在bootsect.s中,要用到BIOS为我们提供的中断程序,这些中断程序就在物理地址的开始处,所以不能一开始就将system移到开始处。

  3)为进入保护模式做准备:设置GDT,IDT(空表);开启A20地址线;初始化中断芯片8259;

GDT的设置是这样的,编写GDT,共三个描述符,第一个为空,第二个为内核代码段描述符,第三个是数据段描述符,再编写lgdtr指令所需要的6B操作数(指定GDT的位置),最后利用lgdtr将GDT表信息加载到GDTR中。IDT的设置也是同样道理,只不过在这里,IDT是个空表。

  4)设置保护模式标志位(PE),跳到内核执行head.s。

四、head.s

  1)设置各个段寄存器:因为现在已经是保护模式了,在保护模式中,段寄存器中不再是段的基地址了,而是段描述符选择符,所以要把各个段寄存器中的内容设置成对应的选择子,同时还要设置栈指针esp。

  2)重新设置GDT和IDT:新设置的GDT和setup.s中的比只是把段限长从8M改成16M了。新的IDT则变了很多,IDT表中共有256个向量,每个向量都指向一个默认的中断处理程序ignore_int。由于,重新设置了GDT所以,在设置两个表后要重新设置各个段寄存器和栈指针的值。

  3)检测A20地址线和协处理器

  4)为跳转到main做准备:将三个0入栈(envp,argc,argv入栈),main返回地址入栈(其实是不会返回的,但为了规范和以防万一,把下一条指令地址作为返回地址),main函数地址入栈。

  5)设置页表和页目录项表:从内存0地址开始分配五页内存,一个页目录项表,四个页表,目录项表中的前四项是四个页表的地址,四个页表把内核16M的线性地址空间映射到前16M的物理内存中。

 最后,把页目录项表基地址放入CR3中,并设置CR0开启分页机制。

  6)执行函数返回:ret,cpu会将弹出栈中的内容,由于返回地址是main,所以跳转到main执行系统的初始化。

这样,就完成了Linux系统的启动,下面Linux就会在main()中执行初始化了。

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