系统调用
只要执行系统调用或者库函数,检查返回状态以确定调用是否成功,是编程的一条铁律。
系统调用与库函数很像,其幕后会经历诸多步骤:
- 通过调用C语言库函数的外壳函数,发起系统调用
- 对系统调用中断处理例程来说,外壳函数必须保证所有的系统调用参数可用
- 内核需要区分每个系统调用,将系统编号复制到一个特殊的寄存器
- 外壳函数执行一条中断指令int 0x80,引发处理器从用户态切到核心态,并执行系统中断0x80的中断矢量所指向的代码
- 为了响应中断0x80,内核会调用system_call来处理这次中断
- 内核栈中保存寄存器
- 审核系统调用编号的有效性
- 以系统调用编号存放所有调用服务的列表(内核变量sys_call_table)进行索引,发现并调用相应的系统服务
- 从内核栈中恢复各个寄存器,将系统调用返回值放在栈中
- 返回外壳函数,同时将处理器切回用户态
- 若系统调用例程的返回值表明调用有误,外壳函数使用该值设置全局变量errno,然后外壳函数返回到调用程序,同时返回一个整型值,表明系统调用是否成功
一个简单的系统调用,也要完成相当多的工作,因此系统调用开销虽小,却不容忽视
库函数
许多库函数不会使用任何系统调用,有些库函数构建于系统调用层之上,比如fopen利用open来执行打开文件的实际操作,又如printf相对于write,设计库函数为了提供比底层更为方便的调用接口
标准C语言函数库:glibc
确定系统的glibc版本:
/lib/libc.so.6
发现glic共享库的路径:
ldd prog | grep libc
处理系统调用和库函数的错误
不检查状态值,少敲几个字,听起来很诱人,却得不偿失,除了少数几个系统调用从不失败,如getpid总能返回进程id,_exit总能终止进程,绝大多数系统调用和库函数都应该检查返回值以确认是否调用成功
- 系统调用失败时,会将errno设置为一个正值,以标识具体的错误
- 进行错误检查时,应该首先检查函数的返回值是否调用出错,再检查errno确定错误原因
- 少数系统调用即使成功,也返回-1。如getpriority,要判断此类系统调用,应在调用前将errno设置为0,并在调用后对其检查
- perror会打印msg所指向的字符串,紧跟一条与当前errno值相对应的消息
```c
include
void perror(const char *msg);
if (-1 == (fd = (open(pathname, flags, mode)))) { perror(“open”); exit(EXIT_FAILURE); }
- **strerror**会针对errnum指定的错误号,返回相应的字符串
```c
#include <string.h>
char *strerror(int errnum);
处理库函数的错误:
- 某些库函数返回错误信息的方式与系统调用完全相同,返回值为-1,以errno标识具体错误,如remove
- 某些库函数出错会返回-1之外的其他值,仍会设置errno标识具体错误,如fopen,此类可用perror和stderr诊断
还有些库函数根本不使用errno,此类不可用perror和stderr诊断
可移植性问题
系统调用和库函数的受限,被各种标准制约,一部分是SUS制定,一部分是BSD和System V Release 4定义
特性测试宏
_POSIX_SOURCE:定义后会符合POSIX.1-1990和ISO C-1990标准的定义,已经被 _POSX_C_SOURCE取代
- _POSX_C_SOURCE:同 _POSIX_SOURCE
- _XOPEN_SOURCE:定义后,会符合POSIX.1、POSIX.2和XPG4标准
- _BSD_SOURCE:开启BSD定义的支持
- _SVID_SOURCE:符合System V接口规范
- _GNU_SOURCE,定义后,会符合前述所有标准的定义,同时开启对各种GNU扩展定义的支持
系统数据类型
系统数据类型大多定义在,少量在其他文件,使用这些变量,才能保证可移植性
C99为printf定义了名字为z的长度修饰符,表明紧随其后的整型转换是与size_t或ssize_t类型相对应,可以使用%zd来取代%ld外加类型转换了caddr_t 核心地址
clock_t 时钟滴答计数器(进程时间)
comp_t 压缩的时钟滴答
dev_t 设备号(主和次)
fd_set 文件描述符集
fpos_t 文件位置
gid_t 数值组ID
ino_t i节点编号
mode_t 文件类型,文件创建模式
nlink_t 目录项的链接计数
off_t 文件大小和偏移量(带符号的)
pid_t 进程ID和进程组ID(带符号的)
ptrdiff_t 两个指针相减的结果(带符号的)
rlim_t 资源限制
sig_atomic_t 能原子地访问的数据类型
sigset_t 信号集
size_t 对象(例如字符串)大小(不带符号的)
ssize_t 返回字节数的函数(带符号的)(read、write)
time_t 日历时间的秒计数器
uid_t 数值用户ID
wchar_t 能表示所有不同的字符码
初始化操作与使用结构
SUSv3虽然定义了诸如sembuf之类的类的结构,但是未对字段顺序做出规范,且某些实现会增加额外字段使用并非所有实现都定义的宏
并非所有的UNIX实现都对某个宏做了定义,如WCOREDUMP,用于检测子进程是否生成了coredump核心转储文件,应用非常广泛,某些UNIX实现可能并没有定义,因为SUSv3未对其进行规范#ifdef WCOREDUMP // #endif
不同实现所需头文件的变化
有些情况下,包含各种系统实现和库函数原型的头文件,在不同实现之间会有所不同