2012 第5章 Linux系统调用

整理文档很辛苦,赏杯茶钱您下走!

免费阅读已结束,点击下载阅读编辑剩下 ...

阅读已结束,您可以下载文档离线阅读编辑

资源描述

第5章Linux系统调用系统调用功能概述系统调用的处理过程系统调用的实例分析如何增加一个系统调用系统调用概述系统调用(SYSTEMCALL)OS内核中都有一组实现系统功能的过程,系统调用就是对上述过程的调用。编程人员利用系统调用,向OS提出服务请求,由OS代为完成。一般情况下,进程是不能够存取系统内核的。它不能存取内核使用的内存段,也不能调用内核函数,CPU的硬件结构保证了这一点。只有系统调用是一个例外。5.1Linux系统调用-功能系统调用是用户态进入内核态的唯一入口:一夫当关,万夫莫开。常用系统调用:控制硬件:如write/read调用。设置系统状态或读取内核数据——getpid()、getpriority()、setpriority()、sethostname()进程管理:如fork()、clone()、execve()、exit()等优点编程容易,从硬件设备的低级编程中解脱出来提高了系统的安全性,可以先检查请求的正确性5.1Linux系统调用-功能陷入指令系统子程序sub0A0sub1A1subnAnsubiAi......陷入处理机构1)保护处理机现场2)取系统调用功能号并寻找子程序入口3)恢复处理机现场并返回入口地址表A0A2Ai......An....系统调用....用户程序5.2Int0x80指令Linux中实现系统调用利用了i386体系结构中的软件中断。即调用了int$0x80汇编指令。这条汇编指令将产生向量为128的编程异常,CPU便被切换到内核态执行内核函数,转到了系统调用处理程序的入口:system_call()。int$0x80指令将用户态的执行模式转变为内核态,并将控制权交给系统调用过程的起点system_call()处理函数。system_call()函数system_call()检查系统调用号,该号码告诉内核进程请求哪种服务。内核进程查看系统调用表(sys_call_table),找到所调用的内核函数入口地址。接着调用相应的函数,在返回后做一些系统检查,最后返回到进程。系统调用和普通函数调用API是用于某种特定目的的函数,供应用程序调用,而系统调用供应用程序直接进入系统内核。Linux内核提供了一些C语言函数库,这些库对系统调用进行了一些包装和扩展,因为这些库函数与系统调用的关系非常紧密,所以习惯上把这些函数也称为系统调用。有的API函数在用户空间就可以完成工作,如一些用于数学计算的函数,因此不需要使用系统调用。有的API函数可能会进行多次系统调用。不同的API函数也可能会有相同的系统调用。比如malloc(),calloc(),free()等函数都使用相同的方法分配和释放内存。系统命令、内核函数系统调用与系统命令系统命令相对API来说,更高一层。每个系统命令都是一个执行程序,如ls命令等。这些命令的实现调用了系统调用。系统调用与内核函数系统调用是用户进入内核的接口层,它本身并非内核函数,但是它由内核函数实现。进入内核后,不同的系统调用会找到各自对应的内核函数,这些内核函数被称为系统调用的“服务例程”。如系统调用getpid()实际调用的服务例程为sys_getpid(),或者说系统调用getpid()是服务例程sys_getpid()的封装例程。封装例程(wrapperroutine)由于陷入指令是一条特殊指令,依赖操作系统实现的平台,如在i386体系结构中,这条指令是int$0x80(陷入指令),不是用户在编程时应该使用的语句,因为这将使得用户程序难于移植。在标准C库函数中,为每个系统调用设置了一个封装例程,当一个用户程序执行了一个系统调用时,就会调用到C函数库中的相对应的封装例程。系统调用过程系统调用过程…xyz()…system_call:…sys_xyz()…ret_from_sys_call:…iretxyz(){…int0x80…}sys_xyz(){…}在应用程序在标准库系统调用系统调用调用中的中的封装例程处理程序服务例程系统调用用户态内核态system_call()片段􀂋…pushl%eax/*将系统调用号压栈*/SAVE_ALL...Cmpl$(NR_syscalls),%eax/*检查系统调用号JbnobadsysMovl$(-ENOSYS),24(%esp)/*堆栈中的eax设置为-ENOSYS,作为返回值Jmpret_from_sys_callsystem_call片段(续)nobadsys:…call*sys_call_table(%eax,4)//调用系统调用表中调用号为eax的系统调用例程movl%eax,EAX(%esp)//将返回值存入堆栈中Jmpret_from_sys_callsystem_call()函数(见教材P234页)首先将系统调用号(eax)和可以用到的所有CPU寄存器保存到相应的堆栈中(由SAVE_ALL完成);对用户态进程传递过来的系统调用号进行有效性检查(eax是系统调用号,它应该小于NR_syscalls)如果是合法的系统调用,再进一步检测该系统调用是否正被跟踪;根据eax中的系统调用号调用相应的服务例程。服务例程结束后,从eax寄存器获得它的返回值,并把这个返回值存放在堆栈中,让其位于用户态eax寄存器曾存放的位置。然后跳转到ret_from_sys_call(),终止系统调用程序的执行。SAVE_ALL宏定义#defineSAVE_ALLcld;pushl%es;pushl%ds;pushl%eax;pushl%ebp;pushl%edi;pushl%esi;pushl%edx;pushl%ecx;pushl%ebx;movl$(__KERNEL_DS),%edx;movl%edx,%ds;movl%edx,%es;•将寄存器中的参数压入到核心栈中(这样内核才能使用用户传入的参数。)•因为在不同特权级之间控制转换时,INT指令不同于CALL指令,它不会将外层堆栈的参数自动拷贝到内层堆栈中,所以在调用系统调用时,必须把参数指定到各个寄存器中调用总控程序(system_call)执行流程图系统调用表与调用号这样系统调用处理程序一旦运行,就可以从EAX中得到系统调用号,然后再去系统调用表中寻找相应服务例程。一个应用程序调用fork()封装例程,那么在执行int$0x80之前就把EAX寄存器的值置为2(即__NR_fork)。这个寄存器的设置是libc库中的封装例程进行的,因此用户一般不关心系统调用号系统调用表与调用号核心中为每个系统调用定义了一个唯一的编号,这个编号的定义在linux/include/asm/unistd.h中(最大为NR_syscall)同时在内核中保存了一张系统调用表,该表中保存了系统调用编号和其对应的服务例程地址。第n个表项包含系统调用号为n的服务例程的地址。系统调用陷入内核前,需要把系统调用号一起传入内核。而该标号实际上是系统调用表(sys_call_table)的下标在i386上,这个传递动作是通过在执行int$0x80前把调用号装入eax寄存器实现。这样系统调用处理程序一旦运行,就可以从eax中得到系统调用号,然后再去系统调用表中寻找相应服务例程。系统调用号#define__NR_exit1#define__NR_fork2#define__NR_read3#define__NR_write4#define__NR_open5#define__NR_close6#define__NR_waitpid7#define__NR_creat8#define__NR_link9#define__NR_unlink10#define__NR_execve11#define__NR_chdir12#define__NR_time13系统调用表(arch/i386/kernel/entry.s)dataENTRY(sys_call_table).longSYMBOL_NAME(sys_ni_syscall).longSYMBOL_NAME(sys_exit).longSYMBOL_NAME(sys_fork).longSYMBOL_NAME(sys_read).longSYMBOL_NAME(sys_write).longSYMBOL_NAME(sys_open).longSYMBOL_NAME(sys_close).longSYMBOL_NAME(sys_waitpid).longSYMBOL_NAME(sys_creat).longSYMBOL_NAME(sys_link).longSYMBOL_NAME(sys_unlink).longSYMBOL_NAME(sys_execve).longSYMBOL_NAME(sys_chdir).longSYMBOL_NAME(sys_time).longSYMBOL_NAME(sys_mknod)1.系统调用表记录了各个系统调用的服务例程的入口地址。2.以系统调用号为偏移量能够在该表中找到对应处理函数地址。3.在linux/include/linux/sys.h中定义的NR_syscalls表示该表能容纳的最大系统调用数,一般NR_syscalls=256。系统调用表(sys_call_table)+eax*4ENTRY(sys_call_table).longSYMBOL_NAME(sys_ni_...).longSYMBOL_NAME(sys_exit).longSYMBOL_NAME(sys_fork).longSYMBOL_NAME(sys_read).longSYMBOL_NAME(sys_write).longSYMBOL_NAME(sys_open)………….longSYMBOL_NAME(sys_getuid)sys_call_table首地址ENTRY(sys_call_table).longSYMBOL_NAME(sys_ni_...).longSYMBOL_NAME(sys_exit).longSYMBOL_NAME(sys_fork).longSYMBOL_NAME(sys_read).longSYMBOL_NAME(sys_write).longSYMBOL_NAME(sys_open)………….longSYMBOL_NAME(sys_getuid)sys_call_table首地址eax系统调用的返回当服务例程结束时,system_call()从eax获得系统调用的返回值,并把这个返回值存放在曾保存用户态eax寄存器栈单元的那个位置上,然后跳转到ret_from_sys_call(),终止系统调用处理程序的执行。当进程恢复它在用户态的执行前,RESTORE_ALL宏会恢复用户进入内核前被保留到堆栈中的寄存器值。其中eax返回时会带回系统调用的返回码(负数说明调用错误,0或正数说明正常完成)ret_from_sys_callcli#关中断cmpl$0,need_resched(%ebx)jnereschedule#如果进程描述符中的need_resched位不为0,则重新调度cmpl$0,sigpending(%ebx)jnesignal_return#若有未处理完的信号,则处理restore_all:RESTORE_ALL#堆栈弹栈,返回用户态系统调用的返回值所有的系统调用返回一个整数值。正数或0表示系统调用成功结束负数表示一个出错条件这里的返回值与封装例程返回值的约定不同内核没有设置或使用errno变量封装例程在系统调用返回取得返回值之后设置这个变量当系统调用出错时,返回的那个负值将要存放在errno变量中返回给应用程序5.3系统调用-实例分析假设源文件名为getpid.c,内容是:#includesyscall.h#includeunistd.h#includestdio.h#i

1 / 52
下载文档,编辑使用

©2015-2020 m.777doc.com 三七文档.

备案号:鲁ICP备2024069028号-1 客服联系 QQ:2149211541

×
保存成功