.webp)
【Process】进程间通信
进程间通信
1. 什么是进程间通信
IPC:Inter Processes Communication
进程是一个独立执行的,单独的可执行内存空间/单元。其中每一个进程使用的资源都是自行管理,申请,自行使用。进程与进程之间,实际上是存在【进程隔离】
程序 (Program) 中不同的进程 (process) 进行配合之后,才可以完成整个的程序需求。需要完成进程与进程之间的【合理,安全,高效】的进程通信,从而满足代码的执行。
2. 进程通信的功能
- 数据传递:
- 进程中数据,需要通过数据传递,提供给其他进程。例如 A 进程从文件中读取的数据,B 进程收到之后,利用网络发送到其他终端。
- 资源共享:
- 多个进程中的数据,可以通过多种手段,进行数据共享操作,类似于数据传递。
- 通知事件:
- 一个进程需要向其他进程,或者其他进程组通知时间,告知当前程序发生了什么事情。
- 进程控制:
- 有些进程需要其他进程进行控制执行,例如 Debug 程序,利用 debug 和 breakpoint 将程序执行,一步一步完成,可以根据程序中的变量变化过程,进行程序情况判断。
3. Linux 进程通信 IPC 的组成部分
- Unix 基础进程通信方式
- System V 进程间通信
- POSIX 进程间通信 (POSIX Portable Operating System Interface,可移植操作系统接口)
- Socket 进程间通信,套接字用于网络通信
- 在 Linux 中整合了以上所有的进程通信方式

4. 信号
4.1 什么是信号
咱们学过 Qt 中的【信号和槽机制】,组件发送信号,由其他组件进行接收之后,完成对应槽函数/任务函数。
发送信号的组件是一个进程,接收信号进行【处理】的也是一个进程。【进程间通信】
目前的信号属于【软件层】实现。在硬件层面称之为【中断】,例如按键。软件层实现的内容是将一个进程的信号发送给当前进程,或者其他进程,从而满足通信操作。
信号可以在**【用户内存空间】和【内核内存空间】**中进行传递。用户空间包括当前进程占用的【栈区,堆区,BSS区,数据区,代码区】
![]()
如果进程当前处于未执行状态,信号可以对当前进程进行唤醒操作,或者将信号进行存储,当前进程进入执行状态时,将信号提供给当前进程。
4.2 信号是异步通信方式
信号对于进程而言是一个【异步通信方式】,发送信号的进程无需确认目标进程是否收到对应的信号内容,继续执行后续的代码内容。
信号可以直接进行【用户空间】和【内核空间】数据交互,内核进程可以知晓用户发送了哪些信号,以及当前进程的相关状态,从而对用户进程进行相对应处理和关注。
系统中每一个信号都是
SIG
开头,同时系统中整合不同的信号。每一个信号都有一个唯一编号,和对应的唯一【宏标记 】,在<signal.h>
利用 kill -l 查询当前计算机中支持的信号有哪些
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX
常见信号解释
- SIGHUP(1):挂起信号,通常在用户终端连接(如网络连接)断开时发送给相关进程,进程接收到该信号后可能会进行重新初始化等操作。
- SIGINT(2):中断信号,一般是用户在终端按下
Ctrl + C
时发送给当前正在运行的进程,用于请求进程终止。- SIGQUIT(3):退出信号,用户按下
Ctrl + \
时发送,它比SIGINT
更强烈,通常会导致进程进行核心转储(core dump)。- SIGKILL(9):强制终止信号,这个信号不能被进程捕获,处置或忽略,一旦发送,进程会立即终止。
- SIGTERM(15):终止信号,是
kill
命令默认发送的信号,进程可以选择捕获该信号并进行一些清理操作后再终止。
5. 信号操作相关函数
5.1 kill 函数
函数文档
#include <signal.h> int kill(pid_t pid, int sig);
- 功能:
- 给予指定进程发送目标信号
- 参数解释
pid
:这是一个pid_t
类型的参数,用于指定要接收信号的进程或进程组,其取值有以下几种情况:
pid > 0
:信号将被发送给进程 ID 为pid
的进程。pid == 0
:信号将被发送给调用进程所在进程组中的所有进程。pid == -1
:信号将被发送给调用进程有权限发送信号的所有进程,但不包括 1 号进程(init
或systemd
进程)。pid < -1
:信号将被发送给进程组 ID 为|pid|
的所有进程。sig
:这是一个int
类型的参数,用于指定要发送的信号。常见的信号值有:
SIGTERM
(通常值为 15):请求进程正常终止,进程可以捕获该信号并进行一些清理操作后再退出。SIGKILL
(通常值为 9):强制终止进程,进程无法捕获该信号进行其他处置,会立即被终止进程执行。SIGINT
(通常值为 2):通常由用户按下Ctrl + C
产生,用于请求进程中断。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
/*
int kill(pid_t pid, int sig);
*/
int main(int argc, char const *argv[])
{
pid_t pid = 0;
pid = fork();
if (-1 == pid)
{
perror("fork failed!");
}
if (0 == pid)
{
// 子进程
int count = 10;
while (count > 0)
{
printf("Son Process Running~~~ Exit in %d seconds\n", count);
count -= 1;
sleep(1);
}
}
else
{
// 父进程
printf("Parent Process Running~~~ Exit in 10 seconds!\n");
sleep(2);
/*
pid 是当前子进程 PID 给予当前 父进程发送一个 SIGINT 信号,当前子进程会被中断。
*/
// kill(pid, SIGINT);
// 发生信号 SIGINT 当前进程组,进程组所有进程会被中断
kill(0 - getpgid(getpid()), SIGINT);
sleep(8);
printf("Parent Process Exited!\n");
}
return 0;
}
5.2 alarm 函数
函数文档
#include <unistd.h> unsigned int alarm(unsigned int seconds);
- 功能 :
alarm
是一个非常有用的系统调用函数,主要用于设置一个定时器,当定时器超时后,会向调用进程发送SIGALRM
信号。默认动作是终止调用 alarm 函数的进程。- 参数解释:
seconds
这是一个unsigned int 类型的参数,用于指定定时器的超时时间,单位为秒。
- 当
seconds
为 0 时,会取消之前设置的所有未超时的定时器。- 当
seconds
大于 0 时,会设置一个新的定时器,在seconds
秒后向调用进程发送SIGALRM
信号。- 返回值情况
- 如果之前没有设置过定时器,或者之前设置的定时器已经超时,
alarm
函数会返回 0。- 如果之前设置的定时器还未超时,
alarm
函数会返回之前设置的定时器剩余的秒数,并且会用新的定时器替代之前的定时器。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
int main(int argc, char const *argv[])
{
int second = 5;
int left_second = alarm(10);
printf("Alarm left seconds : %d\n", left_second); // 0 因为第一次设置 alarm
while (second > 0)
{
printf("Process Running! %d seconds left!\n", second);
sleep(1);
second -= 1;
}
left_second = alarm(2);
printf("Last Alarm left seconds : %d\n", left_second); //5 上一个 alarm 剩余 5秒
printf("Process Exit in ten seconds!\n");
sleep(10);
printf("Process Exit!\n"); // 不会执行,因为 alarm 2 秒之后就发送 SIGALRM 信号
return 0;
}
5.3 raise 函数
函数文档
#include <signal.h> int raise(int sig);
- 函数功能:
raise
函数用于向调用进程自身发送一个信号- 函数参数:
sig
:这是一个整数类型的参数,代表要发送的信号编号。在不同的系统中,信号编号对应的信号类型是标准化的,例如:SIGINT
(通常值为 2):该信号一般由用户按下 Ctrl + C 组合键时产生,用于请求进程中断。SIGTERM
(通常值为 15):这是一个通用的终止信号,用于请求进程正常终止。SIGKILL
(通常值为 9):这是一个强制终止信号,进程无法捕获和忽略该信号,会被直接终止。- 返回值:
- 若信号成功发送,
raise
函数返回 0。- 若发送信号时出现错误,函数返回非零值。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int main(int argc, char const *argv[])
{
int second = 10;
while (second > 0)
{
printf("Process Running! Exit in %d second!\n", second);
second -= 1;
if (5 == second)
{
// raise 给予当前进程进行信号发送
// SIGINT 当前进程收到信号之后,中断执行!
raise(SIGTERM);
}
sleep(1);
}
return 0;
}
5.4 abort 函数
函数文档
#include <stdlib.h> void abort(void);
abort
函数的核心功能是异常终止当前进程。它会执行以下操作:
- 发送
SIGABRT
信号给调用进程。默认情况下,这个信号会导致进程异常终止,并且可能会生成一个核心转储文件(core dump),该文件包含了进程终止时的内存状态信息,可用于后续的调试分析。- 若进程已经为
SIGABRT
信号设置了信号处理函数,且该处理函数没有返回(例如通过longjmp
跳出),abort
函数会尝试终止进程。- 若
SIGABRT
信号被阻塞,abort
函数会先解除对该信号的阻塞,然后再发送信号。使用场景
- 错误处理:当程序遇到无法恢复的严重错误时,可以调用
abort
函数来终止程序,避免程序继续运行导致更严重的后果。例如,在内存分配失败且无法采取其他补救措施时,可以调用abort
。- 断言失败:在调试过程中,当某些条件不满足时,可以使用
abort
函数来终止程序,帮助开发者快速定位问题。
5.5 pause 函数
函数文档
- **在 Windows 操作系统环境下,pause 函数在 **
<stdio.h>
头文件中,是一个 C 语言标准库函数- 在 Unix/Linux 操作系统环境下,pause 函数是一个【系统调用函数 System Calls】,所在头文件是
<unistd.h>
#include <unistd.h> int pause(void);
- 函数功能:
- 是使调用进程(或线程)进入****睡眠状态,直到接收到一个信号为止。该函数在
<unistd.h>
头文件中声明。- 返回值
- 如果信号的处理函数返回,
pause
函数返回 -1,并将errno
设置为EINTR
。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
// 提供给 signal 函数注册处理目标信号的函数声明
void sighandler(int sig);
int main(int argc, char const *argv[])
{
#if 0
printf("Process Running!\n");
pause();
printf("Process Continue Running!\n");
#endif
pid_t pid = -1;
pid = fork();
if (-1 == pid)
{
perror("fork failed!\n");
}
if (0 == pid)
{
// 信号处理注册函数对当前进程收到目标信号做对应的处理
// 当前注册处理 SIGALRM ,采用的处理方式/函数 sighandler
signal(SIGALRM, sighandler);
int count = 10;
while (count > 0)
{
printf("Son Process Running! Exit in %d Second!\n", count);
sleep(1);
count -= 1;
if (5 == count)
{
// 暂停,当前进程进入睡眠状态
pause();
}
}
}
else
{
int count = 10;
while (count > 0)
{
printf("Father Process Running! Exit in %d Second!\n", count);
sleep(1);
count -= 1;
}
// 利用 kill 向指定进程发送 SIGALRM 信号
kill(pid, SIGALRM);
sleep(10);
}
return 0;
}
void sighandler(int sig)
{
// alarm 对当前进程进行唤醒操作。
alarm(1);
}
5.6 signal 信号注册处理函数
函数文档
#include <signal.h> /* 系统中预定义的一个 类型重命名,sighandler_t 是一个函数指针,对应的函数类型要求 1. 返回值为 void 类型 2. 函数的参数参数列表为 int 类型,参数 int 类型对应的是当前 signal 注册的捕获的信号数 据 signal 函数底层可以认为有一个类似于 map 的数据结构,信号对应 键值,处理函数对应 实值 */ typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);
- 函数功能:
- 用于设置信号处理方式,也就是当进程接收到特定信号时所采取的动作。
- 参数解释:
signum
:这是一个整数类型的参数,表示要设置处理方式的信号编号。常见的信号如SIGINT
(编号 2,通常由用户按下Ctrl + C
产生)、SIGTERM
(编号 15,用于请求进程正常终止)等。handler
:它是一个函数指针,指向信号处理函数。这个参数有三种常见的取值情况:
- 自定义信号处理函数:用户可以定义自己的函数来处理特定信号,函数的原型必须是
void function_name(int)
,其中int
类型的参数用于接收信号编号。SIG_IGN
:这是一个预定义的常量,表示忽略该信号。当设置为SIG_IGN
时,进程接收到指定信号后将不会有任何响应。SIG_DFL
:同样是一个预定义的常量,表示使用信号的默认处理方式。不同的信号有不同的默认处理行为,例如SIGINT
的默认行为是终止进程、- 返回值:
- 若函数调用成功,返回之前该信号的处理函数指针。
- **若调用失败,返回 **
SIG_ERR
,并设置errno
以指示错误类型。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
/*
系统中预定义的一个 类型重命名,sighandler_t 是一个函数指针,对应的函数类型要求
1. 返回值为 void 类型
2. 函数的参数参数列表为 int 类型,参数 int 类型对应的是当前 signal 注册的捕获的信号数
据
signal 函数底层可以认为有一个类似于 map 的数据结构,信号对应 键值,处理函数对应 实值
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
*/
void signal_handler(int signal_num);
int main(int argc, char const *argv[])
{
pid_t pid = -1;
pid = fork();
if (-1 == pid)
{
perror("fork Failed!\n");
}
if (0 == pid)
{
/*
以下函数相当于注册了三个信号,当前进程如果接收到对应的三个信号,按照对应信号方式处理
*/
signal(SIGINT, signal_handler);
signal(SIGQUIT, signal_handler);
signal(SIGALRM, signal_handler);
// SIGKILL 无法被忽略
// signal(SIGKILL, SIG_IGN);
printf("Son Process Running!\n");
pause();
printf("Son Process Continue Running!\n");
pause();
printf("Son Process Continue Running!\n");
pause();
printf("Son Process Continue Running!\n");
}
else
{
printf("Parent Processs Running!\n");
int count = 3;
// 发送 SIGKILL 信号
// kill(pid, SIGKILL);
while (count > 0)
{
sleep(5);
switch (count)
{
case 1:
kill(pid, SIGINT);
break;
case 2:
kill(pid, SIGQUIT);
break;
case 3:
kill(pid, SIGALRM);
break;
default:
printf("All Signal Killed!\n");
break;
}
count -= 1;
}
printf("Parent Processs All Signal Killed!\n");
}
return 0;
}
void signal_handler(int signal_num)
{
if (signal_num == SIGINT)
{
printf("Get SIGINT!\n");
}
else if (signal_num == SIGQUIT)
{
printf("Get SIGQUIT!\n");
}
else if (signal_num == SIGALRM)
{
printf("Get SIGALRM!\n");
}
}
6. 信号集
6.1 信号集概述
信号的集合 (Signal Set),信号集可以存储多个信号,方便进程进行批量处理。
对应的数据类型:sigset_t
常用函数:
- sigemptyset
- sigfillset
- sigaddset
- sigdelset
- sigismember
6.2 sigset_t 类型概述
// 假设系统最多支持 64 个信号
#define _NSIG 64
typedef struct {
unsigned long sig[_NSIG / (8 * sizeof(unsigned long))];
} sigset_t;
可以根据当前系统的情况进行底层占用内存空间数据判断
- 如果是 64 位系统,底层 sig 数组,占用内存 8 个字节,对应 64 bit 位
- unsigned long sig[_NSIG / (8 * sizeof(unsigned long))];
- unsigned long sig[64 / 8 * 8 ] == > unsigned long sig[1]
- 如果是 32 位系统,底层 sig 数组,同样占用内存 8 个字节,对应 64 bit 位
- unsigned long sig[_NSIG / (8 * sizeof(unsigned long))];
- 32 位环境下,long 类型实际对应数据类型为 int 类型
- unsigned int sig[64 / 8 * 4] = int sig[2]
6.3 sigemptyset
函数文档
#include <signal.h> int sigemptyset(sigset_t *set);
- **功能: **
sigemptyset
函数用于将指定的信号集set
初始化为空集,也就是该信号集中不包含任何信号。- **sigset_t 底层 sig 数组所有的二进制位都是 0 **
- 参数:
sigset_t *set
函数所需参数是当前 sigset_t 结构体指针。- 返回值:
- 成功返回 0,失败返回 -1
6.4 sigfillset
函数文档
#include <signal.h> int sigfillset(sigset_t *set);
- 功能:
sigfillset
函数用于将指定的信号集set
初始化为满集,也就是该信号集中包含系统支持的信号。- sigset_t 底层 sig 数组所有的二进制位都是 1
- 参数:
sigset_t *set
函数所需参数是当前 sigset_t 结构体指针。- 返回值:
- 成功返回 0,失败返回 -1
6.5 sigaddset
函数文档
#include <signal.h> int sigaddset(sigset_t *set, int signum);
- 功能:
- 在当前 sigset_t 信号集中,添加指定信号
- 注意,信号集中存储的信号不可以重复,因为底层数据为 64 个 bit 位,对应系统的 64 个信号,类似于c++中的set
- 参数:
sigset_t *set
函数所需参数是当前 sigset_t 结构体指针。int signum
目标添加到当前信号集的信号- 返回值
- 当前信号不存在,且添加成功返回 0
- 添加失败返回 -1
6.6 sigdelset
函数文档
#include <signal.h> int sigdelset(sigset_t *set, int signum);
- 功能:
- 在当前 sigset_t 信号集中,删除指定信号
- 参数:
sigset_t *set
函数所需参数是当前 sigset_t 结构体指针。int signum
目标删除到当前信号集的信号- 返回值
- 删除成功返回 0
- 删除失败返回 -1
6.7 sigismember
函数文档
#include <signal.h> int sigismember(const sigset_t *set, int signum);
- 功能:
- 在当前 sigset_t 信号集中,判断指定信号是否存在
- 参数:
sigset_t *set
函数所需参数是当前 sigset_t 结构体指针。int signum
目标搜索的信号- 返回值
- 存在返回 1
- 不存在返回 0
- 错误/失败返回 -1
#define _POSIX_SOURCE
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
/*
sigemptyset
sigfillset
sigaddset
sigdelset
sigismember
*/
int main(int argc, char const *argv[])
{
sigset_t sigset;
int ret = 0;
// 空信号集
sigemptyset(&sigset);
// sigaddset(&sigset, SIGINT);
sigaddset(&sigset, SIGKILL);
printf("sigset : %ld\n", sigset);
ret = sigismember(&sigset, SIGINT);
if (1 == ret)
{
printf("SIGINT Exist!\n");
}
else
{
printf("SIGINT not Exist!\n");
}
sigset_t sigset1;
sigfillset(&sigset1);
sigdelset(&sigset1, SIGALRM);
ret = sigismember(&sigset1, SIGALRM);
if (1 == ret)
{
printf("SIGALRM Exist!\n");
}
else
{
printf("SIGALRM not Exist!\n");
}
return 0;
}
- 感谢你赐予我前进的力量