Unix通过系统向内核发出系统调用实现了用户态进程和硬件设备之间的大部分接口。
系统调用通过软中断向内核发出一个明确的请求。
lib的标准C库所定义的一些API引用了封装例程(其唯一目的就是发布系统调用)。通常情况下,每个系统调用对应一个封装例程,而封装例程定义了应用程序使用的API。
一个API没必要对应一个特定的系统调用,API可能直接提供用户态的服务或者一个单独的API函数可能调用几个系统调用。
POSIX标准针对API而不针对系统调用。
大部分封装例程返回一个整数,其值的含义依赖于相应的系统调用。返回值-1通常表示内核不能满足进程的请求。在libc库中定义的errno变量包含特定的出错码。
在内核中,正数或0表示系统调用成功结束,而负数表示一个出错条件。
为了把系统调用号与相应的服务例程关联起来,内核利用了一个系统调用分派表。这个表存放在sys_all_table数组中,有NR_syscalls个表项。
system_call()函数首先把系统调用号和这个异常处理程序可以用到的所有CPU寄存器保存到相应的栈中。
system_call和system_entry()函数是linux中所有系统调用的公共入口。
普通C函数的参数传递是通过把参数值写入活动的程序栈(用户态栈或者内核态栈)实现的。因为系统嗲用是一种横跨用户和内核两大陆地的特殊函数,所以既不能使用用户态也不能使用内核态栈。更确切的说,在发出系统调用之前,系统调用的参数被写入CPU寄存器,然后在调用服务例程之前,内核再把存放在CPU中的参数拷贝至内核态堆栈中。
system_call()和system_entry()使用SAVE_ALL()宏把这些寄存器的值保存在内核态堆栈中。
首部没有下划线的函数或宏要用额外的时间对所请求的线性地址区间进行有效性检查,而有下划线的则会跳过检查。
access_ok()宏对系统调用以参数传递来的线性地址的有效性只进行粗略检查,该检查只保证用户态进程不会试图侵扰内核地址空间。
把访问进程地址空间的每条内核指令的地址放到一个叫做异常表的结构中,主要的异常表在建立内核程序映像时由C编译器自动生成。它存放在内核代码段的__ex_table节,其起始与终止地址由C编译器产生的两个符号__start__ex_table和__stop__ex_table来标识。
此外,每个动态状态的内核模块都包含有自己的局部异常表。
每个异常表的表项是一个exception_table_entry结构。
search_exception_tables()函数用来在所有异常表中查找一个指定地址,若这个地址在某一个表中,则返回指向相应的exception_table_entry结构的指针,否则就返回NULL。
可执行文件包含一个代码段,这个代码段可能又依次被划分为节。
.section允许程序员指定可执行文件的哪部分包含紧接着要执行的代码。.previous伪指令强制汇编程序把紧接着的代码插入到遇到上一个.section伪指令激活的节。
尽管系统调用主要由用户态进程使用,但也可以被内核线程调用,内核线程不能使用库函数,为了简化相应封装例程的声明linux定义了从SYSCALL_DEFINE0到SYSCALL_DEFINE6的一组宏,每个宏名字的数字0-6对应着系统调用所用的参数个数。