参考来源:哈工大,李治军老师,操作系统公开课
--> 我们使用水龙头接水时,我们根本不需要知道水是怎么从水厂来的,只要打开水龙头这个 “接口”,就可以取到水了。
--> 对于操作系统来说,用户不需要关心硬件是怎么工作的,这些由操作系统来完成,而我们只需要使用操作系统提供的“接口”就可以了。
1. 什么是操作系统的接口?
(1) 命令行是怎么回事?

(2) 图形按钮又是怎么回事?

- 消息机制:不断的从消息循环队列中不断取消息的循环,每一个消息都对应消息处理函数。
(3) 那么,什么是操作系统的接口?
- 根据上面两个例子,我们发现:
- 操作系统提供了这些重要函数。
-
接口就是操作系统提供的这些重要函数。。。接口表现为函数调用,由于函数是系统提供,因此,操作系统接口就是系统调用。 |
小总结:操作系统接口就是系统调用 system_call |
(4) 操作系统的接口都有哪些?
- POSIX:Portable Operating System Interface of Unix(IEEE制定的一个标准族)
- 简单看几个操作系统接口

- 如果想要查看所有的接口,那么查找POSIX手册即可。
2. 系统调用的实现
(1) 实现一个 whoami 系统调用

(2) 解决问题1:用户态、内核态和特权级
将内核程序和用户程序隔离
- 段寄存器CS的低两位:CPL (进程当前段的特权级)
- 段选择子的低两位:RPL (进程对段访问的请求权限)
- 处理器总共提供了6个段寄存器来保存段选择子;因此同一时刻,可能存在多个RPL(每个段选择子都有一个RPL),但只要段选择子CS中的RPL才等于CPL。
- 这是因为:CS寄存器也叫“代码段寄存器”。
- 段寄存器DS的低两位:DPL (目标段的特权级)
- 当 DPL>=max{CPL,RPL}时,可以访问。
- 在GDT表中,
whoami()
的DPL被置为0,为内核态,对应内存属于内核段。而用户程序main(){whoami();}
的CPL是3,因此不能直接访问内核。
(3) 解决问题2:中断门(中断方法)
jmp和mov都不能进入内核,只有中断才能进入内核。 |
<1> 硬件提供了“主动进入内核的方法”

<2> 系统调用的实现

<3> 系统调用实现细节

- 对比 <2>... 和 <3>... 中的图片内容对C语言的宏函数进行展开,得到write函数
- 可以看到的是:type=int, name=write, atype=int, a=fd, btype=const char* , b=buff, ctype=off_t, c=count.
- 因此,函数头是
int write(int fd, const char* buff, off_t=count){...}
- 函数体是内嵌汇编
- _NR_write 是系统调用号
- 系统调用必须通过 int 0x80 调用号进入内核。但是,许多程序都要调用内核,并且调用函数也不相同,那么怎么调用想要的函数呢?这就需要系统调用号
<4> int 0x80中断的处理

- 核心就是在IDT中断向量表中的80号进行初始化.
- 将IDT中80中断的DPL置为3。
- 将段选择符设置为8,因此,最低两位的CPL是00(内核态)。
- 段选择符+处理函数入口偏移,找到处理函数。
- ...中断返回时,会把CS的最低两位重新置为3,变为用户态。
<5> 中断处理程序:system_call

- sys_call_table就是系统调用函数表
- 4*%eax 其中,4表示每个函数占4字节,%eax的内容是系统调用号。
- 这里,%eax被置为了4,说明 sys_write 函数在第五位(从0开始)。
<6> _sys_call_table

3. 总结
操作系统的接口:系统调用
问题一:操作系统怎么阻止你进入内核?
将分段的内存设置特权级,用户态在用户段,内核态在内核段。根据特权级,有访问权限。
问题二:用户怎么调用操作系统内核代码(或者说使用系统调用函数)
--->以用户调用C语言的 printf() 函数为例:
1. 此时,CPL=3,处于用户态;
2. 通过库函数,将 printf() 变成 int ox80代码
3. int 0x80代码 在系统初始化时,做成了一个system_call
4. 在中断处理时,将IDT中 0x80 处的 DPL 置为3,能够访问IDT的0x80;
5. 然后把CPL置为0,进入内核态。
6. 查表sys_call_table,根据系统调用号,找到你要调用的函数sys_write。
7. 中断结束时,又将 CPL重新置为3,进入用户态。
- 调用门
- 中断门
- 陷阱们
- 任务门
由于现在只是希望初步的了解操作系统,因此不做深入的学习。