# 一.什么是操作系统

1.官方的概念

操作系统是管理计算机的硬件资源,控制其他程序运行并为用户提供交互操作界面的系统软件的集合

2.我的自己的理解

操作系统是一个硬件之上的软件,负责管理我们的计算机的硬件资源,是我们能够更加高效的使用这些硬件资源, 二是提供给用户一个友好的交互操作界面。 使用户能够通过操作系统高效的让计算机解决我们的需求。

# 二. 操作系统管理那些硬件资源

  1. CPU管理
  2. 内存管理
  3. 文件管理
  4. 磁盘管理
  5. 终端管理

# 三.学习操作系统的目标?

我们应该学到哪种程度?

  1. 只知道操作系统向我们提供了哪些接口,如何使用, 例如fork(), write(),read()的使用。
  2. 不光知道提供的哪些接口,还知道这些功能中操作系统干了什么,如何实现的。
  3. 知道了操作系统如何实现的,动手实现一个操作系统 我的目标是给我一个板子,我就能在上面写一个操作系统。 为了这个目标可能要花费几年的时间,至少在找工作前,我应该学到第二个阶段。

# 四. 计算机怎么工作的?

# 存储程序思想

在早期中图灵想到,人的计算过程能否被机器实现, 比如1+2=3, 人从纸上进行获取了数据和要进行的操作,之后在大脑中运算,最后输出结果到纸上。

之后就出现了图灵机的思想,输入数字进行加法运算,然后输出。

但是这种思想不能够算是通用,因为只能进行+法不能进行减法, 也就是只能做一种事情。

比如厨师只会做一种菜, 只用提供蔬菜,就只能产生一种菜品。 但是如果我们讲每一道菜的制作过程和菜材料,都送给厨师。让它根据菜谱使用对应的蔬菜,制作出对应的菜品。 就成了通用图灵机。

通用图灵机:我们通过过程和数据都送给机器,来得到对应的结果。

后来的冯洛依曼在1946年提出了存储程序思想: 讲程序和数据放到计算机内部的存储器中,计算机在程序的控制下一步一步的处理。

程序就相当于菜谱,而数据就是原材料。

现代计算机的核心思想: 取指和执行交替执行。 将程序和数据放在存储器中, 通过pc(ip) 指向程序的开始位置。 控制器来控制取指令和执行的过程。

# 五.计算机的启动过程

# 计算机执行的第一条指令是什么? (PC指向哪条指令?)

第一条指令的地址由硬件的开发者决定。

实模式的寻址方式与保护模式的寻址方式不同, 开机时处于实模式。

当计算机通电后,在x86架构的计算机中, 在开机时处于实模式,cs代码段被置为0xFFFF,即全1,而ip被置为0x0000,即全0。 之后cs自动的左移动4位二进制, 与ip想加。

最终得出地址: 0xFFFF0。 由于刚开机内存中没有任何动心,甚至有没有内存都不知道。 因此刚开始寻址实在一个固化着BIOS程序(基本输入输出系统),这个程序的起始地址为0xFFFF0。

这个程序作用是检查计算机的各个硬件,比如cpu,内存,磁盘等,也就是开机自检。

当开机自检完成,接下来的指令是,从磁盘中的0号柱面0号磁道将0号扇区的数据(一个扇区512个字节),读取到内存的0x7C00地址处。 0号柱面0号磁道0号扇区约定俗成的为操作系统的第一段程序,引导启动程序。

之后设置cs代码段的值为0x07C0。ip为0x0000; (cs左移四位)即寻址0x7C000,从这里继续执行。

硬盘的第一个扇区上存放着开机后执行的第一段我们可以控制的程序。我们通过修改这一段程序,来实现我们想要的功能。

# 一段引导扇区的汇编代码:bootsect.s

img

阅读方式: 在某一行中会有两个指令, 从左到右的顺序读。

第一条汇编指令: mov ax #BOOTSEG

经推测, #BOOTSEG 应该是一个类似于c语言的宏定义, 提前设置好了一个值, 之后由汇编器进行汇编,讲期进行替换,生成二进制。

这条语句的意思是, 讲0x07c0 送到ax 通用寄存器。

第二条汇编指令:mov ds,ax。

将ax的值0x07c0 ,送到ds, 数据段寄存器

第三条汇编指令:mov ax,#INITSEG。

含义是将立即数值 0x9000 送到ax寄存器。

第四条: mov es,ax。

讲ax的值0x9000 送到es 额外段寄存器。

第五条: mov cx,#256

将立即数256送到 cx 寄存器

第六条: sub si ,si

讲si寄存器的值 减去 si寄存器的值, 讲结果送往 si寄存器。 也就是自己减去自己, 结果为0, 也就是说将si寄存器置为 0。

第七条: sub di, di

与第六条同理。 将di寄存器置为0。

第八条: rep

根据cx的值,重复执行后面的指令。

第九条:movw

移动一个字。

movw: 将DS:SI的内容送至ES:DI,注意是复制过去,原来的代码还在。

ds的值为0x07c0 ,es的值为0x9000。

猜测每次移动一个字,那么si和di就会加1。 所以上面将si和di初始化为0。

第十条: jmpi go , INITSEG

jmp是直接跳转,加了一个i就是间接跳转, go 的值是一个量, INITSEG 是起始地址。

也就是说跳转到 INITSEG + go 的值对应的地址的 位置。

上面这10条指令的含义就是, 将内存中起始地址为0x7c000位置的512个字节(256个字) 加载到内存中起始地址空间为 0x90000的位置。

然后跳转到 0x90000 + go 的位置, 而go的位置就是正好第11条汇编指令的位置,接着下图。

img

11: 将cs的值0x9000移动到ax寄存器。

12->14条指令,将ax寄存器的值0x9000 送到ds,es,ss 三个段寄存器中。

15条指令: 将值0xff00 送到 堆栈栈顶指针寄存器。

之后从load_setup开始

16: 将dx寄存器设置为0。

17:将cx寄存器的高8位(ch) 设置为0, 将低8位(cl)设置为值为2.

18: 将bx的值设置为0x0200。

19:因为SETUPLEN的值为4,即0x0004。 所以加上0x0200就是0x0204。

将这个值设置给ax寄存器, 高八位(ah)的值为2,低8位(al)的值为4。

20:int 0x13, 值得注意的是这里的int不是指的是整形变量,而是中断单词的缩写, 这条指令的意思是执行中断程序,而编号为13的中断程序是 磁盘相关程序。

从16到20指令的意思就是,为读取磁盘这个中断程序做准备, 设置ax,bx ,cx,dx 寄存器的值, 因为中断程序会以这些寄存器的值为参数来执行。

ax寄存器的高8位表明的是,值为2,表示是要读取磁盘。 低8位值为4,表明读取4个扇区, 因为磁盘读取的单位是扇区,一个扇区512个字节。

dx寄存器:高8位的值代表 磁头号,低8位是驱动器号。

cx寄存器:高8位 柱面号, 低8位扇区号

上面的三个寄存器,表明了从磁盘的哪里读取,读取多少个扇区。

而 es 额外段加上 bx 寄存器 组成的地址, 就是从磁盘读取的内容所存放的地址。

es左移4位,再加上bx寄存器的值, 在这里值是 0x90200正好是从引导扇区后面的那个扇区的起始地址。

将上面的setup程序所占用的4个扇区读取到内存当中后。 就接着执行引导程序的下面的代码。

img

这里的代码含义就是,在黑屏幕上打印logo(Loading system...)。 之后读取真正的os系统的代码,读取到4个扇区的后面。

img

上面的第一段是, 调用的read_it模块的代码, 作用是读取真正的os系统。

将操作系统读取到0x10000的位置, 使用的是:es(0x1000)寄存器.

下面的代码就是, 引导扇区的标记, 只有最后两个字节的值= 0xAA55时,才能被识别为引导扇区。 在完成开机自检后,可能会扫描到多个磁盘,但是需要判断第一个具有引导扇区的磁盘,来加载操作系统。

当操作系统被加载完成, 也就是read_it模块的代码执行完成, 就会执行接下来的

jmpi 0,SETUPSEG 指令, 而SETUPSEG的值为 0x9020。

ip的值被设置为0, 而cs 代码段的值设置为 0x9020。 之后CS:IP 值为 0x90200, 组成了 第二个扇区,存储着setup 程序的第一条指令的地址, 开始执行, setup程序。

# 总结, bootsect.s 都做了什么?

在上面我们执行完,引导程序(bootsect.s)后,将引导程序移动到了0x90000.将setup.s移动到了0x90200. 将操作系统(system)模块,移动到了0x10000.

# setup.s 汇编程序的执行.

img

  1. 设置硬件参数, 获取实际的内存大小, 显卡的参数, 光标的位置等...
  2. 将系统(system)从0x10000, 移动到0x00000. 在当时的年代, 操作系统的大小通常不会超过0x80000,也就是512K字节大小.

中断向量表: 存储中断程序地址的表(地址:0x0000),被操作系统覆盖掉, 我们用不了了实模式的中断程序了, 因为要开始进入保护模式了, 保护模式的中断程序入口不一样.

如果我们在上面不移动引导扇区程序的位置, 那么顺序加载的4个扇区大小的setup程序就会被覆盖.

  1. 将模式进行改变, 从实模式变为保护模式. 实模式的寻址方式与保护模式的寻址方式不同.

img

  1. 跳转到操作系统的第一个指令处(0x0000) 主要的区别如下:
  • 位数改变, 从16位变成32位. 本来就是32位的cpu,但是一直在实模式当做16位cpu来使用.

  • 汇编代码不同, 实模式的汇编代码与保护模式的汇编代码不一样.

    img

  • 实模式的寻址方式, 是段寄存器左移4位加上对应偏移指针的值.
    保护模式的寻址方式,是cs寄存器的值,变成gdt表的偏移也叫段选择子.

例如 cs的值为8, 就表明从gdt表的起始基址加8个字节对应的位置开始数的 8个字节, 就是一个表项.

而gdt(全局描述符表), 可以理解为一个数组, 每一个表项的大小为64bit,8个字节. 从gdt中取出32位的段基地址, 再加上32位的ip寄存器, 就组成了新的地址

img

初始化gdt表,显示的设置前几个字节的值, 第一行是起始地址0, 第二个是8.

石墨

上main的 .word 命令就是读取一个字到gdt对应的位置.

img

寻址位置为 段基址的值0x00000000 + ip(0x00000000) = 0;

img

于是跳转到操作系统的第一条指令的地址位置.

# 六. 系统调用

# 1.什么是系统调用?

系统调用就是操作系统提供的函数调用, 操作系统给上层提供的接口, write, read, 上层应用通过系统调用来进入操作系统(内核态),使用操作系统提供的功能.

# 2. 三个问题

# 操作系统 为什么不让我们直接 jmp 或者 mov整个内存的 数据 ?

  • 操作系统启动后, 如果我们可以随意的 JMP , mov 内存的所有数据, 操作系统就极其有可能被我们破坏掉.
  • 如果内存中的某个地方,不应该被访问,比如保存着用户的账号和密码, 就可能被别人获取.

img

# 操作系统如何实现的不让我们使用 直接使用 jmp 或者 mov ?

是通过CPU的硬件设计实现的. 通过硬件检查 CPL 和 DPL. (DPL >= CPL) ? 允许访问 : 拒绝访问

  • CPL : 当前程序级别

  • DPL : 目标程序级别

操作系统将代码段的种类,分为了内核段和用户段, 分别存放的是操作系统的数据和用户的数据.

当CPU 执行内核段的程序时, 表明操作系统处于内核态, 当CPU执行用户段的程序时, 表明操作系统属于用户态

内核段的等级为0, 用户段的等级为3 , 这个值越低表明权限越高

当前段的权限级别,保存在CS段寄存器的最低两位.
目标访问段的权限级别存在,GDT表中对应的表项中. img

当执行操作系统中的head.s代码时, 会重新设置 GDT表(setup里面设置的gdt表和idt表都是临时的), 将所有未使用的内存空间的表项全部初始化为0, 这个时候的表项的DPL全都为0

img

# 想要执行, 应该怎么办?

对于Inter x86架构, 唯一的方式是 中断.
int指令, 将当前执行的段的cs中的 CPL(cs的最后两位), 修改为0, '进入内核'

用户程序发起的调用内核代码的唯一方式
系统调用的核心:

TIP

  1. 用户程序中包含一段包含int指令的代码
  2. 操作习题写中断处理,获取想调程序的编号(中断号,功能号)
  3. 操作系统根据编号执行相应的代码

# 例子: 以C语言中的printf()函数进行举例:

我们在自己写的应用程序中调用C函数库的printf()函数, printf()函数,首先将传递的参数进行处理, 将字符串转换为 char * buf, 统计字符串中字符的个数, 之后通过一段宏展开一个3参数的模板函数, _syscall3(...)

img

然后发现 _syscall3() 函数里面写了一段内嵌式汇编代码. 如下图:

img

展开的函数:

//type = int , name = write atype = int, a = fd
//进行相应的替换后
int wtite(int fd, ...){
    //保存中断程序运行结果
    long __res;
    __asm__ valatile("内嵌汇编代码...");
    if(__res >= 0 ){
        return (int)__res;
    }
    errno=-__res;
    retur -1;
}

# int 0x80 是什么?

int 0x80 是一个中断程序, 通过查找IDT表(中断描述符表,这个表跟GDT表非常的类似), IDT表记录了所有的中断的出来程序的入口地址.

img

而 0x80, 则表示执行的是 IDT表中第80个表项, 这个表项记录了这个中断程序的入口地址, 然后跳去这个地址去执行中断程序.

# 2. 系统调用接口的实现(插座背后的故事...)

更新时间: 2024年5月26日星期日下午4点47分