系统调用是一个软中断,中断号是0x80,它是上层应用程序与Linux系统内核进行交互通信的唯一接口。
这个中断的设置在kernel/sched.c中441行函数中
void sched_init(void) { int i; struct desc_struct * p; if (sizeof(struct sigaction) != 16) panic("Struct sigaction MUST be 16 bytes"); set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss)); set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt)); p = gdt+2+FIRST_TSS_ENTRY; for(i=1;i<NR_TASKS;i++) { task[i] = NULL; p->a=p->b=0; p++; p->a=p->b=0; p++; } /* Clear NT, so that we won't have troubles with that later on */ __asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl"); ltr(0); lldt(0); outb_p(0x36,0x43); /* binary, mode 3, LSB/MSB, ch 0 */ outb_p(LATCH & 0xff , 0x40); /* LSB */ outb(LATCH >> 8 , 0x40); /* MSB */ set_intr_gate(0x20,&timer_interrupt); outb(inb_p(0x21)&~0x01,0x21); set_system_gate(0x80,&system_call); }
通过int 0x80,就可使用内核资源。不过,通常应用程序都是使用具有标准接口定义的C函数库间接的使用内核的系统调用,即应用程序调用C函数库中的函数,C函数库中再通过int 0x80进行系统调用。
所以,系统调用过程是这样的:
应用程序调用libc中的函数->libc中的函数引用系统调用宏->系统调用宏中使用int 0x80完成系统调用并返回
下面是sys_call_table的定义文件
位于./include/sys.h
extern int sys_setup (); // 系统启动初始化设置函数。 (kernel/blk_drv/hd.c,71) extern int sys_exit (); // 程序退出。 (kernel/exit.c, 137) extern int sys_fork (); // 创建进程。 (kernel/system_call.s, 208) extern int sys_read (); // 读文件。 (fs/read_write.c, 55) extern int sys_write (); // 写文件。 (fs/read_write.c, 83) extern int sys_open (); // 打开文件。 (fs/open.c, 138) extern int sys_close (); // 关闭文件。 (fs/open.c, 192) extern int sys_waitpid (); // 等待进程终止。 (kernel/exit.c, 142) extern int sys_creat (); // 创建文件。 (fs/open.c, 187) extern int sys_link (); // 创建一个文件的硬连接。 (fs/namei.c, 721) extern int sys_unlink (); // 删除一个文件名(或删除文件)。 (fs/namei.c, 663) extern int sys_execve (); // 执行程序。 (kernel/system_call.s, 200) extern int sys_chdir (); // 更改当前目录。 (fs/open.c, 75) extern int sys_time (); // 取当前时间。 (kernel/sys.c, 102) extern int sys_mknod (); // 建立块/字符特殊文件。 (fs/namei.c, 412) extern int sys_chmod (); // 修改文件属性。 (fs/open.c, 105) extern int sys_chown (); // 修改文件宿主和所属组。 (fs/open.c, 121) extern int sys_break (); // (-kernel/sys.c, 21) extern int sys_stat (); // 使用路径名取文件的状态信息。 (fs/stat.c, 36) extern int sys_lseek (); // 重新定位读/写文件偏移。 (fs/read_write.c, 25) extern int sys_getpid (); // 取进程id。 (kernel/sched.c, 348) extern int sys_mount (); // 安装文件系统。 (fs/super.c, 200) extern int sys_umount (); // 卸载文件系统。 (fs/super.c, 167) extern int sys_setuid (); // 设置进程用户id。 (kernel/sys.c, 143) extern int sys_getuid (); // 取进程用户id。 (kernel/sched.c, 358) extern int sys_stime (); // 设置系统时间日期。 (-kernel/sys.c, 148) extern int sys_ptrace (); // 程序调试。 (-kernel/sys.c, 26) extern int sys_alarm (); // 设置报警。 (kernel/sched.c, 338) extern int sys_fstat (); // 使用文件句柄取文件的状态信息。(fs/stat.c, 47) extern int sys_pause (); // 暂停进程运行。 (kernel/sched.c, 144) extern int sys_utime (); // 改变文件的访问和修改时间。 (fs/open.c, 24) extern int sys_stty (); // 修改终端行设置。 (-kernel/sys.c, 31) extern int sys_gtty (); // 取终端行设置信息。 (-kernel/sys.c, 36) extern int sys_access (); // 检查用户对一个文件的访问权限。(fs/open.c, 47) extern int sys_nice (); // 设置进程执行优先权。 (kernel/sched.c, 378) extern int sys_ftime (); // 取日期和时间。 (-kernel/sys.c,16) extern int sys_sync (); // 同步高速缓冲与设备中数据。 (fs/buffer.c, 44) extern int sys_kill (); // 终止一个进程。 (kernel/exit.c, 60) extern int sys_rename (); // 更改文件名。 (-kernel/sys.c, 41) extern int sys_mkdir (); // 创建目录。 (fs/namei.c, 463) extern int sys_rmdir (); // 删除目录。 (fs/namei.c, 587) extern int sys_dup (); // 复制文件句柄。 (fs/fcntl.c, 42) extern int sys_pipe (); // 创建管道。 (fs/pipe.c, 71) extern int sys_times (); // 取运行时间。 (kernel/sys.c, 156) extern int sys_prof (); // 程序执行时间区域。 (-kernel/sys.c, 46) extern int sys_brk (); // 修改数据段长度。 (kernel/sys.c, 168) extern int sys_setgid (); // 设置进程组id。 (kernel/sys.c, 72) extern int sys_getgid (); // 取进程组id。 (kernel/sched.c, 368) extern int sys_signal (); // 信号处理。 (kernel/signal.c, 48) extern int sys_geteuid (); // 取进程有效用户id。 (kenrl/sched.c, 363) extern int sys_getegid (); // 取进程有效组id。 (kenrl/sched.c, 373) extern int sys_acct (); // 进程记帐。 (-kernel/sys.c, 77) extern int sys_phys (); // (-kernel/sys.c, 82) extern int sys_lock (); // (-kernel/sys.c, 87) extern int sys_ioctl (); // 设备控制。 (fs/ioctl.c, 30) extern int sys_fcntl (); // 文件句柄操作。 (fs/fcntl.c, 47) extern int sys_mpx (); // (-kernel/sys.c, 92) extern int sys_setpgid (); // 设置进程组id。 (kernel/sys.c, 181) extern int sys_ulimit (); // (-kernel/sys.c, 97) extern int sys_uname (); // 显示系统信息。 (kernel/sys.c, 216) extern int sys_umask (); // 取默认文件创建属性码。 (kernel/sys.c, 230) extern int sys_chroot (); // 改变根系统。 (fs/open.c, 90) extern int sys_ustat (); // 取文件系统信息。 (fs/open.c, 19) extern int sys_dup2 (); // 复制文件句柄。 (fs/fcntl.c, 36) extern int sys_getppid (); // 取父进程id。 (kernel/sched.c, 353) extern int sys_getpgrp (); // 取进程组id,等于getpgid(0)。(kernel/sys.c, 201) extern int sys_setsid (); // 在新会话中运行程序。 (kernel/sys.c, 206) extern int sys_sigaction (); // 改变信号处理过程。 (kernel/signal.c, 63) extern int sys_sgetmask (); // 取信号屏蔽码。 (kernel/signal.c, 15) extern int sys_ssetmask (); // 设置信号屏蔽码。 (kernel/signal.c, 20) extern int sys_setreuid (); // 设置真实与/或有效用户id。 (kernel/sys.c,118) extern int sys_setregid (); // 设置真实与/或有效组id。 (kernel/sys.c, 51) // 系统调用函数指针表。用于系统调用中断处理程序(int 0x80),作为跳转表。 fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read, sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link, sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod, sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount, sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm, sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access, sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir, sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid, sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys, sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit, sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid, sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask, sys_setreuid, sys_setregid };
其中sys_call_table的类型是fn_ptr类型,其中sys_call_table[0]元素为sys_setup,它的类型是fn_ptr类型,它实际上是函数sys_setup的
入口地址。
它的定义如下:
typedef int (*fn_ptr) (); // 定义函数指针类型。
下面的实例代码有助于理解函数指针:
#include<stdio.h> typedef int (*MyFunc)(); MyFunc Func1; int Func2() { printf("This is a sample output!/n"); return 0; } int main() { Func1=Func2; //Func2(); //(*Func1)(); printf("%x/n",(*Func1)); printf("%x/n",Func2); return 0; }
system_call系统调用入口函数
#### int 0x80 --linux 系统调用入口点(调用中断int 0x80,eax 中是调用号)。 .align 2 _system_call: cmpl $nr_system_calls-1,%eax # 调用号如果超出范围的话就在eax 中置-1 并退出。 ja bad_sys_call push %ds # 保存原段寄存器值。 push %es push %fs pushl %edx # ebx,ecx,edx 中放着系统调用相应的C 语言函数的调用参数。 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 # ds,es 指向内核数据段(全局描述符表中数据段描述符)。 mov %dx,%es movl $0x17,%edx # fs points to local data space mov %dx,%fs # fs 指向局部数据段(局部描述符表中数据段描述符)。 # 下面这句操作数的含义是:调用地址 = _sys_call_table + %eax * 4。参见列表后的说明。 # 对应的C 程序中的sys_call_table 在include/linux/sys.h 中,其中定义了一个包括72 个 # 系统调用C 处理函数的地址数组表。 call _sys_call_table(,%eax,4) pushl %eax # 把系统调用号入栈。 movl _current,%eax # 取当前任务(进程)数据结构地址??eax。 # 下面97-100 行查看当前任务的运行状态。如果不在就绪状态(state 不等于0)就去执行调度程序。 # 如果该任务在就绪状态但counter[??]值等于0,则也去执行调度程序。 cmpl $0,state(%eax) # state jne reschedule cmpl $0,counter(%eax) # counter je reschedule # 以下这段代码执行从系统调用C 函数返回后,对信号量进行识别处理。 ret_from_sys_call: # 首先判别当前任务是否是初始任务task0,如果是则不必对其进行信号量方面的处理,直接返回。 # 103 行上的_task 对应C 程序中的task[]数组,直接引用task 相当于引用task[0]。 movl _current,%eax # task[0] cannot have signals cmpl _task,%eax je 3f # 向前(forward)跳转到标号3。 # 通过对原调用程序代码选择符的检查来判断调用程序是否是超级用户。如果是超级用户就直接 # 退出中断,否则需进行信号量的处理。这里比较选择符是否为普通用户代码段的选择符0x000f # (RPL=3,局部表,第1 个段(代码段)),如果不是则跳转退出中断程序。 cmpw $0x0f,CS(%esp) # was old code segment supervisor ? jne 3f # 如果原堆栈段选择符不为0x17(也即原堆栈不在用户数据段中),则也退出。 cmpw $0x17,OLDSS(%esp) # was stack segment = 0x17 ? jne 3f # 下面这段代码(109-120)的用途是首先取当前任务结构中的信号位图(32 位,每位代表1 种信号), # 然后用任务结构中的信号阻塞(屏蔽)码,阻塞不允许的信号位,取得数值最小的信号值,再把 # 原信号位图中该信号对应的位复位(置0),最后将该信号值作为参数之一调用do_signal()。 # do_signal()在(kernel/signal.c,82)中,其参数包括13 个入栈的信息。 movl signal(%eax),%ebx # 取信号位图??ebx,每1 位代表1 种信号,共32 个信号。 movl blocked(%eax),%ecx # 取阻塞(屏蔽)信号位图??ecx。 notl %ecx # 每位取反。 andl %ebx,%ecx # 获得许可的信号位图。 bsfl %ecx,%ecx # 从低位(位0)开始扫描位图,看是否有1 的位, # 若有,则ecx 保留该位的偏移值(即第几位0-31)。 je 3f # 如果没有信号则向前跳转退出。 btrl %ecx,%ebx # 复位该信号(ebx 含有原signal 位图)。 movl %ebx,signal(%eax) # 重新保存signal 位图信息??current->signal。 incl %ecx # 将信号调整为从1 开始的数(1-32)。 pushl %ecx # 信号值入栈作为调用do_signal 的参数之一。 call _do_signal # 调用C 函数信号处理程序(kernel/signal.c,82) popl %eax # 弹出信号值。 3: popl %eax popl %ebx popl %ecx popl %edx pop %fs pop %es pop %ds iret
./include/unistd.h文件中系统调用符号和调用号的对应定义
// 以下是内核实现的系统调用符号常数,用于作为系统调用函数表中的索引值。( include/linux/sys.h ) #define __NR_setup 0 /* used only by init, to get system going */ /* __NR_setup 仅用于初始化,以启动系统 */ #define __NR_exit 1 #define __NR_fork 2 #define __NR_read 3 #define __NR_write 4 #define __NR_open 5 #define __NR_close 6 #define __NR_waitpid 7 #define __NR_creat 8 #define __NR_link 9 #define __NR_unlink 10 #define __NR_execve 11 #define __NR_chdir 12 #define __NR_time 13 #define __NR_mknod 14 #define __NR_chmod 15 #define __NR_chown 16 #define __NR_break 17 #define __NR_stat 18 #define __NR_lseek 19 #define __NR_getpid 20 #define __NR_mount 21 #define __NR_umount 22 #define __NR_setuid 23 #define __NR_getuid 24 #define __NR_stime 25 #define __NR_ptrace 26 #define __NR_alarm 27 #define __NR_fstat 28 #define __NR_pause 29 #define __NR_utime 30 #define __NR_stty 31 #define __NR_gtty 32 #define __NR_access 33 #define __NR_nice 34 #define __NR_ftime 35 #define __NR_sync 36 #define __NR_kill 37 #define __NR_rename 38 #define __NR_mkdir 39 #define __NR_rmdir 40 #define __NR_dup 41 #define __NR_pipe 42 #define __NR_times 43 #define __NR_prof 44 #define __NR_brk 45 #define __NR_setgid 46 #define __NR_getgid 47 #define __NR_signal 48 #define __NR_geteuid 49 #define __NR_getegid 50 #define __NR_acct 51 #define __NR_phys 52 #define __NR_lock 53 #define __NR_ioctl 54 #define __NR_fcntl 55 #define __NR_mpx 56 #define __NR_setpgid 57 #define __NR_ulimit 58 #define __NR_uname 59 #define __NR_umask 60 #define __NR_chroot 61 #define __NR_ustat 62 #define __NR_dup2 63 #define __NR_getppid 64 #define __NR_getpgrp 65 #define __NR_setsid 66 #define __NR_sigaction 67 #define __NR_sgetmask 68 #define __NR_ssetmask 69 #define __NR_setreuid 70 #define __NR_setregid 71
这是一系列宏,它们的定义在unistd.h中,基本形式为#define _NR_name value,name为系统函数名字,value是一个整数值,是name所对应的系统函数指针在sys_call_table中的偏移量。
系统调用宏也在本文件内定义,采用内联汇编,如下:
// 以下定义系统调用嵌入式汇编宏函数。 // 不带参数的系统调用宏函数。type name(void)。 // %0 - eax(__res),%1 - eax(__NR_##name)。其中name 是系统调用的名称,与 __NR_ 组合形成上面 // 的系统调用符号常数,从而用来对系统调用表中函数指针寻址。 // 返回:如果返回值大于等于0,则返回该值,否则置出错号errno,并返回-1。 #define _syscall0(type,name) / type name(void) / { / long __res; / __asm__ volatile ( "int $0x80" / // 调用系统中断0x80。 :"=a" (__res) / // 返回值??eax(__res)。 :"" (__NR_##name)); / // 输入为系统中断调用号__NR_name。 if (__res >= 0) / // 如果返回值>=0,则直接返回该值。 return (type) __res; errno = -__res; / // 否则置出错号,并返回-1。 return -1;} // 有1 个参数的系统调用宏函数。type name(atype a) // %0 - eax(__res),%1 - eax(__NR_name),%2 - ebx(a)。 #define _syscall1(type,name,atype,a) / type name(atype a) / { / long __res; / __asm__ volatile ( "int $0x80" / : "=a" (__res) / : "" (__NR_##name), "b" ((long)(a))); / if (__res >= 0) / return (type) __res; / errno = -__res; / return -1; / } // 有2 个参数的系统调用宏函数。type name(atype a, btype b) // %0 - eax(__res),%1 - eax(__NR_name),%2 - ebx(a),%3 - ecx(b)。 #define _syscall2(type,name,atype,a,btype,b) / type name(atype a,btype b) / { / long __res; / __asm__ volatile ( "int $0x80" / : "=a" (__res) / : "" (__NR_##name), "b" ((long)(a)), "c" ((long)(b))); / if (__res >= 0) / return (type) __res; / errno = -__res; / return -1; / } // 有3 个参数的系统调用宏函数。type name(atype a, btype b, ctype c) // %0 - eax(__res),%1 - eax(__NR_name),%2 - ebx(a),%3 - ecx(b),%4 - edx(c)。 #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) __res; / errno=-__res; / return -1; / }