这一周学的主要内容是linux系统调用,先从linux操作系统原理与应用的第6章入手,接着学习了M.Tim Jones的Linux系统调用再探。
提到系统调用自然想到了API(应用编程接口),linux遵循了Unix的API规范POSIX,POSIX旨在提高UNIX环境下应用程序的可移植性,更在源代码级提供了C的编程接口给操作系统的服务程序。API和系统调用的关系有两种:其一是多个API函数可能使用相同的系统调用来封装,其二是一个API函数可能使用多个系统调用来封装。
我们首先还是来搞清楚由应用程序开始调用系统调用到完成调用服务返回的整个过程,给系统调用提供最终服务的是内核函数,也叫到系统调用的服务例程,它的上一层是系统调用处理程序,首先应用程序在用户态中调用一个系统调用,紧接着会根据这个系统调用找到libc库中相应的封装例程,在封装例程中执行0x80中断,也就是系统调用软中断,接下来便要转入内核态执行system_call系统调用处理程序,并最终调用系统调用服务例程,也就是内核函数。系统调用实际上是一个陷阱门,在处理程序时并不关中断,在进行内核初始化时会调用陷阱门初始化函数,这就要涉及到设置中断描述符表项了,包括表项的索引,也就是中断号,因为系统调用处理程序是一段代码,故要设置段选择符和偏移量,门描述符中还有类型,以及请求特权级DPL。
系统调用可以采用两种方式调用,一种是传统的系统调用,也就是调用系统调用已经封装好的函数,第二种则是通过system_call()函数来调用内核函数。
系统调用参数最多不能超过6个用到的寄存器顺序依次是eax,ebx,ecx,edx,esi,edi,这些寄存器就是用户态和内核态切换时保存参数的媒介。与参数数量相关的还包括封装的例程,这就用到了_syscall0~5六个宏了,这些宏需要2+2n个参数,第一对是系统调用的返回值类型和名称,后面依次是第1~n个参数的返回值类型和名称,这样就实现了按参数个数多少来为系统调用封装例程了。
系统调用表在内核中对应的文件是arch/x86/kernel/ syscall_table_32.S,在2.6.36内核中对应的系统调用号最大为341。为内核添加一个新的系统调用的步骤大致如下:首先要为新的系统调用定义相关的函数实现,并放入内核的指定位置,紧接着分配一个系统调用号,通过在unistd_32.h头文件中增加宏定义来指定系统调用号,并更新NR_syscalls,接下来再更新系统调用表,最后将内核重新编译。
这周在编程过程中再次用到了/proc/kallsyms文件,这是包含内核中所有符号的内核符号表文件,它在内核启动时将内核要用到的所有内核函数的地址和名称链接入内核,启动后并加载入内存中,这个文件当然还包括其他所有内核符号的存放的地址和名称。有了这个文件我们可方便的查询系统调用服务例程的址,以及其它普通函数的地址,以及搜索相关的内核导出符号表。