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
(1)创建管道,获取管道读写两端文件描述符的属性,并分析
1 #include<stdio.h>
2 #include<fcntl.h>
3 #include<unistd.h>
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<stdio.h>
2 #include<fcntl.h>
3 #include<unistd.h>
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<stdio.h>
2 #include<fcntl.h>
3 #include<unistd.h>
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 #include<stdio.h>
2 #include<unistd.h>
3 #include<fcntl.h>
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
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<fcntl.h>
4 #include<errno.h>
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<stdio.h>
2 #include<unistd.h>
3 #include<fcntl.h>
4 #include<errno.h>
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 #include<stdio.h>
2 #include<unistd.h>
3 #include<fcntl.h>
4 #include<errno.h>
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<stdio.h>
2 #include<unistd.h>
3 #include<fcntl.h>
4 #include<errno.h>
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<stdio.h>
2 #include<unistd.h>
3 #include<fcntl.h>
4 #include<errno.h>
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 }
验证如下:
首先创建一个命名管道fifo,然后创建一个write.c文件打开fifo进行写,,再创建一个read.c文件打开fifo进行读
write.c文件
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<fcntl.h>
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<stdio.h>
2 #include<unistd.h>
3 #include<fcntl.h>
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 }
此时我们让两个程序跑起来可以看到如下结果:
我们可以看到它两是不同的进程,通过命名管道也可以进行进程间通信
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<stdio.h>
2 #include<unistd.h>
3 #include <sys/shm.h>
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命令查看,我们可以看到我们刚才创建的共享内存,如下图所示: