操作系统启动过程
Posted PacosonSWJTU
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了操作系统启动过程相关的知识,希望对你有一定的参考价值。
【README】
本文内容总结自 《操作系统-哈工大李治军老师》,内容非常棒,墙裂推荐;
【1】概述
1)问题:操作系统启动时,它应该做什么事情?
计算机启动时,需要把存储在磁盘上的操作系统os的代码读入内存;
由引导扇区程序 bootsect.s 完成;
2)引导扇区程序做的工作包括(先后顺序):
- 1. bios程序(基本输入输出系统)把引导扇区程序拷贝到 0x90000;
- 2. 读取从第2个扇区开始后续4个扇区的内容到0x90200为首地址的内存空间;【setup程序】
- 3. 打印系统启动提示字符串,如loading system ;
- 4. 读取从第6个扇区开始后续n个扇区的内容到 0x90a00为首地址的内存空间;【操作系统程序】
- 5. 接着 引导扇区执行完成,跳转到 setup程序【0x90200】执行;
【1.1】setup 程序模块
1)setup.s作用:
会读取机器参数,包括光标位置,内存大小,显卡参数等硬件参数读入,放入到数据结构,以便操作系统对硬件进行管理;即完成操作系统启动前的参数初始化;
2)jmp do_move :
把【0x90000】的代码(即操作系统代码)移动到【0x0000】内存地址(即0地址),参见 setup 程序执行步骤表;
3)setup安装程序解释如下:
步骤 | 指令 | 描述 |
1 | int 0x15 | 15号中断,读取扩展内存数(总内存大小)到ax; 其中 1M以后的内存都叫扩展内存; |
2 | mov [2],ax | 把ax保存的扩展内存大小赋值给【0x90002】,因为基地址是0x90000,偏移量2 |
3 | do_move | 源地址:ds:si=0x9000:0x0000;即0x90000; 目标地址:es:di=0x0000:0x0000;即0x0000; 移动字节数:cx=#0x8000即8*16^3=2^15=64个扇区; 即把【0x90000】的代码(即setup代码和操作系统system代码)移动到【0x0000】内存地址(即0地址) |
【补充】
- 步骤3解释了为啥BIOS一开始需要把引导扇区从0x7c00移动到0x90000,原因在于腾出来的空间0x0000~0x8ffff 用于存放操作系统程序;
- 由步骤3可知:操作系统程序从内存0地址开始存放,自始至终都从0地址到0x8000地址中;后面的内存空间存放应用程序代码,如word,qq等;
4)接着setup做最后一件事情,即进入保护模式,就结束执行了;
Setup程序执行的最后一条指令是 jmpi 0,8 ;表示跳转到 cs:ip=8:0内存地址的指令执行;
【例】jmpi
Jmpi 0,SETUPSEG | jump intersegment-段间跳转: 目标地址=cs:ip=SETUPSEG:0=SETUPSEG:0; 即下面跳转到SETUPSEG为标号(锚点)的汇编指令执行; |
【问】为啥要进入保护模式?
因为段基址+偏移量寻址,如cs:ip寻址,cs左移4位加上ip,又cs,ip存储大小16bit(16位机),最多可以寻址20位=1M内存空间,这显然是不够的;
为了寻址到更大内存空间,如4G,需要切换到新的寻址模式,即保护模式,即从16位机切换到32位机模式(也叫保护模式);
最后一条指令: 切换到保护模式,启动32位寻址方式;
【问】16位模式和32位模式有什么不一样?
即 cpu的指令解释程序不一样;或者解释电路不一样;
如16位机解释地址的逻辑是 cs左移4位加上ip;
而32位机的解释逻辑不一样了;
Setup最后3条指令:
步骤 | 指令 | 描述 |
1 | mov ax,#0x0001 | 把1赋值给ax; |
2 | mov cr0,ax | 把ax或1赋值给cr0;cpu进入保护模式;进入保护模式的解释执行电路; cr0寄存器最后一位为0,表示实模式,16位机; cr0寄存器最后一位为1,表示保护模式,32位机; |
3 | jmpi 0,8 | 跳转到 cs:ip=8:0内存地址的指令执行,进入保护模式,开启32位机的程序执行过程; |
【2】保护模式的解释执行原理
【2.1】保护模式下的地址翻译原理
1)借助gdt来做,gdt,全局描述符表(它是硬件);如下:
索引(选择子) | 内存基址 |
0 | 0,0,0,0 |
8 | 0x07FF 0x0000 0x9A00 0x00C0 |
实模式(16位机):寻址方式为 cs左移4位+ip;
保护模式(32位机):寻址方式为cs作为索引(选择子)查gdt表得到基址+ip;
2)Gdt表的初始化由 setup程序来完成;
Gdt全局描述符表被初始化后的样子:
索引 | 基址 |
0 | 0 |
8(第8个字节) | 0x07FF 0x0000 0x9A00 0x00C0 |
word 0,0,0,0 每个字表示2个字节;
所以第8个字节开始的连续8个字节数据(因为GDT项是8个字节)为 0x07FF 0x0000 0x9A00 0x00C0 ;
所以基址是 0x00000000;所以 jmpi 0,8 通过gdt寻址是跳转到 0x0000:0x0000=0x0000地址(0地址是,操作系统程序或system模块首地址);
【补充】
同理, IDT表(中断描述符表)存储的是中断服务程序入口地址;
【2.2】makefile
编写操作系统代码,除了写源码外,还需要学习怎么编写操作系统控制代码,这就是著名的 makefile ;
在编写大型软件时,你要控制软件的合成结构,就必须用makefile;
1)Makefile如何做的?
- 操作系统程序经过makefile编译后的文件叫做操作系统image镜像;把这个镜像写入0磁道0扇区;
- 再用这个image进行开机引导时,操作系统就会顺利成章被读进来,然后执行引导扇区程序,setup安装程序,读入system模块程序并执行;
2)操作系统镜像的编译程序:
补充:操作系统的Image镜像的内容包括: 第1个扇区(引导扇区程序)+第2~5个扇区(setup程序)+后面system模块(操作系统程序); 此外,由 tools/system: boot/head.o init/main.o 可知,system操作系统模块包括 head.o main.o drivers.o 等; |
所以 head.s 是sytem模块的第一个文件;
【2.3】head.s 做了什么事情?
Head.s 需要重新初始化 IDT中断描述符表, GDT全局描述符表;
1)jmpi 0,8 就是跳转到head.s 去执行;
2)执行 head.s 即进入了保护模式;
- 现在进入的是保护模式,即32位机模式,执行的是32位的汇编代码;
- 如代码 movl 0x10, %eax 其中源操作数在前,目标操作数在后;
- 16位与32位模式下的寄存器表示不同: 32位机器的寄存器是 eax,16位寄存器是 ax;
- 32位机器只能执行32位汇编代码,无法执行16位汇编代码;
3)head.s 设置分页之后
Head.s 中的setup_paging执行完成后, 接着跳转到c语言的main() 函数执行;
Main函数长什么样子?
从汇编如何跳转到C函数,如何做到的?靠 栈 来做的;
【2.4】执行main函数
1)main函数会依次初始化内存,键盘,磁盘等;
2)main函数初始化以下计算机部件,如下:
序号 | C函数 | 描述 |
1 | mem_init() | 初始化内存 |
2 | trap_init() | 初始化硬件中断向量 |
3 | blk_dev_init() | 初始化内核 |
4 | chr_dev_init() | 字符设备初始初始化 |
5 | tty_init() | 初始化终端,如键盘 |
6 | time_init() | time_init() 函数初始化主机的系统滴答定时器硬件。 它安装定时器的中断处理程序,并配置定时器以产生周期性滴答声。 滴答中断处理程序通常称为 do_timer_interrupt()。 |
7 | sched_init() | Sched_init() 初始化内核的 pidhash[] 表,这是一个查找表,用于快速将进程 ID 映射到内核使用的进程描述符。 sched_init() 函数然后初始化内核的各种内部计时器使用的向量和下半部分处理程序。 |
8 | buffer_init() | 初始化缓冲 |
9 | dh_init() | 初始化硬盘 |
10 | floppy_init() | 初始化软盘 |
11 | sti() | cli()和sti()有点类似于汇编指令中的CLI和STL,当某个任务在执行的过程中不想被中断,则可以在任务的开始出执行cli(),在任务的结束处执行sti(),恢复中断的执行。 |
【补充】看下 mem_init() 函数-初始化内存
【小结】计算机启动总结为两件事:
- 1)把操作系统代码读入内存; 以便操作系统取指执行;
- 2)操作系统运行资源的初始化(main.c中的各种初始化函数),如内存,终端(如键盘),磁盘等;为什么要初始化,因为操作系统是管理计算机硬件的软件系统;
补充:保护模式下的中断处理函数入口;也是根据索引查找 IDT=中断描述符表中的中断处理函数入口地址;
【小结】
本文讲了 bootsect, setup, head.s, main.c, mem_init() 内存初始化函数;
- Bios 读入启动扇区 bootsect(磁盘0磁道0扇区,即第1个扇区),
- Bootsect里面的程序,读取setup, system模块;跳转到 setup模块执行;
- Setup读取机器参数(内存大小,光标位置,显卡参数等),把setup连同system模块拷贝到内存0地址,跳转到 system模块的head.o 去执行,即进入保护模式;
- Head.o执行完成后(初始化 GDT,IDT),跳转到main.c 执行;
- Main.c 主要初始化计算机运行的各种资源,如内存,终端如键盘,硬盘等;
【补充】main.c 调用函数初始化
main函数初始化以下计算机部件,如下:
序号 | C函数 | 描述 |
1 | mem_init() | 初始化内存 |
2 | trap_init() | 初始化硬件中断向量 |
3 | blk_dev_init() | 初始化内核 |
4 | chr_dev_init() | 字符设备初始初始化 |
5 | tty_init() | 初始化终端,如键盘 |
6 | time_init() | time_init() 函数初始化主机的系统滴答定时器硬件。 它安装定时器的中断处理程序,并配置定时器以产生周期性滴答声。 滴答中断处理程序通常称为 do_timer_interrupt()。 |
7 | sched_init() | Sched_init() 初始化内核的 pidhash[] 表,这是一个查找表,用于快速将进程 ID 映射到内核使用的进程描述符。 sched_init() 函数然后初始化内核的各种内部计时器使用的向量和下半部分处理程序。 |
8 | buffer_init() | 初始化缓冲 |
9 | dh_init() | 初始化硬盘 |
10 | floppy_init() | 初始化软盘 |
11 | sti() | cli()和sti()有点类似于汇编指令中的CLI和STL,当某个任务在执行的过程中不想被中断,则可以在任务的开始出执行cli(),在任务的结束处执行sti(),恢复中断的执行。 |
以上是关于操作系统启动过程的主要内容,如果未能解决你的问题,请参考以下文章