2026/4/6 15:23:32
网站建设
项目流程
阿里巴巴怎么做不花钱的网站,福州网站设计培训,2017年网站外链怎么做,中国vs菲律宾世预赛Linux IPC进阶#xff1a;信号与System V共享内存
一、信号#xff1a;进程间的异步通知机制
信号是Linux内核向进程发送的“事件通知”#xff0c;用于处理异常、同步或异步交互#xff08;如进程终止、定时提醒#xff09;。信号的特点是“异步性”——进程无需主动等待…Linux IPC进阶信号与System V共享内存一、信号进程间的异步通知机制信号是Linux内核向进程发送的“事件通知”用于处理异常、同步或异步交互如进程终止、定时提醒。信号的特点是“异步性”——进程无需主动等待内核会在合适时机中断进程当前操作执行信号处理逻辑。1.1 信号核心基础1.1.1 信号的核心概念每个信号对应一个唯一编号可通过kill -l查看所有信号共64种其中1-31为普通信号34-64为实时信号信号的三种处理方式SIG_DFL默认处理如SIGTERM终止进程、SIGINT中断进程SIG_IGN忽略处理进程不响应该信号自定义处理通过signal()函数注册自定义回调函数。常用关键信号SIGINT2键盘中断CtrlCSIGTERM15默认终止信号kill命令默认发送SIGALRM14闹钟超时信号alarm()函数触发SIGCHLD17子进程终止/暂停时内核向父进程发送的信号SIGUSR110/SIGUSR212用户自定义信号可自由分配用途。1.1.2 核心信号函数函数原型功能关键参数说明返回值int kill(pid_t pid, int sig);向指定PID的进程发送信号sigpid目标进程PID0表示同组进程-1表示所有进程sig信号编号成功0失败-1sighandler_t signal(int signum, sighandler_t handler);注册信号处理函数修改信号的响应方式signum信号编号handlerSIG_DFL/SIG_IGN/自定义函数指针成功返回旧处理函数失败返回SIG_ERRunsigned int alarm(unsigned int seconds);设置闹钟seconds秒后内核发送SIGALRM信号seconds超时时间秒0表示取消之前的闹钟返回剩余秒数若之前有闹钟否则0int pause(void);使进程阻塞直到收到一个可捕获的信号无参数被信号唤醒后返回-1errno设为EINTR1.2 信号实战代码解析以下结合10个实战代码覆盖“信号发送、定时、阻塞、自定义处理”等核心场景每个示例含功能说明、编译运行步骤及关键逻辑解析。示例1信号测试基础11signaltest.c功能循环打印当前进程PID用于测试信号接收配合kill命令发送信号。代码#includestdio.h#includeunistd.h#includestdlib.hintmain(){while(1){printf(pid:%d\n,getpid());sleep(1);}return0;}编译运行gcc 11signaltest.c -o signaltest ./signaltest测试方式打开新终端执行kill -2 进程PID发送SIGINT信号程序会中断退出。核心解析通过getpid()获取当前进程PID循环打印便于定位目标进程是信号测试的基础模板。示例2信号发送工具12kill.c功能通过命令行参数指定目标进程PID和信号编号发送信号模拟kill命令的核心功能。代码#includestdio.h#includeunistd.h#includestdlib.h#includesignal.h#includesys/types.hintmain(intargc,char*argv[]){if(argc3){fprintf(stderr,usage: %s pid signal_number\n,argv[0]);exit(1);}pid_tpid(pid_t)atoi(argv[1]);intsigatoi(argv[2]);intretkill(pid,sig);if(ret-1){perror(kill error);exit(1);}return0;}编译运行gcc 12kill.c -o mykill# 向PID为1234的进程发送SIGINT2号信号./mykill12342核心解析命令行参数校验需传入2个参数PID和信号编号否则提示用法atoi()将字符串参数转为整数PID和信号编号kill()核心函数向目标PID进程发送指定信号失败通过perror()打印错误原因如PID不存在、无权限。示例3闹钟信号13alarm.c功能设置5秒闹钟超时后内核发送SIGALRM信号触发默认处理终止进程。代码#includestdio.h#includeunistd.h#includestdlib.hintmain(intargc,char*argv[]){alarm(5);// 5秒后发送SIGALRM14号信号while(1){printf(im processing...\n);sleep(1);}return0;}编译运行gcc 13alarm.c -o alarmtest ./alarmtest现象程序循环打印5秒后自动退出SIGALRM默认处理是终止进程。示例4进程阻塞14pause.c功能演示pause()函数的阻塞特性进程运行5秒后阻塞直到收到可捕获的信号才继续。代码#includestdio.h#includeunistd.hintmain(){inti0;while(1){printf(i am listen music... \n);sleep(1);i;if(i5){pause();// 阻塞等待信号}}return0;}编译运行gcc 14pause.c -o pausetest ./pausetest测试方式程序打印5次后阻塞打开新终端执行kill -10 进程PID发送SIGUSR1信号进程会继续打印。核心解析pause()使进程进入“可中断睡眠”状态直到收到一个“可捕获”的信号忽略的信号无法唤醒被唤醒后返回-1进程继续执行后续逻辑。示例5自定义闹钟处理15signal_alarm.c功能通过signal()注册自定义函数处理SIGALRM信号避免进程被终止实现“超时后切换状态”。代码#includestdio.h#includeunistd.h#includestdlib.h#includesignal.hintflag0;voidmyhandle(intnum)// 自定义信号处理函数{flag1;// 收到信号后修改标志位}intmain(intargc,char*argv[]){signal(SIGALRM,myhandle);// 注册SIGALRM的处理函数为myhandlealarm(5);// 5秒后发送SIGALRMwhile(1){if(0flag){printf(im processing...\n);}else{printf(im off duty....\n);// 超时后切换为该状态}sleep(1);}return0;}核心解析自定义处理函数myhandle()参数为信号编号功能是修改全局标志位flagsignal(SIGALRM, myhandle)将SIGALRM信号的处理方式改为自定义函数逻辑前5秒flag0打印“processing”5秒后收到SIGALRMflag1切换为“off duty”进程不终止。示例6信号唤醒阻塞16sign_con.c功能演示pause()阻塞后通过自定义信号处理函数唤醒且不终止进程。代码#includestdio.h#includeunistd.h#includesignal.hvoidmyhandle(intnum)// 空处理函数仅唤醒进程{}intmain(intargc,char*argv[]){signal(SIGCONT,myhandle);// 注册SIGCONT信号的处理函数inti0;while(1){printf(im listen music...,pid:%d\n,getpid());sleep(1);i;if(3i){pause();// 第3次后阻塞}}return0;}测试方式程序打印3次后阻塞新终端执行kill -18 进程PID发送SIGCONT信号进程继续打印。核心解析自定义处理函数可以是空实现核心作用是“捕获信号并唤醒pause()”避免进程被信号的默认处理终止。示例7用户自定义信号处理17signal_user.c功能处理用户自定义信号SIGUSR1和SIGUSR2实现“接收指定次数后切换处理方式”忽略/默认。代码修正拼写错误后#includestdio.h#includeunistd.h#includestdlib.h#includesignal.h#includestring.hvoidsigusr1_handler(intsigno){staticintcount0;count;printf(Received SIGUSR1 %d times\n,count);if(3count){signal(SIGUSR1,SIG_IGN);// 接收3次后忽略SIGUSR1}return;}voidsigusr2_handler(intsigno){staticintcount0;count;printf(Received SIGUSR2 %d times\n,count);if(4count){signal(SIGUSR2,SIG_DFL);// 接收4次后恢复默认处理}return;}intmain(){if(signal(SIGUSR1,sigusr1_handler)SIG_ERR){perror(signal SIGUSR1 error);exit(1);}if(signal(SIGUSR2,sigusr2_handler)SIG_ERR){perror(signal SIGUSR2 error);exit(1);}while(1){printf(playing pid:%d\n,getpid());sleep(1);}return0;}核心解析修正代码错误原代码中sginal是拼写错误应改为signalSIGUSR1接收3次后通过signal(SIGUSR1, SIG_IGN)设置为忽略后续再发送SIGUSR1无响应SIGUSR2接收4次后通过signal(SIGUSR2, SIG_DFL)恢复默认处理SIGUSR2默认处理是终止进程。示例8处理SIGCHLD避免僵尸进程18signal_child.c功能父进程注册SIGCHLD信号处理函数在子进程终止时自动回收资源避免僵尸进程。代码#includestdio.h#includeunistd.h#includestdlib.h#includesignal.h#includestring.h#includesys/types.h#includesys/wait.hvoidhandler(intsig){pid_trecyclewait(NULL);// 回收子进程资源printf(pid:%d,Child %d terminated\n,getpid(),recycle);}intmain(){signal(SIGCHLD,handler);// 注册SIGCHLD处理函数pid_tpidfork();if(pid0){inti10;while(i--){printf(I am parent pid:%d\n,getpid());sleep(1);}}elseif(pid0){intj3;while(j--){printf(I am child pid:%d\n,getpid());sleep(1);}exit(0);// 子进程3秒后终止}else{perror(fork error);exit(1);}return0;}核心解析僵尸进程成因子进程终止后父进程未回收其资源PCB解决方案子进程终止时内核向父进程发送SIGCHLD信号父进程在处理函数中调用wait()回收资源现象子进程3秒后终止父进程立即打印“Child X terminated”无僵尸进程残留。示例9单个处理函数处理多个信号19signal_handlenum.c功能用一个自定义处理函数处理SIGUSR1和SIGUSR2通过信号编号区分不同信号执行不同逻辑。代码修正逻辑错误后#includestdio.h#includeunistd.h#includestdlib.h#includesignal.h#includestring.hvoidsig_handler(intsigno){if(SIGUSR1signo){staticintcount0;printf(father help\n);count;if(count3){signal(SIGUSR1,SIG_IGN);// 3次后忽略SIGUSR1}}elseif(SIGUSR2signo)// 原代码逻辑错误需移出SIGUSR1判断{staticintcount0;printf(mother help\n);count;if(count4){signal(SIGUSR2,SIG_DFL);// 4次后恢复默认}}return;}intmain(){signal(SIGUSR1,sig_handler);signal(SIGUSR2,sig_handler);while(1){printf(playing pid:%d\n,getpid());sleep(1);}return0;}核心解析原代码将SIGUSR2的逻辑写在SIGUSR1的判断内部导致无法响应SIGUSR2。修正后通过if-else区分信号编号实现“一个函数处理多个信号”的需求。二、System V共享内存高效的进程间数据共享System V共享内存是由Unix System V标准定义的IPC机制核心优势是高效——多个进程直接映射同一块内核内存区域到自己的地址空间无需数据拷贝管道、消息队列需内核/用户空间拷贝。但共享内存本身无同步机制需搭配信号、信号量等使用避免数据竞争。2.1 共享内存核心基础2.1.1 与管道的核心区别特性管道匿名/有名System V共享内存读写方式半双工需顺序读写全双工任意进程可读写阻塞特性读空/写满会阻塞无阻塞需手动同步数据拷贝用户→内核→用户2次拷贝无拷贝直接操作共享内存数据持久化随进程退出/管道关闭销毁随内核生命周期需手动删除数据结构内核队列FIFO连续内存区域类似字符数组2.1.2 共享内存编程步骤共享内存的使用遵循固定流程核心是“申请→映射→读写→撤销→删除”生成唯一键值key通过ftok()函数由文件路径和项目ID生成申请共享内存通过shmget()函数向内核申请指定大小的共享内存内存映射通过shmat()函数将内核共享内存映射到当前进程的用户空间读写操作直接操作映射后的内存地址如memcpy()、strcpy()撤销映射通过shmdt()函数断开进程与共享内存的映射关系删除共享内存通过shmctl()函数删除内核中的共享内存对象避免残留。2.1.3 核心函数解析函数原型功能关键参数说明返回值key_t ftok(const char *pathname, int proj_id);生成唯一的IPC键值pathname任意存在的文件路径proj_id1-255的整数通常用ASCII单字符成功返回key失败-1int shmget(key_t key, size_t size, int shmflg);申请/获取共享内存keyftok生成的键值size申请大小字节shmflg权限8进制 标志IPC_CREAT创建IPC_EXCL检测存在成功返回共享内存IDshmid失败-1void *shmat(int shmid, const void *shmaddr, int shmflg);映射共享内存到用户空间shmid共享内存IDshmaddr映射地址NULL表示内核自动分配shmflg0读写/SHM_RDONLY只读成功返回映射地址失败(void*)-1int shmdt(const void *shmaddr);撤销共享内存映射shmaddrshmat返回的映射地址成功0失败-1int shmctl(int shmid, int cmd, struct shmid_ds *buf);控制共享内存删除/查询属性shmid共享内存IDcmdIPC_RMID删除bufNULL仅删除无需查询属性成功0失败-12.1.4 常用管理命令ipcs -a# 查看所有System V IPC对象共享内存、信号量、消息队列ipcs -m# 仅查看共享内存ipcrm -m shmid# 删除指定shmid的共享内存强制清理残留2.2 共享内存实战代码解析以下两个示例实现“共享内存写进程”和“共享内存读进程”完成进程间字符串传递。示例1共享内存写进程20shm_w.c功能生成key→申请共享内存→映射→写入字符串“hello”→撤销映射。代码#includesys/types.h#includesys/ipc.h#includestdio.h#includestdlib.h#includeunistd.h#includestring.h#includesys/shm.hintmain(intargc,char*argv[]){// 1. 生成key路径./项目ID为!key_tkeyftok(./,!);if(-1key){perror(ftok);return1;}printf(key: 0x%x\n,key);// 2. 申请4096字节的共享内存权限0666不存在则创建intshmidshmget(key,4096,IPC_CREAT|0666);if(-1shmid){perror(shmget);return1;}printf(shmid is %d\n,shmid);// 3. 映射共享内存内核自动分配地址读写权限void*pshmat(shmid,NULL,0);// 原代码!SHM_RDONLY等价于0读写if((void*)-1p){perror(shmat);return1;}// 4. 写入数据memcpy适用于任意二进制数据strcpy适用于字符串charbuf[1024]hello;memcpy(p,buf,strlen(buf)1);// 1 包含字符串结束符\0// 5. 撤销映射shmdt(p);return0;}示例2共享内存读进程20shm_r.c功能生成相同key→获取已存在的共享内存→映射→读取数据→撤销映射可选删除。代码#includesys/types.h#includesys/ipc.h#includestdio.h#includestdlib.h#includeunistd.h#includestring.h#includesys/shm.hintmain(intargc,char*argv[]){// 1. 生成与写进程相同的key路径和项目ID必须一致key_tkeyftok(./,!);if(-1key){perror(ftok);return1;}printf(key: 0x%x\n,key);// 2. 获取已存在的共享内存大小和权限需与写进程一致intshmidshmget(key,4096,IPC_CREAT|0666);if(-1shmid){perror(shmget);return1;}printf(shmid is %d\n,shmid);// 3. 映射共享内存读写权限void*pshmat(shmid,NULL,0);if((void*)-1p){perror(shmat);return1;}// 4. 读取数据直接访问映射地址printf(mem:%s\n,(char*)p);// 5. 撤销映射shmdt(p);// 6. 可选删除共享内存通常由最后一个进程执行// shmctl(shmid,IPC_RMID,NULL);return0;}编译运行步骤# 编译写进程gcc 20shm_w.c -o shm_w# 编译读进程gcc 20shm_r.c -o shm_r# 终端1运行写进程生成共享内存并写入数据./shm_w# 输出key: 0x2128019b示例值shmid is 12345示例值# 终端2运行读进程读取共享内存数据./shm_r# 输出key: 0x2128019bshmid is 12345mem:hello# 可选清理共享内存ipcrm -m12345替换为实际shmid核心注意事项key一致性读/写进程的ftok()参数路径和proj_id必须完全一致否则生成的key不同无法访问同一共享内存共享内存残留共享内存随内核生命周期存在进程退出后不会自动删除需通过shmctl(shmid, IPC_RMID, NULL)或ipcrm命令手动删除同步问题当前示例未加同步机制若写进程未写完读进程读取会得到不完整数据。实际使用需搭配信号如写完成后发送信号通知读进程或信号量映射权限shmat()的shmflg设为SHM_RDONLY时进程仅能读取共享内存写入会触发段错误。三、总结信号与共享内存的协同应用本文解析的两类IPC机制各有侧重实际开发中常协同使用信号负责“异步通知”如进程间状态同步、异常处理适合传递简单控制信息不适合传递大量数据共享内存负责“高效数据共享”适合传递大量数据但无同步机制需信号/信号量辅助避免数据竞争。核心学习要点信号掌握kill()发送信号、signal()注册自定义处理函数理解SIGALRM、SIGCHLD等常用信号的应用场景共享内存牢记“key→shmget→shmat→读写→shmdt→shmctl”的固定流程注意key一致性和资源清理实操避坑编译信号代码无需额外链接库共享内存代码需包含完整头文件测试时注意进程PID和共享内存ID的正确性避免操作错误对象。