前言
如果,想要深入的学习Linux系统调用中的fcntl函数,还是需要去自己阅读Linux系统中的帮助文档。
具体输入命令:
man 2 fcntl
即可查阅到完整的资料信息。
fcntl函数
fcntl函数是Linux系统API中的一员,它的主要有以下几个作用:
- 改变已经打开的文件性质.
- 类似 dup 的功能。
- 改变文件的文件状态标志(fl_flags)。
- 得到或设置文件锁记录。
在这篇文章中,我们主要介绍功能前1和3。
它的函数原型是长这样的:
int fcntl(int fd, int cmd, …);
接下来,我来介绍一下它的多个参数与返回值
参数:
fd : 表示需要操作的文件描述符
cmd: 表示对文件描述符进行如何操作(一些系统定义宏值)
- F_DUPFD : 复制文件描述符,复制的是第一个参数fd,得到一个新的文件描述符(返回值)
int ret = fcntl(fd, F_DUPFD);
- F_GETFL : 获取指定的文件描述符文件状态flag
获取的flag和我们通过open函数传递的flag是一个东西。
- F_SETFL : 设置文件描述符文件状态flag
必选项:O_RDONLY, O_WRONLY, O_RDWR 不可以被修改
可选性:O_APPEND, O)NONBLOCK
O_APPEND 表示追加数据
NONBLOK 设置成非阻塞
... :可变参数,看需求添加。
阻塞和非阻塞:描述的是函数调用的行为。
在使用这个函数之前,我们需要往C/C++文件中导入这些头文件:
#include <unistd.h>
#include <fcntl.h>
下面我来通过一个例子,来演示一下这个函数的具体作用:
代码演示:往已经存在的文件里追加数据
/*
子进程向父进程发送信息
设置管道非阻塞
int flags = fcntl(fd[0], F_GETFL); // 获取原来的flag
flags |= O_NONBLOCK; // 修改flag的值
fcntl(fd[0], F_SETFL, flags); // 设置新的flag
*/
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
int fd[2] ;
int ret = pipe(fd);
if(ret == -1){
perror("pipe");
exit(0);
}
pid_t pid = fork();
int flags = fcntl(fd[0],F_GETFL); //获取原来文件描述符标志信息
flags |= O_NONBLOCK;//给原来的文件描述符标志信息中添加非阻塞的标志信息
fcntl(fd[0],F_SETFL,flags);//设置新的标志信息
if(pid > 0){
close(fd[1]);//只读不写,关闭写端
char buf[1024];
while (1)
{
sleep(2);
printf("这里是父进程,我的pid是:%d,正在接受来自子进程的信息...\n",getpid());
int len = read(fd[0],buf,sizeof(buf)-1);
printf("len: %d , %s\n", len, buf);
memset(buf,0,1024);
}
}else if(pid == 0){
close(fd[0]);//只写不读,关闭读端
while(1){
printf("这里是子进程,我的pid是:%d,正在向父进程发送数据...\n",getpid());
char buf[] = "hello,i am child";
write(fd[1],buf,sizeof(buf));
sleep(10);
}
}else{
perror("fork");
return -1;
}
return 0;
}
有些人可能会问,我明明已经有写的权限了,我为什么不直接用write函数往文件里写文件。而先要用fcntl去追加权限呢?
这是因为如果你只有写的权限的话,你直接写就是把原来文件里的数据完全覆盖掉。而有O_APPEND权限则是可以在原来文件的末尾处添加数据,而不是覆盖了。
代码演示:将管道的文件描述符改为非阻塞,父进程读取子进程写入的数据:
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
/*
设置管道非阻塞
int flags = fcntl(fd[0], F_GETFL); // 获取原来的flag
flags |= O_NONBLOCK; // 修改flag的值
fcntl(fd[0], F_SETFL, flags); // 设置新的flag
*/
int main() {
// 在fork之前创建管道
int pipefd[2];
int ret = pipe(pipefd);
if(ret == -1) {
perror("pipe");
exit(0);
}
// 创建子进程
pid_t pid = fork();
if(pid > 0) {
// 父进程
printf("i am parent process, pid : %d\n", getpid());
// 关闭写端
close(pipefd[1]);
// 从管道的读取端读取数据
char buf[1024] = {0};
int flags = fcntl(pipefd[0], F_GETFL); // 获取原来的flag
flags |= O_NONBLOCK; // 修改flag的值
fcntl(pipefd[0], F_SETFL, flags); // 设置新的flag
while(1) {
int len = read(pipefd[0], buf, sizeof(buf));
printf("len : %d\n", len);
printf("parent recv : %s, pid : %d\n", buf, getpid());
memset(buf, 0, 1024);
sleep(1);
}
} else if(pid == 0){
// 子进程
printf("i am child process, pid : %d\n", getpid());
// 关闭读端
close(pipefd[0]);
char buf[1024] = {0};
while(1) {
// 向管道中写入数据
char * str = "hello,i am child";
write(pipefd[1], str, strlen(str));
sleep(5);
}
}
return 0;
}
输出结果:
nowcoder@nowcoder:~/Linux/lession22$ ./noblock1
这里是子进程,我的pid是:24822,正在向父进程发送数据...
这里是父进程,我的pid是:24821,正在接受来自子进程的信息...
len: 17 , hello,i am child
这里是父进程,我的pid是:24821,正在接受来自子进程的信息...
len: -1 ,
这里是父进程,我的pid是:24821,正在接受来自子进程的信息...
len: -1 ,
这里是父进程,我的pid是:24821,正在接受来自子进程的信息...
len: -1 ,
^c
因为我们将这个管道的读端这个文件描述符改为了非阻塞的。所以当父进程循环的时候,如果管道中没有数据,read函数不会阻塞在那里,而是会直接返回-1。也就是表示读取失败。紧接着printf继续输出内容。不会停止下来。