11. fcntl - 文件控制 見出しへのリンク
函数介绍 見出しへのリンク
fcntl
系统调用用于对已打开的文件描述符执行各种控制操作。它是文件I/O操作的重要补充,可以用来复制文件描述符、设置文件状态标志、管理文件锁等。
函数原型 見出しへのリンク
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
功能 見出しへのリンク
对文件描述符执行各种控制操作,包括复制、设置标志、管理锁等。
参数 見出しへのリンク
int fd
: 文件描述符int cmd
: 控制命令F_DUPFD
: 复制文件描述符(返回大于等于arg的最小可用fd)F_DUPFD_CLOEXEC
: 复制文件描述符并设置FD_CLOEXEC标志F_GETFD
: 获取文件描述符标志F_SETFD
: 设置文件描述符标志F_GETFL
: 获取文件状态标志F_SETFL
: 设置文件状态标志F_GETLK
: 获取文件锁信息F_SETLK
: 设置文件锁(非阻塞)F_SETLKW
: 设置文件锁(阻塞)
...
: 可选参数,根据命令不同而不同
返回值 見出しへのリンク
- 根据命令不同返回不同值:
F_DUPFD
,F_DUPFD_CLOEXEC
: 成功返回新的文件描述符,失败返回-1F_GETFD
,F_GETFL
: 成功返回标志值,失败返回-1- 其他命令:成功返回0,失败返回-1
- 失败时设置errno
相似函数 見出しへのリンク
dup()
,dup2()
: 复制文件描述符open()
: 打开文件flock()
: 文件锁操作lockf()
: 另一种文件锁接口
示例代码 見出しへのリンク
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
int main() {
int fd, new_fd;
int flags;
printf("=== Fcntl函数示例 ===\n");
// 创建测试文件
fd = open("test_fcntl.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
if (fd == -1) {
perror("创建测试文件失败");
exit(EXIT_FAILURE);
}
printf("成功创建测试文件,文件描述符: %d\n", fd);
// 写入测试数据
const char *test_data = "Hello, fcntl test data!";
if (write(fd, test_data, strlen(test_data)) == -1) {
perror("写入测试数据失败");
close(fd);
exit(EXIT_FAILURE);
}
// 示例1: 复制文件描述符(F_DUPFD)
printf("\n示例1: 复制文件描述符\n");
// 复制文件描述符,要求新fd >= 10
new_fd = fcntl(fd, F_DUPFD, 10);
if (new_fd == -1) {
perror("复制文件描述符失败");
} else {
printf(" 成功复制文件描述符: %d -> %d\n", fd, new_fd);
// 验证两个fd指向同一文件
char buffer[100];
lseek(new_fd, 0, SEEK_SET); // 移动new_fd的位置
ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer[bytes_read] = '\0';
printf(" 从原fd读取数据: %s\n", buffer);
}
close(new_fd);
}
// 示例2: 获取和设置文件描述符标志(F_GETFD, F_SETFD)
printf("\n示例2: 文件描述符标志操作\n");
// 获取当前文件描述符标志
flags = fcntl(fd, F_GETFD);
if (flags == -1) {
perror("获取文件描述符标志失败");
} else {
printf(" 当前文件描述符标志: 0x%x\n", flags);
if (flags & FD_CLOEXEC) {
printf(" 包含FD_CLOEXEC标志(exec时关闭)\n");
} else {
printf(" 不包含FD_CLOEXEC标志\n");
}
}
// 设置FD_CLOEXEC标志
if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) {
perror("设置FD_CLOEXEC标志失败");
} else {
printf(" 成功设置FD_CLOEXEC标志\n");
// 验证设置结果
flags = fcntl(fd, F_GETFD);
if (flags != -1 && (flags & FD_CLOEXEC)) {
printf(" 验证: FD_CLOEXEC标志已设置\n");
}
}
// 示例3: 获取和设置文件状态标志(F_GETFL, F_SETFL)
printf("\n示例3: 文件状态标志操作\n");
// 获取当前文件状态标志
flags = fcntl(fd, F_GETFL);
if (flags == -1) {
perror("获取文件状态标志失败");
} else {
printf(" 当前文件状态标志: 0x%x\n", flags);
// 分析标志位
if (flags & O_RDONLY) printf(" 只读模式\n");
if (flags & O_WRONLY) printf(" 只写模式\n");
if (flags & O_RDWR) printf(" 读写模式\n");
if (flags & O_APPEND) printf(" 追加模式\n");
if (flags & O_NONBLOCK) printf(" 非阻塞模式\n");
}
// 设置追加模式
if (fcntl(fd, F_SETFL, flags | O_APPEND) == -1) {
perror("设置追加模式失败");
} else {
printf(" 成功设置追加模式\n");
// 验证设置结果
flags = fcntl(fd, F_GETFL);
if (flags != -1 && (flags & O_APPEND)) {
printf(" 验证: 追加模式已设置\n");
}
}
// 示例4: 非阻塞模式演示
printf("\n示例4: 非阻塞模式演示\n");
// 创建管道用于演示
int pipefd[2];
if (pipe(pipefd) == -1) {
perror("创建管道失败");
} else {
printf(" 创建管道成功: 读端=%d, 写端=%d\n", pipefd[0], pipefd[1]);
// 设置读端为非阻塞模式
flags = fcntl(pipefd[0], F_GETFL);
if (fcntl(pipefd[0], F_SETFL, flags | O_NONBLOCK) == -1) {
perror(" 设置非阻塞模式失败");
} else {
printf(" 成功设置读端为非阻塞模式\n");
// 尝试从空管道读取(应该立即返回错误)
char pipe_buffer[10];
ssize_t bytes_read = read(pipefd[0], pipe_buffer, sizeof(pipe_buffer));
if (bytes_read == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
printf(" 非阻塞读取: 管道为空,立即返回EAGAIN\n");
} else {
perror(" 非阻塞读取失败");
}
}
}
close(pipefd[0]);
close(pipefd[1]);
}
// 示例5: 文件描述符复制对比
printf("\n示例5: 文件描述符复制对比\n");
// 使用fcntl复制
int fcntl_dup = fcntl(fd, F_DUPFD, 0);
if (fcntl_dup != -1) {
printf(" fcntl F_DUPFD复制: %d -> %d\n", fd, fcntl_dup);
close(fcntl_dup);
}
// 使用fcntl复制并设置CLOEXEC
int fcntl_dup_cloexec = fcntl(fd, F_DUPFD_CLOEXEC, 0);
if (fcntl_dup_cloexec != -1) {
printf(" fcntl F_DUPFD_CLOEXEC复制: %d -> %d\n", fd, fcntl_dup_cloexec);
// 检查CLOEXEC标志
int cloexec_flags = fcntl(fcntl_dup_cloexec, F_GETFD);
if (cloexec_flags != -1 && (cloexec_flags & FD_CLOEXEC)) {
printf(" 验证: FD_CLOEXEC标志已设置\n");
}
close(fcntl_dup_cloexec);
}
// 使用dup复制
int dup_fd = dup(fd);
if (dup_fd != -1) {
printf(" dup复制: %d -> %d\n", fd, dup_fd);
close(dup_fd);
}
// 使用dup2复制到指定fd
int target_fd = 100;
int dup2_fd = dup2(fd, target_fd);
if (dup2_fd != -1) {
printf(" dup2复制到fd %d: %d -> %d\n", target_fd, fd, dup2_fd);
close(dup2_fd);
}
// 示例6: 多进程文件描述符继承演示
printf("\n示例6: 多进程文件描述符继承演示\n");
pid_t pid = fork();
if (pid == -1) {
perror("fork失败");
} else if (pid == 0) {
// 子进程
printf(" 子进程 %d 继承了文件描述符 %d\n", getpid(), fd);
// 子进程可以使用继承的文件描述符
char child_buffer[100];
lseek(fd, 0, SEEK_SET);
ssize_t bytes_read = read(fd, child_buffer, sizeof(child_buffer) - 1);
if (bytes_read > 0) {
child_buffer[bytes_read] = '\0';
printf(" 子进程读取数据: %s\n", child_buffer);
}
exit(EXIT_SUCCESS);
} else {
// 父进程
printf(" 父进程 %d 等待子进程结束\n", getpid());
wait(NULL);
}
// 示例7: 错误处理演示
printf("\n示例7: 错误处理演示\n");
// 尝试对无效文件描述符操作
if (fcntl(999, F_GETFD) == -1) {
printf(" 操作无效文件描述符: %s\n", strerror(errno));
}
// 尝试使用无效命令
if (fcntl(fd, 999, 0) == -1) {
printf(" 使用无效命令: %s\n", strerror(errno));
}
// 清理资源
printf("\n清理资源...\n");
if (close(fd) == -1) {
perror("关闭文件失败");
} else {
printf("成功关闭文件描述符 %d\n", fd);
}
// 删除测试文件
if (unlink("test_fcntl.txt") == -1) {
perror("删除测试文件失败");
} else {
printf("成功删除测试文件\n");
}
return 0;
}