☰
学习嵌入式Day32:系统编程之信号与共享内存
2026/11/5 1:57:54 网站建设 项目流程

前面我学习了管道(无名管道、有名管道)这种进程通信方式。今天继续学习两种非常重要的 IPC 机制:信号(Signal)和System V 共享内存。 信号是异步通知机制,用来给进程发送事件通知;共享内存是所有 IPC 里速度最快的通信方式,适合大批量数据传输。


一、信号 Signal

1.信号基础概念

信号是 Linux 系统的一种异步通信方式,内核用来通知进程发生了某个事件。进程收到信号之后,可以选择:

  1. 默认处理(SIG_DFL)
  2. 忽略信号(SIG_IGN)
  3. 自定义捕获信号,执行我们自己写的回调函数

重点考点:SIGKILL(9)和SIGSTOP(19)无法被捕获、无法阻塞、无法忽略,只能执行系统默认行为!

2. 常见信号一览

信号名编号作用
SIGALRM14alarm 定时器产生的闹钟信号
SIGTERM15终止信号,kill默认发送这个,可以被捕获
SIGUSR110用户自定义信号 1
SIGUSR212用户自定义信号 2
SIGCHLD17子进程退出 / 停止,通知父进程(回收子进程核心信号)
SIGCONT18唤醒被暂停的进程
SIGSTOP19暂停进程,不可捕获
SIGTSTP20终端 Ctrl+Z,后台暂停
SIGINT2Ctrl+C 中断信号

3. 信号相关 API

1. kill () —— 给指定进程发送信号
int kill(pid_t pid, int sig);
  • 功能:向 pid 对应的进程发送编号为 sig 的信号
  • 参数:
    • pid:接收信号的进程 PID
    • sig:信号编号(kill -l可以查看所有信号)
  • 返回值:成功返回 0,失败返回 - 1
2. signal () —— 信号捕获、注册信号处理函数
sighandler_t signal(int signum, sighandler_t handler);

handler 支持三种写法:

  • SIG_DFL:使用系统默认处理方式
  • SIG_IGN:忽略这个信号
  • 自定义函数指针:收到信号后执行自定义回调函数

函数原型原始写法:void (*signal(int signum, void (*handler)(int)))(int);可读性差,所以系统封装了sighandler_t类型简化书写。

4. 信号简单示例(捕获 SIGINT)

#include <stdio.h> #include <sys/types.h> #include <signal.h> #include <stdlib.h> #include <unistd.h> void myhandle(int num) { printf("ctrl c 被按下"); } int main(int argc, char **argv) { signal(SIGINT, myhandle); while (1) { printf("i am working pid: %d\n", getpid()); sleep(1); } return 0; }

注意:这个程序按 Ctrl+C 不会直接退出,因为我们捕获了 SIGINT;但是如果发送kill -9 pid,程序会直接被杀掉,SIGKILL 不能捕获。


二、System V 共享内存(Share Memory)

1. 共享内存是什么?

System V 共享内存是 UNIX/Linux 提供的进程间通信方式,也是速度最快的 IPC。 原理:在内核开辟一块内存区域,多个进程把这块内存映射到自己的虚拟地址空间,多个进程直接读写同一块内存,不需要内核拷贝数据。

2. 整体流程

生成key ==> 申请共享内存对象 ==> 映射到本地地址 ==> 读写数据 ==> 撤销映射 ==> 删除共享内存对象

3. 和管道的对比

对比项管道共享内存
读写权限单向管道半双工双方都可以读写
阻塞特性自带读写阻塞没有读写阻塞,必须配合信号 / 信号量做同步
数据保留读走数据就没了不手动删除,数据会一直保留
空间大小受限自定义大小,适合大批量数据传输
同步机制自带同步无同步,需要额外 IPC 同步

重点:共享内存没有自带同步,一定要自己加同步保护(信号、信号量),否则会出现竞态条件!

4.全套 API 详解

1. ftok () —— 生成唯一 key
key_t ftok(const char *pathname, int proj_id);
  • 功能:根据文件路径 + 项目 id,生成唯一的 IPC 键值 key
  • 参数:
    • pathname:一个存在的文件路径(文件不要删除)
    • proj_id:自定义整数,一般用单个 ASCII 字符
  • 返回值:成功返回唯一 key,失败 - 1
2. shmget () —— 申请共享内存
int shmget(key_t key, size_t size, int shmflg);
  • 功能:用 key 向内核申请一块共享内存,返回 shmid
  • 参数:
    • key:ftok 生成的键值
    • size:共享内存大小,一般 4K 对齐
    • shmflg:权限标志,IPC_CREAT创建;IPC_CREAT|IPC_EXCL检测是否已经存在
  • 返回值:成功返回 shmid,失败 - 1
3. shmat () —— 映射共享内存到本地地址
void *shmat(int shmid, const void *shmaddr, int shmflg);
  • 功能:把内核的共享内存映射到当前进程的虚拟地址
  • 参数:
    • shmid:shmget 返回的 id
    • shmaddr:填 NULL,让系统自动分配地址
    • shmflg:0 = 读写;SHM_RDONLY只读
  • 返回值:成功返回映射后的地址,失败返回(void *)-1

映射之后,就可以像普通内存一样读写,memcpy、strcpy都可以用

4. shmdt () —— 撤销映射
int shmdt(const void *shmaddr);
  • 功能:断开当前进程和共享内存的映射关系
  • 参数:shmat 返回的映射地址
  • 返回值:成功 0,失败 - 1

⚠️ 撤销映射 ≠ 删除共享内存,只是当前进程不再访问这块内存!

5. shmctl () —— 控制共享内存(删除)
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • 功能:修改属性、删除共享内存
  • 参数:
    • shmid:共享内存 id
    • cmd = IPC_RMID:删除共享内存对象
    • buf:填 NULL,只做删除
  • 返回值:成功 0,失败 - 1

5. 配套命令行工具

#查看所有SystemV IPC:共享内存、消息队列、信号量 ipcs -a #删除指定共享内存 ipcrm -m shmid

6. 共享内存读写示例

写端(shm_write.c)

#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <signal.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main(int argc, char **argv) { //申请key值 key_t key = ftok("./", 100); if (-1 == key) { perror("ftok"); return 1; } printf("key is 0x%x\n", key); //申请内存 int shmid = shmget(key, 4096, IPC_CREAT | 0666); if (-1 == shmid) { perror("shmget"); return 1; } //内存映射到用户空间 void *p = shmat(shmid, NULL, !SHM_RDONLY); if ((void*)-1 == p) { perror("shmat"); return 1; } //内存写入 char buf[50] = "hello"; memcpy(p, buf, strlen(buf)); //解除映射 shmdt(p); return 0; }

读端(shm_read.c)

#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <signal.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main(int argc, char **argv) { //申请key值 key_t key = ftok("./", 100); if (-1 == key) { perror("ftok"); return 1; } printf("key is 0x%x\n", key); //申请内存 int shmid = shmget(key, 4096, IPC_CREAT | 0666); if (-1 == shmid) { perror("shmget"); return 1; } //内存映射到用户空间 void *p = shmat(shmid, NULL, !SHM_RDONLY); if ((void*)-1 == p) { perror("shmat"); return 1; } //内存读出 printf("mem:%s\n", (char*)p); //解除映射 shmdt(p); return 0; }

三、总结

  1. 信号:异步事件通知,SIGKILL、SIGSTOP不可捕获,kill发信号、signal注册捕获函数;
  2. 共享内存:最快 IPC,流程:ftok -> shmget -> shmat -> 读写 -> shmdt -> shmctl IPC_RMID;
  3. 共享内存没有同步机制,工程里一般搭配信号或者信号量,解决多进程同时读写的冲突;
  4. 命令行ipcs/ipcrm用来调试 SystemV 共享内存。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询