Linux 提供了具有良好定义、数量有限、直接进入内核的入口点,这些入口点被称为系统调用。 前面的文章学习了 Linux 文件以及文件系统操作的基础知识,并介绍了一些系统调用函数。那么应用程序在调用系统函数时,内核的处理流程是怎样的呢? 在 Linux 系统中,分为用户空间和内核空间。从系统安全和稳定方面考虑,用户空间的程序不能直接执行内核代码或者操作内核数据。但是,内核以 API 的形式,提供了一系列服务供应用程序访问,是为了从内核获得某项服务或资源。 系统调用过程从编程角度来说,系统调用与 C 语言函数的调用很相似。然而,在执行系统调用时,会历经许多处理步骤: •应用程序调用系统函数 API,发起系统调用。•API 将系统调用参数传入到特定寄存器。•API 将系统调用编号负值到一个特殊的 CPU 寄存器(%eax)中。•API 执行一条中断指令(int 0x80),引发处理器从用户态切换到内核态。并执行系统中断 0x80 的中断向量所指向的代码。•响应中断 0x80,内核调用 system_calll() 代码来处理这次中断。•system_call() 调用相应的系统调用服务例程。•内核态代码执行完成后,处理器切换回用户态。•若系统调用服务例程的返回值有错误,API 会使用该值设置全局变量 errno。•API 返回到调用程序,并返回一个整型值,表示系统调用是否成功。 system_call() 处理流程可以把 system_call() 认为是中断 0x80 的服务例程,作为内核处理系统的入口函数。其内部的处理流程有以下几步: •在内核中保存寄存器值。•检测系统调用编号的有效性。•搜索存放系统服务例程的列表(sys_call_table),找到与系统调用编号匹配的系统调用服务例程。•如果系统调用服务例程带有参数,则检测参数的有效性。然后执行系统调用函数。•系统调用处理结束后,会将结果返回给 system_call()。•从内核栈中恢复各个寄存器值,将系统调用返回值置于栈中。•返回应用程序调用的 API,同时将处理器切换回用户态。 处理流程图上面说了这么多,用图片表示一下更为直观。以系统调用 execve() 为例,看一下处理流程。
|