博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Linux——进程间通信(二):获取和设置文件描述符属性,匿名管道非阻塞读写特性,命名管道,创建共享内存
阅读量:85 次
发布时间:2019-02-25

本文共 19799 字,大约阅读时间需要 65 分钟。

文章目录:


1. 匿名管道的非阻塞读写特性

1.1 获取文件描述符属性和设置文件描述符属性

fcntl函数:设置/获取文件描述符的属性

  • int fcntl(int fd, int cmd, … /* arg */ );
  • 需要一个宏 F_GETFL获取文件描述符的属性—>传给cmd,此时可变参数列表就可以不用传递任何值   eg:fcnlt(fd[0],F_GETFL)
  • F_SETFL设置文件描述符的属性,需要指定设置文件描述符的属性(O_RDONLY / O_WRONLY / O_RDWR)—>要把这些文件描述符属性设置到文件描述符当中,需要采用按位或的方式
  • 非阻塞属性:O_NONBLOCK
  • 返回值:如果是获取(F_GETFL),返回的就是文件描述符的属性
  • 头文件:#include<fcntl.h>

(1)创建管道,获取管道读写两端文件描述符的属性,并分析

1 #include
2 #include
3 #include
4 int main() 5 {
6 //创建管道 7 int fd[2]; 8 int ret1 = pipe(fd); 9 if(ret1 < 0) 10 {
11 perror("pipe\n"); 12 return 0; 13 } 14 15 //获取读端的文件描述符属性 16 int ret = fcntl(fd[0],F_GETFL); 17 printf("read-->fd[0]-->ret:%d\n",ret); 18 19 //获取写端的文件描述符属性 20 ret = fcntl(fd[1],F_GETFL); 21 printf("write-->fd[1]-->ret:%d\n",ret); 22 return 0; 23 } ~

当我们将如上程序跑起来并对照内核源码会发现,fd[0]只读,fd[1]只写

在这里插入图片描述

(2)给读写两端的文件描述符设置非阻塞状态(设置文件描述符属性)

先将写端屏蔽掉,给读端设置非阻塞属性

1 #include
2 #include
3 #include
4 int main() 5 {
6 //创建管道 7 int fd[2]; 8 int ret1 = pipe(fd); 9 if(ret1 < 0) 10 {
11 perror("pipe\n"); 12 return 0; 13 } 14 15 //获取读端的文件描述符属性 16 int ret = fcntl(fd[0],F_GETFL); 17 printf("read-->fd[0]-->ret:%d\n",ret); 18 19 fcntl(fd[0],F_SETFL,ret | O_NONBLOCK); 20 21 ret = fcntl(fd[0],F_GETFL); 22 printf("read-->fd[0]-->ret:%d\n",ret); 23 //获取写端的文件描述符属性 24 //ret = fcntl(fd[1],F_GETFL); 25 //printf("write-->fd[1]-->ret:%d\n",ret); 26 return 0; 27 }

将程序运行起来我们会看到,给读端增加非阻塞属性后,返回了一个十进制的2048(八进制为00004000),对比内核源码我们可以看到,如下图所示,说明我们刚才给文件描述符增加了一个非阻塞属性

在这里插入图片描述


将读端屏蔽掉,给写端增加非阻塞属性

1 #include
2 #include
3 #include
4 int main() 5 {
6 //创建管道 7 int fd[2]; 8 int ret1 = pipe(fd); 9 if(ret1 < 0) 10 {
11 perror("pipe\n"); 12 return 0; 13 } 14 15 //获取读端的文件描述符属性 16 //int ret = fcntl(fd[0],F_GETFL); 17 //printf("read-->fd[0]-->ret:%d\n",ret); 18 19 //fcntl(fd[0],F_SETFL,ret | O_NONBLOCK); 20 21 //ret = fcntl(fd[0],F_GETFL); 22 //printf("read-->fd[0]-->ret:%d\n",ret); 23 24 25 //获取写端的文件描述符属性 26 int ret = fcntl(fd[1],F_GETFL); 27 printf("write-->fd[1]-->ret:%d\n",ret); 28 29 fcntl(fd[1],F_SETFL,ret | O_NONBLOCK); 30 31 ret = fcntl(fd[1],F_GETFL); 32 printf("write-->fd[1]-->ret:%d\n",ret); 33 34 return 0; 35 }

将程序运行起来我们会看到,给读端增加非阻塞属性后,返回了一个十进制的2049

在这里插入图片描述

1.2 匿名管道的非阻塞读写特性

(1)创建匿名管道,再创建子进程,让子进程进行进程间通信

(2)因为父子进程中的文件描述符表都拥有fd[0],fd[1],我们可以规定父进程读,子进程写
(3)再测试非阻塞属性

1.2.1 读端进行读(非阻塞),写端不写(不操作)

情况一:写端不关闭

  • 读端调用read,read函数返回-1,表示管道当中没有内容

验证如下:

我们首先将读端和写端都设置成非阻塞属性,然后不对写端进行操作(不关闭),让读端读,为了方便我们看到现象,我们在读端和写端都写入一个死循环,让进程不要退出,然后让读端打印出read的返回值和读到buf中的内容,测试代码如下:

1 #include
2 #include
3 #include
4 5 void SetNonBlock(int fd) 6 {
7 int flag = fcntl(fd,F_GETFL); 8 fcntl(fd,F_SETFL,flag | O_NONBLOCK); 9 } 10 int main() 11 {
12 int fd[2]; 13 int ret = pipe(fd); 14 if(ret < 0) 15 {
16 perror("pipe\n"); 17 return 0; 18 } 19 20 21 pid_t _fork = fork(); 22 if(_fork < 0) 23 {
24 perror("fork\n"); 25 return 0; 26 } 27 else if(_fork == 0) 28 {
29 //child-write 30 close(fd[0]); 31 SetNonBlock(fd[1]); 32 //写端不关闭 33 while(1) 34 {
35 sleep(1); 36 } 37 } 38 else 39 {
40 //father-read 41 close(fd[1]); 42 SetNonBlock(fd[0]); 43 char buf[1024] = {
0}; 44 int read_size = read(fd[0],buf,sizeof(buf) - 1); 45 while(1) 46 {
47 printf("red_size:%d buf:%s\n",read_size,buf); 48 } 49 } 50 return 0; 51 }

让程序跑起来后我们可以看到如下结果,read的返回值是-1,且没有在buf中读到内容,但此时我们并不知道read返回的 -1 是读取失败返回的 -1 还是非阻塞状态管道中没有内容返回的 -1

在这里插入图片描述
当我们查看man手册中read的介绍时我们可以看到这样一句话:
在这里插入图片描述
大概意思是一个文件描述符被引用到一个非套接的文件中字并且被标记为O_NONBLOCK(非阻塞属性),当前的错误码会被返回,而这个错误码设置在一个宏errno(在#inclued<errno.h>中)中,所以可以对如上代码的返回值进行判断,如果错误码等于EAGAIN则说明是正常情况,是管道为空,改进代码如下:

1 #include
2 #include
3 #include
4 #include
5 6 void SetNonBlock(int fd) 7 {
8 int flag = fcntl(fd,F_GETFL); 9 fcntl(fd,F_SETFL,flag | O_NONBLOCK); 10 } 11 int main() 12 {
13 int fd[2]; 14 int ret = pipe(fd); 15 if(ret < 0) 16 {
17 perror("pipe\n"); 18 return 0; 19 } 20 21 22 pid_t _fork = fork(); 23 if(_fork < 0) 24 {
25 perror("fork\n"); 26 return 0; 27 } 28 else if(_fork == 0) 29 {
30 //child-write 31 close(fd[0]); 32 SetNonBlock(fd[1]); 33 //写端不关闭 34 while(1) 35 {
36 sleep(1); 37 } 38 } 39 else 40 {
41 //father-read 42 close(fd[1]); 43 SetNonBlock(fd[0]); 44 char buf[1024] = {
0}; 45 int read_size = read(fd[0],buf,sizeof(buf) - 1); 46 while(1) 47 {
48 if(read_size < 0) 49 {
//如果错误码为EAGAIN应该认为是正常情况 50 if(errno == EAGAIN) 51 {
52 printf("管道为空\n"); 53 printf("red_size:%d buf:%s\n",read_size,buf); 54 } 55 } 56 57 } 58 } 59 return 0; 60 }

将程序跑起来可以看到结果如下:读端调用read,read函数返回-1,表示管道当中没有内容

在这里插入图片描述

情况二:写端关闭

  • 调用read函数返回-1
     
    因为关闭写端,管道里为空,所以可以确定返回的那个-1就是管道为空,所以此处不用做errno==EAGAIN判断

验证如下:

我们将写端的fd[0],fd[1]都关闭,让读端进行读并打印read的返回值和读到buf中的内容

1 #include
2 #include
3 #include
4 #include
5 6 void SetNonBlock(int fd) 7 {
8 int flag = fcntl(fd,F_GETFL); 9 fcntl(fd,F_SETFL,flag | O_NONBLOCK); 10 } 11 int main() 12 {
13 int fd[2]; 14 int ret = pipe(fd); 15 if(ret < 0) 16 {
17 perror("pipe\n"); 18 return 0; 19 } 20 21 22 pid_t _fork = fork(); 23 if(_fork < 0) 24 {
25 perror("fork\n"); 26 return 0; 27 } 28 else if(_fork == 0) 29 {
30 //child-write 31 close(fd[0]); 32 //写端关闭 33 close(fd[1]); 34 while(1) 35 {
36 sleep(1); 37 } 38 } 39 else 40 {
41 //father-read 42 close(fd[1]); 43 SetNonBlock(fd[0]); 44 char buf[1024] = {
0}; 45 int read_size = read(fd[0],buf,sizeof(buf) - 1); 46 printf("red_size:%d buf:%s\n",read_size,buf); 47 } 48 return 0; 49 }

在这里插入图片描述

1.2.2 写端进行写(非阻塞),读端不读(不操作)

情况一:读端关闭

  • 当通过fd[1],往管道中去写的时候,会导致管道破裂,调用写的进程会被信号终止(变为僵尸进程)

首先我们不对写端进行非阻塞属性设置,关闭读端,并设置循环让读端不要退出,让写端一直进行写,代码如下:

1 #include
2 #include
3 #include
4 #include
5 6 void SetNonBlock(int fd) 7 {
8 int flag = fcntl(fd,F_GETFL); 9 fcntl(fd,F_SETFL,flag | O_NONBLOCK); 10 } 11 int main() 12 {
13 int fd[2]; 14 int ret = pipe(fd); 15 if(ret < 0) 16 {
17 perror("pipe\n"); 18 return 0; 19 } 20 21 22 pid_t _fork = fork(); 23 if(_fork < 0) 24 {
25 perror("fork\n"); 26 return 0; 27 } 28 else if(_fork == 0) 29 {
30 //child-write 31 close(fd[0]); 32 int count = 0; 33 while(1) 34 {
35 int write_size = write(fd[1],"a",1); 36 printf("count:%d\n",count++); 37 } 38 } 39 else 40 {
41 //father-read 42 //读端关闭 43 close(fd[0]); 44 close(fd[1]); 45 while(1) 46 {
47 sleep(1); 48 } 49 } 50 return 0; 51 }

将程序跑起来我们会看到,程序并没有任何打印,并且子进程变成了一个僵尸进程,说明子进程已经退出来,因为我们已经将读端关闭掉了,相当于往水管中注水,一端进行注水,一端进行流水,若将流水端堵住,一直往里注水,最后管道会破裂

在这里插入图片描述
当我们给写端加上非阻塞属性,会发现结果是一样的

1 #include
2 #include
3 #include
4 #include
5 6 void SetNonBlock(int fd) 7 {
8 int flag = fcntl(fd,F_GETFL); 9 fcntl(fd,F_SETFL,flag | O_NONBLOCK); 10 } 11 int main() 12 {
13 int fd[2]; 14 int ret = pipe(fd); 15 if(ret < 0) 16 {
17 perror("pipe\n"); 18 return 0; 19 } 20 21 22 pid_t _fork = fork(); 23 if(_fork < 0) 24 {
25 perror("fork\n"); 26 return 0; 27 } 28 else if(_fork == 0) 29 {
30 //child-write 31 close(fd[0]); 32 //给写端加上非阻塞属性 33 SetNonBlock(fd[1]); 34 int count = 0; 35 while(1) 36 {
37 int write_size = write(fd[1],"a",1); 38 printf("count:%d\n",count++); 39 } 40 } 41 else 42 {
43 //father-read 44 //读端关闭 45 close(fd[0]); 46 close(fd[1]); 47 while(1) 48 {
49 sleep(1); 50 } 51 } 52 return 0; 53 }

在这里插入图片描述

情况二:读端不关闭

  • write返回 -1 ,errno等于EAGAIN,表示管道写满了

让写端一直写,不关闭读端,让读端一直循环不要退出,判断errno是否等于EAGAIN,若管道写满直接break,任何让子进程一直死循环,方便观察现象

1 #include
2 #include
3 #include
4 #include
5 6 void SetNonBlock(int fd) 7 {
8 int flag = fcntl(fd,F_GETFL); 9 fcntl(fd,F_SETFL,flag | O_NONBLOCK); 10 } 11 int main() 12 {
13 int fd[2]; 14 int ret = pipe(fd); 15 if(ret < 0) 16 {
17 perror("pipe\n"); 18 return 0; 19 } 20 21 22 pid_t _fork = fork(); 23 if(_fork < 0) 24 {
25 perror("fork\n"); 26 return 0; 27 } 28 else if(_fork == 0) 29 {
30 //child-write 31 close(fd[0]); 32 //给写端加上非阻塞属性 33 SetNonBlock(fd[1]); 34 int count = 0; 35 while(1) 36 {
37 int write_size = write(fd[1],"a",1); 38 printf("count:%d\n",count++); 39 if(write_size < 0) 40 {
41 printf("write_size:%d\n",write_size); 42 if(errno == EAGAIN) 43 {
44 printf("管道已满\n"); 45 break; 46 } 47 48 } 49 } 50 while(1) 51 {
52 sleep(1); 53 } 54 } 55 else 56 {
57 //father-read 58 close(fd[1]); 59 while(1) 60 {
61 sleep(1); 62 } 63 } 64 return 0; 65 }

验证结果如下:

在这里插入图片描述

2. 命名管道

  • (1)命名管道也是在内核中开辟了一块缓冲区,这块缓冲区是有标识符的,可以被任何进程通过标识符找到;
  • (2)命令创建:mkfifo [filename]
    在这里插入图片描述
  • (3)命名管道的生命周期也跟随进程
    在这里插入图片描述
  • (4)命名管道有标识符,所以命名管道支持不同进程之间的进程通信
  • (5)其他特性和命名管道一样

验证如下:

首先创建一个命名管道fifo,然后创建一个write.c文件打开fifo进行写,,再创建一个read.c文件打开fifo进行读

在这里插入图片描述

write.c文件

1 #include
2 #include
3 #include
4 5 int main() 6 {
7 int fd = open("./fifo",O_RDWR); 8 9 if(fd < 0) 10 {
11 perror("open\n"); 12 return 0; 13 } 14 15 char buf[] = {
"linux is so easy!"}; 16 while(1) 17 {
18 sleep(3); 19 write(fd,buf,17); 20 } 21 close(fd); 22 return 0; 23 }

read.c文件

1 #include
2 #include
3 #include
4 5 int main() 6 {
7 int fd = open("./fifo",O_RDWR); 8 9 if(fd < 0) 10 {
11 perror("open\n"); 12 return 0; 13 } 14 15 while(1) 16 {
17 char buf[1024] = {
0}; 18 read(fd,buf,sizeof(buf)-1); 19 20 printf("buf:%s\n",buf); 21 } 22 close(fd); 23 return 0; 24 }

在这里插入图片描述

此时我们让两个程序跑起来可以看到如下结果:

在这里插入图片描述

在这里插入图片描述
我们可以看到它两是不同的进程,通过命名管道也可以进行进程间通信

3. 共享内存

3.1 共享内存的原理

  • (1)在物理内存中创建一块内存
  • (2)不同的进程通过页表映射,将同一块物理内存映射到自己的虚拟地址空间
  • (3)不同的进程,操作进程虚拟地址,通过页表的映射,就相当于操作同一块内存,从而完成了数据的交换
    在这里插入图片描述

3.2 共享内存的接口

3.2.1创建共享内存

int shmget(key_t key, size_t size, int shmflg);

  • key: 共享内存的标识符,用来标识一块共享内存,在操作系统中,共享内存的标识是不能重复的。可直接给一个32位的16进制的数字
  • size: 共享内存大小
  • shmflg:
                IPC_CREAT:如果key标识的共享内存不存在,则创建
                IPC_EXCL | IPC_CREAT:如果key标识的共享内存存在,则报错
  • 权限: 按位或8进制数字 eg:0664(这个是共享内存的读写属性)
  • 返回值:
                -1:失败
                >0:成功,返回的是共享内存的操作句柄,后续是通过操作句柄来进行操作共享内存的
  • 查看共享内存的命令:ipcs -m
  • 共享内存的生命周期跟随操作系统内核

接下来我们创建一个共享内存,代码如下:

1 #include
2 #include
3 #include
4 5 #define key 0x12121212 6 7 int main() 8 {
9 int shmid = shmget(key,1024, IPC_CREAT | 0644); 10 if(shmid < 0) 11 {
12 perror("shmget\n"); 13 return 0; 14 } 15 return 0; 16 }~

首先我们不创建共享内存,使用ipcs -m命令查看共享内存如下图所示:

在这里插入图片描述

然后我们让如上代码跑起来创建一个标识符为0x12121212的共享内存,再使用ipcs -m命令查看,我们可以看到我们刚才创建的共享内存,如下图所示:

在这里插入图片描述

转载地址:http://cvn.baihongyu.com/

你可能感兴趣的文章