接口
#include <fcntl.h>
int fcntl(int fd, int cmd, ...);
返回值:
正确返回值根据命令码而定,错误返回-1。
fcntl是用来修改已经打开文件的属性的函数,包含5个功能:
-
复制一个已有文件描述符,功能和dup和dup2相同,对应的cmd:F_DUPFD、F_DUPFD_CLOEXEC。
当使用这两个cmd时,需要传入第三个参数,fcntl返回复制后的文件描述符,此返回值是之前未被占用的描述符,并且必须一个大于等于第三个参数值。F_DUPFD命令要求返回的文件描述符会清除对应的FD_CLOEXEC标志;F_DUPFD_CLOEXEC要求设置新描述符的FD_CLOEXEC标志。 -
获取、设置文件描述符标志,对应的cmd:F_GETFD、F_SETFD。
用于设置FD_CLOEXEC标志,此标志的含义是:当进程执行exec系统调用后此文件描述符会被自动关闭。 -
获取、设置文件访问状态标志,对应的cmd:F_GETFL、F_SETFL。
获取当前打开文件的访问标志,设置对应的访问标志,一般常用来设置做非阻塞读写操作。 -
获取、设置记录锁功能,对应的cmd:F_GETLK、F_SETLK、F_SETLKW。
作为记录锁功能使用,在我的另一篇博客中有介绍《POSIX记录锁》[https://blog.csdn.net/rikeyone/article/details/88743394] -
获取、设置异步I/O所有权,对应的cmd:F_GETOWN、F_SETOWN。
获取和设置用来接收SIGIO/SIGURG信号的进程id或者进程组id。返回对应的进程id或者进程组id取负值。
示例
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
/*
* fcntl function has these cmd arguments:
* F_DUPFD、F_DUPFD_CLOEXEC : dup file descriptor
* F_GETFD、F_SETFD : set file FD_CLOEXEC flag
* F_GETFL、F_SETFL: file open flags
* F_GETLK、F_SETLK、F_SETLKW : file lock
*/
#define pr_debug(fmt,...) do{ printf("[%ld] DEBUG: "fmt,(long)getpid(),##__VA_ARGS__); fflush(stdout); }while(0)
#define pr_info(fmt,...) do{ printf("[%ld] INFO: "fmt,(long)getpid(),##__VA_ARGS__); fflush(stdout); }while(0)
#define pr_err(fmt,...) do{ printf("[%ld] ERROR: "fmt,(long)getpid(),##__VA_ARGS__);fflush(stdout); }while(0)
#define err_exit(fmt,...) do{ printf("[%ld] ERROR: "fmt,(long)getpid(),##__VA_ARGS__); exit(1); }while(0)
int main(int argc, char *argv[])
{
int fd, ret, access;
pid_t pid;
char buffer[100] = {0};
pr_info("argc:%d\n", argc);
if (argc == 2)
sscanf(argv[1], "%d", &fd);
else
fd = open("testfile", (O_RDWR|O_NONBLOCK|O_APPEND|O_SYNC|O_CREAT), (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH));
if (fd < 0)
err_exit("Open failed, errno = %d, %s\n", errno, strerror(errno));
pr_info("file descriptor = %d\n", fd);
ret = fcntl(fd, F_GETFL, 0);
if (ret < 0)
err_exit("fcntl failed, errno = %d, %s\n", errno, strerror(errno));
access = ret & O_ACCMODE;
switch (access) {
case O_RDWR:
pr_info("read write mode\n");
break;
case O_RDONLY:
pr_info("read only mode\n");
break;
case O_WRONLY:
pr_info("write only mode\n");
break;
default:
pr_err("unknown access mode\n");
break;
}
if (ret & O_NONBLOCK)
pr_info("non block access mode enable\n");
if (ret & O_APPEND)
pr_info("append mode enable\n");
if (ret & O_SYNC)
pr_info("sync writes enable\n");
if ((ret = fcntl(fd, F_GETFD, 0)) < 0)
err_exit("Get CLOSEXEC flag failed\n");
else
pr_info("close on exec flag:%d\n", ret);
if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0)
err_exit("Set CLOSEXEC flag failed\n");
if ((ret = fcntl(fd, F_GETFD, 0)) < 0)
err_exit("Get CLOSEXEC flag failed\n");
else
pr_info("close on exec flag:%d\n", ret);
if (argc == 2)
exit(0);
pid = fork();
if (pid < 0)
err_exit("fork failed\n");
else if (pid > 0) {
pr_info("parent process: fd = %d\n", fd);
wait(NULL);
} else {
sprintf(buffer, "%d", fd);
pr_info("child process: fd = %d\n", fd);
execlp("./fcntl", "./fcntl", buffer, (char *)0);
}
exit(0);
}
在我的测试机ubuntu 14.04 上运行结果如下显示:
$ ./fcntl
[13591] INFO: argc:1
[13591] INFO: file descriptor = 3
[13591] INFO: read write mode
[13591] INFO: non block access mode enable
[13591] INFO: append mode enable
[13591] INFO: sync writes enable
[13591] INFO: close on exec flag:0
[13591] INFO: close on exec flag:1
[13591] INFO: parent process: fd = 3
[13592] INFO: child process: fd = 3
[13592] INFO: argc:2
[13592] INFO: file descriptor = 3
[13592] ERROR: fcntl failed, errno = 9, Bad file descriptor
这里顺便测试了 FD_CLOEXEC 的功能,当父进程设置了对应的标志后,子进程再去访问对应的fd会报错 ERROR: fcntl failed, errno = 9, Bad file descriptor