5.1.2 系统调用表
系统调用表sys_call_table存储了所有系统调用对应的服务例程的函数地址,在arch/i386/kernel/syscall_table.S文件中被定义:
001 ENTRY(sys_call_table) 002 .long sys_restart_syscall /* 0 - old "setup()" system call, used for restarting */ 003 .long sys_exit 004 .long sys_fork 005 .long sys_read 006 .long sys_write 007 .long sys_open /* 5 */ ¡¡ 320 .long sys_getcpu 321 .long sys_epoll_pwait 322 .long sys_utimensat /* 320 */ 323 .long sys_signalfd 324 .long sys_timerfd 325 .long sys_eventfd 326 .long sys_fallocate从中可发现两个特别之处。首先,所有系统调用服务例程的命名均遵守一定的规则,即在系统调用名称之前增加"sys_"前缀,比如open系统调用对应sys_open函数。
其次,内核提供的系统调用数目非常有限,到2.6.23版本的内核也不过才达到仅仅325个,使用"man 2 syscalls"命令即可以浏览到所有系统调用的添加历史。这也是系统调用与C库函数的区别之一:系统调用通常只提供最小的接口,C库函数则在此基础之上提供更多复杂的功能。
5.1.3 系统调用号
既然系统调用表集中存放了所有系统调用服务例程的地址,那么系统调用在内核中的执行就可以转化为从该表获取对应的服务例程并执行的过程。
这个过程中一个很重要的环节就是系统调用号。每个系统调用都拥有一个独一无二的系统调用号,用户应用通过它,而不是系统调用的名称,来指明要执行哪个系统调用。
系统调用号的定义在include/asm-i386/unistd.h文件。
#define __NR_restart_syscall 0 #define __NR_exit 1 #define __NR_fork 2 #define __NR_read 3 #define __NR_write 4 #define __NR_open 5 ...... #define __NR_getcpu 318 #define __NR_epoll_pwait 319 #define __NR_utimensat 320 #define __NR_signalfd 321 #define __NR_timerfd 322 #define __NR_eventfd 323 #define __NR_fallocate 324将其与sys_call_table的定义相比较可以发现,每个系统调用号都依次对应了sys_call_table中的某一项。内核正是将系统调用号作为下标去获取sys_call_table中的服务例程函数地址。
系统调用号与系统调用为相依相生的关系,一旦分配就不能再有任何变更,即使该系统调用被删除,它所拥有的系统调用号也不能被回收利用。
5.1.4 系统调用服务例程
系统调用最终由系统调用服务例程完成明确的操作。所有的系统调用服务例程集中声明在include/linux/syscalls.h文件,但分散定义在很多不同的文件。比如getpid系统调用用于获取当前进程的PID,它的服务例程sys_getpid在kernel/timer.c文件中定义为:
asmlinkage long sys_getpid(void) { return current->tgid; }除了都具有"sys_"前缀之外,所有的系统调用服务例程命名与定义还必须遵守其他的一些规则。首先,函数定义中必须添加asmlinkage标记,通知编译器仅从堆栈中获取该函数的参数。
其次,必须返回一个long类型的返回值表示成功或错误,通常返回0表示成功,返回负值表示错误。当然,getpid系统调用非常简单,不可能会失败,通过命令"man 2 getpid"可以查看它的手册,里面也明确指出了这一点。
每个系统调用的系统调用号、命名以及操作目的都是固定的,但内核如何去实现并没有明确规定,不同版本、不同架构的内核实现都有可能会有所变化。
5.1.5 如何使用系统调用
如图5.2所示,用户应用可以通过两种方式使用系统调用。第一种方式是通过C库函数,包括系统调用在C库中的封装函数和其他普通函数。
图5.2 使用系统调用的两种方式
第二种方式是使用_syscall宏。2.6.18版本之前的内核,在include/asm-i386/unistd.h文件中定义有7个_syscall宏,分别是:
_syscall0(type,name); _syscall1(type,name,type1,arg1); _syscall2(type,name,type1,arg1,type2,arg2); _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3); _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4); syscall5type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,type5,arg5); syscall6type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,type5,arg5,type6,arg6);其中,type表示所生成系统调用的返回值类型,name表示该系统调用的名称,typeN、argN分别表示第N个参数的类型和名称,它们的数目和_syscall后面的数字一样大。这些宏的作用是创建名为name的函数,_syscall后面跟的数字指明了该函数的参数的个数。
比如sysinfo系统调用用于获取系统总体统计信息,使用_syscall宏定义为:
_syscall1(int, sysinfo, struct sysinfo *, info);展开后的形式为:
int sysinfo(struct sysinfo * info) { long __res; __asm__ volatile("int $0x80" : "=a" (__res) : "
"(116),"b" ((long)(info))); do { if((unsigned long)(__res) >= (unsigned long)(-(128 + 1))) { errno = -(__res); __res = -1; } return (int) (__res); } while (0); }可以看出,_syscall1(int, sysinfo, struct sysinfo *, info)展开成一个名为sysinfo的函数,原参数int就是函数的返回类型,原参数struct sysinfo *和info分别构成新函数的参数。
在程序文件里使用_syscall宏定义需要的系统调用,就可以在接下来的代码中通过系统调用名称直接调用该系统调用。下面是一个使用sysinfo系统调用的实例。
代码清单5.1 sysinfo系统调用使用实例
#include #include #include #include #include /* for struct sysinfo */ _syscall1(int, sysinfo,struct sysinfo *, info); int main(void) { struct sysinfo s_info; int error; error = sysinfo(&s_info); printf("code error = %d/n", error); printf("Uptime = %lds/nLoad: 1 min %lu / 5 min %lu / 15 min %lu/n" "RAM: total %lu / free %lu / shared %lu/n" "Memory in buffers = %lu/nSwap: total %lu / free %lu/n" "Number of processes = %d/n", s_info.uptime, s_info.loads[0], s_info.loads[1], s_info.loads[2], s_info.totalram, s_info.freeram, s_info.sharedram, s_info.bufferram, s_info.totalswap, s_info.freeswap, s_info.procs); exit(EXIT_SUCCESS); }但是自2.6.19版本开始,_syscall宏被废除,我们需要使用syscall函数,通过指定系统调用号和一组参数来调用系统调用。
syscall函数原型为:
int syscall(int number, ...);其中number是系统调用号,number后面应顺序接上该系统调用的所有参数。下面是gettid系统调用的调用实例。
代码清单5.2 gettid系统调用使用实例
#include #include #include #define __NR_gettid 224 int main(int argc, char *argv[]) { pid_t tid;tid = syscall(__NR_gettid); }大部分系统调用都包括了一个SYS_符号常量来指定自己到系统调用号的映射,因此上面紫色的部分可重写为:
tid = syscall(SYS_gettid); |