Linux0.11启动过程

1.         从系统加电起所执行程式的顺序为:

ROM BIOS bootsect.S setup.S head.S main.c
2.         ROM BIOS
PC机加电后,80x86结构的CPU将自动进入实模式,并从地址0xFFFF0开始自动执行某些系统的检测。并在物理地址0处开始初始化中断向量。此后,他将可启动设备的第一个扇区(磁盘引导扇区512字节)读入内存绝对地址0x7C00处,并跳到这个地方去执行(执行bootsect.S处指令)。
3.         bootsect.S
执行期间,他会将自己移动到内存绝对地址0x90000开始处并继续执行。该程式的主要作用是首先把从磁盘第2个扇区开始的4个扇区的setup模块(有setup.S编译而成)加载到内存紧接着bootsect后面位置处(0x90200),然后利用BIOS中断0x13取磁盘参数表中当前启动引导盘的参数,接着在屏幕上显示“Loading system…”字符串。再者把磁盘上setup模块后面的system模块加载到内存0x10000开始的地方。随后确定根文档系统的设备号,若没有指定,则根据所保存的引导盘的每磁道扇区数判断出盘的类型和种类(是1.44MB A盘吗?)并保存其设备号于root_dev(引导块的508地址处),最后长跳转到setup程式的开始处(0x90200)执行setup程式。
4.         setup.S
是个操作系统加载程式,他的主要作用是利用ROM BIOS中断读取机器系统数据,并将这些数据保存到0x90000开始的位置(覆盖掉了bootsect程式所在的地方)。这些参数将被内核中相关程式使用,例如字符设备和驱动程式等。然后setup程式将system模块从0x10000~0x8ffff(当时认为内核系统模块system的长度不会超过此值:512KB)整个块向下移动到内存绝对地址0x00000处。接下来加载中断描述符表寄存器(idtr)和全局描述符表寄存器(gdtr),开启A20地址线,重新配置两个中断控制芯片9259A,将硬件中断号重新配置为0x20~0x2f。最后配置CPU的控制寄存器CR0(也称机器状态字),从而进入32位保护模式运行,并跳转到位于system模块最前面部分的head.S程式继续运行。
5.         head.S
程式在被编译生成目标文档后会和内核其他程式一起被链接成system模块,位于system模块的最前面开始部分,这也就是为什么称其为头部(head)程式的原因。System模块将被放置在磁盘上setup模块之后开始的扇区中,即从磁盘上第6个扇区开始放置。这个程式的功能比较单一。首先是加载各个数据段寄存器,重新配置中断描述符表idt,共256项,并使各个表项均指向一个只报错误的哑中断子程式ignore_int。(中断描述符表中每个描述符项也占8字节)。在配置好中断描述符表之后,本程式又重新配置了全局段描述符表gdt。接着配置管理内存的分页处理机制,将页目录表放在绝对物理地址0开始处(也是本程式所处的物理内存位置,因此这段程式将被覆盖),紧随后面放置共可寻址16MB内存的4个页表,并分别配置他们的表项。最后head.S程式利用返回指令将预先放置在堆栈中的/init/main.c程式的入口地址弹出,去运行main()程式。
6.        
内核中的main()函数的执行过程
(1)   
保存根设备号ROOT_DEV;高速缓存末端地址buffer_memory_end;机器内存数mem_end;主内存开始地址main_memory_start
(2)   
假如在Makefile文档中定义了内存虚拟盘符号RAMDISK,则初始化虚拟盘,此时内存将减小。
(3)   
初始化虚拟盘rd_init()
a.  
首先将MAJOR_NR设定为1,然后再包含头文档blk.h,这样能够用来确定设备操作宏对应的函数。
b.
将块设备结构的请求操作指针进行赋值blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST。宏DEVICE_REQUEST对应的函数是由上步通过MAJOR_NR值在blk.h头文档中进行确定的。是do_rd_request
c.  
初始化虚拟盘在物理内存中的起始地址,占用字节长度,并对整个虚拟盘区清0,最后返回盘区长度。
(4)    mem_init()
主内存初始化
a.  
该函数对1MB以上内存区域以页面为单位进行管理前的初始化配置工作。一个页面长度为4K,该函数把1MB以上任何物理内存划分成一个个页面,并使用页面映射字节数组mem_map[]来管理任何这些页面。每当一个物理内存页面被占用时,就把mem_map[]中对应的字节值增1,若字节值为0,则表示对应页面空闲。若字节值大于或等于1,则表示对应页面被占用或被不同程式共享占用。(linux0.11内核最多能管理16MB的物理内存,大于16MB的内存将弃置不用)。
(5)    trap_init()
陷阱门(硬件中断向量)初始化。
a.  
中断描述符表idt的位置实在head.S中配置的,他被配置在内存页目录(页目录从0x0000开始)和也表之后,全局描述表之前。
b.
首先为Intel CPU保留的中断号int0~16配置中断门或陷阱门,并将对应的中断处理程式地址添加到其中。
c.  
int17~48的中断门或陷阱门先均配置为reserved,以后各硬件初始化时会重新配置自己的陷阱门。
d.
配置协处理器中断0x2d陷阱门描述符,并允许其产生中断请求,配置并口中断描述符。
e.  
当进入Intel CPU保留的中断处理程式时,该中断处理程式首先保存当前CPU寄存器状态,并在最后恢复寄存器状态。大部分的CPU保留中断处理程式都是打印出一些状态信息后返回,只有少数几个进行了特别处理(例如页异常)。
(6)    blk_dev_init()
块设备初始化。
a.  
该函数用来初始化请求数组request[],将任何请求项置为空闲项。
b.
任何一个块设备都是通过数据缓冲区进行操作的。每个块设备都有自己的设备号,通过request.dev的值决定确定当前请求属于哪一个块设备。
(7)    chr_dev_init()
字符设备初始化。(这是个空函数)
(8)    tty_init()
初始化串口终端和控制台终端。
a.  
调用rs_init()配置两个串口的中断门描述符。串口1使用的中断是int 0x24,中断处理过程指针是rs1_interrupt。串口2使用的中断是int 0x23,中断处理过程指针是rs2_interrupt,然后在对串口1和串口2进行初始化。最后允许主8259A响应IRQ3IRQ4的中断请求(串口中断请求)。
b.
调用con_init()初始化控制台终端。该函数首先根据setup.S程式取得的系统硬件参数初始化配置几个本函数专用的静态全局变量,然后根据显示卡模式(单色还是彩色显示)和显示卡类型(EGA/VGA还是CGA)分别配置显示内存的起始位置连同显示索引寄存器和显示数值寄存器端口号。最后配置键盘中断0x21陷阱门描述符,keyboard_interrupt是键盘中断处理过程地址,取消8259A中对键盘中断的屏蔽,允许键盘发出IRQ1请求,最后复位键盘控制器以允许键盘开始正常工作。
(9)    time_init()
配置开机启动时间
a.  
该函数首先读取CMOS实时时钟信息作为开机时间,并计算出从1970110时起到开机当日经过的秒数,保存至全局变量startup_time中。
(10)sched_init()
调度程式初始化(加载任务0trldtr)。
a.  
在全局描述符表中配置初始任务(任务0)的任务状态段描述符LDT和局部数据表描述符TSS0,全局描述符gdthead.S中配置的。
b.
初始化指向每一个任务描述符数据结构(进程描述符)的指针数组,使其都为NULL,数组0元素指向任务0的进程描述符,这个描述符定义在内核地址空间中,而其后任何进程描述符都在新分配的一页内存的起始地址处。这页内存的最后的最后剩余部分用作该进程的内核栈。(用户栈是在该进程的线性地址空间顶端在使用时动态进行分配的)。
c.  
然后在将全局描述符任务0以后的描述符全初始化为0
d.
复位NT标志,当NT置位时,那么当前中断任务执行iret指令时就会引起任务转换。
e.  
将任务0TSS段选择符加载进寄存器tr,将局部描述符表段选择符加载到局部描述符表寄存器ldtr中。
f.  
初始化8253定时器通道0选择工作方式3,二进制计数方式。通道0的输出引脚接在中断控制主芯片的IRQ0上,他每10毫秒发出一个IRQ0请求。
g.
配置时钟中断处理程式句柄(配置时钟中断门)在0x20上,修改中断控制器屏蔽码,允许时钟中断。
h.
配置系统调用中断门,在0x80上,这个中断能够被任何程式所执行,用作系统调用,系统调用中断处理程式system_call()
(11)buffer_init()
缓冲区管理初始化,建立内存链表等。
a.  
确定缓冲区在内存中的起始位置和结束位置。
b.
初始化这段缓冲区,建立空闲缓冲块循环链表,并获取系统中系统中缓冲块数目,初始化每一个缓冲头结构并将其链接位循环链表。
c.  
初始化内核中缓冲块散列队列(哈希表)散列数组,置数组中任何指针位NULL
(12)hd_init()
硬盘初始化
a.  
首先将MAJOR_NR设定为3,然后包含头文档blk.h,这样就能够用来确定设备操作宏对应的函数。
b.
将块设备结构的请求操作指针进行赋值,blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST,宏DEVICE_REQUEST对应的函数是由上一步通过MAJOR_NR值在blk.h头文档中进行确定的,是do_hd_request()
c.  
配置硬盘中断描述符,hd_interrupt是其中断处理过程,修改中断控制器屏蔽码允许硬盘控制器发送中断请求信号。
(13)floppy_init()
软驱初始化
a.  
首先设定MAJOR_NR2,然后包含头文档blk.h,这样就能够用来确定设备操作宏对应的函数。
b.
将块设备结构的请求操作指针进行赋值,blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST,宏DEVICE_REQUEST对应的函数是由上一步通过MAJOR_NR值在blk.h头文档中进行确定的,是do_fd_request()
c.  
配置硬盘中断描述符,修改中断控制器屏蔽码允许软盘控制器发送中断请求信号。
(14)sti()
任何初试化工作都作完了,开启中断。
(15)move_to_user_mode()
移到用户模式下执行,该函数利用iret指令实现从内核模式移动到初始任务0中去执行。
(16)
调用fork()创建出子进程(任务1),父进程(任务0)则循环执行pause()函数。
a.   pause()
函数在前面通过static inline _syscall0(int, pause)进行定义。
b. pause()
将当前任务转换为可中断等待状态,然后执行调度函数schedule(),调度函数只要发现系统中没有其他任务能够运行时,就会转换到任务0,而不依赖于任务0的状态。
(17)
任务1执行init()函数
a.  
调用setup(),这个函数只能被调用一次,再调用则会直接返回-1
b.
假如已在include/linux/config.h配置文档中定义了符号常数HD_TYPE,则取定义好的参数作为硬盘信息数组hd_info[]中的数据,否则就需要读取boot/setup.S程式存放在内存0x90080处开始硬盘参数表来初始化hd_info[]结构数组,并确定含有的硬盘个数存放在NR_HD中。
c.  
配置硬盘分区结构数组hd[],该数组的项0和项5分别表示两个硬盘的整体参数,而项1469分别表示硬盘的4个分区的参数。
d.
调用rd_load()尝试创建并加载虚拟盘,假如当前根设备是软盘并且ramdisk的长度不为0则加载虚拟盘,并将根设备号修改成虚拟盘设备号。
e.  
调用mount_root()安装根文档系统,这个函数首先初始化文档表数组(共64项,即系统同时只能打开64个文档)和终极块表,将文档结构中的引用计数配置为0(表示空闲),并把终极块表中各项结构的设备字段初始化为0(也表示空闲)。从根设备上读取文档系统终极块,并取得文档系统根i节点,对终极块和i节点进行配置,配置该终极块的被安装文档系统i节点和被安装到i节点字段为该i节点。再配置当前工作目录和根目录i节点,此时当前进程是1号进程(init进程),然后对根文档系统资源作统计工作,然后显示出来。
f.  
以读写访问方式打开设备/dev/tty0,他对应终端控制台(指的是键盘和显示器,输入是键盘,输出是显示器)。由于这是第一次打开文档操作,因此产生的文档句柄号肯定是0,该句柄是UNIX类操作系统默认的控制台标准输入句柄stdin,再把他复制到标准输出句柄stdout和标准错误输出句柄stderr
g.
创建一个子进程(任务2),打开/etc/rc文档,并以他作为标准输入,然后调用execve()执行/bin/sh程式,由于/etc/rc文档是标准输入,这样shell程式/bin/sh就能够运行rc文档中配置的命令了,由于这里sh的运行方式是非交互方式的,因此再执行完rc文档后就会立即退出,进程2也随之结束。
h.
父进程再等待进程2结束后,就进入无限循环,首先再创建一个子进程,对于所创建的子进程将关闭任何以前还遗留的句柄(stdinstdoutstderr),新创建一个会话并配置进程组号。重新打开/dev/tty0作为stdin,并复制到stdoutstderr,再次执行系统解释程式/bin/sh。父进程则进入无限循环,并调用wait()等待。
i.   
现在整个启动初始化过程结束并运行进入shell和用户进行交互界面。

 

你可能感兴趣的:(Linux0.11启动过程)