linux学习:进程通信(管道+信号)

news2025/2/5 17:52:20

目录

管道

无名管道(PIPE)

特征 

例子,通过pipe向父进程发送一段数据

​编辑

有名管道(FIFO)

特征

例子

 注意

信号

特征

api 

例子1

例子2

例子3

例子4

信号相关的内核数据结构


管道

管道分为无名管道和有名管道

管道,那么可以想象他就像一根水管,连接两个进程,一个进 程要给另一个进程数据,就好像将水灌进管道一样,另一方就可以读取出来了,反过来也一 样

无名管道(PIPE)

常用于一对一的亲缘进程间通信的方式

特征 

  •  没有名字,因此无法使用 open( )。
  • 只能用于亲缘进程间(比如父子进程、兄弟进程、祖孙进程……)通信。
  • 半双工工作方式:读写端分开。
  • 写入操作不具有原子性,因此只能用于一对一的简单通信情形。
  • 不能使用 lseek( )来定位。
  • 只能在一个进程中被创建出来,然后通过继承的方式将他的文件描述符传递给子进程
  • 两个文件描述符,一个只能用来读,另一个只能用来写,“半双工”通信方式

例子,通过pipe向父进程发送一段数据

1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <unistd.h>
5 #include <errno.h>
6
7 int main(int argc, char **argv)
8 {
9      int fd[2]; // 1.用来存放 PIPE 的两个文件描述符
10
11     if(pipe(fd) == -1) // 2.创建 PIPE,并将文件描述符放进 fd[2]中
12     {
13     perror("pipe()");
14         exit(1);
15     }
16
17     pid_t x = fork(); // 3.创建一个子进程,他将会继承 PIPE 的描述符
18
19     if(x == 0) // 子进程
20     {
21         char *s = "hello, I am your child\n";
22         write(fd[1], s, strlen(s)); // 4.通过写端 fd[1]将数据写入 PIPE
23     }
24
25     if(x > 0) // 父进程
26     {
27         char buf[30];
28         bzero(buf, 30);
29
30         read(fd[0], buf, 30); // 5.通过读端 fd[0]将数据从 PIPE 中读出
31         printf("from child: %s", buf);
32     }
33
34     close(fd[0]); // 关闭文件描述符
35     close(fd[1]);
36     return 0;
37 }

有名管道(FIFO)

存在于文件系统之中,提供写入原子性特征

特征

  • 有名字,存储于普通文件系统之中。
  • 任何具有相应权限的进程都可以使用 open( )来获取 FIFO 的文件描述符。
  • 跟普通文件一样:使用统一的 read( )/write( )来读写。
  • 跟普通文件不同:不能使用 lseek( )来定位,原因同 PIPE。
  • 具有写入原子性,支持多写者同时进行写操作而数据不会互相践踏。
  • First In First Out,最先被写入 FIFO 的数据,最先被读出来。

例子

示两个普通进程(Jack 和 Rose)如何通过 FIFO 互相传递信息:Jack 从键盘接收一段输入并发送给 Rose,Rose 接收到数据之后将 其显示到屏幕上

.h文件  定义管道名字

1 #ifndef _HEAD4FIFO_H_
2 #define _HEAD4FIFO_H_
3
4 #include <stdio.h>
5 #include <unistd.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <fcntl.h>
9
10 #include <sys/stat.h>
11 #include <sys/types.h>
12
13 #define FIFO "/tmp/fifo4test" // 有名管道的名字
14
15 #endif

Jack.c文件   写数据

1 #include "head4fifo.h" 2
3 int main(int argc, char **argv)
4 {
5     if(access(FIFO, F_OK))
6     {
7         mkfifo(FIFO, 0644);
8     }
9
10     int fifo = open(FIFO, O_WRONLY); // 以只写方式打开 FIFO
11
12     char msg[20];
13     bzero(msg, 20);
14
15     fgets(msg, 20, stdin);
16     int n = write(fifo, msg, strlen(msg)); // 将数据写入 FIFO
17
18     printf("%d bytes have been sended.\n", n);
19     return 0;
20 }

Rose.c   读数据

1 #include "head4fifo.h" 2
3 int main(int argc, char **argv)
4 {
5     if(access(FIFO, F_OK))
6     {
7         mkfifo(FIFO, 0644);
8     }
9
10     int fifo = open(FIFO, O_RDONLY); // 以只读方式打开管道
11
12     char msg[20];
13     bzero(msg, 20);
14
15     read(fifo, msg, 20); // 将数据从 FIFO 中读出
16     printf("from FIFO: %s", msg);
17
18     return 0;
19 }

 注意

  • 代码第 5 行中的函数 access( )通过指定参数 F_OK 可用来判断一个文件是否存在, 另外还可以通过别的参数来判断文件是否可读、是否可写、是否可执行等
  • 当刚开始运行 Jack 而尚未运行 Rose,或者刚开始运行 Rose 而尚未运行 Jack 时, open 函数会被阻塞,因为管道文件(包括 PIPE、FIFO、SOCKET)不可以在只有读端或 者只有写端的情况下被打开
  • 当 Jack 已经打开但还没写入数据之前,Rose 将在 read( )上阻塞睡眠,直到 Jack 写入数据完毕为止。因为缺省状态下是以阻塞方式读取数据的,可以使用 fcntl( )来使得 fifo 变成非阻塞模式
  • 不仅打开管道会有可能发生阻塞,在对管道进行读写操作时也有可能发生阻塞

可以多个进程写入管道,由管道一一写到日志文件,例如日志文件

信号

唯一一种异步通信方式

信号是一种比较特别的 IPC,大部分的信号是异步的,换句话讲:一般情况下,进程什 么时候会收到信号、收到什么信号是无法事先预料的(除了某几个特殊的信号之外),信号 的到来就像你家门铃的响起一样,你不知道他什么时候会响

特征

前面 31 个信号都有一个特殊的名字,对应 一个特殊的事件,比如 1 号信号 SIGHUP(Signal Hang UP),表示每当系统中的一个 控制终端被关闭(即挂断,hang up)时,即会产生这个信号,有时会将他们称为非实时 信号,这些信号都是从 Unix 系统继承下来的,他们还有个名称叫“不可靠信号”

  • 非实时信号不排队,信号的响应会相互嵌套。
  • 如果目标进程没有及时响应非实时信号,那么随后到达的该信号将会被丢弃。
  • 每一个非实时信号都对应一个系统事件,当这个事件发生时,将产生这个信号。
  • 如果进程的挂起信号中含有实时和非实时信号,那么进程优先响应实时信号并且会 从大到小依此响应,而非实时信号没有固定的次序

 后面的 31 个信号(从 SIGRTMIN[34] 到 SIGRTMAX[64])是 Linux 系统新增的 实时信号,也被称为“可靠信号”

  • 实时信号的响应次序按接收顺序排队,不嵌套。
  • 即使相同的实时信号被同时发送多次,也不会被丢弃,而会依次挨个响应。
  • 实时信号没有特殊的系统事件与之对应

 

 收到信号处理步骤

  1. 如果该信号被阻塞,那么将该信号挂起,不对其做任何处理,等到解除对其阻塞为 止。否则进入 2
  2. 如果该信号被捕捉,那么进一步判断捕捉的类型
    • 如果设置了响应函数,那么执行该响应函数
    • 如果设置为忽略,那么直接丢弃该信号
    • 否则进入 3
  3. 执行该信号的缺省动作

api 

 

目标进程必须先使用 signal( )来为某个信号设 置一个响应函数,或者设置忽略某个信号,才能改变信号的缺省行为,这个过程称为“信号 的捕捉”。注意,对一个信号的“捕捉”可以重复进行,signal( )函数将会返回前一次设 置的信号响应函数指针。对于所谓的信号响应函数的接口,规定必须是:void (*)(int);

如果一个进程临时不想响应某个或者某些信号,可以通过设置“阻塞掩码 (block mask)”来达到此目的。在设置信号的阻塞掩码时,并不一定要挨个地设置,而 是可以多个信号同时设置,这时就需要用到所谓的信号集

信号所携带的额外的数据是下面这个联合体

union sigval
{
    int sigval_int;
    void * sigval_prt;
}

利用 siqqueue( )发送信号的同时可以携带一个整型数据或者一个 void 型 指针,目标进程要想获取这些额外的数据

act 参数比较复杂,其类型结构体 struct sigaction 的定义如下

struct sigaction
{
    void (*sa_handler)(int);//标准信号响应函数指针
    void (*sa_sigaction)(int, siginfo_t *, void *);//扩展信号响应函数指针
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restorer)(void);
};

例子1

信号的“发送”和“捕捉”——在命令行给一个指定的进程发送某些信 号,观察设置信号响应的三种处理方式

1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <signal.h>
4
5 void f(int sig)
6 {
7     printf("catched a signal: %d\n", sig);
8 }
9
10 int main(int argc, char **argv)
11 {
12     signal(SIGHUP, SIG_IGN); // ① 设置 SIGHUP 响应动作为:忽略
13     signal(SIGINT, SIG_DFL); // ② 设置 SIGINT 响应动作为:缺省
14     signal(SIGQUIT, f); // ③ 设置 SIGQUIT 响应动作为:执行函数 f( )
15
16     printf("[%d]: I am waitting for some signal...\n", 17 getpid());
18     pause();// 暂停进程,静静等待信号的到来…
19
20     return 0;
21 }

例子2

信号的“阻塞”操作——子进程给父进程发送一个信号,父进程 先阻塞该信号,随后解除阻塞的过程

1 #include <stdio.h>
2 #include <signal.h>
3
4 void sighandler(int sig)
5 {
6     printf("[%d]: catch %d.\n", getpid(), sig);
7 }
8
9 int main(int argc, char **argv)
10 {
11     pid_t x = fork();
12
13     if(x > 0) // 父进程
14     {
15         signal(SIGINT, sighandler); // 设置 SIGINT 的响应函数
16
17         sigset_t sigmask;
18         sigemptyset(&sigmask);
19         sigaddset(&sigmask, SIGINT); // 将 SIGINT 添加到信号集中
20
21         #ifdef TEST
22             printf("[%d]: block SIGINT...\n", getpid());
23             sigprocmask(SIG_BLOCK, &sigmask, NULL); // 设置阻塞
24         #endif
25         sleep(5); // 睡眠 5 秒,信号在此期间到来。
26         #ifdef TEST
27             printf("[%d]: unblock SIGINT...\n", getpid());
28             sigprocmask(SIG_UNBLOCK, &sigmask, NULL); // 解除阻塞
29         #endif
30         wait(NULL); // 让子进程先退出,从而正确显示 Shell 命令提示
30     }
31
32     if(x == 0)
33     {
34         sleep(1); // 睡眠 1 秒钟,保证父进程做好准备工作
35         if(kill(getppid(), SIGINT) == 0) // 给父进程发送信号 SIGINT
36         {
37             printf("[%d]: SIGINT has been sended!\n", 38 getpid());
39         }
40     }
41
42     return 0;
43 }

例子3

“实时信号”和“非实时信号”的区别——进程 machine_gun 向 target “开火”:将所有信号(除了 SIGKILL 和 SIGSTOP)“同时”发送给 target,观察进程 怎么处理这些信号。为了体现 target“同时”收到了这些信号,可以让其先对所有代码阻 塞一段时间,等收完全部信号之后,再同时一并放开阻塞

target.c

1 #include <stdio.h>
2 #include <unistd.h>
3 #include <signal.h>
4
5 void sighandler(int sig)
6 {
7     fprintf(stderr, "catch %d.\n", sig);
8 }
9
10 int main(int argc, char **argv)
11 {
12     sigset_t sigs;
13     sigemptyset(&sigs);
14
15     int i;
16     for(i=SIGHUP; i<=SIGRTMAX; i++)
17     {
18         if(i == SIGKILL || i == SIGSTOP)
19             continue;
20
21         signal(i, sighandler); // 为信号 i 设置响应函数
22         sigaddset(&sigs, i); // 将信号 i 添加到信号集中
23     }
24
25     printf("[%d]: blocked signals for a while...\n", getpid());
26     sigprocmask(SIG_BLOCK, &sigs, NULL); // 阻塞所有信号
27     sleep(10);
28
29     printf("[%d]: unblocked signals.\n", getpid());
30     sigprocmask(SIG_UNBLOCK, &sigs, NULL); // 放开所有阻塞
31
32     return 0;
33 }

machine_gun,c

1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <signal.h>
5
6 int main(int argc, char **argv)
7 {
8     if(argc != 2)
9     {
10         printf("Usage: %s <target-PID>\n", argv[0]);
11     }
12     int i;
13     for(i=SIGHUP; i<=SIGRTMAX; i++)
14     {
15         if(i == SIGKILL || i == SIGSTOP || // 不可捕捉的信号不发
16             i == 32 || i == 33) // 未定义的信号不发
17             continue;
18
19         kill(atoi(argv[1]), i); // 向指定进程发送信号 i
20     }
21
22     return 0;
23 }

输出结果中,省略的部分是严格从大到小的实时信号,可见如果一个进程如果同 时收到多个实时信号时,他们的响应次序是按照信号值由大到小排队的。下半部分从 1 到 31 的信号值是无序的,说明非实时信号的响应是不排队的,还注意到 target 没有打印 18 号信号!说明非实时信号是不可靠的,在传递的过程中有可能被丢弃 

例子4

进程间如何使用“扩展信号响应函数”来通信——信号发送者携带额外 的数据,目标进程获取这些数据

1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <strings.h>
4 #include <unistd.h>
5 #include <signal.h>
6
7 void sighandler(int sig, siginfo_t *sinfo, void *p)
8 {
9     printf("catch %d.\n", sig);
10
11     if(sinfo->si_code == SI_QUEUE) //判断信号是否由 sigqueue 发送
12     {
13         printf("%d\n", sinfo->si_int);
14     }
15 }
17 int main(int argc, char **argv)
18 {
19     pid_t x = fork();
20
21     if(x > 0)
22     {
23         struct sigaction act;
24         bzero(&act, sizeof(act));
25         act.sa_sigaction = sighandler;
26         act.sa_flags |= SA_SIGINFO; // 该选项确保使用扩展响应函数
27         sigaction(SIGINT, &act, NULL); // 捕捉 SIGINT
28
29         pause(); // 坐等信号的到来……
30     }
31
32     if(x == 0)
33     {
34         sleep(1);
35
36         union sigval data;
37         data.sival_int = 100; // 额外数据
38         sigqueue(getppid(), SIGINT, data); // 给父进程发 SIGINT
39     }
40
41     return 0;
42 }

信号相关的内核数据结构

  • 每一个线程都使用一个 PCB(即 task_struct)来表示,因此 pending(不是指针) 就是一个线程单独私有的,当我们使用 pthread_kill( )给一个指定的线程发送某信号时, 这些信号将会被存储在这个链队列中
  • signal 是一个指向线程共享的信号挂起队列相关结构体的指针,实际上,一个线程 组(即一个进程)中的所有线程中的signal指针都指向同一个结构体,当我们使用诸如kill( ) 来给一个进程发送某信号的时候,这些信号将会被存储在 shared_pending 这个线程共享 的链队列中
    • 如果一个进程中有超过 1 条线程,那么这些共享的挂起信号将会被随机的某条线程响 应,为了能确保让一个指定的线程响应来自进程之外的、发送给整个进程的某信号,一般的 做法如下
    • 除 了指 定要 响 应某 信号 的线 程 外, 其他 线 程对 这些 信号 设 置阻 塞。 即 使 用 sigprocmask( )或者 pthread_sigmask( )将这些需要阻塞的信号添加到信号阻塞掩码 blocked 当中。
  • sighand 也是一个指针,因此也是进程中的所有线程共享的,他指向跟信号响应函 数相关的数据结构,结构体 struct sighand_struct{}中的数组 action 有 64 个元素,一 一对应 Linux 系统支持的 64 个信号(其中 0 号信号是测试用的,32 号和 33 号信号保留), 每一个元素是一个 sigaction{}结构体,其成员就是标准 C 库函数 sigaction( )中的第二 个参数的成员,可见,该函数相当于是一个应用层给内核设置信号响应策略的窗口

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1598918.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

应对电力行业勒索攻击,看亚信安全防勒索方案的3大技战法

来源&#xff1a;中国电力大数据创新联盟会刊 近年来&#xff0c;国际、国内的勒索事件频发&#xff0c;勒索已经成为具备国家背景的团伙组织危害行为&#xff0c;各关基单位需要在原有的实战化安全攻防的基础之上&#xff0c;专项针对数据勒索进行防护体系建设&#xff0c;建立…

头文件修改触发重新构建依赖于此的文件 gcc 标志的使用 -MF -MD -MP

1&#xff0c;文件布局 2&#xff0c;头文件重构建 Makefile TARGET : libicarithmetic.soDEBUG_FLAGS : -O3 HEADER_FLAGS : -MD -MF $*.d -MP all: $(TARGET)SRC_C : \src/add.c \src/circ.c \src/div.c \src/mul.c \src/sub.cOBJ_C : $(SRC_C:.c.c.o) DEP_C : $(SRC_C:.c.…

单细胞分析|映射和注释查询数据集

reference映射简介 在本文中&#xff0c;我们首先构建一个reference&#xff0c;然后演示如何利用该reference来注释新的查询数据集。生成后&#xff0c;该reference可用于通过cell类型标签传输和将查询cell投影到reference UMAP 等任务来分析其他查询数据集。值得注意的是&am…

腾讯云幻兽帕鲁一键开服教程

腾讯云作为领先的云计算服务提供商&#xff0c;为广大用户提供了便捷、高效的游戏服务器搭建解决方案。其中&#xff0c;幻兽帕鲁一键开服功能&#xff0c;更是让游戏开服变得简单易懂。本文将为大家详细介绍腾讯云幻兽帕鲁一键开服的步骤&#xff0c;帮助大家轻松搭建自己的游…

net 5+ 服务创建

worker Service创建 在新版本服务中的创建&#xff0c;名称是Worker Service&#xff0c;从.NET Core 2.1开始&#xff0c;就可以使用辅助角色服务模板了&#xff0c;即Worker Service。它可以编写长期服务&#xff0c;作为 Windows 服务进行托管&#xff0c;还支持跨平台部署…

做抖音小店保证金可以不交吗?不交保证金,会有什么后果?

哈喽~我是电商月月 说到最赚钱的软件&#xff0c;大家第一个想的就是抖音了&#xff0c;很多不想直播&#xff0c;但又想在抖音上赚钱的人就选择了抖音小店 但普通人创业&#xff0c;开店遇到的第一个困难就是类目保证金的缴纳 几千块钱虽然能拿的出来&#xff0c;但怕就怕在…

提升数据质量的三大要素:清洗prompt、数据溯源、数据增强(含Reviewer2和PeerRead)​

前言 我带队的整个大模型项目团队超过40人了&#xff0c;分六个项目组&#xff0c;每个项目组都是全职带兼职&#xff0c;且都会每周确定任务/目标/计划&#xff0c;然后各项目组各自做任务拆解&#xff0c;有时同组内任务多时 则2-4人一组 方便并行和讨论&#xff0c;每周文档…

初始监控工具--zabbix和安装

一、Zabbix 1. 监控系统的必要性 作为一个技术人员&#xff0c;需要会使用监控系统查看服务器状态以及网站流量指标&#xff0c;利用监控系统的数据去了解上线发布的结果和网站的健康状态。 2. 监控软件的作用 利用一个优秀的监控软件&#xff0c;我们可以: ● 通过一个友…

Qt 创建控件的两种方式

目录 Qt 创建控件的两种方式 通过ui界面创建控件 通过代码方式创建控件 Qt 创建控件的两种方式 通过ui界面创建控件 这里当然我们是需要先有一个项目的&#xff0c;按照我们之前创建项目的步骤&#xff0c;我们可以先创建一个 Widget 的项目&#xff0c;但是 MainWindow 也…

【代码随想录】day46:单词拆分,多重背包

单词拆分 1.把单词看成物品&#xff0c;字符串看成背包—>完全背包问题 2.排列问题&#xff1a;因为物品之间的组成顺序很重要&#xff0c;所以需要考虑顺序 。因为"apple" “apple” “pen” 或者 “pen” “apple” “apple” 是不可以的 d[j]:字符串长度为…

LD3320语音模块开发以及未来拿到其他模块的开发方式

当我们拿到一块模块进行开发的时候&#xff0c;一定要拿到配套的使用手册&#xff0c;不然在短时间内根本下不了手 一、使用source Insight来阅读源码 1.建立文件夹 2. 在source Insight放入该文件 3.添加源码 4.解决Source Insight乱码的问题 5.让各个代码模块之间有关联 二、…

数据结构面试题(常见概念题)

什么是 AVL 树&#xff1f; AVL 树是平衡二叉查找树&#xff0c;增加和删除节点后通过树形旋转重新达到平衡。右旋是以某个节点为中心&#xff0c;将它沉入当前右子节点的位置&#xff0c;而让当前的左子节点作为新树的根节点&#xff0c;也称为顺时针旋转。同理左旋是以某个节…

SpringBoot通过UUid实现文件上传接口及问题解决

在controller中&#xff0c;添加对应的方法体&#xff1a; PostMapping("/upload")ResponseBodypublic ApiRestResponse upload(HttpServletRequest httpServletRequest, RequestParam("file")MultipartFile file) throws IOException {String fileName f…

国外新闻媒体稿件宣发:海外pr发稿干货秘籍-大舍传媒

一、了解目标市场和受众 发布新闻稿件的首要步骤是了解你的目标市场和受众。在撰写新闻稿件之前&#xff0c;你需要研究你的目标市场&#xff0c;了解他们的需求、兴趣和习惯。你还需要了解你的受众&#xff0c;包括他们的年龄、性别、职业、地理位置和媒体使用习惯等。这些信…

基于springboot实现在线考试系统设计【项目源码+论文说明】

基于springboot实现在线考试管理系统演示 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了基于JavaWeb技术的在线考试系统设计与实现的开发全过程。通过分析基于Java Web技术的在线考试系统设计与实现管理的不…

数据结构复习指导之绪论(算法的概念以及效率的度量)

文章目录 绪论&#xff1a; 2.算法和算法评价 知识总览 2.1算法的基本概念 知识点回顾与重要考点 2.2算法效率的度量 知识总览 1.时间复杂度 2.空间复杂度 知识点回顾与重要考点 归纳总结 绪论&#xff1a; 2.算法和算法评价 知识总览 2.1算法的基本概念 算法( Al…

Spring Boot 整合 Redis 集群详解

前言&#xff1a; 项目中需要使用 Redis 做缓存数据库&#xff0c;本文分享一下 Spring Boot 项目集成 Redis 的过程以及踩过的坑。 Spring Boot 集成 Redis 可以分为三大步&#xff0c;如下&#xff1a; 在 proerties 或者 yml 文件中添加 redis 和 lettuce 配置。项目 pom…

[已解决]问题:root.users.hdfs is not a leaf queue

问题&#xff1a;root.users.hdfs is not a leaf queue CDH集群报错&#xff1a; Exception in thread “main” org.apache.hadoop.yarn.exceptions.YarnException: Failed to submit application_1713149630679_0005 to YARN : root.users.hdfs is not a leaf queue 思路 …

mybatis-puls 条件分析插件

一&#xff0c;能做什么 我们在平时的开发中,会遇到一些慢sql. MP也提供了性能分析插件,如果超过这个时间就停止运行! 二&#xff0c;如何实现 2.1引入条件分析插件 //性能分析BeanProfile({"dev","test"}) //设置dev 和 test环境开启public Performanc…

RAG学习笔记系列(三)

RAG 中的 Agent Agent Agent 是使用 LLM 进行推理&#xff0c;为其提供一系列工具完成一个任务。 工具包括一系列定义好的函数&#xff0c;比如&#xff1a;代码函数、外部API、其他的 Agent。 OpenAI 助理 OpenAI 助理基本上实现了很多工具供 LLM 选择&#xff0c;比如&a…