首页 > 系统相关 >Linux系统编程——进程间通信

Linux系统编程——进程间通信

时间:2022-11-08 20:05:09浏览次数:47  
标签:int mmap 编程 间通信 mem fd Linux 进程 include

在学习Linux系统编程总结了笔记,并分享出来。

09-linux-day06(进程间通信)

目录:一、学习目标二、进程通信——管道1、管道的概念2、管道通信举例3、父子进程实现ps、grep命令4、ps、grep命令实现问题解决5、管道的读写行为6、管道大小和优劣三、进程通信——FIFO1、fifo实现通信写端2、fifo使用注意事项四、进程通信——mmap1、mmap映射开始2、mmap注意事项3、mmap实现父子进程通信4、匿名映射5、mmap实现无血缘关系进程通信6、mmap(MAP_SHSRED)再次说明五、进程通信——信号1、信号的概念

一、学习目标

1、熟练使用pipe进行父子进程间通信

2、熟练使用pipe进行兄弟进程间通信

3、熟练使用fifo进行无血缘关系的进程间通信

4、熟练掌握mmap函数的使用

5、掌握mmap创建匿名映射区的方法

6、使用mmap进行有血缘关系的进程间通信

7、使用mmap进行无血缘关系的进程间通信

二、进程通信——管道


》IPC方法:进程间通信,通过内核提供的缓冲区进行数据交换的机制。

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



Linux系统编程——进程间通信_操作系统


在进程间完成数据传递需要借助操作系统提供特殊的方法,如:文件、管道、信号、共享内存、消息队列、套接字、命名管道等。随着计算机的蓬勃发展,一些方法由于自身设计缺陷被淘汰或者弃用。现今常用的进程间通信方式有:


1)pipe 管道 (只能有血缘关系的,使用最简单)2)fifo 信号 (开销最小)3)mmap 共享映射区 (无血缘关系) 速度最快
4)本地socket 本地套接字 (最稳定)
5)信号(携带信息量最小)
6)共享内存
7)消息队列


1、管道的概念

管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。有如下特质:
1. 其本质是一个伪文件(实为内核缓冲区)
2. 由两个文件描述符引用,一个表示读端,一个表示写端。
3. 规定数据从管道的写端流入管道,从读端流出。
管道的原理: 管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。



Linux系统编程——进程间通信_多进程_02





Linux系统编程——进程间通信_unix_03



管道的局限性:
① 数据一旦被读走,便不在管道中存在,不可反复读取。
②由于管道采用半双工通信方式。因此,数据只能在一个方向上流动。
③ 只能在有公共祖先的进程间使用管道。
常见的通信方式有,单工通信(广播)、半双工通信(对讲机)、全双工通信(打电话)。

2、管道通信举例

》创建管道

man pipe

int pipe(int pipefd[2]);

  pipefd读写文件描述符,0代表读,1代表写

  返回值:失败返回-1,成功返回0

函数调用成功返回r/w两个文件描述符。无需open,但需手动close。规定:fd[0] → r; fd[1] → w,就像0对应标准输入,1对应标准输出一样。向管道文件读写数据其实是在读写内核缓冲区。

管道创建成功以后,创建该管道的进程(父进程)同时掌握着管道的读端和写端。如何实现父子进程间通信呢?通常可以采用如下步骤:



Linux系统编程——进程间通信_unix_04



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

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

3. 父进程关闭管道读端,子进程关闭管道写端。父进程可以向管道中写入数据,子进程将管道中的数据读出。由于管道是利用环形队列实现的,数据从写端流入管道,从读端流出,这样就实现了进程间通信。

练习:

>touch pipe.c

>vi pipe.c



1 #include<stdio.h>
2 #include<unistd.h>
3
4 int main()
5 {
6 int fd[2];//声明数组
7 pipe(fd);//创建管道
8 pid_t pid = fork();//创建子进程
9
10 if(pid == 0){
11 //son
12 sleep(3);
13 write(fd[1],"hello",5);
14 }
15 else if(pid > 0){
16 //parent
17 char buf[12]={0};
18 int ret = read(fd[0],buf,sizeof(buf));//read会等待write写
19 if(ret > 0){
20 write(STDOUT_FILENO,buf,ret);//相当于printf,打印
21 }
22 }
23 return 0;
24 }



>make

>./pipe

(父进程会等待,直到管道中有数据。read函数特性,读设备(如:管道),read默认是阻塞的,只要对方打开了管道,就一直(等待)阻塞,直到有水(数据)传来,等待接水(接收数据)。)

3、父子进程实现ps、grep命令



Linux系统编程——进程间通信_操作系统_05



>touch pipe_ps.c

>vi pipe_ps.c



1 #include<stdio.h>
2 #include<unistd.h>
3
4 int main()
5 {
6 int fd[2];
7 pipe(fd);
8 pid_t pid = fork();
9
10 if(pid == 0){
11 //son
12 //son --> ps
13 //1.先重定向
14 dup2(fd[1],STDOUT_FILENO);//标准输出重定向到管道写端
15 //2.execlp
16 execlp("ps","ps","aux",NULL);//ps aux标准输出
17 }
18 else if(pid > 0){
19 //parent
20 //1.先重定向,标准输入重定向到管道读端
21 dup2(fd[0],STDIN_FILENO);
22 //2.execlp
23 execlp("grep","grep","bash",NULL);//grep bash等待标准输入
24 }
25 return 0;
26 }



>make

>./pipe_ps

(ps没有退,产生了一个僵尸进程!)

4、ps、grep命令实现问题解决

pipe_ps.c代码的问题:1)产生了僵尸进程ps;2)父进程认为还有写端存在,就有可能还有人给发数据,继续等待。



Linux系统编程——进程间通信_操作系统_06



>vi pipe_ps.c



1 #include<stdio.h>
2 #include<unistd.h>
3
4 int main()
5 {
6 int fd[2];
7 pipe(fd);
8 pid_t pid = fork();
9
10 if(pid == 0){
11 //son
12 //son --> ps
13 //关闭读端
14 close(fd[0]);
15 //1.先重定向
16 dup2(fd[1],STDOUT_FILENO);//标准输出重定向到管道写端
17 //2.execlp
18 execlp("ps","ps","aux",NULL);
19 }
20 else if(pid > 0){
21 //parent
22 //关闭写端
23 close(fd[1]);
24 //1.先重定向,标准输入重定向到管道读端
25 dup2(fd[0],STDIN_FILENO);
26 //2.execlp
27 execlp("grep","grep","bash",NULL);//grep bash等待标准输入
28 }
29 return 0;
30 }



>make

>./pipe_ps

5、管道的读写行为


使用管道需要注意以下4种特殊情况(假设都是阻塞I/O操作,没有设置O_NONBLOCK标志):
1. 如果所有指向管道写端的文件描述符都关闭了(管道写端引用计数为0),而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样。
2. 如果有指向管道写端的文件描述符没关闭(管道写端引用计数大于0),而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。
3. 如果所有指向管道读端的文件描述符都关闭了(管道读端引用计数为0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。当然也可以对SIGPIPE信号实施捕捉,不终止进程。具体方法信号章节详细介绍。
4. 如果有指向管道读端的文件描述符没关闭(管道读端引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。
总结:
① 读管道:1. 管道中有数据,read返回实际读到的字节数。
    2. 管道中无数据:
    (1) 管道写端被全部关闭,read返回0 (好像读到文件结尾)
    (2) 写端没有全部被关闭,read阻塞等待(不久的将来可能有数据递达,此时会让出cpu)
    ② 写管道:1. 管道读端全部被关闭, 进程异常终止(也可使用捕捉SIGPIPE信号,使进程不终止)
    2. 管道读端没有全部关闭:
    (1) 管道已满,write阻塞。
    (2) 管道未满,write将数据写入,并返回实际写入的字节数。


读管道:

  写端全部关闭——read读到0,相当于读到文件末尾

  写端没有全部关闭

    有数据——read 读到数据

    没有数据——read 阻塞 ,fcntl 函数可以更改非阻塞

写管道:

  读端全部关闭——?产生一个信号SIGPIPE,程序异常终止。

  读端未全部关闭

    管道已满——write阻塞。如果要显示现象,读端一直不读,写端狂写。

    管道未满——write正常写入

测试:(写端全部关闭——read读到0,相当于读到文件末尾)

>vi pipe.c



1 #include<stdio.h>
2 #include<unistd.h>
3
4 int main()
5 {
6 int fd[2];//声明数组
7 pipe(fd);//创建管道
8 pid_t pid = fork();//创建子进程
9
10 if(pid == 0){
11 //son
12 sleep(3);
13 close(fd[0]);//关闭读端
14 write(fd[1],"hello",5);
15 close(fd[1]);
16 while(1){
17 sleep(1);
18 }
19 }
20 else if(pid > 0){
21 //parent
22 close(fd[1]);//关闭写端
23 char buf[12]={0};
24 while(1){
25
26 int ret = read(fd[0],buf,sizeof(buf));//read会等待write写
27 if(ret == 0){
28 printf("read over!\n");
29 break;
30 }
31 if(ret > 0){
32 write(STDOUT_FILENO,buf,ret);//相当于printf,打印
33 }
34 }
35
36 }
37 return 0;
38 }



>make

>./pipe

(这时候打开另一个终端,ps aux查看子进程仍然活着,使用kill -9 pid杀死)

测试:(读端全部关闭——?产生一个信号SIGPIPE,程序异常终止。)

>vi pipe.c



1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4 #include<sys/wait.h>
5
6 int main()
7 {
8 int fd[2];//声明数组
9 pipe(fd);//创建管道
10 pid_t pid = fork();//创建子进程
11
12 if(pid == 0){
13 //son
14 sleep(3);
15 close(fd[0]);//关闭读端
16 write(fd[1],"hello",5);
17 close(fd[1]);
18 while(1){
19 sleep(1);
20 }
21 }
22 else if(pid > 0){
23 //parent
24 close(fd[1]);//关闭写端
25 close(fd[0]);
26
27 int status;
28 wait(&status);
29 if(WIFSIGNALED(status)){
30 printf("killed by %d\n", WTERMSIG(status));
31 }
32
33 //父进程只是关闭读写两端,但是不退出
34 while(1){
35 sleep(1);
36 }
37
38 char buf[12]={0};
39 while(1){
40
41 int ret = read(fd[0],buf,sizeof(buf));//read会等待write写
42 if(ret == 0){
43 printf("read over!\n");
44 break;
45 }
46 if(ret > 0){
47 write(STDOUT_FILENO,buf,ret);//相当于printf,打印
48 }
49 }
50
51 }
52 return 0;
53 }



>make

>./pipe

输出:kill by 13

(这时候打开另一个终端,kill -l查看kill的13号信号的标识为:SIGPIPE)

6、管道大小和优劣

》计算管道大小

可以使用ulimit –a 命令来查看当前系统中创建管道文件所对应的内核缓冲区大小。通常为:

pipe size            (512 bytes, -p) 8

也可以使用fpathconf函数,借助参数选项来查看。使用该宏应引入头文件<unistd.h>

>man fpathconf

long fpathconf(int fd, int name);

    成功:返回管道的大小失败:-1,设置errno

》优缺点

优点:简单

缺点:1)只能有血缘关系的进程通信;2)父子进程只能单方向通信,如果需要双向通信,需要创建多根管道。 

三、进程通信——FIFO

1、fifo实现通信写端

FIFO:有名管道,实现无血缘关系进程通信

FIFO常被称为命名管道,以区分管道(pipe)。管道(pipe)只能用于“有血缘关系”的进程间。但通过FIFO,不相关的进程也能交换数据。

FIFO是Linux基础文件类型中的一种。但,FIFO文件在磁盘上没有数据块,仅仅用来标识内核中一条通道。各进程可以打开这个文件进行read/write,实际上是在读写内核通道,这样就实现了进程间通信。

》创建一个管道的伪文件:

法一:

>mkfifo myfifo

>ls -lrt

(prw-rw-r-- 1 wang wang 0 7月 2 12:36 myfifo)

法二:可以用函数创建

>man 3 mkfifo

int mkfifo(const char *pathname, mode_t mode);

》内核会针对fifo文件开辟一个缓冲区,操作fifo文件,可以操作缓冲区,实现进程间通信——实际上就是文件读写

测试:

>touch fifo_w.c

>vi fifo_w.c



1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4 #include<sys/stat.h>
5 #include<fcntl.h>
6 #include<string.h>
7
8 int main(int argc, char *argv[])
9 {
10 if(argc != 2){
11 printf("./a.out fifoname\n");
12 return -1;
13 }
14 //当前目录有一个myfifo文件
15 //打开fifo文件
16 int fd = open(argv[1],O_WRONLY);
17 //写
18 char buf[256];
19 int num = 1;
20 while(1){//循环写
21 memset(buf,0x00,sizeof(buf));
22 sprintf(buf,"xiaoming%04d",num++);
23 write(fd,buf,strlen(buf));
24 sleep(1);
25 }
26
27 //关闭描述符
28 close(fd);
29 return 0;
30 }



>touch myfifo

>touch fifo_r.c

>vi fifo_r.c



1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4 #include<sys/stat.h>
5 #include<fcntl.h>
6 #include<string.h>
7
8 int main(int argc, char *argv[])
9 {
10 if(argc != 2){
11 printf("./a.out fifoname\n");
12 return -1;
13 }
14 //当前目录有一个myfifo文件
15 //打开fifo文件
16 int fd = open(argv[1],O_RDONLY);
17 //写
18 char buf[256];
19 int ret;
20 while(1){//循环读
       memset(buf, 0x00,sizeof(buf));
21 ret = read(fd,buf,sizeof(buf));
22 if(ret > 0){
23 printf("read:%s\n",buf);
24 }
25 }
26
27 //关闭描述符
28 close(fd);
29 return 0;
30 }



>make

>./fifo_w

(这时候打开另一个终端,./fifo_r读myfifo种的数据。也可以打开多个终端写,多个终端读,抢占读/写数据)

2、fifo使用注意事项

>vi fifo_w.c



1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4 #include<sys/stat.h>
5 #include<fcntl.h>
6 #include<string.h>
7
8 int main(int argc, char *argv[])
9 {
10 if(argc != 2){
11 printf("./a.out fifoname\n");
12 return -1;
13 }
14 //当前目录有一个myfifo文件
15 //打开fifo文件
16 printf("begin open....\n");
17 int fd = open(argv[1],O_WRONLY);
18 printf("end open....\n");
19 //写
20 char buf[256];
21 int num = 1;
22 while(1){//循环写
23 memset(buf,0x00,sizeof(buf));
24 sprintf(buf,"xiaoming%04d",num++);
25 write(fd,buf,strlen(buf));
26 sleep(1);
27 }
28
29 //关闭描述符
30 close(fd);
31 return 0;
32 }



>vi fifo_r.c



1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4 #include<sys/stat.h>
5 #include<fcntl.h>
6 #include<string.h>
7
8 int main(int argc, char *argv[])
9 {
10 if(argc != 2){
11 printf("./a.out fifoname\n");
12 return -1;
13 }
14 //当前目录有一个myfifo文件
15 //打开fifo文件
16 printf("begin open read....\n");
17 int fd = open(argv[1],O_RDONLY);
18 printf("begin open end....\n");
19 //写
20 char buf[256];
21 int ret;
22 while(1){//循环读
23 memset(buf, 0x00,sizeof(buf));
24 ret = read(fd,buf,sizeof(buf));
25 if(ret > 0){
26 printf("read:%s\n",buf);
27 }
28 }
29
30 //关闭描述符
31 close(fd);
32 return 0;
33 }



>make

>./fifo_w

(这时候打开另一个终端,./fifo_r读myfifo种的数据。也可以打开多个终端写,多个终端读,抢占读/写数据)

FIFOs

  Opening the read or write end of a FIFO blocks until the other end is also opened (by another process or thread.) See fifo (7) for further details.

open注意事项:打开fifo文件的时候,read端会阻塞等待write端open,write端同理,也会阻塞等待另外一端打开。

>man 7 fifo

查看fifo信息

四、进程通信——mmap

存储映射I/O (Memory-mapped I/O) 使一个磁盘文件与存储空间中的一个缓冲区相映射。于是当从缓冲区中取数据,就相当于读文件中的相应字节。于此类似,将数据存入缓冲区,则相应的字节就自动写入文件。这样,就可在不适用read和write函数的情况下,使用地址(指针)完成I/O操作。    使用这种方法,首先应通知内核,将一个指定文件映射到存储区域中。这个映射工作可以通过mmap函数来实现。



Linux系统编程——进程间通信_操作系统_07


1、mmap映射开始

>man mmap

》创建映射区

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

  addr 传 NULL

  length 映射区的长度

  prot

    PROT_READ 可读

    PROT_WRITE 可写

  flags

    MAP_SHARED 共享的,对内存的修改会影响到源文件

    MAP_PRIVATE 私有的

  fd 文件描述符,open打开一个文件

  offset 偏移量

  返回值:成功返回可用的内存首地址;失败返回MAP_FAILED

》释放映射区

同malloc函数申请内存空间类似的,mmap建立的映射区在使用结束后也应调用类似free的函数来释放。

借鉴malloc和free函数原型,尝试装自定义函数smalloc,sfree来完成映射区的建立和释放。思考函数接口该如何设计?

int munmap(void *addr, size_t length);

  addr 传mmap的返回值

  length mmap创建的长度

  返回值:成功返回0,失败返回-1

练习:

>touch mem.txt

>vi mem.txt

xxxxxxxxxxxxx

>touch mmap.c

>vi mmap.c



1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4 #include<sys/stat.h>
5 #include<fcntl.h>
6 #include<sys/mman.h>
7 #include<string.h>
8
9 int main(int argc, char *argv[])
10 {
11 int fd = open("mem.txt",O_RDWR);
12
13 //创建映射区
14 char *mem = mmap(NULL,8,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//如果是MAP_PRIVATE,不会更改源文件
15
16 if(mem == MAP_FAILED){
17 perror("mmap error");
18 return -1;
19 }
20
21 //拷贝数据
22 strcpy(mem,"hello");
23
24 //释放mmap
25 munmap(mem, 8);
26
27 close(fd);
28 return 0;
29 }



>make

>./mmap

>cat mem.txt

2、mmap注意事项



Linux系统编程——进程间通信_unix_08



解答:

(1)不能!!!(测试:mem++,然后释放,make后运行./mmap)

(2)文件的大小对映射区操作有影响,尽量避免。(测试:写入helloworld,make后运行./mmap,cat mem.txt文件为xxxxxxxxxxxxx时结果为:helloworldxxx;mem.txt文件为xxxxxxxx时,make后运行./mmap,cat mem.txt结果为helloworl)

另外如果不越界,而是大于文件大小,结果如何?

文件的大小对映射区操作有影响,尽量避免。

测试:在(5)中修改ftruncate(fd,8);mmap中修改为20。(测试:写入helloworld,make后运行./mmap,cat mem.txt文件为xxxxxxxx时,make后运行./mmap,cat mem.txt结果为helloworl)

(3)不可以,offset必须是4k的整数倍。(测试:ps aux > mem.txt;然后ls -l mem.txt看下文件大小,将offset改为1000,make后运行./mmap报错:mmap error:Invalid argument)

注意:0也是4096的整数倍!!!

(可以用ls -l mmap.c,然后stat mmap.c查看文件大小及块数)

(4)没有影响。(测试:将close(fd);放到strcpy(mem,"hello");之前,make后运行./mmap)

(5)不可以用大小为0的文件。(测试:将打开文件更改为:int fd = open("mem.txt",O_RDWR|O_CREAT|O_TRUNC,0664);(创建并且截断文件,保证打开时大小为0)make后./mmap报错:总线错误(核心已转储);在其后增加代码:ftruncate(fd,8);扩展文件大小,cat mem.txt结果为hellowor)

(6)不可以,Permission denied没有权限,原因映射到内存,隐含一次读操作。(测试:int fd = open("mem.txt",O_RDWR);将打开文件时O_RDWR更改为:O_WRONLY;make后./mmap报错:mmap err: Permisson denied)

(7)不可以,Permission denied没有权限,原因后期修改数据MAP_SHARED,可以修改源文件。(测试:int fd = open("mem.txt",O_RDWR);将打开文件时O_RDWR更改为:O_RDONLY;make后./mmap报错:mmap err: Permisson denied)

所以:open文件时候权限应该大于等于mmap时的权限!!!

(8)很多情况。

(9)会死的很难看 o(∩_∩)o 哈哈

总结:使用mmap时务必注意以下事项:
1.    创建映射区的过程中,隐含着一次对映射文件的读操作。

2.    当MAP_SHARED时,要求:映射区的权限应 <=文件打开的权限(出于对映射区的保护)。而MAP_PRIVATE则无所谓,因为mmap中的权限是对内存的限制。

3.    映射区的释放与文件关闭无关。只要映射建立成功,文件可以立即关闭。

4.    特别注意,当映射文件大小为0时,不能创建映射区。所以:用于映射的文件必须要有实际大小!!    mmap使用时常常会出现总线错误,通常是由于共享文件存储空间大小引起的。

5.    munmap传入的地址一定是mmap的返回地址。坚决杜绝指针++操作。

6.    文件偏移量必须为4K的整数倍

7.    mmap创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。

3、mmap实现父子进程通信

父子等有血缘关系的进程之间也可以通过mmap建立的映射区来完成数据通信。但相应的要在创建映射区的时候指定对应的标志位参数flags:

MAP_PRIVATE:  (私有映射)  父子进程各自独占映射区;

MAP_SHARED:  (共享映射)  父子进程共享映射区;

练习:

>touch mem.txt

>vi mem.txt

xxxxx

>touch mmap_child.c

>vi mmap_child.c



1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4 #include<sys/stat.h>
5 #include<fcntl.h>
6 #include<sys/mman.h>
7 #include<sys/wait.h>
8
9 int main(int argc, char *argv[])
10 {
11 //先打开文件(有源文件)
12 int fd = open("mem.txt",O_RDWR);
13
14 //创建映射区
15 int *mem = mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
16
17 if(mem == MAP_FAILED){
18 perror("mmap error");
19 return -1;
20 }
21
22 //fork子进程
23 pid_t pid = fork();
24
25 //父进程和子进程交替修改数据
26 if(pid == 0){
27 //son
28 *mem = 100;
29 printf("child,*mem = %d\n",*mem);
30 sleep(3);
31 printf("child,*mem = %d\n",*mem);
32 }
33 else if(pid > 0){
34 //parent
35 sleep(1);
36 printf("parent,*mem = %d\n",*mem);
37 *mem = 1001;
38 printf("parent,*mem = %d\n",*mem);
39 wait(NULL);//回收子进程
40 }
41
42 //释放mmap
43 if(munmap(mem, 4) < 0){
44 perror("munmap err");
45 }
46
47 close(fd);
48 return 0;
49 }



>make

>./mmap_child

》结论:父子进程共享:1. 打开的文件  2. mmap建立的映射区(但必须要使用MAP_SHARED)

4、匿名映射

通过使用我们发现,使用映射区来完成文件读写操作十分方便,父子进程间通信也较容易。但缺陷是,每次创建映射区一定要依赖一个文件才能实现。通常为了建立映射区要open一个temp文件,创建好了再unlink、close掉,比较麻烦。 可以直接使用匿名映射来代替。其实Linux系统给我们提供了创建匿名映射区的方法,无需依赖一个文件即可创建映射区。同样需要借助标志位参数flags来指定。

缺点:匿名映射无法实现无血缘关系的进程的通信!!!

使用MAP_ANONYMOUS (或MAP_ANON), 如:

int *p = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);

  "4"随意举例,该位置表大小,可依实际需要填写。

测试:

>touch mmap_anon.c

>vi mmap_anon.c

(重定向头文件:head -7 mmap_child.c > mmap.anon.c)



1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4 #include<sys/stat.h>
5 #include<fcntl.h>
6 #include<sys/mman.h>
7 #include<sys/wait.h>
8
9 int main(int argc, char *argv[])
10 {
11 //创建映射区
12 int *mem = mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);
13
14 if(mem == MAP_FAILED){
15 perror("mmap error");
16 return -1;
17 }
18
19 //fork子进程
20 pid_t pid = fork();
21
22 //父进程和子进程交替修改数据
23 if(pid == 0){
24 //son
25 *mem = 101;
26 printf("child,*mem = %d\n",*mem);
27 sleep(3);
28 printf("child,*mem = %d\n",*mem);
29 }
30 else if(pid > 0){
31 //parent
32 sleep(1);
33 printf("parent,*mem = %d\n",*mem);
34 *mem = 1001;
35 printf("parent,*mem = %d\n",*mem);
36 wait(NULL);//回收子进程
37 }
38
39 //释放mmap
40 if(munmap(mem, 4) < 0){
41 perror("munmap err");
42 }
43
44 close(fd);
45 return 0;
46 }



>make

>./mmap_anon

需注意的是,MAP_ANONYMOUS和MAP_ANON这两个宏是Linux操作系统特有的宏。

》/dev/zero 聚宝盆,可以随意映射

》/dev/null 无底洞,一般错误信息重定向到这个文件中,不会占用磁盘空间。

在类Unix系统中如无该宏定义,可使用如下两步来完成匿名映射区的建立。

1)fd = open("/dev/zero", O_RDWR);

2)p = mmap(NULL, size, PROT_READ|PROT_WRITE, MMAP_SHARED, fd, 0);

5、mmap实现无血缘关系进程通信

实质上mmap是内核借助文件帮我们创建了一个映射区,多个进程之间利用该映射区完成数据传递。由于内核空间多进程共享,因此无血缘关系的进程间也可以使用mmap来完成通信。只要设置相应的标志位参数flags即可。若想实现共享,当然应该使用MAP_SHARED了。

>touch mmap_w.c

>vi mmap_w.c



1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4 #include<sys/stat.h>
5 #include<fcntl.h>
6 #include<sys/mman.h>
7 #include<sys/wait.h>
8
9 typedef struct _Student{
10 int sid;
11 char sname[20];
12 }Student;
13
14
15 int main(int argc, char *argv[])
16 {
17 if(argc != 2){
18 printf("./a.out filename\n");
19 return -1;
20 }
21
22 //1.open file
23 int fd = open(argv[1],O_RDWR|O_CREATE|O_TRUNC,0666);
24 int length = sizeof(Student);
25
26 ftruncate(fd,length);//指定大小
27 //2.mmap
28 Student *stu = mmap(NULL,length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
29
30 if(stu == MAP_FAILED){
31 perror("mmap error");
32 return -1;
33 }
34
35 //3.修改内存数据
36 int num = 1;
37 while(1){
38 stu->sid = num;
39 sprintf(stu->sname,"xiaoming-%03d",num++);
40 sleep(1);//相当于每隔1s修改一次映射区的内容
41 }
42
43
44 //4.释放映射区,关闭文件描述符
45 if(munmap(stu, length) < 0){
46 perror("munmap err");
47 }
48 close(fd);
49
50 return 0;
51 }



>touch mmap_r.c

>vi mmap_r.c



1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4 #include<sys/stat.h>
5 #include<fcntl.h>
6 #include<sys/mman.h>
7 #include<sys/wait.h>
8
9 typedef struct _Student{
10 int sid;
11 char sname[20];
12 }Student;
13
14
15 int main(int argc, char *argv[])
16 {
17 if(argc != 2){
18 printf("./a.out filename\n");
19 return -1;
20 }
21
22 //1.open file
23 int fd = open(argv[1],O_RDWR);
24 int length = sizeof(Student);
25
26 //2.mmap
27 Student *stu = mmap(NULL,length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
28
29 if(stu == MAP_FAILED){
30 perror("mmap error");
31 return -1;
32 }
33
34 //3.read data
35 while(1){
36 printf("sid=%d,sname=%s\n",stu->sid,stu->sname);
37 sleep(1);//相当于每隔1s修改一次映射区的内容
38 }
39
40
41 //4.close and munmap
42 if(munmap(stu, length) < 0){
43 perror("munmap err");
44 }
45 close(fd);
46
47 return 0;
48 }



>make

>./mmap_w xxx

(这时候打开另一个终端,./mmap_r xxx查看;多开终端,读到的结果一样!)

原理图分析:(映射两块不同的内存!)



Linux系统编程——进程间通信_队列_09



6、mmap(MAP_SHSRED)再次说明

 >vi mmap_child.c



1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4 #include<sys/stat.h>
5 #include<fcntl.h>
6 #include<sys/mman.h>
7 #include<sys/wait.h>
8
9 int main(int argc, char *argv[])
10 {
11 //先打开文件(有源文件)
12 int fd = open("mem.txt",O_RDWR);
13
14 //创建映射区
15 int *mem = mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_PRIVATE,fd,0);
16
17 if(mem == MAP_FAILED){
18 perror("mmap error");
19 return -1;
20 }
21
22 //fork子进程
23 pid_t pid = fork();
24
25 //父进程和子进程交替修改数据
26 if(pid == 0){
27 //son
28 *mem = 100;
29 printf("child,*mem = %d\n",*mem);
30 sleep(3);
31 printf("child,*mem = %d\n",*mem);
32 }
33 else if(pid > 0){
34 //parent
35 sleep(1);
36 printf("parent,*mem = %d\n",*mem);
37 *mem = 1001;
38 printf("parent,*mem = %d\n",*mem);
39 wait(NULL);
40 }
41
42 //释放mmap
43 if(munmap(mem, 4) < 0){
44 perror("munmap err");
45 }
46
47 close(fd);
48 return 0;
49 }



>make

>./mmap_chilid

(查看父子进程的数据,得知MAP_PRIVATE,无法共享)

 如果进程要通信,flags必须设为MAP_SHARED,否则无法通信!

作业



Linux系统编程——进程间通信_unix_10



 》多进程拷贝



1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4 #include<sys/stat.h>
5 #include<fcntl.h>
6 #include<sys/mman.h>
7 #include<sys/wait.h>
8 #include<stdlib.h>
9 #include<string.h>
10
11 int main(int argc, char *argv[])
12 {
13 int n = 5;
14 //输入参数至少是3,第4个参数可以是进程个数
15 if(argc < 3){
16 printf("./a.out src dst [n]\n");
17 return 0;
18 }
19 if(argc == 4){
20 n = atoi(argv[3]);
21 }
22 //打开源文件
23 int srcfd = open(argv[1],O_RDONLY);
24 if(srcfd < 0){
25 perror("open err");
26 exit(1);
27 }
28 //打开目标文件
29 int dstfd = open(argv[2],O_RDWR|O_CREAT|O_TRUNC,0664);
30 if(dstfd < 0){
31 perror("open dst err");
32 exit(1);
33 }
34 //目标拓展,从原文件获得文件大小,stat
35 struct stat sb;
36 stat(argv[1],&sb);//为了计算大小
37 int len = sb.st_size;
38 truncate(argv[2],len);
39 //将源文件映射到缓冲区
40 char *psrc = mmap(NULL, len, PROT_READ,MAP_SHARED,srcfd,0);
41 if(pdst == MAP_FAILED){
42 perror("mmap dst err");
43 exit(1);
44 }
45 //创建多个子进程
46 int i = 0;
47 for(i = 0; i < n; i++){
48 if(fork() == 0){
49 break;
50 }
51 }
52 //计算子进程需要拷贝的起点和大小
53 int cpsize = len/n;
54 int mod = len%n;
55 //数据拷贝,memcpy
56 if(i < n){//子进程
57 if(i == n-1){//最后一个子进程
58 //(首地址,源地址,大小)
59 memcpy(pdst+i*cpsize,psrc+i*cpsize,cpsize+mod);
60 }
61 else{
62 memcpy(pdst+i*cpsize,psrc+i*cpsize,cpsize);
63 }
64 }
65 else{
66 for(i = 0; i < n; i++){
67 wait(NULL);
68 }
69 }
70 //释放映射区
71 if(munmap(mem, len) < 0){
72 perror("munmap dst err");
73 exit(1);
74 }
75 //关闭文件
76 close(srcfd);
77 close(dstfd);
78 return 0;
79 }



五、进程通信——信号

1、信号的概念

信号的特点:简单,不能带大量信息,满足特定条件发生。

信号的机制:进程B发送给进程A,内核产生信号,内核处理。

信号的产生:

  按键产生:Ctrl+c,Ctrl+z,Ctrl+\

  调用函数:kill,raise,abort

  定时器:alarm,setitimer

  命令产生:kill

  硬件异常:段错误,浮点型错误,总线错误,SIGPIPE 

信号的状态:

  产生

  递达:信号到达并且处理完

  未决:信号被阻塞了

信号的默认处理方式:

  忽略

  执行默认动作

  捕获

信号的4要素:

  编号

  事件

  名称

  默认处理动作(可通过man 7 signal查看):忽略,终止,终止+core,暂停,继续

kill -l 可以查看信号种类,一共64个(只学习前31个)。

在学习Linux系统编程总结了笔记,并分享出来。

标签:int,mmap,编程,间通信,mem,fd,Linux,进程,include
From: https://blog.51cto.com/u_15405812/5834769

相关文章

  • Linux命令基础——makefile+gdb+IO
    在学习Linux命令基础总结了笔记,并分享出来。08-linux-day03(makefile-gdb-IO)目录:附:ftp工具介绍——FlashFXP一、学习目标二、makefile1、makefile编写12、makefile编写23、......
  • Linux命令基础——08-linux-day02(vim-gcc-library)
    在学习Linux命令基础总结了笔记,并分享出来。08-linux-day02(vim-gcc-library)目录:一、学习目标二、vim1、vim光标的移动2、vim删除内容3、vim复制粘贴与可视模式4、vim查找......
  • Linux命令基础——stat-readdir-dup2
    在学习Linux命令基础总结了笔记,并分享出来。08-linux-day04(stat-readdir-dup2)目录:一、学习目标二、文件和目录操作1、打开最大文件数量2、stat函数介绍3、stat函数介绍2与......
  • Linux命令基础——vim+gcc+ibrary
    在学习Linux命令基础总结了笔记,并分享出来。08-linux-day02(vim-gcc-library)目录:一、学习目标二、vim1、vim光标的移动2、vim删除内容3、vim复制粘贴与可视模式4、vim查找......
  • Linux高并发网络编程开发——tcp三次握手-并发
    在学习Linux高并发网络编程开发总结了笔记,并分享出来。10-Linux系统编程-第11天(tcp三次握手-并发)  一、学习目标1、熟练掌握三次握手建立连接过程2、熟练掌握四次挥手断开......
  • Linux 中使用脚本启动 Java 服务
    Linux中使用脚本启动Java服务#!/bin/sh#服务启动参数#JAVA_OPTS="-Xms512m-Xmx512m-XX:MetaspaceSize=512m-XX:MaxMetaspaceSize=1024m-XX:ParallelGCThreads=......
  • 面向对象编程(四)
    面向对象编程(四)一、面向对象的魔法方法1.魔法方法简介在类中,有一些内置好的特定的方法,方法名是“__xx__”,在进行特定的操作时会被调用,这些方法被称为魔法方法,不需......
  • dotnet Core 在linux 下设置成Service
    1、新建.service文件cd/etc/systemd/system//进入改目录touchCore.service//新建Core服务文件viCore.service//编辑2、插入下面代码注意自己的服务名,以及项......
  • 20221107 24. Linux 核心编译与管理
    24.1编译前的任务:认识核心与取得核心源代码“核心(kernel)”是整个操作系统的最底层,他负责了整个硬件的驱动,以及提供各种系统所需的核心功能,包括防火墙机制、是否支持LVM......
  • 20220802 鸟哥 Linux 私房菜【归档】
    参考资料鸟哥Linux私房菜-基础学习篇(第四版)鳥哥私房菜-基礎學習篇目錄-forCentOS7环境信息CentOS7.x书中使用版本:7.1练习使用版本:7.9目录00.计算......