1. dup函数
当调用dup函数时,内核在进程中创建一个新的文件描述符,该文件描述符是当前可用文件描述符的最小数值,这个文件描述符指向oldfd所拥有的文件表项。
函数原型:
#include <unistd.h>
int dup(int oldfd);
参数说明:oldfd为已经打开的文件描述符
返回值:成功返回一个新文件描述符,失败返回-1并设置errno
2. 使用dup函数
使用dup函数复制一个已经打开文件的文件描述符
图1-dup函数
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main(void) {
int fd = open("test.txt", O_RDWR);
if(fd == -1){
perror("open error");
exit(1);
}
//此时打开一个文件的描述符是3
printf("fd = %d\n", fd);
//fd是当前打开的文件的描述符
int newfd = dup(fd);
if(newfd == -1){
perror("dup error");
exit(1);
}
//此时0,1,2,3都被占用了,可用的最小文件描述符是4
//所以newfd指向fd所拥有的文件表项,newfd = 4
printf("newfd = %d\n", newfd);
char buf1[] = "fd : hello\n";
char buf2[] = "newfd : world\n";
//然后都向newfd和fd指向的文件中写数据
write(fd , buf1 , sizeof(buf1));
write(newfd , buf2 , sizeof(buf2));
return 0;
}
运行结果:
通过write函数向newfd和fd指向的文件中写数据,然后再查看test.txt文件里的内容,发现newfd和fd两个文件描述符都指向了test.txt文件。
3. dup2函数
假如fd1的值为1,当前文件描述符表中最小可用的文件描述符是3,那么新文件描述符3指向文件描述符1所指向的文件,相当于fd2会复制fd1。
函数原型:
#include <unistd.h>
int dup2(int fd1, int fd2);
参数说明:
fd1为已经打开的文件描述符
fd2为新的文件描述符(fd1拷贝到fd2)
返回值:成功返回一个新文件描述符,失败返回-1并设置errno
4. 使用dup2函数
使用dup2函数复制一个已经打开文件的文件描述符
图2-dup2函数
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
int main(void) {
int oldfd = open("test1.txt", O_RDWR);
if(oldfd == -1){
perror("open error");
exit(1);
}
printf("--------dup2之前--------\n");
//此时打开一个文件,描述符是3
printf("oldfd = %d\n", oldfd);
//再打开一个test2文件,描述符是4,同时newfd指向test2.txt文件
int newfd = open("test2.txt", O_RDWR);
if(newfd == -1){
perror("open error");
exit(1);
}
printf("newfd = %d\n", newfd);
/* oldfd是当前打开的文件的描述符,此时dup2函数会把oldfd文件描述符拷贝给newfd,
之前我们学过文件描述符的本质实际是一个指针去指向文件,
oldfd拷贝到newfd这个过程就是把oldfd指向文件的地址给了newfd,然后newfd
也指向了这个文件,同时newfd断开了之前指向的文件
*/
printf("--------dup2之后--------\n");
newfd = dup2(oldfd , newfd);
if(newfd == -1){
perror("dup2 error");
exit(1);
}
printf("newfd = %d\n", newfd);
char buf1[] = "oldfd : hello\n";
char buf2[] = "newfd : world\n";
//然后都向newfd和oldfd指向的文件中写数据
write(oldfd , buf1 , sizeof(buf1));
write(newfd , buf2 , sizeof(buf2));
return 0;
}
运行结果:
从图中执行结果来看,在调用dup2之前,oldfd指向test1.txt文件,newfd指向test2.txt文件,在调用dup2之后,关闭newfd对test1.txt文件的指向,转而指向了test1.txt文件,然后查看test1.txt文件的内容也说明了newfd确实指向了test1.txt文件。
5. dup和dup2实现文件共享
dup和dup2都是复制现有的一个文件描述符实现文件共享时,但是无论复制出多少个文件描述符,这些文件描述符都只共享一个文件表,所以使用所有描述符去操作文件时,使用的都是共享文件表中的同一个文件位移量,只要对文件进行读写操作时就会更新文件表中的文件位移量,因此就不会出现数据覆盖
我们可以通过下图来说明dup和dup2是如何实现文件共享的。
图3-dup和dup2实现文件共享
从图3来看,文件描述符3和4共享一个文件表
,说明3和4指向了同一个文件,任何一个文件描述符对文件进行读写操作都会更新文件表中的f_pos的值,也就是共享f_pos位置
,这样就不会出现数据覆盖。
6. 实现mycat
现在通过dup2函数来实现一个cat命令的程序,实现cat file1 > file2 命令相似功能。
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
//封装错误信息
void sys_err(int fd, char *err_name)
{
if(fd < 0){
perror(err_name);
exit(1);
}
}
int main(void)
{
int fd_f1, fd_f2, fd_out;
int ret;
char buf[1024];
//O_RDONLY:只读方式打开file1
fd_f1 = open("file1", O_RDONLY);
sys_err(fd_f1, "open file1 error");
//O_RDONLY:只读方式打开file2
fd_f2 = open("file2", O_RDONLY);
sys_err(fd_f2, "open file2 error");
/*
O_WRONLY:只写方式打开
O_TRUNC:打开的文件存在并且以只写方式,则情况文件数据
O_CREAT:打开的文件不存在就创建
0644:文件访问权限
*/
//打开out文件
fd_out = open("out", O_WRONLY|O_TRUNC|O_CREAT, 0644);
sys_err(fd_out, "open out error");
//把标准输出重定向到out文件
dup2(fd_out, STDOUT_FILENO);
//从file1读取数据1024字节到buf
while ((ret = read(fd_f1, buf, sizeof(buf)))) {
//往out文件写数据
write(fd_out, buf, ret);
}
//阻塞读取终端
while ((ret = read(STDIN_FILENO, buf, sizeof(buf)))) {
//往out文件写数据,同时把数据重定向到标准输出
write(fd_out, buf, ret);
}
//从file2读取数据1024字节到buf中
while ((ret = read(fd_f2, buf, sizeof(buf)))) {
//往out文件写数据,
write(fd_out, buf, ret);
}
//关闭流
close(fd_f1);
close(fd_f2);
close(fd_out);
return 0;
}
7. dup和dup2的区别
dup和dup2的区别在于:
1. dup 返回的新文件描述符是当期进程的文件描述符表中最小可用的文件描述符
2. dup2的fd2可以指定新描述符的数值,比如:fd2已经打开一个文件,则会先关闭fd2,如果fd2 = fd1的话,那么返回fd2。