接口
什么是接口。
以生活中常见的插座为例,我们怎么使用的呢?我们只需要将插头插入到插座内部,就可以轻松地使用,我们不必知道里面哪些是火线,哪些是零线。。。
所以这是一个抽象化的概念,它将功能封装好,然后方便别人调用。
我们要进行学习的有两个
- 什么是操作系统接口?
- 上层应用调用接口时,在内部是究竟怎么运作的?
操作系统接口
简单来说,操作系统是连接上层用户和操作系统软件的
比如我们键入一个hello命令,屏幕上显示了hello,它方便了使用,屏蔽了细节。
我们平常使用计算机通过如下方式。
我们进行深层次的剖析,究竟哪些是操作系统给我们的接口呢?
命令行
首先从命令行开始,从我们在计算机中敲入命令,内部究竟发生了什么呢?
什么是命令?
命令其实就是一段用c语言写的程序而已。当我在命令行输入如下命令./output "hello"
(实际上没有这个命令,是自己实现的)
其实这个命令实际是我们写的这样一段程序:
#include <stdio.h>
int main(int argc,char * argv[]) {
printf("ECHO:%S\n",argv[1]);
}
执行我们的命令就会在屏幕中打出:
ECHO:hello
将上面的程序经过下面的编译
gcc -o output output.c
然后就可以通过这个可执行文件来进行执行命令了。
那么敲入命令发生了什么呢?其实敲入的命令都是在shell里的。
其实shell也是一段程序:/bin/sh
在之前的学习过程中,知道了操作系统的启动,在启动之后,就会执行/bin/sh
这个就是shell
int main(int argc,char* argv[]){
char cmd[20];
while(1) {
scanf("%s",cmd);
if(!fork()) {
exec(cmd);
}
else {
wait();
}
}
}
这个现在可以不用先完全搞懂。后面会学。
总结一下,我们从输入到输出,内部做了哪些事情。首先通过shell的函数,调用output,然后通过可执行文件,将这个结果输出。
图形按钮
当鼠标点下去的时候,通过中断,放到系统消息队列里面,应用程序要写一个getMessage,这个是一个循环,不断的读取。要从操作系统里面把这些消息一个一个的读取出来。根据拿出来的消息来进行相应的操作,比如是鼠标按下的消息,执行相应的函数。
所以图形界面也是没有那么复杂,主要还是调用函数,应用程序通过函数的调用来获取消息队列中的消息,通过调用一些函数。来进行文件的读写功能。等等。
所以无论是命令行还是图形界面,都是一些普通的C代码加上一些重要的函数。比如你要使用显示器 就使用print函数,若果要使用键盘,就需要getMessage函数。等等
所以说操作系统就是提供这样的重要的函数,这个就是操作系统的接口了,接口表现为函数调用,又由系统提供,所以称为系统调用。
常见的接口
之前我们提到的printf不算是一个接口,它其实是包装了一个write函数,我们不需要完全背下来这些接口,我们需要知道去哪里查。
POSIX,这个是统一的接口。
系统调用的实现
实现一个whoami系统调用
用户程序调用whoami,一个字符串“lizhijun”放在操作系统中,取出来打印。
用户程序:
main(){
whoami();
}
-------------------
内存中
whoami(){
printf(100,8);
}
"lizhijun"
进入内核取出”lizhijun“,打印出来。
思考一个问题,为什么我写的用户程序,一定要通过whoami()来获取呢?这些都存在内存中,为什么不可以直接调用printf(100,8);来直接打印出来呢?
同理应用程序在内存中,操作系统也在内存中,应用程序访问操作系统提供的功能,为什么不能直接“跳”进去?
答案是不允许的。
不能随意的调用数据,不能随意的jmp。如果可以的话,可以看到root的密码,可以修改它,可以通过显存看到别人word的内容。
这是相当危险的!
如何实现这种安全的系统
要将内核程序和用户程序隔离!要区分内核态和用户态,需要处理器硬件的实现。
处理器将内存割了很多区域,分为用户态和内核态。在内存中叫做用户段和内核段。处于用户态的程序不能使用内核段的内存。
DPL是用来描述目标内存段的特权级,上面我们所说的whoami()就是一个目标内存段,通俗的理解为是要访问的目标段。操作系统初始化时,硬件设置成0表示内核态,3是用户态。实际上这个DPL就初始化在GDT表中,等于0
也就是说head.s在初始化GDT表的时候,就把DPL置为0。
RPL是当前的特权级,这个取决于你执行的是什么指令,如果我们执行的是上面所说的那个main()函数的话,需要CS:IP
来确定指令所在的地址,这里面的CS的最低两位,就代表着这个内存段的特权级。
所以要检查DPL是不是大于CPL,也就是说CPL越小越好调用其他的内存段。
总结一下:
当操作系统启动的时候,head.s会针对内核态的代码和内核态的数据建立GDT表象,对应的DPL就设置为0,初始化好了之后,就进入到用户态执行,当用户态执行的时候,启动一个用户程序,比如我们启动一个PPT,它中的CPL就等于3,所以不能直接访问内核态的数据。
我们应该怎么进入内核态
硬件提供了“主动进入内核的方法”,对于Intel X86,那就是中断指令int,当然只有一部分的指令可以进入内核态
int指令将使Cs中CPL改为0,“进入内核”
这是用户程序发起的调用内核代码的唯一方式。
通过调用库函数printf()展开write的宏,然后在调用write,包含int 0x80中断,从而进入了内核态。
下面好好说一下这个宏都做了什么事情
int0x80做了什么??
以前讲过通过查GDT表来查询跳转到哪里去执行,而int指令则要查询IDT表
中断的意思就是将停下来,跳到另一个地方去执行,执行完再回来。
现在cs = 8 ip = system_call
之前的代码jmp 0,8
中的8和这个8是一样的,这个要跳到8,就是查询GDT表,找到内核的代码段。
System_call要干的事情
_sys_call_table: