在Linux中只要调用open()
函数就可以给被操作的文件分配一个文件描述符,除了使用这种方式Linux系统还提供了一些其他的 API 用于文件描述符的分配,相关函数有三个:dup
, dup2
, fcntl
。
1. dup
1.1 函数详解
dup函数的作用是复制文件描述符,这样就有多个文件描述符可以指向同一个文件了。函数原型如下:
1 2
| #include <unistd.h> int dup(int oldfd);
|
下图展示了 dup()
函数具体行为, 这样不过使用 fd1
还是使用fd2
都可以对磁盘文件A
进行操作了。
被复制出的新文件描述符是独立于旧的文件描述符的,二者没有连带关系。也就是说当旧的文件描述符被关闭了,复制出的新文件描述符还是可以继续使用的。
1.2 示例代码
下面的代码中演示了通过dup()
函数进行文件描述符复制, 并验证了复制之后两个新、旧文件描述符是独立的,二者没有连带关系。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <fcntl.h>
int main() { int fd = open("./mytest.txt", O_RDWR|O_CREAT, 0664); if(fd == -1) { perror("open"); exit(0); } printf("fd: %d\n", fd);
const char* pt = "你好, 世界......"; write(fd, pt, strlen(pt));
int newfd = dup(fd); printf("newfd: %d\n", newfd);
close(fd);
const char* ppt = "((((((((((((((((((((((骚年,你要相信光!!!))))))))))))))))))))))"; write(newfd, ppt, strlen(ppt)); close(newfd);
return 0; }
|
2. dup2
2.1 函数详解
dup2() 函数是 dup() 函数的加强版,基于dup2() 既可以进行文件描述符的复制, 也可以进行文件描述符的重定向。文件描述符重定向就是改变已经分配的文件描述符关联的磁盘文件。
dup2() 函数原型如下:
1 2 3 4 5 6 7 8 9
| #include <unistd.h>
int dup2(int oldfd, int newfd);
|
- 参数:
oldfd
和``newfd` 都是文件描述符
- 返回值: 函数调用成功返回新的文件描述符, 调用失败返回 -1
关于这个函数的两个参数虽然都是文件描述符,但是在使用过程中又对应了不同的场景,具体如下:
场景1:
假设参数 oldfd
对应磁盘文件 a.txt
, newfd
对应磁盘文件b.txt
。在这种情况下调用dup2
函数, 是给newfd
做了重定向,newfd 和文件 b.txt 断开关联, 相当于关闭了这个文件, 同时 newfd 指向了磁盘上的a.txt文件
,最终 oldfd 和 newfd 都指向了磁盘文件 a.txt。
场景2:
假设参数 oldfd
对应磁盘文件 a.txt
, newfd
不对应任何的磁盘文件(newfd 必须是一个大于等于0的整数)。在这种情况下调用dup2
函数, 在这种情况下会进行文件描述符的复制,newfd 指向了磁盘上的a.txt文件
,最终 oldfd 和 newfd 都指向了磁盘文件 a.txt。
场景3:
假设参数 oldfd
和newfd
两个文件描述符对应的是同一个磁盘文件 a.txt
, 在这种情况下调用dup2
函数, 相当于啥也没发生, 不会有任何改变。
2.2 示例代码
给dup2() 的第二个参数指定一个空闲的没被占用的文件描述符就可以进行文件描述符的复制了, 示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <fcntl.h>
int main() { int fd = open("./111.txt", O_RDWR|O_CREAT, 0664); if(fd == -1) { perror("open"); exit(0); } printf("fd: %d\n", fd);
const char* pt = "你好, 世界......"; write(fd, pt, strlen(pt));
int fd1 = 1023;
dup2(fd, fd1);
close(fd);
const char* ppt = "((((((((((((((((((((((骚年,你要相信光!!!))))))))))))))))))))))"; write(fd1, ppt, strlen(ppt)); close(fd1);
return 0; }
|
将两个有效的文件描述符分别传递给 dup2() 函数,就可以实现文件描述符的重定向了。将第二个参数的文件描述符重定向到参数1文件描述符指向的文件上。
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <fcntl.h>
int main() { int fd = open("./111.txt", O_RDWR|O_CREAT, 0664); if(fd == -1) { perror("open"); exit(0); } printf("fd: %d\n", fd);
const char* pt = "你好, 世界......"; write(fd, pt, strlen(pt));
int fd1 = open("./222.txt", O_RDWR|O_CREAT, 0664); if(fd1 == -1) { perror("open1"); exit(0); }
dup2(fd, fd1);
close(fd);
const char* ppt = "((((((((((((((((((((((骚年,你要相信光!!!))))))))))))))))))))))"; write(fd1, ppt, strlen(ppt)); close(fd1);
return 0; }
|
3. fcntl
3.1 函数详解
fcntl() 是一个变参函数, 并且是多功能函数,在这里只介绍如何通过这个函数实现文件描述符的复制
和获取/设置已打开的文件属性
。该函数的函数原型如下:
1 2 3 4
| #include <unistd.h> #include <fcntl.h>
int fcntl(int fd, int cmd, ... );
|
- 参数:
- fd: 要操作的文件描述符
- cmd: 通过该参数控制函数要实现什么功能
- 返回值:函数调用失败返回 -1,调用成功,返回正确的值:
- 参数 cmd = F_DUPFD:返回新的被分配的文件描述符
- 参数 cmd = F_GETFL:返回文件的flag属性信息
fcntl() 函数的 cmd 可使用的参数列表:
参数 cmd 的取值 |
功能描述 |
F_DUPFD |
复制一个已经存在的文件描述符 |
F_GETFL |
获取文件的状态标志 |
F_SETFL |
设置文件的状态标志 |
文件的状态标志指的是在使用 open() 函数打开文件的时候指定的 flags 属性, 也就是第二个参数
1
| int open(const char *pathname, int flags);
|
下表中列出了一些常用的文件状态标志:
文件状态标志 |
说明 |
O_RDONLY |
只读打开 |
O_WRONLY |
只写打开 |
O_RDWR |
读、写打开 |
O_APPEND |
追加写 |
O_NONBLOCK |
非阻塞模式 |
O_SYNC |
等待写完成(数据和属性) |
O_ASYNC |
异步I/O |
O_RSYNC |
同步读和写 |
3.2 复制文件描述符
使用 fcntl() 函数进行文件描述符复制, 第二个参数 cmd 需要指定为 F_DUPFD
(这是个变参函数其他参数不需要指定)。
1
| int newfd = fcntl(fd, F_DUPFD);
|
使用 fcntl() 复制文件描述符, 函数返回值为新分配的文件描述符,示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <fcntl.h>
int main() { int fd = open("./mytest.txt", O_RDWR|O_CREAT, 0664); if(fd == -1) { perror("open"); exit(0); } printf("fd: %d\n", fd);
const char* pt = "你好, 世界......"; write(fd, pt, strlen(pt));
int newfd = fcntl(fd, F_DUPFD); printf("newfd: %d\n", newfd);
close(fd);
const char* ppt = "((((((((((((((((((((((骚年,你要相信光!!!))))))))))))))))))))))"; write(newfd, ppt, strlen(ppt)); close(newfd);
return 0; }
|
3.3 设置文件状态标志
通过 open()
函数打开文件之后, 文件的flag属性就已经被确定下来了,如果想要在打开状态下修改这些属性,可以使用 fcntl()
函数实现, 但是有一点需要注意, 不是所有的flag 属性都能被动态修改, 只能修改如下状态标志: O_APPEND
, O_NONBLOCK
, O_SYNC
, O_ASYNC
, O_RSYNC
等。
得到已打开的文件的状态标志,需要将 cmd 设置为 F_GETFL,得到的信息在函数的返回值中
1
| int flag = fcntl(fd, F_GETFL);
|
设置已打开的文件的状态标志,需要将 cmd 设置为 F_SETFL,新的flag需要通过第三个参数传递给 fcntl() 函数
1 2 3 4 5 6
| int flag = fcntl(fd, F_GETFL);
flag = flag | O_APPEND;
fcntl(fd, F_SETFL, flag);
|
举例: 通过fcntl()函数获取/设置已打开的文件属性
,先来描述一下场景:
如果要往当前文件中写数据, 打开一个新文件, 文件的写指针在文件头部,数据默认也是写到文件开头,如果不想将数据写到文件头部, 可以给文件追加一个O_APPEND
属性。实例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <fcntl.h>
int main() { int fd = open("./111.txt", O_RDWR); if(fd == -1) { perror("open"); exit(0); } printf("fd: %d\n", fd);
int flag = fcntl(fd, F_GETFL); flag = flag | O_APPEND; fcntl(fd, F_SETFL, flag);
const char* ppp = "((((((((((((((((((((((骚年,你要相信光!!!))))))))))))))))))))))"; write(fd, ppp, strlen(ppp)); close(fd);
return 0; }
|