接口
连接两个东西,信号转换,屏蔽细节…
操作系统接口
连接上层用户和操作系统软件,方便了使用,屏蔽了细节。
操作系统接口的形式
为应用层提供一些重要的函数,如printf,write,read等。接口表现为函数调用,又由系统提供,所以称为系统调用(system call)。
操作系统接口的一个标准 POSIX
实现POSIX标准中要求的接口的操作系统,应用程序可以在它们之上无差别运行。
系统调用的实现思路
走系统调用这条路更加安全,如果对访问所有内存都不做区分,那么用户可以随意调用数据。随机jmp。甚至可以看到root密码并修改它。
操作系统将内核程序和用户程序隔离,设计出内核态和用户态。用户态必须通过系统调用才能进入内核态执行。用户态执行在用户段下
内核态执行在内核段下。
为了区分内核态和用户态,需要有一种处理器硬件设计,也就是处理器保护环:
硬件提供了主动进入内核的方法:
对于Intel x86,那就是中断指令:
- int int指令将使CS中的CPL改成0, “进入内核
- 这是用户程序发起的调用内核代码的唯一方式 (此时,CPL=3而 DPL=0)
系统调用的核心就是:
- 用户程序中包含一段有int指令的代码
- 操作系统中事先有中断处理函数,int指令后面就是这个处理函数所在的编号
- 操作系统根据编号执行相应的中断处理函数
实际上 这个编号是固定的,系统调用的中断号为0x80
系统调用具体实现
以printf为例,应用程序调用printf,库函数printf内部有调用库函数write(),改函数内部会做系统调用write,在linux/lib/write.c中
#include <unistd.h>
_syscall3(int, write, int, fd, const char* buf, off_t, count)
#define _syscall3(type, name, atype,a,btype,b,ctype,c) \
type name(atype a, btype b, ctype c) \
{
long __res; \
__asm__ volatile(
"int 0x80"
: "=a"(__res)
: "" (__NR_#name),"b"((long)(a)),"c"((long)(b)), "d"((long)(c));
:);
if(__res >=0 )
return (type) __resl
errno=-__res;
return -1;
}
这段内嵌汇编,主要是将__NR_write放入eax中,a,b,c三个参数分别放到ebx,ecx,edx中。
__NR_write是以宏定义的形式放在unistd.h中的,值为4,这个号码指示了系统调用应该去执行write对应的处理函数。
函数处理完成后,返回值在eax中,通过__res变量拿到,最后return出去。
只要是三个参数的系统调用都可以使用_syscall3宏
现在来揭晓这段代码可以运作的前提
void sched_init(void)
{
set_system_gate(0x80, &system_call;
}
sched_init中将0x80号中断处理函数设为system_call了。做这件事情的函数叫做set_system_gate,它的本质是设置中断向量表中的0x80所代表的表项,设置该表项中的各个字段,尤其是处理函数的入口点偏移为system_call的地址值
linux/include/asm/system.h
#define set_system_gate(n, addr) \
_set_gate(&idt[n], 15, 3,addr); //idt是中断向量表的基址
#define _set_gate(gate_addr, type, dpl, addr)\
__asm__(
"movw %%dx, %%ax \n\t"
"movw %0, %%dx\n\t"
"movl %%eax, %1\n\t"
"movl %%edx, %2"
:
: "i"( (short)(0x8000+(dpl<<13)+type<<8)), "o"(*((char*)(gate_addr))), "o"(*(4+(char*)(gate_addr))),\
"d"((char*)(addr))), "a"(0x00080000))
);