简析linux内核的执行流程
----从bootsect.s到main.c(内核版本0.11)
Linux启动的第一阶段(从开机到main.c)
3个任务:
A、启动BIOS,准备实模式下的中断向量表和中断服务程序。
B、从启动盘加载操作系统程序到内存。
C、为执行32的main函数做过渡准备。
内存变化如下:
① 、0xFE000到0xFFFFF是BIOS启动块,其中上电后第一条指令在0xFFFF0。
② 、而后0x00000到0x003FF总共1KB存放中断向量表,而接下去的地址到0x004FF共256B存放BIOS数据,从0x0E05B开始的约8KB的内存中存放中断服务程序。
③ 、利用BIOS中断0x19h把硬盘的第一扇区bootsect.s的代码加载到内存中,即0x07c00处,后转到该处执行。
④ 、将bootsect.s的代码复制到0x90000处。
⑤ 、利用中断0x13h将setup.s程序加载到内存0x90200处。
⑥ 、再将剩余的约240个扇区的内容加载到0x10000~0x2EFFF处。
⑦ 、开始转到setup.s处执行,第一件事就利用BIOS提供的中断服务程序从设备上获取内核运行的所需系统数据并存在0x90000的地址处,这时将原来bootsect.s的代码覆盖得只剩2Byte的空间。
⑧ 、关中断并将系统代码复制到0x00000处,将原来放在这里的中断向量表与BIOS数据区覆盖掉,地址范围是0x00000~0x1EFFF。同时制作两表与两寄存器。
⑨ 开地址线A20,寻址空间达到4GB,后对8259重新编程,改变中断号。
⑩ 、转到head.s(大小是25K+184B)执行,执行该程序完后是这样的:
0x00000~0x04FFF:页目录与4个页表,每一项是4KB,共20KB;
0x05000~0x05400:共1KB的空间是软盘缓冲区;
0x05401~0x054b8:共184B没用;
0x054b9~0x05cb8:共2KB的空间存中断描述符表;
0x05cb9~0x064b8:共2KB的空间存全局描述符表;
之后就是main函数的代码了!
第二阶段、从main.c函数到系统准备完毕阶段。
第一步:创建进程0,并让进程0具备在32位保护模式下载主机中的运算能力。流程是:(红色字体的是流程,加粗字是注释)
复制根设备和硬盘参数表
物理内存规划格局(main.c的112行~126行,其中有
虚拟盘设置与初始化 次数均设置成100,然后再依据主内存的 起始位置和终止位置将处于主内存的所有页面的使 用次数全部清零,系统以后把使用次数为0的页面 视为空闲页面。)
内存管理结构mem_map初始化
异常处理类中断服务程序挂接(在main.c的127行,trap()函数定义
初始化块设备与字符设备请求项结构(在main.c的128、129行,blk_dev_init() 定义在 kernel/blk_dev/ll_rw_blk.c,
chr_dev_init()定义在kernel/chr_dev/tty_io.c)
将串口与显示器外设的中断服务程序挂接(在main.c的130行,
开启时间设置(在main.c的131行,time_init()定义于main.c函数
系统开始激活进程0(在main.c的131行,sched_init()定义在kernel/
依据时钟中断设置,系统调用服务程序挂接。系统调用函数是对用户程序的最基本支持,利用的是
进程相关事务初始化设置 系统调用软中断,详细见下面讲
时钟中断设置
系统调用服务程序挂接
初始化缓冲区管理结构(在main.c中133行,
初始化硬、软盘(main.c中134、135行,hd_init与floppy_init定义于kernel/blk_drv/hd.c和kernel/blk_drv/floppy.c)
开中断 (main.c中136行,sti())
第二步:以进程0为母本创建进程1,使进程1不仅仅具备进程0所拥有的能力,而且还能以文件的形式与外设进行数据交互。流程是:
操作系统为进程0创建进程1做准备
user_mode()定义在include/asm/system.h,实现从
内核态到用户态。进程0正式开始执行,而后
执行main.c的138行的“if(!fork())”,开始创建
在进程槽中为进程1申请一个空闲位置并获取进程号
进程1,此时将执行unisted.h中的syscall0宏函数
,得到一个编号,对于fork函数,其值是2,具体
在这个程序中的第62行有定义,然后执行软中断
复制进程0的信息之前,先将一些数据压栈
初步设置进程1管理结构 的2偏移值在系统调用sys_call_table 中找到sys_fork函数,跳到该函数执行。进 入后首先申请一个空闲位置并获取进程号。
这同样在system_call.s函数中的sys_fork
进程0创建进程1的过程中发生时钟中断 中的_find_copy_
fork.c中,后返回到sys_fork中,在
从中断返回 复制进程信息前,再将一些数据压栈,
那就是eax,此时是1,它从task[64]中
得到的。之后执行copy_process,跳到
调整进程1的管理结构 定义处kernel/fork.c中,设置进程1的
会响应并执行kernel/system_call.s函数
中的timer_interrupt定义处,先压栈后
设置进程1的线性地址空间及物理页面 进入kernel/sched.c
的do_timer函数,别忘了此时仍在进程
0执行,然后便发现时间片还没完,所以
跳出,并返回到ret_from_sys_call。这是
继续调整进程1的管理结构 在kernel/system_call.s中定义的,
接着直接将刚才的压栈数据出栈,继续完成
刚才创建进程1的任务,即在fork.c中继续
调整进程1的管理结构,同时设置进程
进程0准备切换到进程11的线性地址空间及物理页面,直到
执行到这条语句“p->state=TASK_
RUNNING;return
系统切换到进程1执行 进程1创建完成。后跳出copy_process.c
函数,返回到system_call.s,将压栈的
进程1开始执行 的寄存器值出栈,此时eax是1。后中断返回
_res的值就是eax的值,一判断成立,就返
进程1开始以数据块的形式操作硬盘 回该值。最后回到了main.c
Pause函数也和fork函数一样,这里就不
将找到的缓冲块与请求项挂接 讲了,进入sys_pause()后将进程0
置为可中断等待状态,并调用在kernel
/sched.c定义的schedule()函数切换进程。
进程切换中断返回后执行了第一条语句
将请求项与硬盘处理函数挂接 是“if(_res>=0)”,一判断,
判断为真,执行init()函数,这在main.c
定义。进入init.c后其程序执行流程见附录,
进行硬盘读盘前的工作准备 各个程序执行目的正如左边写的一
样。
给硬盘下达读盘命令
进程1由于等待读盘操作挂起
系统切换到进程0执行
进程0执行过程中发生硬盘中断
再次响应硬盘中断,并唤醒进程1
进程1开始加载根文件系统
进程1准备加载根文件系统超级块
进程1继续加载根文件系统
进程1准备获取根目录节点
进程1加载根目录节点
进程1结束加载根文件系统的过程
进程1与内核文件表挂接,为打开文件做准备
确定打开操作的起点
获取枝梢i节点---dev目录文件的i节点
确定dev的目录文件i节点为枝梢节点
继续返回枝梢i节点
查找tty0文件的i节点
将tty0设备文件的i节点,返回给sys_open系统调用
分析tty0文件i节点
设置文件管理结构并返回给用户进程
进程复制tty0文件句柄
进程1继续复制tty0文件句柄
第三步:以进程1为母本创建进程2,使进程2在全面具备进程1所拥有的能力和环境的基础上,进一步具备支持”人机交互“的能力,最终实现准备阶段完成。流程如下:
进程1准备创建进程2
复制进程2的管理结构并进行调整
调整进程2管理结构中与文件有关的内容
进程1执行过程中发生时钟中断
进程1从时钟中断返回
进程1查找它自己的子进程
对进程2的状态进行处理
切换到进程2执行
为打开/etc/rc文件做准备
进程2打开”/etc/rc“配置文件
通过压栈为加载shell文件做准备
为参数和环境变量设置做准备
得到shell文件i节点
为加载参数和环境变量做准备
根据i节点,为shell文件进行检测
检测shell文件头
备份文件头并进行分析
对shell文件进行进一步分析
拷贝参数与环境变量
调整进程2的管理结构
继续调整进程2的管理结构
释放进程2继承的页面
检测协处理器
调整shell程序所在的线性地址空间
为shell程序准备参数与环境变量
继续调整进程2的管理结构
调整EIP,使其指向shell程序的入口地址
Shell程序执行发生缺页中断
缺页中断中shell程序加载前检测
为即将载入的内容申请页面
为shell载入新获得的页面
根据shell进程的情况,调整页面内容
将线性地址空间与程序所在的物理页面相对应
Shell进程准备创建update进程
进程2开始执行/etc/rc文件
准备加载update进程
Shell程序检测“etc/rc”文件
Shell进程退出
Shell进程退出善后处理
进程1清理shell进程管理结构
至此系统可以开始正常运转了!!!!!!!!!
参考书籍:《linux内核设计的艺术》新设计团队(著)
《linux内核完全注释》陈炯(著)
附录2:fs/sys_read(unsigned
{
......
68
69return
......
72
73
.........
看69行是说如果读的文件是字符设备文件,那么返回读的字符串,72行则是如果读的是一般文件,另作处理,在这里第一次读的是一般文件,返回值是ERROR,第二次就不一样了,所以就不会出现退出的情况!