简单描述Linux0.11的启动与初始化过程。

启动过程中需要关注:IDT, GDT, LDT, TSS, 页表, 堆栈这些数据。

一:启动过程

启动的代码文件为bootsect.s、setup.s、head.s

bootsect.s也就是启动扇区的代码。这段代码主要是将setup.s和head.s中的内容读入内存的相应区域。然后开始执行setup.s

setup.s

1:使用BIOS中断来获得相关系统信息:内存大小,硬盘分区信息,显示卡信息

2:将head.s代码拷贝到内存地址为0X0000的地方。

3:加载idt表和gdt表地址

4:开启A20地址线,只有开启它了才能访问高于1M地址的内存

5:重新设定中断控制器。这之后以前的BIOS中断号就没用了。

6:置位CR0寄存器的最后一位进入保护模式, 然后用jmpi 0, 8指令跳转到地址0x08:0x0000处开始执行,也就是head.s的起始代码处。

这里设定的idt表全部为空,也即这时并不处理任何中断。

gdt表中有三个描述符:0--NULL, 1--内核代码段, 2--内核数据段描述符。

此时内核代码段与内核数据段:基地址为0x00000000, 限长为:8MB

head.s

1:将堆栈设定在static_stack处,堆栈大小为1KB

2:重新设定设定IDT和GDT,此时全部IDT的都设置为ignore_int,即仍然忽略中断。

GDT中包含256个描述符。

0--NULL, 1--内核代码段, 2--内核数据段, 3--保留, 4--进程0的TSS段, 5--进程0的LDT段, 6--进程1的TSS段, 7--进程1的LDT段......

可见系统的GDT中为每个进程都设定了一个TSS和LDT段。

内核代码段和内核数据段:基地址(0x00000000),限长(16MB)

3:设定分页(由于内存管理部分目前没看到,因此关于进程的页表如何设定暂不明白,这里是内核的页表)

在0x00000000处的第一页存放“页目录”,随后存放4页“页表”,每个页表对应于页目录中的一项。

设定的页表,要求线性地址等于物理地址。

4个页表能映射16MB的物理空间,因此这16MB的物理内存地址与线性地址是相同的。(0.11的内核没有PAGE_OFFSET)

4:开始执行init/main.c中的main函数。

方式如下:

pushl $_main                          # 将main函数地址入栈

jmp setup_paging                   # 开始分页

......

setup_paging:

.......

ret                                      # 分页完成后,用ret指令弹出堆栈中的main函数入口地址,并开始执行main函数。

好像内核代码中常用这种弹出堆栈的方式来执行其他的函数

二:main函数

在启动过程中设定好GDT和页表后,开始在main函数中设定其他的内容。

主要是:设定IDT,设备初始化,创建进程0,fork出进程1,用进程1来执行init

main.c中主要的初始化函数是:trap_init,sched_init

trap_init中调用set_trap_gate来设定相应的中断描述符表。

下面以0号中断为例,描述其实现过程

1:调用set_trap_gate(0, &divide_error)

这个宏定义用来设定IDT表中的第0项的陷阱门描述符。

#define set_trap_gate(n, addr)   _set_gate(&idt[n], 15, 0, addr)

在_set_gate中:&idt[n]为第n个描述符的地址, 15为描述符的类型(陷阱门),0为描述符的权限(最高权限),addr为要调用的代码的地址。

_set_gate宏会调用相应的汇编指令,在&idt[n]处写入8字节的描述符。

2:divide_error的实现

该函数是以汇编码的形式实现。与该函数对应有一个处理函数do_divide_error(用C语言实现)

对其他的多数陷阱门处理方式也是如此,有一个汇编方式实现的,还有一个C语言实现的处理函数。

当中断0发生时,先调用divide_error,该函数再调用do_divide_error。

void do_divide_error(long esp, long error_code)

上面为函数原型。第一个参数为出错时的代码地址的指针,第二个参数是错误码。(有些中断不产生错误码,则错误码设成0)

因此在divide_error的汇编代码中,主要的功能就是将出错地址的指针和错误码这两个参数传递给do_divide_error函数,

同时将目前的数据段设定为内核数据段。

sched_init函数

该函数设定了进程0的TSS和LDT描述符,并将它们的选择子加载进了TR和LDTR寄存器。

另外该函数设定了时钟中断和系统调用

这里主要说下系统调用的执行,以fork函数为例。

1:在sched_init中初始化系统调用

set_system_gate(0x80, &system_call)

#define set_system_gate(0x80, &system_call)  _set_gate(&idt[n], 15, 3, addr)

可见系统调用也是一个陷阱门。区别是权限值为3, 因此用户进程能通过int 0x80的中断进入内核,执行系统调用。

2:每个系统调用都有一个对应的编号,fork为第二个系统调用,因此fork的调用号为2。

当执行fork函数的时候,它会用int 0x80来调用system_call函数。

此时fork的调用号被放入eax寄存器中。

3:全部的系统调用函数指针都保存在数组sys_call_table中。

在system_call函数中会执行指令

call _sys_call_table(,%eax, 4)来跳转到eax指定的系统函数代码上,对fork来说就是sys_fork函数。

4:system_call

i) system_call首先将相应的寄存器入栈。

pushl %edx

pushl %ecx

pushl %ebx             这三个寄存器对应了相应的系统调用函数的3个参数

因此0.11中,系统函数最多只能有3个参数。

ii)将ds和es设定为内核数据段,将fs设定为用户进程的数据段,需要用户进程的数据时,可使用fs来访问

iii)用call _sys_call_table(,%eax, 4)来执行系统调用

iii)检查当前进程是否处于可执行状态,检查当前进程的时间片是否用完,相应的执行schedule

iv)最后是对进程信号的处理。(信号机制没看完)

三:进入用户态

在main函数中相关初始化后,main以进程0的身份进入用户态。

然后调用fork函数,创建进程1,进程1调用init函数

init函数加载根文件系统,运行初始化配置命令,然后执行shell程序,这样便进入了命令行窗口。

0.11内核中,每个进程都有一个TSS段和一个LDT段,它们保存在进程描述符strut task_struct结构中。

相应段的描述符保存在GDT表中。

在LDT段中,有3个LDT描述符,0--NULL, 1--进程代码段, 2--进程数据段。

进程n的代码段和数据段:基地址=n*64MB,限长=64MB。(进程0和1的限长为640KB)

因此系统中最多有64个进程。

进程0的task_struct为INIT_TASK,进程0的TSS和LDT描述符在sched_init中设定。

main函数调用move_to_user_mode函数来执行进程0,进入用户态。

0.11内核中所有进程都是属于用户态,不像之后的Linux内核里有内核线程。

move_to_user_mode函数

此函数使用iret返回的方式,从内核态进入用户态。

+------------+

+   ss      +             pushl    $0x17

+------------+

+   esp    +             pushl %%eax                #eax中保存了esp

+------------+

+  eflags  +             pushfl

+------------+

+  cs       +             pushl $0x0f

+------------+

+   eip     +             pushl $1f                       #目的代码的偏移地址

+------------+

首先采用上面的push指令,将相关的数据压入堆栈,然后执行iret将它们弹出堆栈。

于是进程0从堆栈中的cs:eip指向的代码开始执行。

四:fork函数

进程0执行fork函数创建出进程1.

1:调用get_free_page为进程描述符分配内存。

p = (struct task_struct *) get_free_page();

这一页内存,前面保存task_struct内存, 页尾为进程的内核栈,

当一个用户程序调用系统函数进入内核态后,系统函数执行时使用的栈就是这个。

2:设定进程的task_struct结构体

3:内存拷贝,将父亲进程的内存拷贝给新进程。

4:设定新进程在GDT中的TSS和LDT描述符

有关fork最主要的是弄明白了,为什么它可以“返回”两次。

1:调用fork时,CPU自动将父进程的返回地址入栈(即eip寄存器入栈)

2:创建子进程的task_struct后,将TSS段中的eax字段设成0,eip字段设成父进程的返回地址。

3:将子进程的状态设成TASK_RUNNING(就绪状态)

4:fork函数以子进程的pid返回。

5:等到执行schedule,调度到子进程时。会自动将子进程的TSS内容加载进寄存器。

因此这时CPU中eax寄存器值为0, eip为父进程的返回地址。所以子进程从fork函数的下一条指令开始执行,返回值在eax中,为0。

阅读(2793) | 评论(0) | 转发(0) |

linux0.11 init函数,linux0.11启动与初始化相关推荐

  1. 金仓数据库 KingbaseGIS 使用手册(6.11. 空间关系函数)

    6.11. 空间关系函数 6.11.1. ST_3DIntersects ST_3DIntersects -如果几何对象在3维空间内相交,则返回TRUE. 用法 boolean ST_3DInters ...

  2. GoLang之init函数

    文章目录 GoLang之init函数 1.init函数特性 2.init函数执行顺序 3.init函数使用场景 GoLang之init函数 注:本文基于Go SDK v1.8进行讲解 1.init函数 ...

  3. Linux0.11 execve函数(六)

    系列文章目录 Linux 0.11启动过程分析(一) Linux 0.11 fork 函数(二) Linux0.11 缺页处理(三) Linux0.11 根文件系统挂载(四) Linux0.11 文件 ...

  4. Linux0.11内存管理,linux0.11内存管理

    描述linux 0.11的内存管理主要内容. 1:内存初始化 linux 0.11最大支持16MB的物理内存. main函数和mem_init函数对内存进行了初始化. 主要使用数组mem_map[]来 ...

  5. Linux 0.11 fork 函数(二)

    Linux 0.11 系列文章 Linux 0.11启动过程分析(一) Linux 0.11 fork 函数(二) Linux0.11 缺页处理(三) Linux0.11 根文件系统挂载(四) Lin ...

  6. oracle集群启动状态,循序渐进:Oracle 11.2 RAC集群进程的初始化与启动过程

    张大朋(Lunar)Oracle 资深技术专家 Lunar 拥有超过十年的 ORACLE SUPPORT 从业经验,曾经服务于ORACLE ACS部门,现就职于 ORACLE Sales Consul ...

  7. linux 0.11 init/main.c初始化部分

    在head设置了页表.GDT和IDT之后,然后就进入了main程序,这里首先介绍一些参数: ORIG_ROOT_DEV,该参数是读取0x901FC的两个byte读取的数据,这两个byte就是boots ...

  8. c语言return 11,二级C语言教程章节测试11.对函数的进一步讨论

    一.选择题 (1)有以下程序 # include void f(char *s, char *t) { char k; k=*s; *s=*t; *t=k; s++; t--; if (*s) f(s ...

  9. 《BOOST程序库完全开发指南》 第11章 函数与回调

    第11章  函数回调 #include <iostream> #include <boost/assign.hpp> #include <boost/ref.hpp> ...

最新文章

  1. 个人技术生涯的感悟(2)
  2. excel函数大全_让你的EXCEL工作效率翻倍的函数大全
  3. python 遍历文件夹下面所有的文件
  4. HTTP协议---HTTP请求中的常用请求字段和HTTP的响应状态码及响应头
  5. 《集体智慧编程》第六章
  6. 智能家居 (8) ——智能家居项目整合(网络控制线程、语音控制线程,火灾报警线程)
  7. deepsort代码解读
  8. sql虚拟服务器安装,安装SQL Server 2012服务器
  9. TMS320C55x之C/C++语言程序设计
  10. python 数据结构面试_【Python排序面试题】面试问题:所谓数据结构,… - 看准网...
  11. Visual Odometry
  12. STM32:I2S驱动WM8978
  13. 矩阵相乘c语言代码用指针实现,矩阵相乘C语言实现
  14. 给想去阿里面试的同学一些意见
  15. Visual SourceSafe如何支持并行开发
  16. mysql 密码复杂度要求_MySQL设置密码复杂度
  17. R语言基础 期中考试
  18. OPPO A53线刷刷机包 解账号锁
  19. steam游戏的计算机要求,steam游戏怎么看配置是否符合,steam不要求配置的游戏
  20. android软键盘上添加一个按钮

热门文章

  1. 为什么说 C++ 太复杂?有必要这么复杂吗?| 原力计划
  2. 联合国启动有史以来最大规模全球对话,腾讯会议、企业微信全程支持
  3. 小米回应“米家”商标争议;人人 App 回归社交市场;TiDB 2.1.19 发布| 极客头条...
  4. 平均薪资 38.4 万!3 步教你成为区块链开发者,收好这份学习指南!
  5. Go 语言为什么能成功?
  6. Google All in AI 都做了什么?
  7. 喜大普奔,VS Code 开启远程开发新时代!
  8. Google 封杀我写的 Web 浏览器!
  9. 东北到底有没有互联网?!
  10. 打通应用隔阂,AR 如何助力互联网度过寒冬?