Linux/Unix系统编程手册-笔记2.系统调用
Contents
系统调用
- 系统调用将处理器从用户态切换到核心态,以便CPU访问收到保护的内核内存。
- 系统调用的组成是固定的。每个系统调用都有一个唯一的数字来标识。
- 每个系统调用的参数,可以对用户态和内核态之间相互传递的参数加以规范。
系统调用步骤:
- 应用程序调用C语言库中的外壳(wrapper)函数,发起系统调用。
- 外壳函数确保参数可用,并将参数复制到寄存器,供内核使用。
- 外壳函数将系统调用编号复制到一个特殊的CPU寄存器(%eax)中。
- 外壳函数执行一条中断机器指令(int 0x80),引发处理器从用户态切换到核心态,并执行系统中断0x80的中断矢量所指向的代码。
- 为响应中断0x80,内核会调用system_call()响应。
- a.在内核中保存寄存器值。
- b.审核系统调用编号的有效性。
- c.以系统调用编号查找对应的服务程序,检查参数有效性,执行服务程序,最后服务程序会将结果返回给system_call()。
- d.从内核中恢复各寄存器的值,并将系统调用返回值置于栈中。
- e.返回至外壳函数, 同时将处理器切换回用户态。
- 若系统调用服务程序的返回值表明调用有误,外壳函数会使用该返回值来设置全局变量errno
strace 命令可以查看系统调用过程,写一个小程序一探究竟,程序功能为往文件中写一句“hello”:
|
|
编译后执行strace -o ./straceout.txt ./a.out
,输出到调用过程到straceout.txt文件中,内容如下,“=”左边是系统调用,右边是返回值
|
|
省略了前面一坨,在文件最后,和程序功能相关的几个系统调用就在其中,openat打开一个文件并返回一个文件描述符,write向里面写了“hello”, 然后close文件,程序退出。
处理系统调用错误
系统调用失败时,会将全局整形变量errno设置一个正值,以标识具体的错误(需包含<errno.h>头文件)。如果调用系统和库函数成功,errno绝不会被重置为0,所以errno可能为上次调用失败留下的值。因此进行错误检查时,必须首先检查函数的返回值是否表明调用出错,然后检查errno确定错误原因。
函数strerror可以将错误号转成容易理解的错误字符串
|
|
需要注意的是strerror返回的字符串指针指向的是一块静态分配的内存,所以后续调用strerror可能会覆盖该字符串。
编译书中带的源码
下载链接 https://man7.org/tlpi/code/ 一般下载Distribution version即可。
下载解压后,进入tlpi-dist目录执行make
,若报错
|
|
考虑将gcc版本升到7以上,参考 https://www.jianshu.com/p/7a8878397213
可移植性问题
书中给了几个可移植性问题的例子: 0. 数据类型,系统相关的数据类型如pid_t等在不同系统上实现不同,所以程序应使用定义好的系统数据类型,而不是原生的int, long等。
- 结构体在不同系统实现下,内部元素存储顺序不定,所以最好不使用列表初始化的方式:
struct example s = {1, 2, 3}
,而使用下面这种方式。
|
|
- 有些宏可能并没有在所有类UNIX系统上实现,使用前需要先判断是否已定义。如 WCOREDUMP在SUSv3就没有定义。
- 有些时候有些头文件在某些系统上不需要包含。
Exercises
reboot()系统调用
|
|
参数magic需要等于 LINUX_REBOOT_MAGIC1(0xfee1dead) 且magic2 为以下之一才不会调用失败(应该是为了避免误调)
|
|
这几个参数的意义是 Linus Torvalds和他三个女儿的生日。