目录
1 文件描述符简介
1.1 文件描述符特点
1.2 标准文件描述符
1.3 文件描述符的生命周期
2 fcntl()函数
2.1 fcntl()函数简介
2.2 复制文件描述符(F_DUPFD)
2.3 获取/设置文件状态标志(F_GETFL/F_SETFL )
1 文件描述符简介
文件描述符(File Descriptor)是Linux和UNIX系统编程中的一个重要概念,它是一个用于标识打开文件或其他输入/输出资源的非负整数,文件描述符允许程序通过一个抽象的数字来引用文件和其他输入输出资源,而不是直接使用文件名或设备名。
从上面的示例代码中可以看到,调用 open 函数会有一个返回值, 譬如示例代码中的 fd1 和 fd2, 这是一个 int 类型的数据,在 open函数执行成功的情况下, 会返回一个非负整数, 该返回值就是一个文件描述符(file descriptor)。
1.1 文件描述符特点
-
唯一性:每个打开的文件或设备在进程中都有一个唯一的文件描述符。文件描述符是从 3 开始分配的,譬如说进程中第一个被打开的文件对应的文件描述符是 3、第二个文件是 4……以此类推。为什么是从3开始?因为0、 1、 2 这三个文件描述符已经默认被系统占用了,分别分配给了系统标准输入(0)、 标准输出(1)以及标准错误(2)。
-
抽象性:文件描述符提供了一个抽象层,使得对各种类型的输入输出设备的操作看起来像是对文件的操作。
-
有限性:文件描述符的数量是有限的,通常由系统设置决定,例如Linux系统中可以通过
ulimit -a
或ulimit -a
命令查看文件描述符的限制。
该最大值默认情况下是 1024,也就意味着一个进程最多可以打开 1024 个文件,当然这个限制数其实是可以设置的。
1.2 标准文件描述符
在Linux中,每个进程启动时都会自动打开三个标准文件描述符:
- 标准输入(stdin):文件描述符为0,通常用于从键盘接收输入。
- 标准输出(stdout):文件描述符为1,通常用于向屏幕输出文本。
- 标准错误(stderr):文件描述符为2,通常用于输出错误信息。
1.3 文件描述符的生命周期
-
打开:使用
open()
或openat()
等系统调用打开文件时,系统会分配一个新的文件描述符。 -
使用:通过文件描述符,可以使用
read()
、write()
、lseek()
等系统调用来读写数据或移动文件指针。 -
关闭:使用
close()
系统调用关闭文件描述符,释放与该文件描述符关联的资源。 -
重定向:可以将文件描述符重定向到其他文件描述符或文件,例如通过
dup()
或dup2()
。
2 fcntl()函数
2.1 fcntl()函数简介
fcntl()函数可以对一个已经打开的文件描述符执行一系列控制操作,譬如复制一个文件描述符(与 dup、dup2 作用相同)、获取/设置文件描述符标志、获取/设置文件状态标志等,类似于一个多功能文件描述符管理工具箱。 fcntl()函数原型如下所示:
#include <fcntl.h>int fcntl(int fd, int cmd, ... /* arg */ );
参数说明:
- fd:文件描述符,即要控制的文件或设备的标识符。
- cmd:控制命令,指定要执行的操作类型。
- arg(可选):某些命令需要额外的参数,这些参数会通过
arg
传递给fcntl()
。
常见的控制命令(cmd):
- F_DUPFD:复制文件描述符。如果提供了
arg
,则新文件描述符的最小值是arg
,否则是当前进程的最大文件描述符加1。 - F_GETFD:获取文件描述符的标志。FD_CLOEXEC:当执行exec系列函数时,关闭文件描述符;O_NONBLOCK:设置非阻塞模式;O_ASYNC:使文件描述符异步。
- F_SETFD:设置文件描述符的标志。
- F_GETFL:获取文件状态标志。
- F_SETFL:设置文件状态标志。
- F_GETLK:获取记录锁。
- F_SETLK:设置记录锁。
- F_SETLKW:等待直到可以设置记录锁。
- F_GETOWN:获取文件描述符的所有者(通常用于异步I/O)。
- F_SETOWN:设置文件描述符的所有者。
2.2 复制文件描述符(F_DUPFD)
dup 和 dup2用于复制文件描述符,除此之外,还可以通过 fcntl 函数复制文件描 述 符,可用的 cmd包括 F_DUPFD 和 F_DUPFD_CLOEXEC , 这里重点介绍 F_DUPFD 。
使用场景:使用 F_DUPFD
可以方便地在程序中创建文件描述符的副本,这在需要在不同的上下文中使用同一个文件,或者在需要临时改变文件描述符的属性(如设置非阻塞模式)时非常有用。
如何使用:当 cmd=F_DUPFD 时,它的作用会根据 fd 复制出一个新的文件描述符,此时需要传入第三个参数,第三个参数用于指出新复制出的文件描述符是一个大于或等于该参数的可用文件描述符(没有使用的文件描述符) ;如果第三个参数等于一个已经存在的文件描述符,则取一个大于该参数的可用文件描述符。测试示例如下:
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>int main() {int original_fd = open("myfile.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);if (original_fd == -1) {perror("open");return 1;}// 指定新文件描述符的最小值为 10int new_fd = fcntl(original_fd, F_DUPFD, 10);if (new_fd == -1) {perror("fcntl");close(original_fd);return 1;}printf("Original file descriptor: %d\n", original_fd);printf("New file descriptor: %d\n", new_fd);// 关闭文件描述符close(original_fd);close(new_fd);return 0;
}
在这个示例中,我们首先打开一个名为 "myfile.txt" 的文件,获取其文件描述符 original_fd
。接着,我们使用 fcntl()
函数和 F_DUPFD
命令来复制这个文件描述符,并指定新文件描述符的最小值为 10。如果成功,fcntl()
将返回新的文件描述符 new_fd
,它与 original_fd
指向同一个文件。最后,我们打印出原始和新的文件描述符,并关闭它们。运行结果如下:
2.3 获取/设置文件状态标志(F_GETFL/F_SETFL )
cmd=F_GETFL 可用于获取文件状态标志, cmd=F_SETFL 可用于设置文件状态标志。
- cmd=F_GETFL 时不需要传入第三个参数,返回值成功表示获取到的文件状态标志;
- cmd=F_SETFL 时,需要传入第三个参数,此参数表示需要设置的文件状态标志。
这些标志指的就是我们在调用 open 函数时传入的 flags 标志, 可以指定一个或多个(通过位或 | 运算符组合), 但是文件权限标志(O_RDONLY、 O_WRONLY、 O_RDWR)以及文件创建标志(O_CREAT、O_EXCL、 O_NOCTTY、 O_TRUNC)不能被设置;只有 O_APPEND、 O_ASYNC、O_DIRECT、 O_NOATIME 以及 O_NONBLOCK 这些标志可以被修改。 所以对于一个已经打开的文件描述符,可以通过这种方式添加或移除标志。
下面的示例代码,如何、首先获取文件状态标志,然后修改它们,并将修改后的标志应用到文件描述符上:
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>int main() {// 打开文件int fd = open("myfile.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);if (fd == -1) {perror("open");return 1;}// 获取当前文件状态标志int flags = fcntl(fd, F_GETFL, 0);if (flags == -1) {perror("fcntl F_GETFL");close(fd);return 1;}printf("Original file status flags: %o\n", flags);// 打印当前标志并设置为非阻塞模式printf("Setting O_NONBLOCK flag.\n");flags |= O_NONBLOCK; // 添加非阻塞标志// 设置新的文件状态标志if (fcntl(fd, F_SETFL, flags) == -1) {perror("fcntl F_SETFL");close(fd);return 1;}// 再次获取文件状态标志以验证更改flags = fcntl(fd, F_GETFL, 0);if (flags == -1) {perror("fcntl F_GETFL after F_SETFL");close(fd);return 1;}printf("New file status flags after setting O_NONBLOCK: %o\n", flags);// 关闭文件描述符close(fd);return 0;
}
程序中,首先使用 open()
函数打开一个文件,并获取其文件描述符 fd
。然后,我们使用 fcntl()
函数和 F_GETFL
命令来获取文件的当前状态标志,并打印它们。
接着,通过逻辑或操作 flags |= O_NONBLOCK;
来设置 O_NONBLOCK
标志,这将文件描述符设置为非阻塞模式。之后,我们再次使用 fcntl()
函数和 F_SETFL
命令来应用这些更改。并再次获取并打印文件状态标志。
程序运行结果如下: