# 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) 。不要混淆他们！