什么是进程间通信
Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)。

img

什么是管道

​ 管道是一种最基本的IPC机制,也称匿名管道,应用于有血缘关系的进程之间,完成数据传递。调用pipe函数即可创建一个管道。

img

管道有以下特性:

  • 管道的本质是一块内核缓冲区
  • 由两个文件描述符引用,一个表示读端,一个表示写端。
  • 规定数据从管道的写端流入管道,从读端流出。
  • 当两个进程都终结的时候,管道也自动消失。
  • 管道的读端和写端默认都是阻塞的。

管道创建步骤:

  • 父进程调用pipe函数创建管道,得到两个文件描述符fd[0]和fd[1],分别指向管道的读端和写端。

img

  • 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管。

img

  • 父进程关闭管道读端,子进程关闭管道写端。父进程可以向管道中写入数据,子进程将管道中的数据读出,这样就实现了父子进程间通信。

img

相应代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include<iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
using namespace std;

int main()
{
//创建管道
//int pipe(int pipefd[2]);
int fd[2];
int ret = pipe(fd);
if(ret<0)
{
perror("pipe error");
return -1;
}

//创建子进程
pid_t pid = fork();
if(pid<0)
{
perror("fork error");
return -1;
}
else if(pid>0)
{
//关闭读端
close(fd[0]);
sleep(5);
write(fd[1], "hello world", strlen("hello world"));

wait(NULL);//保证父进程阻塞,子进程先退出
}
else
{
//关闭写端
close(fd[1]);

char buf[64];
memset(buf, 0x00, sizeof(buf));
int n = read(fd[0], buf, sizeof(buf));
cout << "read over, n=" << n << " buf=" << buf << endl;

}

return 0;
}

结果如下:

img

子进程读到了父进程发送的数据!!!

原文链接:https://blog.csdn.net/m0_60663280/article/details/121309197

扩展:父进程创建三个子进程并且通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <strings.h>
main(){
int i, r, p1, p2, p3, fd[20];
char buf[50], s[50];
pipe(fd); //父进程建立管道
while((p1 = fork()) == -1); //创建子进程P1,失败时循环
if(p1 == 0){ //由子进程P1返回,执行子进程P1
lockf(fd[1], 1, 0); //加锁锁定写入端
sprintf(buf, "child process P1 is sending messages!\n");
printf ("child process P1!\n");
write(fd[1], buf, 50); //把buf中的50个字符写入管道
sleep(5); //睡眠5秒,让父进程读
lockf (fd[1], 0, 0); //释放管道写入端
exit(0); //关闭
}else{ //从父进程返回,执行父进程
while((p2 = fork()) == -1);
if(p2 == 0){
lockf(fd[1], 1, 0);
sprintf(buf, "child process P2 is sending messages!\n");
printf ("child process P2!\n");
write(fd[1], buf, 50);
sleep(5);
lockf (fd[1], 0, 0);
exit(0);
}else{
while((p3 = fork()) == -1);
if(p3 == 0){
lockf(fd[1], 1, 0);
sprintf(buf, "child process P3 is sending messages!\n");
printf ("child process P3!\n");
write(fd[1], buf, 50);
sleep(5);
lockf (fd[1], 0, 0);
exit(0);
}
wait(0);
if((r = read(fd[0], s, 50)) == -1){
printf ("can`t read pipe!\n");
}else{
printf ("%s\n", s);
}
wait(0);
if((r = read(fd[0], s, 50)) == -1){
printf ("can`t read pipe!\n");
}else{
printf ("%s\n", s);
}
wait(0);
if((r = read(fd[0], s, 50)) == -1){
printf ("can`t read pipe!\n");
}else{
printf ("%s\n", s);
}
exit(0);
}
}
}

img

显示的时候先显示child process P1!过了五秒才会显示chilld process P2!以及child process P1 is sending message!这是因为中间有一个sleep()函数的关系,以及lockf加锁使得其他进程不能插进来并发执行

原文链接:https://blog.csdn.net/bxy0806/article/details/121211176

如果将lockf函数去掉的话

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <strings.h>
main(){
int i, r, p1, p2, p3, fd[20];
char buf[50], s[50];
pipe(fd); //父进程建立管道
while((p1 = fork()) == -1); //创建子进程P1,失败时循环
if(p1 == 0){ //由子进程P1返回,执行子进程P1
//lockf(fd[1], 1, 0); //加锁锁定写入端
sprintf(buf, "child process P1 is sending messages!\n");
printf ("child process P1!\n");
write(fd[1], buf, 50); //把buf中的50个字符写入管道
sleep(5); //睡眠5秒,让父进程读
//lockf (fd[1], 0, 0); //释放管道写入端
exit(0); //关闭
}else{ //从父进程返回,执行父进程
while((p2 = fork()) == -1);
if(p2 == 0){
//lockf(fd[1], 1, 0);
sprintf(buf, "child process P2 is sending messages!\n");
printf ("child process P2!\n");
write(fd[1], buf, 50);
sleep(5);
//lockf (fd[1], 0, 0);
exit(0);
}else{
while((p3 = fork()) == -1);
if(p3 == 0){
//lockf(fd[1], 1, 0);
sprintf(buf, "child process P3 is sending messages!\n");
printf ("child process P3!\n");
write(fd[1], buf, 50);
sleep(5);
//lockf (fd[1], 0, 0);
exit(0);
}
wait(0);
if((r = read(fd[0], s, 50)) == -1){
printf ("can`t read pipe!\n");
}else{
while((p3 = fork()) == -1);
if(p3 == 0){
//lockf(fd[1], 1, 0);
sprintf(buf, "child process P3 is sending messages!\n");
printf ("child process P3!\n");
write(fd[1], buf, 50);
sleep(5);
//lockf (fd[1], 0, 0);
exit(0);
}
wait(0);
if((r = read(fd[0], s, 50)) == -1){
printf ("can`t read pipe!\n");
}else{
printf ("%s\n", s);
}
wait(0);
if((r = read(fd[0], s, 50)) == -1){
printf ("can`t read pipe!\n");
}else{
printf ("%s\n", s);
}
wait(0);
if((r = read(fd[0], s, 50)) == -1){
printf ("can`t read pipe!\n");
}else{
printf ("%s\n", s);
}
exit(0);
}
}
}


如果不加锁的话,迟来的process P2/P1/P3先后同时显示出来,由于sleep的关系再过5s显示下面剩下的语句

进程控制lockf()详解

lockf()函数

利用系统调用lockf(fd,mode,size),对指定区域(有size指示)进行加锁或解锁,以实现进程的同步或互斥。

其中,fd是文件描述字;

mode是锁定方式,mode=1表示加锁,mode=0表示解锁;

size是指定文件fd的指定区域,用0表示从当前位置到文件结尾。

注:有些Linux系统是locking(fd,mode,size)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main(){
int pid ,i;
if((pid = fork())<0){
printf("child fails to create\n");
return 0;
}
else if (pid ==0){
lockf(1,1,0);
for(i = 0;i< 5;i++)
printf("This is child (pid = %d)process:b\n",getpid());
lockf(1,0,0);
return 0;
}
else{
lockf(1,1,0);
for(i =0;i<5;i++)
printf("Parent process:a\n");
lockf(1,0,0);
}
}

img

首先是在父进程中创建了一个子进程,子进程需要输出五次文本信息,并且在输出语句执行之前,将标准输出设备锁住,在输出语句执行之后,将标准输出设备解锁。同样,父进程也是要输出五次文本信息,对标准输出的操作同子进程。这样做能够防止父进程和子进程竞争标准输出(屏幕)资源,即如果父进程正在进行屏幕打印操作,则子进程将无法获得屏幕,即不可能在在输出“Parents Process:a”的时候,中间穿插有“This is child process :b”。从运行结果的截图中可以看到,实验结果和我的分析是相吻合的,先输出五条Parent,再输出五条Child,中间不会有交叉。为了进一步验证我的分析,从反面论证,我把父进程和子进程的lockf语句注释掉,期望运行的时候会看到Parent和Child交叉出现。但是运行了很多次,结果始终和有lockf语句时一样。经过分析,感觉是因为自己电脑运行速度太快了,输出五行文本信息,只需要极少的时间即可完成,因此不用等到子进程去争夺I/O资源,父进程已经输出完毕,把屏幕给释放了。为了验证这个猜想,我逐步增加输出的行数,当增加到500行时,发现了Parent和Child交叉出现的现象,如下图所示。

在这里插入图片描述

原文链接:https://blog.csdn.net/weixin_43074474/article/details/103404382