Linux学习-通信(信号,共享内存)

信号(Signal)

(一)本质与核心价值

信号是 Linux 系统中进程间异步通信的"软中断"机制,核心解决:

  • 进程控制 :如终止、暂停、恢复进程(SIGINT/SIGSTOP/SIGCONT)。
  • 异常处理 :捕获非法操作(SIGSEGV 段错误、SIGPIPE 管道破裂)。
  • 异步通知 :子进程结束通知父进程(SIGCHLD)、定时任务触发(SIGALRM)等。

特点:异步性 (接收方无法预测信号何时到达)、抢占性(打断当前代码流优先处理信号)。

(二)系统信号全量清单(kill -l 输出)

通过 kill -l 可查看系统支持的 64 个信号(不同系统可能有差异),按功能分类关键信号:

1. 常用基础信号(1~31 为非实时信号,32~64 为实时信号 SIGRTMIN~SIGRTMAX
信号编号 信号名 触发场景/默认行为 关键特性
1 SIGHUP 终端断开、进程组领导权变化 常用于"重启配置"(如 nginx 重载)
2 SIGINT Ctrl + c 触发 中断进程,默认终止
3 SIGQUIT Ctrl + \ 触发 终止进程并生成 core dump
4 SIGILL 非法指令执行(如二进制文件格式错误) 进程崩溃,默认终止
5 SIGTRAP 调试陷阱(如 gdb 断点触发) 调试专用,默认终止
6 SIGABRT 进程主动调用 abort() 强制终止并生成 core dump
7 SIGBUS 总线错误(如内存对齐违规) 进程崩溃,默认终止
8 SIGFPE 浮点运算错误(除零、溢出等) 进程崩溃,默认终止
9 SIGKILL kill -9 强制发送 强制终止,无法被捕获/忽略
10 SIGUSR1 用户自定义信号 需编程捕获,实现自定义逻辑
11 SIGSEGV 非法内存访问(越界、空指针) 进程崩溃(段错误),默认终止
12 SIGUSR2 用户自定义信号 需编程捕获,实现自定义逻辑
13 SIGPIPE 管道写端关闭后仍写操作 进程崩溃(管道破裂),默认终止
14 SIGALRM alarm() 定时触发或系统定时任务 定时通知,默认终止
15 SIGTERM kill 默认信号(kill -15 优雅终止进程,可捕获处理
16 SIGSTKFLT 栈溢出(老式架构,现代系统少用) 进程崩溃,默认终止
17 SIGCHLD 子进程退出、暂停或继续 通知父进程回收子进程
18 SIGCONT 显式发送或作业控制恢复 恢复暂停态进程执行
19 SIGSTOP 强制暂停(kill -19 暂停进程,无法被捕获/忽略
20 SIGTSTP Ctrl + z 触发 暂停进程(终端停止信号)
21 SIGTTIN 后台进程读终端 暂停进程,默认终止
22 SIGTTOU 后台进程写终端 暂停进程,默认终止
23 SIGURG 套接字紧急数据到达 通知进程处理紧急数据
24 SIGXCPU 进程 CPU 时间超过限制 进程崩溃,默认终止
25 SIGXFSZ 文件大小超过系统限制 进程崩溃,默认终止
26 SIGVTALRM 虚拟时钟定时(按进程 CPU 时间计时) 定时通知,默认终止
27 SIGPROF 剖析时钟定时(含内核时间) 性能分析工具常用
28 SIGWINCH 终端窗口大小变化 通知进程调整输出格式
29 SIGIO 异步 I/O 事件通知 高性能 I/O 模型(如 epoll
30 SIGPWR 电源状态变化(如电池低电量、断电) 通知进程处理电源事件
31 SIGSYS 系统调用参数错误(如调用未实现的系统调用) 进程崩溃,默认终止
2. 实时信号(SIGRTMIN ~ SIGRTMAX
  • 编号:32~64(具体数量因系统而异,如 SIGRTMIN 可能是 34,需 kill -l 确认)。
  • 特点:高优先级 (比普通信号先处理)、可排队 (避免信号丢失)、用户可控(常用于实时系统、低延迟通知场景)。

(三)信号处理流程与编程

1. 信号处理方式

进程对信号有三种响应策略:

  • 缺省(SIG_DFL :按系统默认行为处理(如 SIGINT 默认终止进程)。
  • 忽略(SIG_IGN :不处理信号(如忽略 SIGCHLD 需自己回收子进程)。
  • 捕获(自定义函数):注册回调函数,信号触发时执行自定义逻辑。
2. 核心函数:signal
  • 头文件:#include <signal.h>

  • 原型:

    c 复制代码
    typedef void (*sighandler_t)(int);  
    sighandler_t signal(int signum, sighandler_t handler);  
  • 作用:注册信号处理函数(绑定信号 signum 与处理逻辑 handler)。

  • 参数:

    • signum:信号编号(如 SIGINT)。
    • handler:处理方式(SIG_IGN/SIG_DFL 或自定义函数地址)。
  • 返回值:

    • 成功:返回之前的信号处理函数(首次注册返回 SIG_DFL)。
    • 失败:返回 SIG_ERR(需检查 errno)。

示例 :捕获 SIGINT 并自定义处理:

c 复制代码
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void handle_sigint(int signum) {
    printf("捕获到信号:%d(SIGINT),进程继续运行...\n", signum);
}

int main() {
    // 注册信号处理函数
    if (signal(SIGINT, handle_sigint) == SIG_ERR) {
        perror("signal");
        return 1;
    }
    printf("进程启动,按 Ctrl + c 测试信号捕获...\n");
    while (1) {
        sleep(1); // 模拟业务逻辑
    }
    return 0;
}

(四)信号发送与控制

1. 命令行发送信号:kill 命令
  • 语法:kill -[信号编号/名称] 进程PID
    • 示例:
      • kill -9 1234:强制终止 PID 为 1234 的进程(发送 SIGKILL)。
      • kill -SIGUSR1 5678:给 PID 5678 发送 SIGUSR1 信号。
2. 编程发送信号函数
函数名 作用 关键参数 示例(简化)
kill 给指定进程发信号 pid_t pid, int sig kill(1234, SIGINT);
raise 给自己所在进程发信号 int sig raise(SIGALRM);
alarm 定时发送 SIGALRM 信号 unsigned int seconds alarm(5);(5 秒后发 SIGALRM
pause 挂起进程,等待信号唤醒 - pause();(被信号打断后继续)

共享内存(Shared Memory)

(一)核心价值

进程间通信(IPC)效率最高的方式,原理:

  1. 内核开辟一块共享内存区域,多个进程通过内存映射将其映射到自己的虚拟地址空间。
  2. 进程直接读写内存地址完成数据交换,跳过"内核-用户空间"的拷贝(如管道、消息队列需两次拷贝)。

适合场景:大数据量、低延迟的进程协作(如多进程并行计算、游戏服务器进程间同步)。

(二)操作流程与函数(6 步完整周期)

1. 创建 IPC Key:ftok
  • 头文件:#include <sys/types.h>#include <sys/ipc.h>
  • 原型:key_t ftok(const char *pathname, int proj_id);
  • 作用:生成唯一的 IPC 键值 ,用于标识共享内存(不同进程需用相同 pathnameproj_id 生成相同 key)。
  • 参数:
    • pathname:存在的文件路径(如 "/tmp"),建议用固定文件避免冲突。
    • proj_id:项目 ID(1~255 的整数,如 'A' 等价于 65)。
  • 返回值:
    • 成功:返回 key_t 类型的键值。
    • 失败:返回 -1(检查 errno,如文件不存在、权限不足)。
2. 创建共享内存:shmget
  • 头文件:#include <sys/types.h>#include <sys/shm.h>
  • 原型:int shmget(key_t key, size_t size, int shmflg);
  • 作用:向内核申请共享内存,返回 共享内存 ID(后续操作的标识)。
  • 参数:
    • keyftok 生成的键值。
    • size:共享内存大小(字节),自动对齐为系统页大小 (如 4096 字节的整数倍)。
    • shmflg:权限标志(如 IPC_CREAT | 0664IPC_CREAT 表示不存在则创建,0664 是权限)。
  • 返回值:
    • 成功:返回共享内存 ID(非负整数)。
    • 失败:返回 -1(检查 errno,如权限不足、内存不足)。
3. 映射共享内存到用户空间:shmat
  • 头文件:#include <sys/types.h>#include <sys/shm.h>
  • 原型:void *shmat(int shmid, const void *shmaddr, int shmflg);
  • 作用:将内核共享内存映射到进程的虚拟地址空间,返回可直接读写的内存指针
  • 参数:
    • shmidshmget 返回的共享内存 ID。
    • shmaddr:期望的映射地址(填 NULL 让系统自动分配)。
    • shmflg:权限(SHM_RDONLY 只读;0 读写,需配合内存权限)。
  • 返回值:
    • 成功:返回映射后的内存首地址(void * 类型)。
    • 失败:返回 (void *)-1(检查 errno,如权限不足)。
4. 读写共享内存
  • 直接通过 shmat 返回的指针操作内存,如:

    c 复制代码
    char *shm_ptr = shmat(shmid, NULL, 0);  
    if (shm_ptr != (void *)-1) {  
        // 写数据  
        sprintf(shm_ptr, "Hello, Shared Memory!");  
        // 读数据  
        printf("共享内存内容:%s\n", shm_ptr);  
    }  
5. 解除映射:shmdt
  • 头文件:#include <sys/types.h>#include <sys/shm.h>
  • 原型:int shmdt(const void *shmaddr);
  • 作用:断开共享内存与进程虚拟地址空间的映射(不销毁共享内存,仅解除当前进程的关联)。
  • 参数:shmaddrshmat 返回的内存首地址。
  • 返回值:
    • 成功:返回 0
    • 失败:返回 -1(检查 errno,如地址无效)。
6. 销毁共享内存:shmctl
  • 头文件:#include <sys/types.h>#include <sys/shm.h>
  • 原型:int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • 作用:控制共享内存(如销毁、查询状态)。
  • 参数:
    • shmid:共享内存 ID。
    • cmd:操作指令(IPC_RMID 表示标记销毁,需所有进程解除映射后实际释放)。
    • buf:填 NULL(销毁时无需额外参数)。
  • 返回值:
    • 成功:返回 0
    • 失败:返回 -1(检查 errno,如权限不足)。

(三)命令行工具

  • 查看 IPC 资源ipcs -a(查看共享内存、信号量、消息队列等)。
  • 删除共享内存
    • ipcrm -m shmid:通过 ID 删除(如 ipcrm -m 1234)。
    • ipcrm -M shmkey:通过 ftok 生成的 key 删除(如 ipcrm -M 0x12345678)。