volatile 和 sig_atomic_t

本文详细解析了sig_atomic_t和volatile的含义及其应用场景,包括它们如何确保数据访问的原子性和防止编译器优化,特别强调了在信号处理、多线程及硬件交互中的重要性。

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

今天看libev代码,看到# define EV_ATOMIC_T sig_atomic_t volatile,由于对sig_atomic_t 类型不熟悉,查了一下。把查到的信息记录下来。

sig_atomic_t 类型指的是只需一条指令完成读写的数据类型,所以不可能是结构体,在linux下就是int类型。

转载地址:http://www.cnblogs.com/GoodGoodWorkDayDayUp/archive/2011/05/14/2046082.html   《volatile 和 sig_atomic_t》

1).volatile

   影响编译器编译的结果,指出,volatile 变量是随时可能发生变化的,每次使用时都需要去内存里重新读取它的值,与volatile变量有关的运算,不要进行编译优化,以免出错,(VC++ 在产生release版可执行码时会进行编译优化,加volatile关键字的变量有关的运算,将不进行编译优化。)。 例如:
   volatile int i=10;
   int j = i;
   ...
   int k = i;
   volatile 告诉编译器i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的可执行码会重新从i的地址读取数据放在k中。 而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在k中。而不是重新从i里面读。这样一来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问,不会出错。
   建议使用volatile变量的场所:
   (1) 并行设备的硬件寄存器
   (2) 一个中断服务子程序中会访问到的非自动变量(全局变量)
   (3) 多线程应用中被几个任务共享的变量
2).volatile和__volatile__:
 a. volatile是C语言定义的关键字,gcc为了需要又定义了__volatile__,它和
    volatile表达的是同一意思。
 b. volatile的本意是"易变的",由于访问寄存器的速度快于访存,所以编译器一般
    都会作优化以减少访存。如果变量加上volatile修饰,则编译器就不会对此变量
    的读写操作进行优化,即不通过寄存器缓冲而直接访存。
 c. __asm__ __volatile__一起指示编译器不要改动优化后面的汇编语句。
3).sig_atomic_t:
   当把变量声明为该类型是,则会保证该变量在使用或赋值时, 无论是在32位还是64位的机器上都能保证操作是原子的, 它会根据机器的类型自动适应。
   今天看源代码时,看到sig_atomic_t这个类型,平时用得较少,平时一般是用int类型来代替。
   这个类型是定义在signal.h文件中。下面来说说这个类型。
   在处理信号(signal)的时候,有时对于一些变量的访问希望不会被中断,无论是硬件中 断还是软件中断,这就要求访问或改变这些变量需要在计算机的一条指令内完成。通常情况下,int类型的变量通常是原子访问的,也可以认为 sig_atomic_t就是int类型的数据,因为对这些变量要求一条指令完成,所以sig_atomic_t不可能是结构体,只会是数字类型。
   在linux里这样定义:
   typedef int __sig_atomic_t;
   另外gnu c的文档也说比int短的类型通常也是具有原子性的,例如short类型。同时,指针(地址)类型也一定是原子性的。 该类型在所有gnu c库支持的系统和支持posix的系统中都有定义。
注:关于int型是不是原子的操作,看下面的gnu文档
http://www.gnu.org/s/hello/manual/libc/Atomic-Types.html
24.4.7.2 Atomic Types

To avoid uncertainty about interrupting access to a variable, you can use a particular data type for which access is always atomic: sig_atomic_t. Reading and writing this data type is guaranteed to happen in a single instruction, so there's no way for a handler to run “in the middle” of an access.

The type sig_atomic_t is always an integer data type, but which one it is, and how many bits it contains, may vary from machine to machine.

— Data Type: sig_atomic_t

This is an integer data type. Objects of this type are always accessed atomically.

In practice, you can assume that int is atomic. You can also assume that pointer types are atomic; that is very convenient. Both of these assumptions are true on all of the machines that the GNU C library supports and on all POSIX systems we know of.

代码高并发卡死原因分析根本原因:信号处理与计数同步问题信号丢失风险SIGCHLD信号是非排队的,当多个子进程同时退出时,可能只触发一次信号处理高并发场景下(5000并发),大量子进程同时退出,信号处理函数无法及时处理所有僵尸进程计数更新延迟 Check and Limit Children 检查并限制子进程 当num_children=500时进入等待在sleep期间,即使有子进程退出,主进程仍在sleep状态无法立即accept新连接信号处理函数更新num_children,但主进程不会重新检查计数(直接continue)竞争条件 High Concurrency Timing Issue 高并发时序问题 具体问题表现连接队列溢出默认监听队列长度(SOMAXCONN)通常为1285000并发远超队列容量,新连接被丢弃输出中反复出现Reached max children表明系统持续过载进程创建瓶颈Windows下fork()模拟效率较低(通过WSL/Cygwin)500进程上限 + 高频创建/销毁导致调度开销剧增输出显示的证据 log Child 83948 exited with status 0 # 子进程退出 New child process 83949 (total: 500) # 立即创建新进程 Reached max children (500), waiting... # 立刻再次达到上限 表明计数更新连接接受存在时间差 请基于以上问题,考虑到我的代码运行在Linux虚拟机中,对我的代码进一步优化, #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/socket.h> #include <sys/wait.h> #include <sys/types.h> #include <errno.h> #include <fcntl.h> #include <netinet/in.h> #include <signal.h> #include "http.h" #include "multiProcess.h" #define MAX_CHILDREN 500 // 最大并发子进程数 #define MAX_WAIT_TIME 5 // 等待子进程退出的最长时间(秒) static volatile sig_atomic_t num_children = 0; // 信号处理函数:回收僵尸进程 void sigchld_handler(int sig) { int saved_errno = errno; pid_t pid; int status; // 非阻塞方式回收所有已退出的子进程 while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { if (WIFEXITED(status)) { printf("Child %d exited with status %d\n", pid, WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { printf("Child %d killed by signal %d\n", pid, WTERMSIG(status)); } num_children--; } errno = saved_errno; } // 信号处理函数:处理SIGTERM/SIGINT void sigterm_handler(int sig) { printf("Received signal %d, shutting down...\n", sig); // 这里可以添加清理代码 exit(EXIT_SUCCESS); } int multiProcessEntry() { int server_sock = -1; int port = 80; struct sockaddr_in client_name; socklen_t client_name_len = sizeof(client_name); // 设置信号处理 struct sigaction sa; sa.sa_handler = sigchld_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; if (sigaction(SIGCHLD, &sa, NULL) == -1) { perror("sigaction"); exit(EXIT_FAILURE); } // 设置终止信号处理 signal(SIGTERM, sigterm_handler); signal(SIGINT, sigterm_handler); signal(SIGPIPE, SIG_IGN); // 忽略SIGPIPE server_sock = startup(&port); printf("HTTP server running on port %d\n", port); // 设置监听套接字为非阻塞 int flags = fcntl(server_sock, F_GETFL, 0); fcntl(server_sock, F_SETFL, flags | O_NONBLOCK); while (1) { // 检查进程数限制 if (num_children >= MAX_CHILDREN) { // 等待部分子进程退出 printf("Reached max children (%d), waiting...\n", MAX_CHILDREN); sleep(1); continue; } int client_sock = accept(server_sock, (struct sockaddr *)&client_name, &client_name_len); if (client_sock == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) { // 没有新连接,短暂休眠后重试 usleep(10000); // 10ms continue; } perror("accept failed"); continue; } pid_t pid = fork(); if (pid < 0) { perror("fork failed"); close(client_sock); } else if (0 == pid) { // 子进程 // 关闭不需要的文件描述符 close(server_sock); // 处理请求 accept_request(client_sock); // 关闭客户端socket close(client_sock); // 子进程退出 exit(EXIT_SUCCESS); } else { // 父进程 // 关闭客户端socket(父进程不需要) close(client_sock); // 增加子进程计数 num_children++; printf("New child process %d (total: %d)\n", pid, num_children); } } // 等待所有子进程退出 while (num_children > 0) { printf("Waiting for %d children to exit...\n", num_children); sleep(1); } close(server_sock); return EXIT_SUCCESS; }
最新发布
08-16
static int s_iServerSocket = 0; static stNetAddr stClientArray[cmnDfn_WORKING_PTHREAD_NUMBER / 2] = {0}; int TCPSvr_Run(void) { int iRet = -1; int iClientSocket; int iTaskID = 0; int iCLientNumber = 0; struct sockaddr_in stClientaddr; socklen_t addrlen = sizeof(stClientaddr); if (s_iServerSocket <= 0) { cmn_PrtError("server socket not available"); } if(listen(s_iServerSocket,cmnDfn_MAX_CLIENT) < 0) { cmn_PrtError("listen error"); } while (g_iExit == 0) { iClientSocket = accept(s_iServerSocket, (struct sockaddr *)&stClientaddr,&addrlen); if(iClientSocket < 0) { if (errno == EINTR) { if (g_iExit == 1) { break; } continue; } else { cmn_PrtError("accept error"); } } stClientArray[iCLientNumber].iServerSocket = iClientSocket; stClientArray[iCLientNumber].iClientNumber = iCLientNumber+1; if(ThreadPoolSubmit(TCPRcvTrd, &stClientArray[iCLientNumber],&iTaskID) < 0) { cmn_PrtError("Error in submitting task to thread pool"); } if(ThreadPoolSubmit(TCPSndTrd, &stClientArray[iCLientNumber],&iTaskID) < 0) { cmn_PrtError("Error in submitting task to thread pool"); } iCLientNumber++; if(iCLientNumber >= cmnDfn_WORKING_PTHREAD_NUMBER / 2) { cmn_PrtError("No available client"); } } iRet = 0; _Exit: return iRet; } volatile int g_iExit = 0; //volatile sig_atomic_t g_iExit = 0; static void ExitCallbkFun(int iSigNum) { g_iExit = 1; } int main(int argc,char *argv[]) { int iRet = -1; int iPacketSize; if (signal(SIGINT, ExitCallbkFun) == SIG_ERR) { cmn_PrtError("Error in setting signal handler"); } if (argc < 2|| argc > 3) { fprintf(stderr,"parameter error"); goto _Exit; } //判断网络协议 if (strcmp(argv[1],"tcp") == 0) { if (argc != 2) { fprintf(stderr,"tcp protocol not supported"); goto _Exit; } if (TCPSvr_Create() < 0) { fprintf(stderr,"TCPSvr_Create error"); goto _Exit; } // 运行TCP客户端 if(TCPSvr_Run() < 0) { fprintf(stderr,"TCPSvr_Run error"); goto _Exit; } } else if (strcmp(argv[1],"udp") == 0) { if (argc != 3) { fprintf(stderr,"udp protocol not supported"); goto _Exit; } // 判断数据包长 if ((iPacketSize = atoi(argv[2])) > MAX_PACKET_SIZE || (iPacketSize = atoi(argv[2])) < 0) { fprintf(stderr,"packet size error"); goto _Exit; } if (UDPSvr_Create() < 0) { fprintf(stderr,"UDPSvr_Create error"); goto _Exit; } // 运行UDP客户端 if(UDPSvr_Run(iPacketSize) < 0) { fprintf(stderr,"UDPSvr_Run error"); goto _Exit; } } iRet = 0; _Exit: if (strcmp(argv[1],"tcp") == 0) { if(TCPSvr_Destroy() < 0) { fprintf(stderr,"TCPSvr_Destroy error"); } } if (strcmp(argv[1],"udp") == 0) { if(UDPSvr_Destroy() < 0) { fprintf(stderr,"UDPSvr_Destroy error"); } } return iRet; }模拟程序运行,分析按下ctrl+c之后程序如何运行
03-22
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值