1、系统调用基本概念
系统调用:内核中所有已实现和可用系统调用的集合。Linux中四种类型的接口:
- 应用编程接口(API)
- 应用二进制接口(ABI)
- 内核内部的 API
- 内核内部的ABI
LSB标准:为了促进Linux不同发行版本间的兼容性,LSB开发了一系列标准,使得各种软件可以很好的在兼容 LSB 标准的系统上运行。
Linux API :是 Linux 内核和用户空间的 API,使用户程序能够通过这个借口访问系统资源和内核提供的服务。Linux API 由两部分组成:Linux 内核的系统调用接口和 GNU C 库(glibc)中的例程。内核 API 主要是标记为 “EXPORT_SYMBOL” 的函数。
GNU C 库:Linux 内核系统调用接口的封装,其中包括 POSIX 兼容应用函数调用和 Linux 专用的函数调用。
POSIX:表示可移植操作系统接口(Portable Operating System Interface of UNIX,缩写为 POSIX ),POSIX标准定义了操作系统应该为应用程序提供的接口标准,是IEEE为要在各种UNIX操作系统上运行的软件而定义的一系列API标准的总称,其正式称呼为IEEE 1003。
Linux ABI:一系列约定的集合,它和具体的 CPU 架构和 OS 相关,只要 OS 遵守相同的 ABI 规范,那么不同的应用就可以实现向前兼容。ABI 包含以下内容:
- 一个特定的处理器集合
- 函数调用惯例
- 系统调用方式
- 可执行文件的格式(ELF 、PE等)
系统调用号:用来唯一的标识每个系统调用。
系统调用表:把系统调用号和相应的服务例程关联起来。
从用户态跟踪一个系统调用到内核:
- 从用户程序中调用fork;
- 在libc库中把fork对应的系统调用号2放入寄存器eax;
- 通过int 0x80陷入内核;
- 在中断描述表IDT中查到系统调用的入口0x80;
- 进入Linux内核的entry_32(64).S文件,从系统调用表sys_call_table中找到sys_fork的入口地址;
- 执行fork.c中的do_fork代码;
- 通过iret或者sysiret返回。
跟踪 cp 所调用的系统调用可以使用如下命令:
strace cp
2、系统调用机制
在用户空间和内核空间之间,有一个叫做Syscall(系统调用, system call)的中间层,是连接用户态和内核态的桥梁。这样即提高了内核的安全型,也便于移植,只需实现同一套接口即可。Linux系统,用户空间通过向内核空间发出Syscall,产生软中断,从而让程序陷入内核态,执行相应的操作。
安全性与稳定性:内核驻留在受保护的地址空间,用户空间程序无法直接执行内核代码,也无法访问内核数据,通过系统调用机制可以增强系统的安全性和稳定性。
性能:Linux上下文切换时间很短,系统调用处理过程非常精简,性能上往往比很多其他操作系统执行要好。
为了让读者清晰明了的理解系统调用的过程,下面以 read 系统调用为例来说明:
当 read() 调用发生时,库函数在保存 read 系统调用号以及参数后,陷入 0x80 中断,执行 0x80 中断处理程序,0x80 中断处理程序接管执行后,先检察其系统调用号,然后根据系统调用号查找系统调用表,并从系统调用表中得到处理 read 系统调用的内核函数 sys_read ,最后传递参数并运行 sys_read 函数。
read 系统调用在内核中所要经历的层次模型:
对于磁盘的一次读请求,首先经过虚拟文件系统层(vfs layer),其次是具体的文件系统层(例如 ext2),接下来是 cache 层(page cache 层)、通用块层(generic block layer)、IO 调度层(I/O scheduler layer)、块设备驱动层(block device driver layer),最后是物理块设备层(block device layer)。
各层的作用:
模型所在层 | 作用 |
---|---|
虚拟文件系统层 | 屏蔽下层具体文件系统操作的差异,为上层的操作提供一个统一的接口。正是因为有了这个层次,所以可以把设备抽象成文件,使得操作设备就像操作文件一样简单。 |
具体的文件系统层 | 不同的文件系统(例如 ext2 和 NTFS)具体的操作过程也不同,每种文件系统定义了自己的操作集合。 |
cache 层 | 为了提高 linux 操作系统对磁盘访问的性能。 Cache 层在内存中缓存了磁盘上的部分数据。当发生数据请求时,如果在 cache 中存在该数据且是最新的,则直接将数据传递给用户程序,免除了对底层磁盘的操作,提高了性能。 |
通用块层 | 接收上层发出的磁盘请求,并最终发出 IO 请求。该层隐藏了底层硬件块设备的特性,为块设备提供了一个通用的抽象视图。 |
IO 调度层 | 接收通用块层发出的 IO 请求,缓存请求并试图合并相邻的请求(如果这两个请求的数据在磁盘上是相邻的),并根据设置好的调度算法,回调驱动层提供的请求处理函数,以处理具体的 IO 请求。 |
块设备驱动层 | 驱动层中的驱动程序对应具体的物理块设备。它从上层中取出 IO 请求,并根据该 IO 请求中指定的信息,通过向具体块设备的设备控制器发送命令的方式,来操纵设备传输数据。 |
物理块设备层 | 设备层中都是具体的物理设备。定义了操作具体设备的规范。 |
相关的内核数据结构:
数据结构 | 作用 |
---|---|
dentry | 联系了文件名和文件的 inode 节点 |
inode | 文件 inode 节点,保存文件标识、权限和内容等信息 |
file | 保存文件的相关信息和各种操作文件的函数指针集合 |
file_operations | 操作文件的函数接口集合 |
address_space | 描述文件的 page cache 结构以及相关信息,并包含有操作 page cache 的函数指针集合 |
address_space_operations | 操作 page cache 的函数接口集合 |
bio | IO 请求的描述 |
数据结构之间的关系:
dentry 对象可以找到 inode 对象, inode 对象中可以找到 address_space 对象, address_space 对象可以找到 address_space_operations 对象。
File 对象可以根据当前进程描述符中提供的信息获得,进而可以找到 dentry 对象、 address_space 对象和 file_operations 对象。
更多有趣的文章,请访问博主目录页
智慧人生,与你相伴