嵌入式解谜日志之Linux操作系统—共享内存
信号通信
概述
- 核心应用:实现进程间异步通信(如中断处理场景)
- 信号范围:1~64号信号,其中前32个具有明确含义,专用于应用编程
信号默认行为
行为 | 说明 |
---|---|
Term | 默认终止进程(如 SIGINT ) |
Ign | 默认忽略信号(如 SIGCHLD ) |
Core | 终止进程并生成core文件(可通过 gdb a.out -c core 调试,如 SIGSEGV ) |
Stop | 默认停止进程(如 SIGSTOP ) |
Cont | 默认继续进程(如 SIGCONT ) |
关键限制:9号(
SIGKILL
)和19号(SIGSTOP
)信号不可捕获或忽略,只能执行默认操作
信号发送函数
kill(pid, sig)
int kill(pid_t pid, int sig);
- 功能:向指定进程发送信号
- 参数:
pid
:目标进程ID(>0
指定进程,=0
同组进程,<-1
指定进程组)sig
:信号编号(通过kill -l
查看)
- 返回值:成功返回
0
,失败返回-1
- 示例:
kill -9 1000
向进程ID 1000发送9号信号
raise(sig)
int raise(int sig);
- 功能:向当前进程发送信号(等价于
kill(getpid(), sig)
) - 返回值:成功返回
0
,失败返回非零值
alarm(seconds)
unsigned int alarm(unsigned int seconds);
- 功能:设置定时器,超时后发送
SIGALRM
信号 - 特性:
- 闹钟只有一个,定时仅一次有效
- 新定时器会覆盖旧定时器
- 设为0可取消现有闹钟
pause()
int pause(void);
功能:挂起进程执行,直到收到信号
- 返回值:总是返回
-1
(被信号中断时)
signal(signum, handler)
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
- 功能:设置信号处理方式
- 参数:
signum
:信号编号handler
:处理方式(三选一):SIG_DFL
:使用默认处理SIG_IGN
:忽略信号- 自定义函数:信号处理函数指针
- 返回值:成功返回旧处理函数,失败返回
SIG_ERR
典型用法:
signal(SIGINT, SIG_IGN);
// 忽略Ctrl+C
signal(SIGALRM, handler);
// 注册自定义闹钟处理
共享内存(System V IPC)
System V IPC对象
对象类型 | 用途 | 核心函数 |
---|---|---|
共享内存(shm) | 高效进程间数据共享 | shmget , shmat , shmdt |
信号量集(sem) | 进程同步控制 | semget , semop , semctl |
消息队列(msg) | 结构化数据传递 | msgget , msgsnd , msgrcv |
IPC对象操作通用流程
生成唯一键值
IPC_PRIVATE
:固定私有键值(0x0
),仅用于有亲缘关系进程ftok()
:通过路径和项目ID生成临时键值
key_t ftok(const char *pathname, int proj_id);
- 参数:
pathname
:任意存在的文件路径(需确保不被删除重建)proj_id
:整型值(常用ASCII字符,如'!'
)
- 返回值:成功返回唯一键值,失败返回
-1
常用管理命令
命令 | 说明 |
---|---|
ipcs -a | 查询所有IPC对象状态 |
ipcrm -m shmid | 删除指定ID的共享内存 |
ipcrm -s semid | 删除指定ID的信号量集 |
ipcrm -q msgid | 删除指定ID的消息队列 |
共享内存特性
- 效率优势:无需数据拷贝,直接内存访问(最快IPC方式)
- 写入限制:写入大小不能超过共享内存空间
- 空数据处理:共享内存未初始化时读取会产生随机数据
- 无阻塞特性:与FIFO不同,共享内存操作无读写阻塞
- 同步需求:需配合信号量等机制实现进程同步
写端程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h> // IPC常量定义
#include <sys/shm.h> // 共享内存操作函数
#include <sys/types.h> // 系统数据类型
#include <unistd.h> // POSIX标准函数int main(int argc, char **argv)
{// 生成唯一键值:当前目录路径 + '!'标识符key_t key = ftok("./", '!');if (-1 == key){perror("ftok");return 1;}printf("key is 0x%x\n", key); // 打印生成的键值// 创建共享内存:指定key/4096字节/创建权限int shmid = shmget(key, 4096, IPC_CREAT | 0666);if (-1 == shmid){perror("shmid");return 1;}// 映射共享内存到进程地址空间(可读写)void *p = shmat(shmid, NULL, !SHM_RDONLY);if ((void *)-1 == p){perror("shmat");return 1;}// 准备测试数据并写入共享内存char buf[] = "hello,this is shm test";memcpy(p, buf, strlen(buf)); // 复制有效字符串内容// 撤销地址映射shmdt(p);return 0;
}
理想运行结果:
key is 0x210217b3 // 键值因系统环境而异
执行说明:成功写入数据后立即退出,无输出错误信息
读端:
#include <stdio.h> // 标准输入输出函数(printf等)
#include <stdlib.h> // 标准库函数(exit等)
#include <string.h> // 字符串操作函数(memcpy等)
#include <sys/ipc.h> // IPC(进程间通信)相关定义(key_t等)
#include <sys/shm.h> // 共享内存相关函数(shmget、shmat等)
#include <sys/types.h> // 基本系统数据类型(pid_t等)
#include <unistd.h> // 系统调用函数(fork、sleep等)int main(int argc, char **argv)
{// 1. 生成共享内存的唯一标识键值(key)// ftok函数:根据路径和项目ID生成key,相同参数生成相同key// 参数1:存在的文件路径(用于确保唯一性)// 参数2:项目ID(0-255之间的字符,用于区分同一目录下的不同IPC对象)key_t key = ftok("./", '!');if (-1 == key) // 生成key失败{perror("ftok"); // 打印错误信息(如文件不存在)return 1; // 非0退出表示程序异常}printf("key is 0x%x\n", key); // 打印生成的16进制key值// 2. 创建或获取共享内存段// shmget函数:创建新的共享内存或获取已存在的共享内存// 参数1:key值(标识共享内存)// 参数2:共享内存大小(字节),4096是常见的页大小倍数// 参数3:标志位,IPC_CREAT表示不存在则创建;0666是权限(rwxrwxrwx)int shmid = shmget(key, 4096, IPC_CREAT | 0666);if (-1 == shmid) // 创建/获取共享内存失败{perror("shmget"); // 打印错误信息(如权限不足、内存已满)return 1;}// 3. 将共享内存附加到当前进程的地址空间// shmat函数:将共享内存映射到进程的虚拟地址空间,返回映射后的指针// 参数1:共享内存标识符(shmid)// 参数2:指定映射地址(NULL表示由系统自动分配)// 参数3:访问权限,!SHM_RDONLY表示可读可写(SHM_RDONLY为只读)void *p = shmat(shmid, NULL, !SHM_RDONLY);if ((void *)-1 == p) // 附加共享内存失败{perror("shmat"); // 打印错误信息(如权限不足)return 1;}// 4. 读取共享内存中的数据char buf[4096] = {0}; // 本地缓冲区,用于存储从共享内存读取的数据// memcpy:内存复制函数,将共享内存的数据复制到本地缓冲区// 参数1:目标地址(本地buf)// 参数2:源地址(共享内存指针p)// 参数3:复制的字节数(buf的大小,确保不越界)memcpy(buf, p, sizeof(buf));printf("从共享内存读取到的数据:%s\n", buf); // 打印读取到的数据// 5. 解除共享内存与当前进程的关联// shmdt函数:分离共享内存,仅断开映射关系,不删除共享内存本身shmdt(p);// 6. (可选)删除共享内存段// 注释说明:取消注释后,程序退出时会彻底删除共享内存// 注意:若有其他进程正在使用该共享内存,删除后可能导致错误// shmctl(shmid, IPC_RMID, NULL);return 0; // 0退出表示程序正常结束
}
理想运行结果(需先运行写端):
key is 0x210217b3
buf hello,this is shm test
读阻塞模拟方案
核心机制
- 读端:初始化后存储PID → 调用
pause()
挂起 → 等待SIGCONT
唤醒 - 写端:获取读端PID → 写入数据 → 发送
SIGCONT
唤醒读端 - 阻塞验证:通过信号控制实现数据就绪前的强制阻塞
读端代码(带阻塞控制)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h> // IPC常量
#include <sys/shm.h> // 共享内存操作
#include <sys/types.h> // 系统类型
#include <unistd.h> // POSIX函数
#include <signal.h> // 信号处理// SIGCONT信号处理函数(空实现)
void myhandle(int num)
{// 仅用于覆盖默认行为,确保pause()可被唤醒
}int main(int argc, char **argv)
{// 注册SIGCONT信号处理signal(SIGCONT, myhandle);// 生成唯一键值(与写端一致)key_t key = ftok("./", '!');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("shmid");return 1;}// 附加共享内存(可读写)void *p = shmat(shmid, NULL, !SHM_RDONLY);if ((void *)-1 == p){perror("shmat");return 1;}// 存储自身PID到共享内存首部pid_t pid = getpid();*(int*)p = pid;printf("recv, pid:%d\n", pid); // 显示读端PID// 核心阻塞点:等待SIGCONT信号pause();// 信号唤醒后读取数据char buf[4096] = {0};memcpy(buf, p, sizeof(buf));printf("buf %s\n", buf); // 显示接收内容// 撤销映射shmdt(p);return 0;
}
写端代码(带唤醒功能)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>int main(int argc, char **argv)
{// 生成相同键值key_t key = ftok("./", '!');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("shmid");return 1;}// 附加共享内存void *p = shmat(shmid, NULL, !SHM_RDONLY);if ((void *)-1 == p){perror("shmat");return 1;}// 读取读端PID(共享内存首部)pid_t pid = 0;memcpy(&pid, p, sizeof(pid));printf("kill, recv pid:%d\n", pid);// 准备测试消息并写入char buf[] = "hello,this is shm test";memcpy(p, buf, strlen(buf));// 撤销映射shmdt(p);// 发送SIGCONT唤醒读端kill(pid, SIGCONT);return 0;
}
执行流程与理想结果
执行顺序
先启动读端:
- 生成共享内存键值
- 存储自身PID
- 主动挂起(
pause()
阻塞)
再启动写端:
- 获取共享内存
- 读取读端PID
- 写入测试消息
- 发送唤醒信号
读端被唤醒:
- 继续执行
- 读取共享数据
- 正常退出
终端输出
读端终端(先运行):
key is 0x210217b3
recv, pid:12345
buf hello,this is shm test
关键行为:输出 recv, pid:12345
后进程挂起,收到信号后继续执行
写端终端(后运行):
key is 0x210217b3
kill, recv pid:12345
关键行为:成功获取PID并发送唤醒信号后立即退出
验证要点
✅ 阻塞效果:读端在 pause() 处明确阻塞
✅ 信号同步:SIGCONT 确保读端仅在数据就绪后执行
✅ 数据一致性:消息完整传递(写端未写入 \0 但缓冲区初始化为零)
⚠️ 典型错误:
先运行写端 → 读取无效PID(0)导致 kill(0, SIGCONT) 失败
缺少信号处理函数 → pause() 永久挂起