基础IO--文件描述符和重定向(dup2系统调用)

本文详细介绍了C语言中open系统调用接口,包括其参数flags及其作用,如O_RDONLY、O_WRONLY、O_RDWR和O_CREAT。还讲解了文件描述符fd的概念,以及如何通过dup2和重定向进行文件操作。重点讨论了printf、fwrite和write的区别,以及它们在缓冲和重定向中的行为。
摘要由CSDN通过智能技术生成

操作文件,除了使用C接口(库函数),我们还可以采用系统接口来进行文件访问
open close read write lseek都属于系统提供的接口,称之为系统调用接口
举例介绍open接口

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

pathname: 要打开或创建的目标文件
flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
参数:
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写
返回值:
成功:新打开的文件描述符
失败:-1

一.文件描述符fd

Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0,标准输出1,标准错误2。0,1,2对应的物理设备一般是:键盘,显示器,显示器
请添加图片描述

一个进程可能打开多个文件,文件描述符就是从0开始的小整数,本质是数组下标。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件
文件描述符的规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符

二.重定向

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>

int main()
{
    close(1);
   int fd = open("myfile", O_WRONLY|O_CREAT, 00644);
   if(fd < 0){
        perror("open");
        return 1;
 }
  printf("fd: %d\n", fd);
  fflush(stdout);
 
  close(fd);
   exit(0);
}

我们关闭1,我们发现,本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中,fd=1。这种现象叫做输出
重定向。常见的重定向有:>, >>, <。
请问fopen做了什么?给调用的用户申请file_struct结构体变量,并返回地址(file*);在底层通过open打开文件,并返回fd,把fd填充进file变量中的inode
为什么在代码尾处要加 fflush(stdout)呢?以下解释是在没有加 fflush(stdout)情况下。我们先关闭了描述符1(1指向显示器),现在关闭后1号文件描述符空出来,打开一个新的文件myfile,1号描述符本来指向显示器,现在指向myfile。file_struct内部我们只关闭了1号描述符,stdout还在,所以stdout对应的文件描述符还是1,接下来直接打印时,这个数据并没有直接刷新到操作系统底层,而是把数据放到了C语言提供的file结构的缓冲区里,还没来得及进程退出刷新,关闭了fd,此时fd就是1,没了1之后,进程退出时消息无法由用户从语言层刷新到操作系统底层,所以我们看不到数据的结果。 加上fflush(stdout)就可以了。但是 fflush(stdout)之前一条printf(“fd: %d\n”, fd);明明已经有\n为什么没有刷新呢?是因为显示器为行刷新,当close(1),又打开新文件时普通文件为全缓冲。
请添加图片描述

重定向的本质是修改,文件描述符fd下标对应的struct file*的内容

三.使用dup2系统调用

函数原型如下:

#include <unistd.h>
int dup2(int oldfd, int newfd);

注:不是拷贝fd!而是拷贝fd对应的数组中的内容,new是old的一份拷贝,数组内容最后和old一致

四.FILE

因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。所以C库当中的FILE结构体内部,必定封装了fd

#include <stdio.h>
#include <string.h>
int main()
{
     const char *msg0="hello printf\n";
     const char *msg1="hello fwrite\n";
     const char *msg2="hello write\n";
     
     printf("%s", msg0);
     fwrite(msg1, strlen(msg0), 1, stdout);
     write(1, msg2, strlen(msg2));
     
     fork();
     
     return 0
 }

运行结果:

hello printf
hello fwrite
hello write

但如果对进程实现输出重定向呢? ./hello > file , 我们发现结果变成了:

hello write
hello printf
hello fwrite
hello printf
hello fwrite

(1)一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。
(2)printf fwrite 库函数会自带缓冲区(进度条例子就可以说明),当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。
(3)而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后
(4)但是进程退出之后,会统一刷新,写入文件当中。
(5)但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。
(5)write 没有变化,说明没有所谓的缓冲。
综上: printf fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。另外,我们这里所说的缓冲区,
都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区,不过不再我们讨论范围之内。
那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统
调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是
C,所以由C标准库提供。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值