在学习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)。
在进程间完成数据传递需要借助操作系统提供特殊的方法,如:文件、管道、信号、共享内存、消息队列、套接字、命名管道等。随着计算机的蓬勃发展,一些方法由于自身设计缺陷被淘汰或者弃用。现今常用的进程间通信方式有:
1)pipe 管道 (只能有血缘关系的,使用最简单)2)fifo 信号 (开销最小)3)mmap 共享映射区 (无血缘关系) 速度最快
4)本地socket 本地套接字 (最稳定)
5)信号(携带信息量最小)
6)共享内存
7)消息队列
1、管道的概念
管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。有如下特质:
1. 其本质是一个伪文件(实为内核缓冲区)
2. 由两个文件描述符引用,一个表示读端,一个表示写端。
3. 规定数据从管道的写端流入管道,从读端流出。
管道的原理: 管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。
管道的局限性:
① 数据一旦被读走,便不在管道中存在,不可反复读取。
②由于管道采用半双工通信方式。因此,数据只能在一个方向上流动。
③ 只能在有公共祖先的进程间使用管道。
常见的通信方式有,单工通信(广播)、半双工通信(对讲机)、全双工通信(打电话)。
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对应标准输出一样。向管道文件读写数据其实是在读写内核缓冲区。
管道创建成功以后,创建该管道的进程(父进程)同时掌握着管道的读端和写端。如何实现父子进程间通信呢?通常可以采用如下步骤:
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命令
>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)父进程认为还有写端存在,就有可能还有人给发数据,继续等待。
>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函数来实现。
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注意事项
解答:
(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查看;多开终端,读到的结果一样!)
原理图分析:(映射两块不同的内存!)
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,否则无法通信!
作业
》多进程拷贝
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系统编程总结了笔记,并分享出来。