errno是否是thread safe的

本文深入探讨了errno在多线程环境下的行为及其线程安全性。通过对glibc源码的详细分析,解释了errno是如何实现的,并揭示了其为何能够作为一个宏被定义。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

errno是线程安全的吗? 假设有A, B两个线程都执行系统调用, 其中A返回EIO, B返回EAGAIN, 在判断返回值时是否会引起混淆?
简单的通过man errno就可以获取答案: errno is thread-local; setting it in one thread does not affect its value in any other thread.
但是errno究竟是如何实现的? 为什么errno还可能是一个宏? 带着疑问我们来研究下glibc. 官网下载到最新的是2.25版本的源码, 我们就以glibc-2.25为例一探究竟.

先看对外暴露的stdlib/errno.h:

1 #include <bits/errno.h> 
2 #undef __need_Emath 
3 #ifndef errno 
4 extern int errno; 
5 #endif 

 

这里的注释指明两点:
1. bits/errno.h是系统相关头文件, 在该文件中会测试__need_Emath与_ERRNO_H宏.
2. 如果bits/errno.h未定义errno为宏则声明外部变量errno.

sysdeps/unix/sysv/linux/bits/errno.h中将其定义为函数:

1 #ifdef _ERRNO_H 
2 # ifndef __ASSEMBLER__ 
3 extern int *__errno_location (void) __THROW __attribute__ ((__const__)); 
4 #  if !defined _LIBC || defined _LIBC_REENTRANT 
5 #  define errno (*__errno_location ()) 
6 #  endif 
7 # endif 
8 #endif 

 

搞清楚errno的定义后再来看看errno的修改. 由于不同架构系统调用部分相同部分不同, glibc使用脚本来动态生成系统调用函数的封装, sysdeps/unix/make-syscalls.sh即生成函数封装的脚本. 它会先去读取syscalls.list保存在calls变量中, 通过sed将注释行与空行删除, 将得到的文件按行输入(读入的前三个参数分别为file caller rest)并判断对应架构目录下是否存在$file.c $file.S $caller.c $caller.S(如果$caller不为-)文件中一个, 如果有则记录在calls变量中. 接下来根据系统调用类型及参数配置不同参数, 最后将其输出, 注意line 256开始的宏定义与包含的文件. 此处有点不明白, 输出的文件是怎么确定的?
make-syscalls.sh脚本输出的信息有何作用? 上文中line 256可以解答这个问题. 先来看下系统调用的模板(defined in sysdeps/unix/syscall-template.S):

1 #define T_PSEUDO(SYMBOL, NAME, N) PSEUDO (SYMBOL, NAME, N) 
2 #define T_PSEUDO_END(SYMBOL) PSEUDO_END (SYMBOL) 
3 T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS) 
4     ret 
5 T_PSEUDO_END (SYSCALL_SYMBOL) 

 

syscall-template.S定义了一组宏用于定义系统调用的接口, 这里仅分析最常见的情况.
看下以PSEUDO开头命名的宏(defined in sysdeps/unix/sysv/linux/arm/sysdep.h):

 1 #undef PSEUDO 
 2 #define PSEUDO(name, syscall_name, args)    \ 
 3     .text;                                  \ 
 4 ENTRY (name);                               \ 
 5     DO_CALL (syscall_name, args);           \ 
 6     cmn r0, $4096; 
 7 #undef PSEUDO_END 
 8 #define PSEUDO_END(name)                    \ 
 9     SYSCALL_ERROR_HANDLER;                  \ 
10 END (name) 

 

因以PSEUDO开头命名的宏较多, 此处仅分析下PSEUDO与PSEUDO_END, 可见两个宏需成对使用, 分别用于系统调用与错误返回, 继续分析DO_CALL(defined in sysdeps/unix/sysv/linux/arm/sysdep.h):

1 #undef DO_CALL 
2 #define DO_CALL(syscall_name, args)         \ 
3     DOARGS_##args;                          \ 
4     ldr r7, =SYS_ify (syscall_name);        \ 
5     swi 0x0;                                \ 
6     UNDOARGS_##args 

 

DO_CALL宏有三条注释, 分别说明:
1. ARM EABI用户接口将系统调用号放在R7中, 而非swi中传递. 这种方式更加高效, 因为内核无需从内存中获取调用号, 这对于指令cache与数据cache分开的架构比较麻烦. 因此swi中必须传递0.
2. 内核通过R0-R6共传递7个参数, 而编译器通常只使用4个参数寄存器其余以入栈方式传参(见AAPCS), 此处需要做转换防止栈帧毁坏并保证内核正确获取参数.
3. 由于缓存系统调用号在发生系统调用时必须保存并恢复R7.
根据注释理解代码就方便多了, 先保存R7并将参数传递给对应寄存器, 将系统调用号传递给R7并调用swi 0x0, 最后恢复寄存器. DOARGS_#args根据传入args值不同展开为不同的宏(都定义在同一文件下), 此处仅分析DOARGS_7情况(UNDOARGS_#args类似, 不展开分析):

 1 #undef  DOARGS_7 
 2 #define DOARGS_7                            \ 
 3     .fnstart;                               \ 
 4     mov ip, sp;                             \ 
 5     push {r4, r5, r6, r7};                  \ 
 6     cfi_adjust_cfa_offset (16);             \ 
 7     cfi_rel_offset (r4, 0);                 \ 
 8     cfi_rel_offset (r5, 4);                 \ 
 9     cfi_rel_offset (r6, 8);                 \ 
10     cfi_rel_offset (r7, 12);                \ 
11     .save { r4, r5, r6, r7 };               \ 
12     ldmia ip, {r4, r5, r6} 

 

先将当前栈指针保存在IP中, 将R4-R7依次入栈, 最后通过IP将已经入栈的参数传递给R4-R7. 中间以cfi开头的宏都是伪指令(defined in sysdeps/generic/sysdep.h), 用于debugger分析程序调用间寄存器状态, 不详细分析了, 具体可参见(http://dwarfstd.org/doc/DWARF5.pdf).
SYS_ify宏(defined in sysdeps/unix/sysv/linux/arm/sysdep.h)用于拼接字符串生成对应的调用号(生成的即是内核定义的系统调用号的宏):

#define SYS_ify(syscall_name) (__NR_##syscall_name)

再回头看PSEUDO_END, 其展开即调用SYSCALL_ERROR_HANDLER(sysdeps/unix/sysv/linux/arm/sysdep.h)然后声明函数结束. SYSCALL_ERROR_HANDLER根据不同预处理宏有不同定义, 此处仅分析使用libc的errno且架构不支持THUMB_INTERWORK情况:

 1 #define SYSCALL_ERROR_HANDLER               \ 
 2 __local_syscall_error:                      \ 
 3     push { lr };                            \ 
 4     cfi_adjust_cfa_offset (4);              \ 
 5     cfi_rel_offset (lr, 0);                 \ 
 6     push { r0 };                            \ 
 7     cfi_adjust_cfa_offset (4);              \ 
 8     bl PLTJMP(C_SYMBOL_NAME(__errno_location)); \ 
 9     pop { r1 };                             \ 
10     cfi_adjust_cfa_offset (-4);             \ 
11     rsb r1, r1, #0;                         \ 
12     str r1, [r0];                           \ 
13     mvn r0, #0;                             \ 
14     POP_PC; 

 

代码还是比较简单的, 首先将LR压栈, 再将R0压栈(注意此时R0为系统调用返回值). 然后获取errno的地址, PLTJMP宏表明__errno_location符号是由程序链接表指定而非静态生成的. 由于函数返回值保存在R0, 出栈时使用R1保存系统调用返回值, 又系统调用返回值为复数, 此处再做一次减法取正, 再将其保存在R0给定的地址上(errno). 最后将R0设置为-1, 将LR出栈并跳转.

待续......

转载于:https://www.cnblogs.com/Five100Miles/p/8459193.html

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/epoll.h> #include <fcntl.h> #include <pthread.h> #include <dirent.h> #include <sys/stat.h> #include <time.h> #include <ctype.h> #include <errno.h> #define PORT 8080 #define MAX_EVENTS 1024 #define THREAD_POOL_SIZE 8 #define BUFFER_SIZE 4096 #define WEB_ROOT "web" // 当前目录作为根目录 // 线程池结构 typedef struct { pthread_t *threads; int thread_count; pthread_mutex_t lock; pthread_cond_t cond; int shutdown; } ThreadPool; // 任务结构 typedef struct { int client_fd; struct sockaddr_in client_addr; } Task; // 全局任务队列 Task *task_queue; int task_count = 0; int task_capacity = 0; ThreadPool thread_pool; // HTTP响应头 const char *http_header = "HTTP/1.1 200 OK\r\n" "Server: SimpleWebServer\r\n" "Connection: close\r\n" "Content-Type: %s\r\n" "Content-Length: %ld\r\n\r\n"; // 十六进制字符转换 int hex_char_to_int(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10; return 0; } // URL解码 void url_decode(char *dst, const char *src) { char a, b; while (*src) { if (*src == '%' && (a = src[1]) && (b = src[2]) && isxdigit(a) && isxdigit(b)) { *dst++ = (hex_char_to_int(a) << 4) | hex_char_to_int(b); src += 3; } else if (*src == '+') { *dst++ = ' '; src++; } else { *dst++ = *src++; } } *dst = '\0'; } // 文件扩展名到MIME类型映射 const char *extension_to_type(const char *ext) { if (strcmp(ext, "html") == 0) return "text/html"; if (strcmp(ext, "htm") == 0) return "text/html"; if (strcmp(ext, "css") == 0) return "text/css"; if (strcmp(ext, "js") == 0) return "application/javascript"; if (strcmp(ext, "jpg") == 0 || strcmp(ext, "jpeg") == 0) return "image/jpeg"; if (strcmp(ext, "png") == 0) return "image/png"; if (strcmp(ext, "gif") == 0) return "image/gif"; if (strcmp(ext, "ico") == 0) return "image/x-icon"; if (strcmp(ext, "json") == 0) return "application/json"; if (strcmp(ext, "txt") == 0) return "text/plain"; if (strcmp(ext, "svg") == 0) return "image/svg+xml"; return "application/octet-stream"; } // 获取文件扩展名 const char *get_file_extension(const char *filename) { const char *dot = strrchr(filename, '.'); if (!dot || dot == filename) return ""; return dot + 1; } // 发送404错误响应 void send_404(int client_fd) { const char *response = "HTTP/1.1 404 Not Found\r\n" "Content-Type: text/html; charset=UTF-8\r\n" "Connection: close\r\n" "Content-Length: 86\r\n\r\n" "<html><head><title>404 Not Found</title></head><body><h1>404 Not Found</h1></body></html>"; send(client_fd, response, strlen(response), 0); } // 发送400错误响应 void send_400(int client_fd) { const char *response = "HTTP/1.1 400 Bad Request\r\n" "Content-Type: text/html; charset=UTF-8\r\n" "Connection: close\r\n" "Content-Length: 84\r\n\r\n" "<html><head><title>400 Bad Request</title></head><body><h1>400 Bad Request</h1></body></html>"; send(client_fd, response, strlen(response), 0); } // 发送500错误响应 void send_500(int client_fd) { const char *response = "HTTP/1.1 500 Internal Server Error\r\n" "Content-Type: text/html; charset=UTF-8\r\n" "Connection: close\r\n" "Content-Length: 92\r\n\r\n" "<html><head><title>500 Internal Error</title></head><body><h1>500 Internal Server Error</h1></body></html>"; send(client_fd, response, strlen(response), 0); } // 处理客户端请求 void handle_request(int client_fd, struct sockaddr_in client_addr) { char buffer[BUFFER_SIZE]; ssize_t bytes_read = recv(client_fd, buffer, BUFFER_SIZE - 1, 0); if (bytes_read <= 0) { close(client_fd); return; } buffer[bytes_read] = '\0'; // 解析HTTP请求 char method[16], path[256], protocol[16]; if (sscanf(buffer, "%s %s %s", method, path, protocol) != 3) { send_400(client_fd); close(client_fd); return; } // 日志输出 char client_ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &(client_addr.sin_addr), client_ip, INET_ADDRSTRLEN); printf("[%s] %s %s %s\n", client_ip, method, path, protocol); // URL解码 char decoded_path[256]; url_decode(decoded_path, path); printf("Decoded path: %s\n", decoded_path); // 只处理GET请求 if (strcmp(method, "GET") != 0 && strcmp(method, "POST") != 0) { send_400(client_fd); close(client_fd); return; } // 安全处理路径 char safe_path[512]; if (strcmp(decoded_path, "/") == 0) { // 尝试多个可能的默认文件 const char *default_files = {"Index.html"}; int found = 0; snprintf(safe_path, sizeof(safe_path), "%s/%s", WEB_ROOT, default_files); if (access(safe_path, F_OK) == 0) { found = 1; } if (!found) { strcpy(safe_path, WEB_ROOT "/Index.html"); } } else { // 处理路径中的斜杠问题 if (decoded_path[0] == '/') { snprintf(safe_path, sizeof(safe_path), "%s%s", WEB_ROOT, decoded_path); } else { snprintf(safe_path, sizeof(safe_path), "%s/%s", WEB_ROOT, decoded_path); } } printf("Final path: %s\n", safe_path); // 防止路径遍历攻击 if (strstr(safe_path, "..")) { printf("Path traversal attempt detected: %s\n", safe_path); send_404(client_fd); close(client_fd); return; } // 检查文件是否存在 struct stat file_stat; if (stat(safe_path, &file_stat) < 0) { perror("stat failed"); send_404(client_fd); close(client_fd); return; } // 检查是否是普通文件 if (!S_ISREG(file_stat.st_mode)) { printf("Not a regular file: %s\n", safe_path); send_404(client_fd); close(client_fd); return; } // 检查文件权限 if (access(safe_path, R_OK) != 0) { perror("access failed"); send_404(client_fd); close(client_fd); return; } // 打开文件 FILE *file = fopen(safe_path, "rb"); if (!file) { perror("fopen failed"); send_500(client_fd); close(client_fd); return; } // 获取MIME类型 const char *ext = get_file_extension(safe_path); const char *mime_type = extension_to_type(ext); // 构建HTTP响应头 char header[1024]; snprintf(header, sizeof(header), http_header, mime_type, file_stat.st_size); // 发送响应头 ssize_t sent = send(client_fd, header, strlen(header), 0); if (sent <= 0) { perror("send header failed"); fclose(file); close(client_fd); return; } // 发送文件内容 char file_buffer[BUFFER_SIZE]; size_t bytes; while ((bytes = fread(file_buffer, 1, sizeof(file_buffer), file)) > 0) { sent = send(client_fd, file_buffer, bytes, 0); if (sent <= 0) { perror("send file content failed"); break; } } fclose(file); close(client_fd); // 打印日志 printf("[%s] %s %s - Sent %ld bytes\n", client_ip, method, decoded_path, file_stat.st_size); } // 线程工作函数 void *worker_thread(void *arg) { while (1) { pthread_mutex_lock(&thread_pool.lock); // 等待任务 while (task_count == 0 && !thread_pool.shutdown) { pthread_cond_wait(&thread_pool.cond, &thread_pool.lock); } if (thread_pool.shutdown) { pthread_mutex_unlock(&thread_pool.lock); pthread_exit(NULL); } // 获取任务 Task task = task_queue[--task_count]; pthread_mutex_unlock(&thread_pool.lock); // 处理任务 handle_request(task.client_fd, task.client_addr); } return NULL; } // 初始化线程池 void thread_pool_init(ThreadPool *pool, int thread_count) { pool->threads = (pthread_t *)malloc(thread_count * sizeof(pthread_t)); if (!pool->threads) { perror("malloc failed"); exit(EXIT_FAILURE); } pool->thread_count = thread_count; pool->shutdown = 0; pthread_mutex_init(&pool->lock, NULL); pthread_cond_init(&pool->cond, NULL); // 初始化任务队列 task_capacity = 256; task_queue = (Task *)malloc(task_capacity * sizeof(Task)); if (!task_queue) { perror("malloc failed"); exit(EXIT_FAILURE); } task_count = 0; // 创建工作线程 for (int i = 0; i < thread_count; i++) { if (pthread_create(&pool->threads[i], NULL, worker_thread, NULL) != 0) { perror("pthread_create failed"); exit(EXIT_FAILURE); } } } // 添加任务到线程池 void add_task(int client_fd, struct sockaddr_in client_addr) { pthread_mutex_lock(&thread_pool.lock); // 如果任务队列已满,扩大容量 if (task_count == task_capacity) { task_capacity *= 2; Task *new_queue = (Task *)realloc(task_queue, task_capacity * sizeof(Task)); if (!new_queue) { perror("realloc failed"); pthread_mutex_unlock(&thread_pool.lock); close(client_fd); return; } task_queue = new_queue; } // 添加任务 task_queue[task_count].client_fd = client_fd; task_queue[task_count].client_addr = client_addr; task_count++; // 通知工作线程 pthread_cond_signal(&thread_pool.cond); pthread_mutex_unlock(&thread_pool.lock); } // 设置文件描述符为非阻塞 void set_nonblocking(int fd) { int flags = fcntl(fd, F_GETFL, 0); if (flags == -1) { perror("fcntl F_GETFL failed"); return; } if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { perror("fcntl F_SETFL failed"); } } int main() { int server_fd, epoll_fd; struct sockaddr_in address; struct epoll_event event, events[MAX_EVENTS]; socklen_t addrlen = sizeof(address); // 创建服务器socket if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { perror("socket failed"); exit(EXIT_FAILURE); } // 设置socket选项 int opt = 1; if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) { perror("setsockopt"); close(server_fd); exit(EXIT_FAILURE); } // 绑定地址和端口 address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(PORT); if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) { perror("bind failed"); close(server_fd); exit(EXIT_FAILURE); } // 开始监听 if (listen(server_fd, 128) < 0) { perror("listen"); close(server_fd); exit(EXIT_FAILURE); } printf("Server listening on port %d\n", PORT); printf("Web root: %s\n", WEB_ROOT); // 创建epoll实例 epoll_fd = epoll_create1(0); if (epoll_fd < 0) { perror("epoll_create1"); close(server_fd); exit(EXIT_FAILURE); } // 添加服务器socket到epoll event.events = EPOLLIN; event.data.fd = server_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) < 0) { perror("epoll_ctl: server_fd"); close(server_fd); close(epoll_fd); exit(EXIT_FAILURE); } // 初始化线程池 thread_pool_init(&thread_pool, THREAD_POOL_SIZE); printf("Thread pool initialized with %d threads\n", THREAD_POOL_SIZE); // 主循环 while (1) { int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); if (nfds < 0) { perror("epoll_wait"); break; } for (int i = 0; i < nfds; i++) { if (events[i].data.fd == server_fd) { // 处理新连接 struct sockaddr_in client_addr; socklen_t addr_len = sizeof(client_addr); int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len); if (client_fd < 0) { perror("accept"); continue; } // 设置非阻塞 set_nonblocking(client_fd); // 添加客户端socket到epoll event.events = EPOLLIN | EPOLLET; event.data.fd = client_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event) < 0) { perror("epoll_ctl: client_fd"); close(client_fd); continue; } // 打印新连接信息 char client_ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &(client_addr.sin_addr), client_ip, INET_ADDRSTRLEN); printf("New connection from %s:%d\n", client_ip, ntohs(client_addr.sin_port)); } else { // 处理客户端请求 int client_fd = events[i].data.fd; // 获取客户端地址 struct sockaddr_in client_addr; socklen_t addr_len = sizeof(client_addr); if (getpeername(client_fd, (struct sockaddr *)&client_addr, &addr_len) < 0) { perror("getpeername failed"); close(client_fd); continue; } // 将任务添加到线程池 add_task(client_fd, client_addr); // 从epoll中移除,避免重复处理 epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, NULL); } } } // 清理 close(server_fd); close(epoll_fd); // 关闭线程池 thread_pool.shutdown = 1; pthread_cond_broadcast(&thread_pool.cond); for (int i = 0; i < thread_pool.thread_count; i++) { pthread_join(thread_pool.threads[i], NULL); } free(thread_pool.threads); free(task_queue); return 0; } 以这一版为准 运行后,windows浏览器偶尔会对GET请求报错:net:ERR_CONTENT_LENGTH_MISMATCH
08-16
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值