第六章系统调用系统调用与API、系统命令、内核函数系统调用处理程序及服务例程封装例程添加新的系统调用系统调用实例•系统调用,顾名思义,说的是操作系统提供给用户程序调用的一组“特殊”接口。•从逻辑上来说,系统调用可被看成是一个内核与用户空间程序交互的接口——它好比一个中间人,把用户进程的请求传达给内核,待内核把请求处理完毕后再将处理结果送回给用户空间。系统调用-内核的出口•Linux的应用编程接口(API)遵循POSIX标准•应用编程接口(API)其实是一组函数定义,这些函数说明了如何获得一个给定的服务;而系统调用是通过软中断向内核发出一个明确的请求•API有可能和系统调用的调用形式一致•API和系统调用关注的都是函数名、参数类型及返回代码的含义•系统调用的实现是在内核完成的,而用户态的函数是在函数库中实现的系统调用与API•系统命令相对应用编程接口更高一层,每个系统命令都是一个可执行程序,比如ls、hostname等,•系统命令的实现调用了系统调用•通过stracels或stracehostname命令可以查看系统命令所调用的系统调用系统调用与系统命令•内核函数在形式上与普通函数一样,但它是在内核实现的,需要满足一些内核编程的要求•系统调用是用户进程进入内核的接口层,它本身并非内核函数,但它是由内核函数实现的•进入内核后,不同的系统调用会找到各自对应的内核函数,这些内核函数被称为系统调用的“服务例程”系统调用与内核函数•当用户态的进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数•系统调用处理程序执行下列操作:–在内核栈保存大多数寄存器的内容–调用所谓系统调用服务例程的相应的C函数来处理系统调用–通过ret_from_sys_call()函数从系统调用返回系统调用处理程序及服务例程…xyz()…system_call:…sys_xyz()…ret_from_sys_call:…iretxyz(){…int0x80…}sys_xyz(){…}在应用程序在libc标准库系统调用系统调用调用中的中的封装例程处理程序服务例程系统调用用户态内核态调用一个系统调用•内核初始化期间调用trap_init()函数建立IDT表中128号向量对应的表项:–set_system_gate(0x80,&system_call);•该调用把下列值装入该门描述符的相应域:–段选择子:–偏移量:指向system_call()异常处理程序–类型:置为15,表示该异常是一个陷阱–DPL(描述符特权级):置为3,这就允许用户态进程调用这个异常处理程序初始化系统调用system_call()函数实现了系统调用处理程序:•它首先把系统调用号和该异常处理程序用到的所有CPU寄存器保存到相应的栈中•把当前进程PCB的地址存放在ebx中•对用户态进程传递来的系统调用号进行有效性检查。若调用号大于或等于NR_syscalls,系统调用处理程序终止•若系统调用号无效,函数就把-ENOSYS值存放在栈中eax寄存器所在的单元,再跳到ret_from_sys_call()•根据eax中所包含的系统调用号调用对应的特定服务例程system_call()函数•每个系统调用至少有一个参数,即通过eax寄存器传递来的系统调用号•用寄存器传递参数必须满足两个条件:–每个参数的长度不能超过寄存器的长度–参数的个数不能超过6个(包括eax中传递的系统调用号),否则,用一个单独的寄存器指向进程地址空间中这些参数值所在的一个内存区即可•在少数情况下,系统调用不使用任何参数•服务例程的返回值必须写到eax寄存器中参数传递跟踪系统调用的执行分析系统调用有两种方法:查看entry.s中的代码细节,阅读相关的源码来分析其运行过程;借助一些内核调试工具,动态跟踪执行路径。结合用户空间的执行路径,该程序的执行大致可归结为以下几个步骤:1、程序调用libc库的封装函数2、调用软中断int0x80进入内核。3、在内核中首先执行system_call函数,接着根据系统调用号在系统调用表中查找到对应的系统调用服务例程4、执行该服务例程5、执行完毕后,转入ret_from_sys_call例程,从系统调用返回libc库中如何对不同的服务例程进行封装?Linux定义了从_syscall0到_syscall5的六个宏每个宏名字的数字0到5对应着系统调用所用的参数个数(系统调用号除外)每个宏严格地需要2+2n个参数,n是系统调用的参数个数。另外两个参数指明系统调用的返回值类型和名字;每一对参数指明相应的系统调用参数的类型和名字例:fork()的封装例程:_syscall0(int,fork)在用户态进行系统调用时,要进行用户态堆栈到内核态堆栈的切换,但在内核中进行系统调用时,不用进行堆栈切换封装例程添加系统调用的步骤:添加系统调用号。系统调用号在unistd.h文件中定义,以“__NR_”开头在系统调用表(在entry.S中)中添加自己的服务例程sys_mysyscall实现系统调用服务例程:把sys_mysyscall加在kernel目录下的系统调用文件sys.c中重新编译内核编写用户态程序添加新的系统调用利用系统调用实现一个调用日志收集系统,实时获取系统调用日志本实例需要完成以下几个基本功能:记录系统调用日志,将其写入缓冲区(内核中),以便用户读取(由syscall_audit()实现);建立新的系统调用,将内核缓冲中的系统调用日志返回到用户空间(由Sys_audit()实现)循环利用系统调用,以便能动态实时返回系统调用的日志(由auditd()实现)系统调用实例日志记录例程Syscall_audit():内核态的服务例程,负责记录系统调用的运行日志,包括调用时刻、调用者PID、程序名等服务例程Sys_audit():新添加的系统调用,其功能是从缓冲区中取数据返回用户空间用户空间服务程序auditd:取回系统中搜集到的系统调用日志信息代码结构体系介绍写入日志数据取回数据Audit系统调用用户程序auditd系统调用服务例程sys_audit()系统调用日志缓冲Audit.o模块my_sysaudit()钩子函数my_audit()钩子函数用户空间内核空间日志记录例程syscall_audit()描述日志数据流向描述系统调用关系程序的体系结构图改entry.S汇编代码,在系统调用表中加入新的系统调用,然后在系统调用入口中加入跳转到日志记录服务例程填加代码文件audit.c,该文件中包含syscall_audit与系统调用sys_audit两个函数体修改i386_ksyms.c文件,在最后加入EXPORT_SYMBOL(my_audit);externint(*my_sysaudit)(unsignedchar,unsignedchar*,unsignedshort,unsignedchar);•EXPORT_SYMBOL(my_sysaudit);把代码集成到内核中修改/arch/i386/kernel/entry.S文件添加audit.c文件到/arch/i386/kernel/目录下修改/arch/i386/kernel/i386-kysms.c文件,在其中导出my_audit与my_sysaudit两个钩子函数修改/arch/i386/kernel/Makefile文件,将audit.c编译入内核添加系统调用StepByStep编写名为audit的模块编译并加载模块insmodaudit.o编写一个用户程序deamon循环调用audit系统调用,并把搜集到的信息打印到屏幕上。重新编译内核StepByStep“内核之旅”网站••电子杂志栏目是关于内核研究和学习的资料•第四期“系统调用”,本期重点和大家讨论系统调用机制。其中涉及到了一些及系统调用的性能、上下文深层问题,同时也穿插着讲述了一些内核调试方法。•实验部分我们利用系统调用与相关内核服务完成了一个搜集系统调用序列的特定任务•下载其代码进行调试