本文共 19799 字,大约阅读时间需要 65 分钟。
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 #include2 #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 #include2 #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 #include2 #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)因为父子进程中的文件描述符表都拥有fd[0],fd[1],我们可以规定父进程读,子进程写 (3)再测试非阻塞属性
- 读端调用read,read函数返回-1,表示管道当中没有内容
验证如下:
我们首先将读端和写端都设置成非阻塞属性,然后不对写端进行操作(不关闭),让读端读,为了方便我们看到现象,我们在读端和写端都写入一个死循环,让进程不要退出,然后让读端打印出read的返回值和读到buf中的内容,测试代码如下:
1 #include2 #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
1 #include2 #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 #include2 #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 }
- 当通过fd[1],往管道中去写的时候,会导致管道破裂,调用写的进程会被信号终止(变为僵尸进程)
首先我们不对写端进行非阻塞属性设置,关闭读端,并设置循环让读端不要退出,让写端一直进行写,代码如下:
1 #include2 #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 #include2 #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 #include2 #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 }
验证结果如下:
- (1)命名管道也是在内核中开辟了一块缓冲区,这块缓冲区是有标识符的,可以被任何进程通过标识符找到;
- (2)命令创建:mkfifo [filename]
- (3)命名管道的生命周期也跟随进程
![]()
- (4)命名管道有标识符,所以命名管道支持不同进程之间的进程通信
- (5)其他特性和命名管道一样
验证如下:
首先创建一个命名管道fifo,然后创建一个write.c文件打开fifo进行写,,再创建一个read.c文件打开fifo进行读
write.c文件
1 #include2 #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 #include2 #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 }
此时我们让两个程序跑起来可以看到如下结果:
- (1)在物理内存中创建一块内存
- (2)不同的进程通过页表映射,将同一块物理内存映射到自己的虚拟地址空间
- (3)不同的进程,操作进程虚拟地址,通过页表的映射,就相当于操作同一块内存,从而完成了数据的交换
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 #include2 #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/