操作系统笔记(一)初识操作系统——启动过程

操作系统

  • 前言
  • 什么是操作系统
  • 揭盖钢琴的盖子
    • 计算机上电后执行的第一条指令
    • 对于x86PC
    • 0x7c00处存放的代码
      • 为什么这里用的汇编代码而不是用C写的呢?
      • 操作系统启动步骤解读
    • 操作系统启动
      • bootsect应该做什么?
      • setup模块,即setup.s做了什么
      • 跳到system模块执行
      • head.s//一段在保护模式(32位)下运行的代码
      • 关于汇编...head.s的汇编和前面不一样?
      • after_page_tables //设置了页表之后
      • 进入main函数
      • 看一看mem_init
  • 总结

前言

一台计算机可以简单的认为成三部分,即一个CPU、一个内存条、一个硬盘。
比如说我们想打印“hello”,并显示出来。这里假设h的地址是300,即300H:0x68(68为h的ASCII码值),CPU把这个内存地址下的变量mov到显存GPU上去。这样依次把这一字符串打印,就完成了操作。
显然这样的操作对于我们来说太麻烦了,那么就有了操作系统的出现。

什么是操作系统

操作系统是位于应用软件(如Word、Matlab…)与计算机硬件(如CPU、内存、显存…)之间的一层,操作系统(如Windows、Linux…)。
有了操作系统,我们想要输出一个“hello”,只需要写入printf(“hello”);就够了。这样是不是就方便很多了。
没有操作系统的计算机硬件,人们往往称为裸机。

总的来说,操作系统就是计算机硬件与应用之间的一层软件1. 方便我们使用硬件,如使用显存2. 高效的使用硬件,如开多个终端(窗口)

除此之外还可以管理一些硬件:
CPU管理
内存管理
终端管理
磁盘管理
文件管理
(以上五个为构成计算机的基本部分)
网络管理
电源管理
多核管理(多cpu、分布式系统)…

揭盖钢琴的盖子

Open the OS
计算机有五大部件组成:输入设备、输出设备、存储器、运算器、控制器。
计算机是如何工作的:取指执行

计算机上电后执行的第一条指令

计算模式=>我们要关注指针IP及其指向的内容

 1. 计算机刚打开电源时,IP=2. 有硬件设计者决定!

执行的第一条指令就是IP指令所指向的命令,一般存在于内存当中。

对于x86PC

 1. x86PC刚开机时cpu处于实模式(实模式:和保护模式对应,实模式的寻址CS:IP(CS左移4+IP),和保护模式不一样)2. 开机时,CS=0XFFFF;IP=0X00003. 寻址0XFFFF0(ROM BIOS(Basic Input Output System)映射区)4. 检查RAM,键盘,显示器,软硬磁盘5. 将磁盘的0磁盘0扇区读入0X7c00处(1个扇区512个Byte)(0磁盘0扇区就是操作系统的引导扇区)6. 设置cs=0x07c0,ip=0x0000

0x7c00处存放的代码

就是从磁盘引导扇区读入的那512个字节

 1. 引导扇区就是启动设备的第一个扇区(开机时按住del键就可以进入启动设备设置界面,可以设置为光盘启动)2. 启动设备信息被设置在CMOS中...(CMOS:互补金属氧化物半导体(64B-128B)。用来存储实时钟和硬件配置信息)。3. 因此,硬盘的第一个扇区上存放着开机后执行的第一段我们可以控制的程序。

引导扇区代码:bootsect.s (s:汇编)

为什么这里用的汇编代码而不是用C写的呢?

实际上,我们写的C程序将来都需要经过编译,那么如果写的C程序经过编译以后,很可能会产生出一些我们不知道的,无法控制的东西,比如说int i这个变量,我们无法知道变量i到底最后会存储在内存中哪个位置;而汇编中的每一条指令最后都变成了机器指令,所以我们可以对其进行完整的控制。
而在引导扇区,我们要在他的引导过程中,对他进行精确的控制,不能有任何差错。
在操作系统中有很多这样的控制。

操作系统启动步骤解读

参考博文1 --操作系统启动步骤解读

参考博文2 --计算机加电后操作系统的启动过程

参考博文3 --段间跳转指令jmpi和实模式寻址

参考博文4 --早期as86汇编指令rep movw解释

操作系统启动

bootsect是操作系统启动的第一个部分。

bootsect应该做什么?

在刚一开始上电的时候,操作系统应该启动在哪里呢?我们把操作系统装在了硬盘上。即刚开始上电的时候后操作系统在磁盘的硬盘上。
而计算机工作是“取指执行”,要想这样工作就要把代码放到内存中才可以。
那么操作系统刚一开始是在磁盘上,是肯定取不了指令的。
所以要做的第一件事就是要把操作系统从磁盘上载入到内存里。
第一步,一上电,bootsect应该把操作系统从磁盘上读到内存中setup区。(由第一段代码,操作系统的引导扇区来完成这个工作。即 bootsect.s)
先将操作系统的代码分段读进来,首先读到setup中;然后紧接着打印出一个开机的logo。
接下来又调用 int 13 号中断,把操作系统中后面的部分system也读进到内存里。
至此,bootsect就完成了他所要完成的工作。
之后交给下一部分执行,即setup.s

setup模块,即setup.s做了什么

根据名字就可以想到:setup将完成OS启动前的设置
int 0x15 15号中断 BIOS中断

 1. 什么是扩展内存?计算机刚刚出来的时候,英特尔,pc机的内存只有1M的容量,通常把1M以后的内存叫做扩展内存。2. 为什么要把扩展内存的大小在一开机的时候读进来,并且保存起来?操作系统是管理硬件的,当然要管理内存。管理内存第一件事就是要知道内存有多大。所以要获取内存的大小

setup将操作系统(system模块)放到了从内存中0地址处开始的地方,至此操作系统的位置将不会发生变化,一直到最后,运行的程序等将会在内存的上面的地址空间运行。
第二步,setup把操作系统放到0地址处,并进入保护模式。
16位机 ——20位寻址(1M)
jmpi ip,cs cs<<4+ip
32位机——32位寻址方式(4G)(保护模式)
有一个非常酷的寄存器:cr0 它的第0位是PE,第31位是PG;PE=1启动保护模式 / 0实模式,PD=1启动分页

1 保护模式下的地址翻译:GDT(Global Descriptor Table)全局描述符表(要求:硬,快。所以用硬件而没用软件)
cs:ip(cs是选择子);gdt的作用:t是table。所以实模式下:cs左移4+ip。保护模式下:根据cs查表+ip,即cs查表之后得到基地址+ip的偏移地址=物理内存地址
2 保护模式下中断处理函数入口:IDT(Interrupt Descriptor Table) 中断描述符表
t仍是table,仿照gdt,通过int n的n进行查表
那么setup到底做了哪些事情呢?

 1. 读了一些硬件的参数,为了将来建立操作系统打下基础。2. 把system挪动到了00地址处,将来操作系统的核心代码就一直在哪里。3. 启动了保护模式,应用了一条32位的高级汇编指令,跳到了0地址去执行。

接下来就跳到了system模块去执行了…

跳到system模块执行

system模块(目标代码)中的第一部分代码? head.s
bootsect是由bootsect.s文件编译出来的;setup是由setup.s文件编译出来的;而sytsem是由很多文件编译出来的。
system是由很多文件编译而成的,为什么是head.s?
我们有一堆源码通过makefile把源码最终产生了一个文件,通常把操作系统最终编译出来的文件叫做Image——镜像。将这个镜像放在0磁道0扇区,这个是可以控制的,可以通过一个指令或一些工具将一个镜像写到任意位置,然后再用这个Image开机引导的时候就会出现从bios到boot到setup到system,操作系统就会被初始化,最终产生一个桌面或者说一个shell,最终这个启动就完成了,就可以开始用操作系统了。
(makefile是一种树状结构,所以Image也是树状结构;父依赖于子,所以是一个厚根的样子,最上层的文件依赖于其对应的.s文件等等很多)

disk:Imagedd bs=8192 if=Image of=dev/PS0                    if=input file/dev/PS0 是软驱A
Image:boot/bootsect boot/setup tools/system tools/buildtools/build boot/bootsect boot/setup tools/system >Imagetools/system:boot/head.o init/main.o$(DRIVERS)...$(LD)boot/head.o init/main.o $(DRIVERS)... -o tools/system    (链接)	

head.s//一段在保护模式(32位)下运行的代码

head.s文件做了什么事呢?
“setup是进入保护模式,head是进入之后的初始化。”
(head.s在这里设置了IDT表和GDT表;setup的时候只是借用一下为了找到0地址的位置,并没有正式的去用。)
从这个文件开始,此时的代码与之前的汇编代码不一样了,因为此时的汇编是32位的了,之前一直是16位;并且源操作数与目的操作数的位置也反过来了。
如: movl $0x10,%eax

关于汇编…head.s的汇编和前面不一样?

(1)as86汇编:能产生16位代码的Intel 8086(386)汇编

mov ax,cs //cs->ax, 目标操作数在前

(2)GNU as汇编:产生32位代码,使用AT&T系统V语法

movl var, %eax //(var)->%eax
movb -4(%ebp) , %al  //取出一字节

(3)内嵌汇编,gcc编译xxx.c会产生中间结果as汇编文件x.s

__asm__(“汇编语句”      		__asm__("movb
:输出							%%fs:%2,%%al"
:输入							:"=a"(_res)
:破坏部分描述);				:"0"(seg),"m"(*(addr)));

0或空表示使用与相应输出一样的寄存器;
a表示使用eax并编号%0;
%2表示addr,m表示使用内存。

after_page_tables //设置了页表之后

head.s完成了操作之后要执行下面的main.c(main.c是个c函数,而head.s是个汇编。所以接下来要从一个汇编跳到一个c函数。这是怎么做的呢?

实际上这和从一个c函数跳到另一个c函数没有根本的区别。
因为c语言最终执行的本质还是由汇编执行,压栈!
ret :从栈里面返回地址

after_page_tables:pushl $0	pushl &0	pushl $0	pushl $L6pushl $_main	jmp set_paging
L6:	jmp L6
setup_paging:设置页表 ret

简单的几句程序,控制流很复杂:

 1. setup_paging执行ret后?会执行函数main() 2. 进入main()后的栈为0,0,0,L63. main()函数的三个参数是0,0,0 4. main()函数返回时进入L6,死循环...

进入main函数

main函数是一个永远不会退出的程序,退出就会进入L6死循环,即死机。

1. main的工作就是xxx_init:内存、中断、设备、时钟、CPU等内容的初始化...
2. main函数的三个参数分别是envp,argv,argc。但此处main并没有使用。
3. 此处main只保留了传统main的形式和命名

看一看mem_init

void mem_init(long start_mem,long end_mem)
end_mem 为内存大小 在setup里面有说,90002开始。
创建了一个mem_map表格,其实是一个数组。
end_mem >>= 12 (一页为4k大小)
这个表格已占用的内存部分为操作系统。

总结

以上简单粗略的记录了一下操作系统的启动过程。
从boot->setup->head->main->main里的mem_init

boot将操作系统从磁盘上读进来
setup获得了一些参数,启动了保护模式(32位)
head初始化了一些GDT表,初始化了一些页表,然后跳到了main
main里又是一堆mem_init,初始化哪些是空闲内存,有哪些硬盘,硬盘是什么样的等等。

即1、把操作系统从磁盘读入内存 -> 取指执行。
2、完成各种初始化,初始化一些数据结构,完成一些管控 -> 操作系统是管理计算机硬件的一个系统。

本文链接:https://my.lmcjl.com/post/8341.html

展开阅读全文

4 评论

留下您的评论.