系统编程(四)进程间通信(IPC: Inter-Process Communication)

0.1 什么是进程间通信

  • 定义:进程间通信(IPC)是指不同进程之间交换信息或协作的机制。

  • 为什么需要

    • 每个进程运行在独立的内存空间,默认无法直接访问其他进程的数据。

    • 某些任务需要多个进程协作才能完成:

      • 例如:客户端请求 → 服务器处理 → 响应客户端。

  • IPC 的作用

    1. 数据传递:多个进程共享信息。

    2. 事件通知:告知另一个进程某事件发生。

    3. 同步控制:确保进程按特定顺序执行。

    4. 资源协调:避免多个进程同时访问同一资源导致冲突。

类比生活场景

  • 邮件系统:A写信 → B收到 → B处理 → 回信。

  • 手机提醒:程序A完成任务 → 发送通知给程序B。


0.2 进程间通信方式

1. 信号(Signal)

  • 定义:轻量级事件通知,只传递事件标志,不传递数据。

  • 特点

    • 异步触发,不需要目标进程主动接收。

    • 适合“事件通知”型任务。

    • 默认动作可为终止、忽略、暂停或生成 core 文件。

  • 应用示例

    • Ctrl+C → SIGINT

    • 定时器到期 → SIGALRM

2. 管道 / 有名管道(Pipe / FIFO)

  • 定义:通过字节流在进程间传递数据。

  • 特点

    • 父子进程间可直接使用匿名管道。

    • 不同进程间需要用有名管道

    • 数据按写入顺序读取。

  • 应用示例

    • 父进程产生数据 → 子进程处理数据。

3. 消息队列(Message Queue)

  • 定义:内核维护的消息队列,多个进程可通过队列发送/接收消息。

  • 特点

    • 可以带优先级

    • 队列存储在内核空间,进程不直接共享内存。

  • 应用示例

    • 多个客户端向服务器发送任务消息,服务器按优先级处理。

4. 共享内存(Shared Memory)

  • 定义:多个进程直接映射同一块内存区域。

  • 特点

    • 数据传递高效,适合大数据量。

    • 必须配合信号量或互斥锁保证同步。

  • 应用示例

    • 游戏中共享地图数据、图像处理共享缓存。

5. 套接字(Socket)

  • 定义:基于 TCP/UDP 的进程间通信机制,可本地或跨网络。

  • 特点

    • 支持不同机器上的进程通信。

    • 可以实现客户端-服务器架构。

  • 应用示例

    • Web 服务器接收浏览器请求 → 响应页面。


0.3 小结

  • 选择方式原则

    • 事件通知 → 信号

    • 小量数据传递 → 管道 / 消息队列

    • 大数据传递 → 共享内存

    • 跨机器通信 → 套接字

  • 注意事项

    • 信号是异步的,处理不当可能导致资源泄露或程序异常。

    • 共享内存必须加锁防止数据竞争。

    • 管道/消息队列有限制大小,过量数据可能阻塞写操作。


1. 信号(Signal)

1.1 信号的概念

  • 定义:信号是操作系统提供的一种异步通信机制,用于通知进程某个事件发生。

  • 特点

    • 轻量级:只传递事件标志,不传递实际数据。

    • 异步:发送信号的进程不关心接收进程是否立即处理。

    • 类似生活类比

      • 教室铃响 → 下课(事件发生通知学生)

      • 手机提醒 → 有新消息(通知用户)

  • 应用场景

    • 异常处理:除零、非法内存访问

    • 用户交互:Ctrl+C 停止程序

    • 定时任务:定时器到期触发


1.2 信号的特点

特点说明
传递信息只传递事件标志,不传递具体数据
软件中断由操作系统内核或进程调用触发,不依赖硬件中断
异步性进程在运行过程中可以被信号打断
可处理性默认动作可执行、忽略或自定义处理

注意事项

  • 信号可能随时到达,如果处理函数执行时间过长,可能干扰进程正常逻辑。

  • 某些信号(如 SIGKILL、SIGSTOP)无法被捕获或忽略。


1.3 信号编号

  • 常规信号:编号 1~31,标准信号

  • 实时信号:编号 34~64,保证排队、不丢失

  • 查看方式

    kell -l
    • 输出所有信号编号及对应名称

示例(常用的):

Linux 标准信号(1-32)一览表

信号名称编号默认行为描述与触发原因
SIGHUP1Term挂起 (Hangup)。控制终端关闭或进程退出后,发送给会话中的其他进程。也用于通知守护进程重载配置。
SIGINT2Term中断 (Interrupt)。来自键盘的中断,通常由 Ctrl + C 产生。
SIGQUIT3Core退出 (Quit)。来自键盘的退出,通常由 Ctrl + \ 产生。不仅终止进程,还会生成 core dump 文件用于调试。
SIGILL4Core非法指令 (Illegal Instruction)。进程尝试执行一条非法、未知或特权指令。
SIGTRAP5Core跟踪陷阱 (Trace/Breakpoint Trap)。由调试器使用,用于在断点处中断被调试的进程。
SIGABRT6Core中止 (Abort)。由 abort() 函数产生,表示程序检测到错误并主动调用终止。
SIGBUS7Core总线错误 (Bus Error)。内存访问错误,例如访问了未对齐的地址或不存在的物理地址。
SIGFPE8Core浮点异常 (Floating-point exception)。错误的算术操作,如除以零、溢出等。
SIGKILL9Term杀死 (Kill)立即无条件终止进程。此信号无法被捕获、阻塞或忽略。是确保能杀死进程的最后手段。
SIGUSR110Term用户自定义信号 1 (User-defined signal 1)。留给程序员自由定义其用途。
SIGSEGV11Core段错误 (Segmentation fault)无效的内存引用,如访问野指针、写入只读内存或访问已释放的内存。
SIGUSR212Term用户自定义信号 2 (User-defined signal 2)。留给程序员自由定义其用途。
SIGPIPE13Term管道破裂 (Broken pipe)。向一个没有读端的管道或套接字进行写入。常见于网络连接已关闭。
SIGALRM14Term闹钟信号 (Alarm clock)。由 alarm() 或 setitimer(ITIMER_REAL) 设置的定时器超时后产生。
SIGTERM15Term终止 (Termination)友好地请求进程终止。这是 kill 命令的默认信号,进程可以捕获它并进行清理工作后退出。
SIGSTKFLT16Term协处理器栈错误 (Stack fault)。早年用于处理数学协处理器的栈错误,现在大多平台已不再使用。
SIGCHLD17Ign子进程状态改变 (Child status changed)。子进程停止终止时,会发送此信号通知其父进程。用于避免僵尸进程。
SIGCONT18Cont继续 (Continue)。让一个被停止的进程恢复执行。即使进程被阻塞,此信号也会被交付。
SIGSTOP19Stop停止 (Stop)暂停进程的执行。这是一个作业控制信号,与 SIGCONT 相对。无法被捕获、阻塞或忽略
SIGTSTP20Stop终端停止 (Terminal stop)。来自终端的停止信号,通常由 Ctrl + Z 产生。进程可以捕获此信号。
SIGTTIN21Stop后台进程读终端 (Background read from tty)。当一个后台进程尝试从终端读取输入时,终端驱动程序会发送此信号来暂停它。
SIGTTOU22Stop后台进程写终端 (Background write to tty)。当一个后台进程尝试向终端输出时,终端驱动程序可能会发送此信号来暂停它。
SIGURG23Ign紧急条件 (Urgent condition)。通知进程带外数据 (out-of-band data) 已经到达网络连接上。
SIGXCPU24Core超过CPU时间限制 (CPU time limit exceeded)。进程消耗的CPU时间超过了其软资源限制。
SIGXFSZ25Core超过文件大小限制 (File size limit exceeded)。进程尝试扩大文件以至于超过了其软资源限制。
SIGVTALRM26Term虚拟定时器告警 (Virtual alarm clock)。由 setitimer(ITIMER_VIRTUAL) 设置的用户模式CPU时间定时器超时。
SIGPROF27Term性能分析定时器告警 (Profiling alarm clock)。由 setitimer(ITIMER_PROF) 设置的定时器超时,用于性能分析。
SIGWINCH28Ign窗口大小改变 (Window resize)。当终端窗口大小发生变化时(如调整了终端模拟器大小),会发送此信号。
SIGIO / SIGPOLL29TermI/O 可能 (I/O now possible)。表示一个异步 I/O 事件发生。
SIGPWR30Term电源故障 (Power failure)。当系统检测到电源故障,即将切换到UPS备用电源时,会发送此信号。
SIGSYS31Core非法系统调用 (Bad system call)。进程执行了一个无效的系统调用(错误的参数或未知的调用号)。
SIGRTMIN 到 SIGRTMAX32-64Term实时信号 (Real-time signals)编号 32 到 64 的信号是实时信号,它们没有预定义的含义,支持排队,不会丢失。它们不属于传统的 1-31 标准信号范围。

1.4 信号的四要素

  1. 编号(Number)

    • 唯一标识信号类型

    • 例如 SIGINT 编号 2

  2. 信号名称(Name)

    • 宏定义形式,如 SIGTERM

    • 编程时通常用名称而非数字,提高可移植性

  3. 对应事件(Event)

    • 信号触发的实际事件,例如 Ctrl+C、定时器到期

  4. 默认处理动作(Default Action)

    • 终止进程(Terminate)

    • 忽略(Ignore)

    • 暂停(Stop)

    • 生成 core 文件(Core Dump)

示例查看

man 7 signal


1.5 信号产生方式

  1. 用户操作

    • Ctrl+C → SIGINT

    • Ctrl+\ → SIGQUIT

    • Ctrl+Z → SIGSTOP

  2. 硬件异常

    • 除零、非法内存访问 → 内核发送 SIGFPE、SIGSEGV

  3. 软件异常

    • 程序调用 abort() → SIGABRT

    • 定时器到期 → SIGALRM

  4. 系统调用

    • kill(pid, sig) → 发送信号

    • raise(sig) → 向自己发送

  5. 命令行工具

    • killkillall 实际是系统调用封装

注意事项

  • 信号发送是异步的,可能随时中断进程执行。

  • 如果进程没有捕获信号,默认动作执行。


1.6 信号处理方式

处理方式说明
默认动作系统预定义,可能终止、暂停、忽略或生成 core
忽略信号使用 SIG_IGN 忽略信号
捕获信号使用自定义 handler 函数处理信号

示例

#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void handle_sigint(int sig) {
    printf("收到 SIGINT,信号编号 %d\n", sig);
}

int main() {
    signal(SIGINT, handle_sigint); // 捕获 Ctrl+C
    while(1) {
        sleep(1);
    }
    return 0;
}

当用户按 Ctrl+C,程序不会退出,而是打印消息。


2. 信号的阻塞集与未决集

2.1 信号异步特性

  • 信号是异步事件,即它可以在任意时刻打断进程执行。

  • 异步意味着:

    • 信号发送者无需等待接收者处理。

    • 处理时机可能不确定。

问题:异步信号可能干扰关键操作(如修改共享数据),需要控制处理时机。


2.2 阻塞信号集(Signal Mask / Blocked Set)

  • 定义:阻塞信号集是当前进程暂时“屏蔽”的信号集合。

  • 作用

    • 当某些信号被加入阻塞集后,进程收到这些信号时不会立即处理

    • 信号状态存入未决信号集,处理会被推迟,直到信号从阻塞集移除。

  • 常用 API

    #include <signal.h>
    
    int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
    int sigemptyset(sigset_t *set);  // 清空集合
    int sigfillset(sigset_t *set);   // 全部加入集合
    int sigaddset(sigset_t *set, int signo); // 加入单个信号
    int sigdelset(sigset_t *set, int signo); // 移除单个信号
    int sigismember(const sigset_t *set, int signo); // 判断信号是否在集合
    
  • 类比

    • 把手机调成“勿扰模式”,通知来了但不打扰你(信号被阻塞)。


2.3 未决信号集(Pending Set)

  • 定义:未决信号集是已经发送但尚未处理的信号集合。

  • 特点

    • 当信号被阻塞时,它会进入未决信号集。

    • 一旦从阻塞集移除,未决信号会立即触发处理。

  • 原理

    • Linux 内核在 task_struct 中维护未决信号集位图。

    • 信号到达时,对应位从 0 → 1。

    • 信号处理完成后,对应位复原为 0。

类比

  • 阻塞集:你在开会时把通知静音。

  • 未决信号集:静音期间收到的消息积压在通知栏,会议结束后立即处理。


2.4 信号阻塞与未决关系

  1. 阻塞信号进入未决信号集

    进程收到信号 S:
    - S 在阻塞集 → 加入未决信号集,处理延迟
    - S 不在阻塞集 → 立即处理
    
  2. 解除阻塞触发处理

    sigprocmask(SIG_UNBLOCK, &set, NULL);
    • 移除阻塞集的信号 → 内核检查未决信号集 → 调用信号处理函数。


2.5 示例代码:阻塞与未决信号

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void sig_handler(int sig) {
    printf("处理信号 %d\n", sig);
}

int main() {
    sigset_t set;
    signal(SIGINT, sig_handler); // 捕获 Ctrl+C

    sigemptyset(&set);
    sigaddset(&set, SIGINT);     // 阻塞 SIGINT
    sigprocmask(SIG_BLOCK, &set, NULL);

    printf("阻塞 SIGINT,尝试按 Ctrl+C\n");
    sleep(5); // 此时 Ctrl+C 不会触发处理函数

    sigprocmask(SIG_UNBLOCK, &set, NULL); // 解除阻塞
    printf("解除阻塞,未决信号立即处理\n");

    sleep(5); // 等待观察信号处理
    return 0;
}

运行效果

  1. 阻塞期间按 Ctrl+C → 不打印任何信息。

  2. 解除阻塞后,未决的 SIGINT 被立即处理 → 打印“处理信号 2”。


2.6 注意事项

  • 信号顺序

    • 常规信号可能丢失(只记录一次未决状态)。

    • 实时信号保证顺序,不丢失。

  • 避免数据竞争

    • 信号处理函数内不要执行非原子操作或使用共享全局数据,除非加锁。

  • 适用场景

    • 临界区保护:在关键操作期间阻塞特定信号。

    • 异步事件处理:控制信号触发时机。


3. 信号相关 API 函数

3.1 kill(向其他进程发送信号)

  • 功能:向指定进程或进程组发送信号。

  • 头文件

#include <sys/types.h>
#include <signal.h>

  • 函数原型

int kill(pid_t pid, int sig);

  • 参数说明

    参数含义
    pid指定发送对象,规则如下:
    pid>0 → 指定进程ID
    pid=0 → 当前进程所在进程组
    pid=-1 → 系统内所有进程(慎用)
    pid<-1 → 指定进程组,组号 =
    sig信号编号或宏,如 SIGINT、SIGTERM
  • 返回值

    • 成功 → 0

    • 失败 → -1

  • 示例

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>

int main() {
    pid_t pid = fork();
    if (pid == 0) { // 子进程
        while(1) { printf("子进程运行中...\n"); sleep(1);}
    } else if (pid > 0) { // 父进程
        sleep(3);
        kill(pid, SIGINT); // 向子进程发送 SIGINT
    }
    return 0;
}

作用:父进程在 3 秒后通知子进程收到 SIGINT,默认子进程被中断。


3.2 raise(向自己发送信号)

  • 功能:给调用进程自己发送信号。

  • 头文件

#include <signal.h>

  • 函数原型

int raise(int sig);

  • 作用

    • 等价于 kill(getpid(), sig)

    • 常用于测试或自杀式终止。

  • 示例

#include <stdio.h>
#include <signal.h>

void handler(int sig) { printf("收到信号 %d\n", sig); }

int main() {
    signal(SIGINT, handler);
    printf("向自己发送 SIGINT\n");
    raise(SIGINT);  // 触发 handler
    return 0;
}


3.3 abort(异常终止)

  • 功能:向自己发送 SIGABRT,产生 core 文件。

  • 头文件

#include <stdlib.h>

  • 函数原型

void abort(void);
  • 应用场景

    • 当程序检测到严重错误,无法继续执行时使用。

  • 示例

#include <stdlib.h>
int main() {
    abort(); // 进程异常终止并生成 core
    return 0;
}

3.4 alarm(定时发送信号)

  • 功能:设置定时器,到期向进程发送 SIGALRM。

  • 头文件

#include <unistd.h>

  • 函数原型

unsigned int alarm(unsigned int seconds);
  • 参数说明

    • seconds:指定秒数

    • alarm(0) → 取消定时器,返回剩余时间

  • 示例

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void handler(int sig) { printf("定时器触发 SIGALRM\n"); }

int main() {
    signal(SIGALRM, handler);
    alarm(3);  // 3秒后发送 SIGALRM
    printf("等待信号...\n");
    pause();   // 阻塞等待信号
    return 0;
}

3.5 setitimer(高精度定时器)

  • 功能:可实现周期性定时器,精度到微秒。

  • 头文件

#include <sys/time.h>
#include <signal.h>

  • 函数原型

int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
  • 参数说明

    参数含义
    which定时器类型:
    ITIMER_REAL → 实际时间(触发 SIGALRM)
    ITIMER_VIRTUAL → 用户态 CPU 时间(触发 SIGVTALRM)
    ITIMER_PROF → 用户 + 内核 CPU 时间(触发 SIGPROF)
    new_value设置新的定时器,包括首次触发和循环间隔
    old_value保存上一次定时器状态,可为 NULL
  • 结构体

struct itimerval {
    struct timeval it_interval; // 后续周期
    struct timeval it_value;    // 首次触发时间
};

struct timeval {
    long tv_sec;   // 秒
    long tv_usec;  // 微秒
};

  • 示例

#include <stdio.h>
#include <signal.h>
#include <sys/time.h>
#include <unistd.h>

void handler(int sig) { printf("SIGALRM触发\n"); }

int main() {
    signal(SIGALRM, handler);
    struct itimerval timer = {{2,0}, {5,0}}; // 第一次 5s 后触发,每隔 2s
    setitimer(ITIMER_REAL, &timer, NULL);
    while(1) sleep(1);
    return 0;
}


3.6 小结与注意事项

  • kill:向其他进程发送信号,常用于进程控制。

  • raise:向自己发送信号,方便测试。

  • abort:异常终止并生成 core。

  • alarm:简单定时器,单位秒。

  • setitimer:高精度、可周期性定时器,单位微秒。

  • 注意事项

    • 定时器只能有一个(每种类型)。

    • 信号处理函数尽量简单,避免复杂逻辑或阻塞操作。

    • 某些信号无法捕获(如 SIGKILL、SIGSTOP)。


4. 给信号注册自定义处理函数

当进程收到信号时,系统提供三种处理方式:

  1. 默认动作(Default):执行信号预定义动作,如终止、暂停或忽略进程。

  2. 忽略信号(Ignore):对信号完全不做处理。

  3. 自定义处理函数(User-defined):由程序员提供函数,收到信号时执行特定操作。

自定义信号处理函数可以在进程中释放资源、记录日志或执行清理操作。


4.1 signal 函数

  • 功能:注册信号处理函数。

  • 头文件

#include <signal.h>
  • 函数原型

typedef void(*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

  • 参数说明

    参数含义
    signum信号编号(如 SIGINT、SIGTERM)
    handler可以是:
    1. SIG_IGN → 忽略信号
    2. SIG_DFL → 默认处理
    3. 函数指针 → 自定义处理函数
  • 返回值

    • 成功 → 返回之前的信号处理函数指针

    • 失败 → SIG_ERR

  • 注意事项

    • 不能捕获 SIGKILL 和 SIGSTOP。

    • 在信号处理函数中,尽量只调用异步安全函数(如 write、_exit),避免 printf、malloc 等可能导致未定义行为。

  • 示例代码

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

char *p = NULL;

void my_handler(int sig) {
    if (p != NULL) {
        free(p);   // 释放资源
        p = NULL;
    }
    printf("收到信号 %d,资源已释放\n", sig);
    _exit(0);  // 安全退出
}

int main() {
    p = (char *)malloc(128);
    signal(SIGINT, my_handler); // 注册 Ctrl+C 处理
    while(1) {
        printf("p 地址: %p\n", p);
        sleep(1);
    }
    free(p);
    return 0;
}

功能:按 Ctrl+C 时调用 my_handler,释放分配的内存并退出程序。


4.2 sigaction 函数

  • 功能:比 signal 更强大、可精确控制信号处理行为。

  • 头文件

#include <signal.h>
  • 函数原型

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

  • 参数说明

    参数含义
    signum信号编号
    act指向新的信号处理方式
    oldact保存原来的信号处理方式,可为 NULL
  • 结构体

struct sigaction {
    void (*sa_handler)(int);               // 旧处理函数
    void (*sa_sigaction)(int, siginfo_t*, void*); // 新处理函数,可获取更多信息
    sigset_t sa_mask;                      // 信号阻塞集(在处理期间阻塞的信号)
    int sa_flags;                           // 控制信号处理行为
    void (*sa_restorer)(void);             // 已弃用
};
  • sa_flags 常用值

    标志含义
    SA_RESTART使被信号打断的系统调用自动重启(已废弃)
    SA_NOCLDSTOP子进程暂停或继续时不发送 SIGCHLD
    SA_NOCLDWAIT子进程退出时不生成僵尸进程
    SA_NODEFER信号处理期间仍可接收相同信号
    SA_RESETHAND处理完信号后恢复默认动作
    SA_SIGINFO使用 sa_sigaction 成员,而不是 sa_handler
  • 示例代码

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void handler(int sig, siginfo_t *info, void *ucontext) {
    printf("收到信号 %d,发送者 PID: %d\n", sig, info->si_pid);
    _exit(0);
}

int main() {
    struct sigaction act;
    act.sa_flags = SA_SIGINFO;
    act.sa_sigaction = handler;
    sigemptyset(&act.sa_mask);

    sigaction(SIGINT, &act, NULL);  // 注册自定义信号处理
    while(1) {
        printf("等待信号...\n");
        sleep(1);
    }
    return 0;
}

功能:按 Ctrl+C 时打印发送信号的进程 PID 并退出。相比 signal,可获取更多信号信息。


4.3 小结

  • signal:简单易用,但功能有限。

  • sigaction:功能强大,可获取信号来源、控制处理期间阻塞的信号,适合复杂场景。

  • 安全建议

    • 信号处理函数要尽量短小、异步安全。

    • 避免在处理函数中调用可能阻塞或不安全的函数(如 printf、malloc 等)。


5. 信号集(Signal Set)

信号集用于管理一组信号,主要用于 阻塞信号(sigprocmask)处理期间屏蔽信号

信号集类型:



#include <signal.h>
typedef struct { ... } sigset_t;


5.1 sigemptyset(清空信号集)

  • 功能:将信号集置为空,即不包含任何信号。

  • 函数原型

int sigemptyset(sigset_t *set);

  • 参数

    • set:要操作的信号集指针

  • 返回值

    • 成功 → 0

    • 失败 → -1

  • 示例

#include <stdio.h>
#include <signal.h>

int main() {
    sigset_t set;
    sigemptyset(&set);  // 清空信号集
    if (sigismember(&set, SIGINT) == 0) {
        printf("SIGINT 不在集合中\n");
    }
    return 0;
}


5.2 sigfillset(填满信号集)

  • 功能:将信号集填满,包含所有信号。

  • 函数原型

int sigfillset(sigset_t *set);

  • 用途

    • 常用于阻塞所有信号,例如临界区保护。

  • 示例

#include <stdio.h>
#include <signal.h>

int main() {
    sigset_t set;
    sigfillset(&set);  // 填满集合
    if (sigismember(&set, SIGINT)) {
        printf("SIGINT 已在集合中\n");
    }
    return 0;
}


5.3 sigaddset(加入信号)

  • 功能:将某个信号加入信号集。

  • 函数原型

int sigaddset(sigset_t *set, int signo);

  • 参数

    • set:信号集

    • signo:信号编号(如 SIGINT)

  • 示例

#include <stdio.h>
#include <signal.h>

int main() {
    sigset_t set;
    sigemptyset(&set);       // 清空集合
    sigaddset(&set, SIGINT); // 加入 SIGINT
    if (sigismember(&set, SIGINT)) {
        printf("SIGINT 已加入集合\n");
    }
    return 0;
}


5.4 sigdelset(删除信号)

  • 功能:从信号集中删除某个信号。

  • 函数原型

int sigdelset(sigset_t *set, int signo);

  • 示例

#include <stdio.h>
#include <signal.h>

int main() {
    sigset_t set;
    sigfillset(&set);         // 填满集合
    sigdelset(&set, SIGINT);  // 删除 SIGINT
    if (!sigismember(&set, SIGINT)) {
        printf("SIGINT 已从集合中删除\n");
    }
    return 0;
}

5.5 sigismember(判断信号是否存在)

  • 功能:判断某个信号是否在集合中。

  • 函数原型

int sigismember(const sigset_t *set, int signo);
  • 返回值

    • 1 → 在集合中

    • 0 → 不在集合中

    • -1 → 错误

  • 示例

#include <stdio.h>
#include <signal.h>

int main() {
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGINT);

    if (sigismember(&set, SIGINT)) {
        printf("SIGINT 在集合中\n");
    } else {
        printf("SIGINT 不在集合中\n");
    }
    return 0;
}

5.6 使用场景

  1. 阻塞信号

    • 在临界区阻止信号打断关键操作,例如:

    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGINT);
    sigprocmask(SIG_BLOCK, &set, NULL); // 阻塞 SIGINT
    

  2. 在信号处理函数中临时屏蔽信号

    • 避免递归调用同一信号。

  3. 配合 sigaction 使用

    • sa_mask 成员即是信号集,用于指定处理期间阻塞的信号。


6. 信号阻塞集(Blocked Signal Set)

6.1 概念

  • 阻塞信号:进程在某段时间不希望处理的信号。

  • 当信号在阻塞信号集中时:

    • 信号不会立即被处理。

    • 信号被标记为未决信号,等待解除阻塞后再处理。

  • 通过阻塞信号可以保护临界区或关键操作,防止信号干扰。


6.2 API 函数

6.2.1 sigprocmask

  • 功能:操作当前进程的信号屏蔽字(阻塞集)。

  • 原型

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  • 参数说明

    参数含义
    howSIG_BLOCK → 添加阻塞信号
    SIG_UNBLOCK → 移除阻塞信号
    SIG_SETMASK → 设置阻塞信号集
    set要操作的信号集
    oldset保存原来的阻塞信号集,可为 NULL
  • 示例

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

int main() {
    sigset_t set, oldset;
    sigemptyset(&set);
    sigaddset(&set, SIGINT);       // 阻塞 SIGINT
    sigprocmask(SIG_BLOCK, &set, &oldset);

    printf("阻塞 SIGINT,3秒内按 Ctrl+C 无效\n");
    sleep(3);

    sigprocmask(SIG_SETMASK, &oldset, NULL); // 恢复原阻塞集
    printf("解除阻塞 SIGINT\n");

    while(1) sleep(1);
    return 0;
}

6.3 使用场景

  1. 保护临界区

    • 在操作共享资源时阻塞可能打断操作的信号。

  2. 延迟处理信号

    • 阻塞信号后,处理可以统一在安全时刻进行。

  3. 配合 sigaction 的 sa_mask 使用

    • 信号处理期间临时阻塞指定信号,防止递归调用。


7. 信号未决集(Pending Signal Set)

7.1 概念

  • 未决信号:进程收到但尚未处理的信号。

  • 产生条件:

    1. 信号到达时被阻塞。

    2. 信号尚未被处理(处理函数未执行)。

  • 特点

    • 每个信号在未决集合中只有一个位。

    • 当信号解除阻塞时才会被处理。

    • 异步发送时,如果多次发送同一信号,未决集合只标记一次。


7.2 查看未决信号

7.2.1 sigpending

  • 功能:获取当前进程的未决信号集。

  • 原型

#include <signal.h>
int sigpending(sigset_t *set);

  • 参数

    • set:存放未决信号集

  • 返回值

    • 成功 → 0

    • 失败 → -1

  • 示例

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

int main() {
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGINT);

    sigprocmask(SIG_BLOCK, &set, NULL); // 阻塞 SIGINT
    printf("阻塞 SIGINT,按 Ctrl+C 尝试发送\n");
    sleep(5);

    sigpending(&set); // 获取未决信号
    if (sigismember(&set, SIGINT)) {
        printf("SIGINT 在未决信号集中\n");
    }

    sigprocmask(SIG_UNBLOCK, &set, NULL); // 解除阻塞,信号处理
    while(1) sleep(1);
    return 0;
}

7.3 阻塞集 vs 未决集

特性阻塞信号集未决信号集
含义当前不希望处理的信号集合已到达但尚未处理的信号集合
修改可通过 sigprocmask 修改由内核自动管理
行为信号到达时,如果在阻塞集 → 标记为未决信号解除阻塞后 → 执行处理
  • 理解:阻塞信号是“我不想处理的信号”,未决信号是“已经来了但是还没处理的信号”。


8. 进程间通信总结与综合示例

进程间通信(IPC)是一种让独立进程互相协作和交换信息的机制。在 Linux 中,信号是最基础的一种 IPC 方式,它具有异步、轻量级的特点,但只传递状态信息而不传递数据。


8.1 总结核心概念

概念功能备注
信号(Signal)通知进程发生了某个事件异步通信;仅标志事件,不传数据
信号编号与名称标识不同事件1-31 常规信号;34-64 实时信号
默认动作系统收到信号后的处理终止、忽略、暂停、核心转储等
阻塞信号集临时屏蔽信号使用 sigprocmasksa_mask
未决信号集已到达但未处理信号信号在阻塞时会进入未决集合
信号处理函数自定义处理逻辑使用 signalsigaction 注册
定时器信号延迟或周期触发信号alarmsetitimer,可实现周期任务

8.2 信号操作 API 回顾

  1. 发送信号

    • kill(pid, sig) → 向其他进程发送信号

    • raise(sig) → 向自己发送信号

    • abort() → 向自己发送 SIGABRT 并生成 core 文件

  2. 阻塞与解除

    • sigemptyset, sigfillset, sigaddset, sigdelset, sigismember

    • sigprocmask(SIG_BLOCK / SIG_UNBLOCK / SIG_SETMASK, ...)

  3. 处理信号

    • signal(signum, handler) → 注册简单自定义函数

    • sigaction(signum, &act, &oldact) → 注册高级自定义函数,可设置阻塞信号和特殊标志

  4. 定时器信号

    • alarm(seconds) → 单次定时器

    • setitimer(ITIMER_REAL, &new, &old) → 可循环定时器,精度到微秒


8.3 综合示例

下面示例演示:

  1. 父进程创建子进程

  2. 阻塞 SIGINT

  3. 子进程在特定时间发送 SIGINT

  4. 使用自定义处理函数处理信号

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>

void handle_sigint(int sig) {
    printf("收到 SIGINT 信号:%d\n", sig);
}

int main() {
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGINT);

    // 阻塞 SIGINT
    sigprocmask(SIG_BLOCK, &set, NULL);

    pid_t pid = fork();
    if (pid == 0) { // 子进程
        sleep(2); // 延迟发送
        kill(getppid(), SIGINT); // 发送 SIGINT 给父进程
        exit(0);
    } else if (pid > 0) { // 父进程
        signal(SIGINT, handle_sigint); // 注册自定义处理函数

        printf("父进程阻塞 SIGINT 3秒\n");
        sleep(3);

        // 解除阻塞
        sigprocmask(SIG_UNBLOCK, &set, NULL);

        // 等待子进程
        wait(NULL);
        printf("父进程结束\n");
    }
    return 0;
}

运行分析

  • 子进程在父进程阻塞 SIGINT 时发送信号 → 信号进入未决集合

  • 父进程解除阻塞后 → 信号处理函数 handle_sigint 执行

  • 展示了阻塞集、未决集、信号处理函数的综合使用


8.4 学习要点

  1. 信号是异步通信:信号不会传递数据,只标记事件。

  2. 阻塞信号是保护机制:在关键操作期间,阻塞信号避免打断。

  3. 未决信号保证可靠性:即使信号被阻塞,也不会丢失。

  4. 定时器与信号结合使用:可实现周期任务或延时操作。

  5. 自定义处理函数:灵活控制信号处理行为,避免直接使用默认动作导致进程异常终止。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值