什么是系统调用
Linux内核中设置了一组用于实现各种系统功能的子程序,称为系统调用.系统调用和普通库函数调用非常相似,只是系统调用由操作系统核心提供,运行于内核态,而普通的函数调用由函数库或用户自己提供,运行于用户态.
为什么需要系统调用
一般来说,进程是不能访问内核的,避免应用程序不正确的使用资源,导致系统奔溃.CPU硬件决定着内存访问权限(保护模式,内存段页管理).在linux中系统调用是用户空间访问内核的唯一手段,除异常和陷入外,他们是内核唯一的合法入口
应用程序如何调用系统调用
- 把系统调用的编号存入 EAX
- 把函数参数存入其它通用寄存器
- 触发 0x80 号中断(int 0x80)
操作系统实现系统调用的基本过程
- 应用程序调用库函数(API)
- API 将系统调用号存入 EAX,然后通过中断调用使系统进入内核态
- 内核中的中断处理函数根据系统调用号,调用对应的内核函数(系统调用)
- 系统调用完成相应功能,将返回值存入 EAX,返回到中断处理函数
- 中断处理函数返回到 API 中
- API 将 EAX 返回给应用程序
linux0.11 系统调用实现(close)
应用程序调用系统调用
在lib/close.c有着close的实现
#define __LIBRARY__
#include
_syscall1(int,close,int,fd)
_syscall1是一个宏,在include/unistd.h中定义。将_syscall1(int,close,int,fd)进行宏展开,可以得到:
int close(int fd)
{
long __res;
__asm__ volatile ("int $0x80"
: "=a" (__res)
: "0" (__NR_close),"b" ((long)(fd)));
if (__res >= 0)
return (int) __res;
errno = -__res;
return -1;
}
这是段内联汇编,将宏NR_close存入存入EAX,将参数fd存入EBX,进行80中断调用调用返回后,从EAX取出返回值,存入res,再通过对res的判断决定传给API的调用者什么样的返回值.其中NR_close就是系统调用的编号,在include/unistd.h中定义
#define __NR_close 6
int 0x80中断的处理
在操作系统启动时,会初始化IDT,设置0x80对应的中断描述符
void sched_init(void)
...
{set_system_gate(0x80,&system_call);};
set_system_gate是个宏,include/asm/system.h中定义为
#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))
填写IDT,将system_call函数地址写到0x80对应的中断描述符中,当发生中断0x80后,调用system_call函数
system_call:
cmpl $nr_system_calls-1,%eax
ja bad_sys_call
push %ds
push %es
push %fs
pushl %edx
pushl %ecx # push %ebx,%ecx,%edx as parameters
pushl %ebx # to the system call
movl $0x10,%edx # set up ds,es to kernel space
mov %dx,%ds
mov %dx,%es
movl $0x17,%edx # fs points to local data space
mov %dx,%fs
call sys_call_table(,%eax,4) #sys_call_table+4*%eax
pushl %eax
movl current,%eax
cmpl $0,state(%eax) # state
jne reschedule
cmpl $0,counter(%eax) # counter
je reschedule
主要为call sys_call_table(,%eax,4)这句话,其中eax放的是系统调用号,sys_call_table是函数指针数组的起始地址,跳到sys_call_table+4*%eax地址执行,即内核实现的sys_close函数