💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 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) 。不要混淆他们!