# POSIX,第 1 部分:错误处理
> 原文:<https://github.com/angrave/SystemProgramming/wiki/POSIX%2C-Part-1%3A-Error-handling>
## 什么是 POSIX 错误处理?
在其他语言中,您可能会看到使用异常实现的错误处理。虽然你在技术上可以在 c 中使用它们(你保留一堆非常 try / catch 块并使用`setjmp`和`longjmp`分别转到这些块),但是 C 中的错误处理通常是通过 posix 错误处理代码来完成的看起来像这样。
```c
int ret = some_system_call()
if(ret == ERROR_CODE){
switch(errno){
// Do different stuff based on the errno number.
}
}
```
在内核中,`goto`的使用被大量用于清理应用程序的不同部分。 **你不应该使用 gotos** 因为它们使代码更难阅读。内核中的 getos 是不必要的,所以不要上课。
## 什么是`errno`以及何时设定?
POSIX 定义了一个特殊的整数`errno`,它在系统调用失败时设置。 `errno`的初始值为零(即没有错误)。当系统调用失败时,它通常会返回-1 表示错误并设置`errno`
## 多线程怎么样?
每个线程都有自己的`errno`副本。这非常有用;否则一个线程中的错误会干扰另一个线程的错误状态。
## 什么时候`errno`重置为零?
除非你专门将它重置为零,否则它不会!当系统调用成功时,他们执行 _ 而不是 _ 重置`errno`的值。
这意味着如果您知道系统调用失败(例如,它返回-1),您应该只依赖于 errno 的值。
## 使用`errno`有什么问题和最佳做法?
当复杂的错误处理使用库调用或系统调用可能会改变`errno`的值时要小心。实际上,将 errno 的值复制到 int 变量更安全:
```c
// Unsafe - the first fprintf may change the value of errno before we use it!
if (-1 == sem_wait(&s)) {
fprintf(stderr, "An error occurred!");
fprintf(stderr, "The error value is %d\n", errno);
}
// Better, copy the value before making more system and library calls
if (-1 == sem_wait(&s)) {
int errno_saved = errno;
fprintf(stderr, "An error occurred!");
fprintf(stderr, "The error value is %d\n", errno_saved);
}
```
同样,如果您的信号处理程序进行任何系统或库调用,那么最好保存 errno 的原始值并在返回之前恢复该值:
```c
void handler(int signal) {
int errno_saved = errno;
// make system calls that might change errno
errno = errno_saved;
}
```
## 如何打印出与特定错误号相关联的字符串消息?
使用`strerror`获取错误值的简短(英文)描述
```c
char *mesg = strerror(errno);
fprintf(stderr, "An error occurred (errno=%d): %s", errno, mesg);
```
## perror 和 strerror 有什么关系?
在之前的页面中,我们使用 perror 将错误打印到标准错误。使用`strerror`,我们现在可以编写`perror`的简单实现:
```c
void perror(char *what) {
fprintf(stderr, "%s: %s\n", what, strerror(errno));
}
```
## 使用 strerror 有什么问题?
不幸的是`strerror`不是线程安全的。换句话说,两个线程不能同时调用它!
有两种解决方法:首先,我们可以使用互斥锁定义临界区和本地缓冲区。所有调用`strerror`的地方的所有线程都应该使用相同的互斥锁
```c
pthread_mutex_lock(&m);
char *result = strerror(errno);
char *message = malloc(strlen(result) + 1);
strcpy(message, result);
pthread_mutex_unlock(&m);
fprintf(stderr, "An error occurred (errno=%d): %s", errno, message);
free(message);
```
或者使用较不便携但线程安全的`strerror_r`
## 什么是 EINTR? sem_wait 是什么意思?读?写?
当信号(例如 SIGCHLD,SIGPIPE,...)传递给过程时,某些系统调用可能会中断。此时系统调用可能会返回而不执行任何操作!例如,字节可能未被读/写,信号量等待可能没有等待。
可以通过检查返回值以及`errno`是否为 EINTR 来检测此中断。在这种情况下,应重试系统调用。通常会看到包含系统调用的以下类型的循环(例如 sem_wait)。
```c
while ((-1 == systemcall(...)) && (errno == EINTR)) { /* repeat! */}
```
小心写`== EINTR`,而不是`= EINTR`。
或者,如果结果值需要稍后使用...
```c
while ((-1 == (result = systemcall(...))) && (errno == EINTR)) { /* repeat! */}
```
在 Linux 上,将`read`和`write`调用到本地磁盘通常不会返回 EINTR(而是自动为您重新启动该功能)。但是,在对应于网络流 _ 的文件描述符上调用`read`和`write`可以 _ 返回 EINTR。
## 哪些系统调用可能被中断并需要包装?
使用 man 页面!手册页包括可由系统调用设置的错误列表(即错误值)。经验法则是“慢”(阻塞)调用(例如,写入套接字)可能会被中断,但快速非阻塞调用(例如 pthread_mutex_lock)则不会。
来自 linux 信号 7 手册页。
“如果在阻止系统调用或库函数调用时调用信号处理程序,则:
* 信号处理程序返回后,调用自动重启;要么
* 调用失败并显示错误 EINTR。发生这两种行为中的哪一种取决于接口以及是否使用 SA_RESTART 标志建立了信号处理程序(请参阅 sigaction(2))。 UNIX 系统的细节各不相同;下面是 Linux 的详细信息。
如果对信号处理程序中断对以下某个接口的阻塞调用,则在使用 SA_RESTART 标志后,如果信号处理程序返回,则将自动重新启动该调用。否则呼叫将失败并显示错误 EINTR:
* read(2),readv(2),write(2),writev(2)和 ioctl(2)调用“慢”设备。 “慢”设备是 I / O 调用可能无限期阻塞的设备,例如终端,管道或套接字。 (根据此定义,磁盘不是慢速设备。)如果慢速设备上的 I / O 调用在信号处理程序中断时已经传输了某些数据,则该调用将返回成功状态(通常,传输的字节数)。 “
注意,很容易相信设置'SA_RESTART'标志足以使整个问题消失。不幸的是,这不是真的:仍有系统调用可能提前返回并设置`EINTR`!有关详细信息,请参见[信号(7)](https://cs-education.github.io/sysassets/man_pages/html/man7/signal.7.html)。
## Errno 例外吗?
有些 POSIX 实用程序每个人都有自己的错误。一种是当你调用`getaddrinfo`时检查错误并转换为字符串的函数是 [gai_strerr](https://linux.die.net/man/3/gai_strerror) 。不要混淆他们!
- UIUC CS241 系统编程中文讲义
- 0. 简介
- #Informal 词汇表
- #Piazza:何时以及如何寻求帮助
- 编程技巧,第 1 部分
- 系统编程短篇小说和歌曲
- 1.学习 C
- C 编程,第 1 部分:简介
- C 编程,第 2 部分:文本输入和输出
- C 编程,第 3 部分:常见问题
- C 编程,第 4 部分:字符串和结构
- C 编程,第 5 部分:调试
- C 编程,复习题
- 2.进程
- 进程,第 1 部分:简介
- 分叉,第 1 部分:简介
- 分叉,第 2 部分:Fork,Exec,等等
- 进程控制,第 1 部分:使用信号等待宏
- 进程复习题
- 3.内存和分配器
- 内存,第 1 部分:堆内存简介
- 内存,第 2 部分:实现内存分配器
- 内存,第 3 部分:粉碎堆栈示例
- 内存复习题
- 4.介绍 Pthreads
- Pthreads,第 1 部分:简介
- Pthreads,第 2 部分:实践中的用法
- Pthreads,第 3 部分:并行问题(奖金)
- Pthread 复习题
- 5.同步
- 同步,第 1 部分:互斥锁
- 同步,第 2 部分:计算信号量
- 同步,第 3 部分:使用互斥锁和信号量
- 同步,第 4 部分:临界区问题
- 同步,第 5 部分:条件变量
- 同步,第 6 部分:实现障碍
- 同步,第 7 部分:读者编写器问题
- 同步,第 8 部分:环形缓冲区示例
- 同步复习题
- 6.死锁
- 死锁,第 1 部分:资源分配图
- 死锁,第 2 部分:死锁条件
- 死锁,第 3 部分:餐饮哲学家
- 死锁复习题
- 7.进程间通信&amp;调度
- 虚拟内存,第 1 部分:虚拟内存简介
- 管道,第 1 部分:管道介绍
- 管道,第 2 部分:管道编程秘密
- 文件,第 1 部分:使用文件
- 调度,第 1 部分:调度过程
- 调度,第 2 部分:调度过程:算法
- IPC 复习题
- 8.网络
- POSIX,第 1 部分:错误处理
- 网络,第 1 部分:简介
- 网络,第 2 部分:使用 getaddrinfo
- 网络,第 3 部分:构建一个简单的 TCP 客户端
- 网络,第 4 部分:构建一个简单的 TCP 服务器
- 网络,第 5 部分:关闭端口,重用端口和其他技巧
- 网络,第 6 部分:创建 UDP 服务器
- 网络,第 7 部分:非阻塞 I O,select()和 epoll
- RPC,第 1 部分:远程过程调用简介
- 网络复习题
- 9.文件系统
- 文件系统,第 1 部分:简介
- 文件系统,第 2 部分:文件是 inode(其他一切只是数据...)
- 文件系统,第 3 部分:权限
- 文件系统,第 4 部分:使用目录
- 文件系统,第 5 部分:虚拟文件系统
- 文件系统,第 6 部分:内存映射文件和共享内存
- 文件系统,第 7 部分:可扩展且可靠的文件系统
- 文件系统,第 8 部分:从 Android 设备中删除预装的恶意软件
- 文件系统,第 9 部分:磁盘块示例
- 文件系统复习题
- 10.信号
- 过程控制,第 1 部分:使用信号等待宏
- 信号,第 2 部分:待处理的信号和信号掩码
- 信号,第 3 部分:提高信号
- 信号,第 4 部分:信号
- 信号复习题
- 考试练习题
- 考试主题
- C 编程:复习题
- 多线程编程:复习题
- 同步概念:复习题
- 记忆:复习题
- 管道:复习题
- 文件系统:复习题
- 网络:复习题
- 信号:复习题
- 系统编程笑话