Linux文件重定向文件缓冲区

目录

一、C文件接口

二、系统文件I/O

2.1认识系统文件I/O

2.2系统文件I/O

2.3系统调用和库函数

2.4open( )的返回值--文件描述符

2.5访问文件的本质

三、文件重定向

3.1认识文件重定向

3.2文件重定向的本质

3.3在shell中添加重定向功能

3.4stdout和stderr

3.5如何理解“linux下一切皆文件” --以对外设的IO操作为例

四、文件缓冲区

4.1认识FILE

4.2文件缓冲区引入

4.3文件缓冲区的原理

4.4解释现象

4.5总结


一、C文件接口

  • stdin & stdout & stderr 
  • C默认会打开三个输入输出流,分别是stdin, stdout, stderr
  • 仔细观察发现,这三个流的类型都是FILE*, fopen返回值类型,文件指针 
  • fwrite向指定文件写入内容
  • fread从指定文件读取内容
  • fprintf根据指定的format(格式)发送信息(参数)到由stream(流)指定的文件,fprintf可以使得信息写入到指定的文件
  • 调用C文件接口,以w的形式打开,若文件不存在,会在当前目录下新建文件,当前路径就是进程的当前路径cwd,如果改变了进程的cwd就可以在其他目录下新建文件
  • w写入前都会对文件进行清空,a在文件结尾追加写,两者都是写入
  • C默认打开的三个输入输出流不是C语言的特性,而是操作系统的特性,进程会默认打开键盘,显示器,显示器

二、系统文件I/O

2.1认识系统文件I/O

  • 文件其实是在磁盘上的,磁盘是外设,对文件进行访问,就是对硬件进行访问

  • 任何用户都不能直接访问硬件的数据 ,而必须通过系统调用

  • 几乎所有的库只要是访问硬件设备,必须封装系统调用

  • C文件接口就是一种库函数,是对系统调用的封装

2.2系统文件I/O

  • 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(例0666) 选项,来指明新文件的访问权限
    O_APPEND: 追加写
    O_TRUNC: 每一次写入都清空文件
    返回值:
    成功:新打开的文件描述符
    失败:-1

代码示例:

umask( )可以用来设置掩码的值

 

  • 比特方位式的标志位传递方式 
  • 通过位运算来实现

2.3系统调用和库函数

  • 上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)
  • open close read write lseek 都属于系统提供的接口,称之为系统调用接口
  • 可以认为,f#系列的函数,都是对系统调用的封装,方便二次开发。

2.4open( )的返回值--文件描述符

  • Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2
  • 0,1,2对应的物理设备一般是:键盘,显示器,显示器
  • linux下文件描述符的分配规则:从0下标开始,寻找最小没有被使用过的数组位置,它的下标就是新文件的文件描述符--结合访问文件的本质来说明

代码示例:

  • 因为C库函数是对系统接口的封装,系统接口下只认识文件描述符,所以C库自己提供的FILE结构体中必定也包含着文件描述符,用_fileno记录

如果关闭了1号文件,printf就无法向1号文件(显示器)写入了 ,但可以向3号文件写入,所以我们打印就只能看到n的值

2.5访问文件的本质

  • 任何一个被打开的文件在内存中都要被管理起来,操作系统如果管理被打开的文件?----先描述再组织

  • 当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件--file结构体(直接或间接包含如下属性:文件的基本属性,文件的内核缓冲区信息,引用计数,struct file*next,在磁盘的什么位置),表示一个已经打开的文件对象
  • 而进程执行open系统调用,所以必须让进程和文件关联起来,每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!
  • 所以,本质上,文件描述符就是该数组的下标,只要拿着文件描述符,就可以找到对应的文件

 

  •  当一个进程open()一个文件时,操作系统会在struct_file的指针数组中从下标为0的地方在开始寻找一个没有被使用过的数组位置,填入要打开文件的struct file*,再将数组下标返回给open( )调用,作为该文件的文件描述符fd
  • 当一个进程要向某个文件写入的时候,操作系统只认识文件描述符,根据文件描述符找到对应的数组下标,根据数组下标位置里的内容找到所对应的文件再写入
  • close关闭文件本质上是清空对应fd数组下标位置的内容,再将该fd内容指向的文件的引用计数--,引用计数为0才释放销毁相应的struct_ file 

三、文件重定向

3.1认识文件重定向

  • 关闭1号文件再打开新文件 ,向1号文件写入内容

可以看到,原来要向1号文件(显示屏)打印的信息,被写入到了新打开的文件,其中,fd=1。这种现象叫做输出重定向

常见的重定向有:>输出重定向, >>追加重定向, <输入重定向

  • 追加重定向

 

  • 输入重定向

3.2文件重定向的本质

  •  文件重定向的本质:将1号文件描述符在指针数组中对应位置的内容,用log.txt文件描述符在指针数组中对应位置的内容进行覆盖,原本数组内的指向1号文件的文件指针就被替换成log.txt的文件指针,当我们再向1号文件描述符写入内容的时候,就是向文件指针指向的log.txt内写入而不再写到标准输出

  • dup2系统调用

  •  原本向显示屏打印的内容被写入到log.txt文件中

3.3在shell中添加重定向功能

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<assert.h>
#include<ctype.h>
#include<fcntl.h>#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGV_SIZE 32#define NONE -1
#define IN_RDIR     0
#define OUT_RDIR    1
#define APPEND_RDIR 2extern char** environ;
char commandline[LINE_SIZE];
char* argv[ARGV_SIZE];
char pwd[LINE_SIZE];
char myenv[LINE_SIZE];int lastcode=0;
int quit=0;char *rdirfilename = NULL;
int rdir = NONE;const char* getuser()
{return getenv("USER");
}const char* gethostname()
{return getenv("HOSTNAME");
}void getpwd()
{getcwd(pwd,sizeof(pwd));
}void check_redir(char *cmd)
{// ls -al -n// ls -al -n >/</>> filename.txtchar *pos = cmd;while(*pos){if(*pos == '>'){if(*(pos+1) == '>'){*pos++ = '\0';*pos++ = '\0';while(isspace(*pos)) pos++;rdirfilename = pos;rdir=APPEND_RDIR;break;}else{*pos = '\0';pos++;while(isspace(*pos)) pos++;rdirfilename = pos;rdir=OUT_RDIR;break;}}else if(*pos == '<'){*pos = '\0'; // ls -a -l -n < filename.txtpos++;while(isspace(*pos)) pos++;rdirfilename = pos;rdir=IN_RDIR;break;}else{//do nothing}pos++;}
}void interact(char* cline,int size)
{getpwd();printf(LEFT"%s@%s %s"RIGHT""LABLE" ",getuser(),gethostname(),pwd);char* s=fgets(cline,size,stdin);assert(s);(void)s;cline[strlen(cline)-1]='\0';//printf("echo : %s",cline);//ls -a -l > myfile.txtcheck_redir(cline);
}int splitstring(char cline[],char* _argv[])
{int i=0;_argv[i++]=strtok(cline,DELIM);while(_argv[i++]=strtok(NULL,DELIM));return i-1;
}void normalexcute(char* _argv[])
{pid_t id=fork();if(id<0){perror("fork");//continue;return ;}else if(id==0){int fd = 0;// 后面我们做了重定向的工作,后面我们在进行程序替换的时候,难道不影响吗???if(rdir == IN_RDIR){fd = open(rdirfilename, O_RDONLY);dup2(fd, 0);}else if(rdir == OUT_RDIR){fd = open(rdirfilename, O_CREAT|O_WRONLY|O_TRUNC, 0666);dup2(fd, 1);}else if(rdir == APPEND_RDIR){fd = open(rdirfilename, O_CREAT|O_WRONLY|O_APPEND, 0666);dup2(fd, 1);}//子进程执行指令//execvpe(argv[0],argv,environ);execvp(argv[0],argv);}else{int status=0;pid_t rid=waitpid(id,&status,0);if(rid==id){lastcode=WEXITSTATUS(status);}}
}int buildcommand(char* _argv[],int _argc)
{if(_argc==2&&strcmp(_argv[0],"cd")==0){chdir(_argv[1]);getpwd();sprintf(getenv("PWD"),"%s",pwd);return 1;}else if(_argc==2&&strcmp(_argv[0],"export")==0){strcpy(myenv,_argv[1]);putenv(myenv);return 1;}else if(_argc==2&&strcmp(_argv[0],"echo")==0){if(strcmp(_argv[1],"$?")==0){printf("%d\n",lastcode);lastcode=0;}else if(*_argv[1]=='$'){char* s=getenv(_argv[1]+1);if(s) printf("%s\n",s);}else{printf("%s\n",_argv[1]);}return 1;}//特殊处理lsif(_argc==2&&strcmp(_argv[0],"ls")==0){_argv[_argc++]="--color";_argv[_argc]=NULL;}return 0;}int main()
{while(!quit){//交互问题,获得命令行参数interact(commandline,sizeof commandline);//字符串分割,解析命令行参数int argc = splitstring(commandline,argv);if(argc==0) continue;//指令的判断int n=buildcommand(argv,argc);        //普通指令的执行if(!n)normalexcute(argv);}return 0;
}

  • 进程历史打开的文件以及文件的重定向关系,并不会被程序替换所影响!!进程程序替换之后影响页表右边的物理地址所指向的内容,虚拟地址并左边的部分并不会受到影响
  • 程序替换并不会影响文件访问

3.4stdout和stderr

  • stdout和stderr对应的硬件设备都是显示屏,访问的都是同一个文件(引用计数)
  • 在重定向的时候,默认只对stdout的fd进行重定向

代码示例:

  • 如果对1号和2号文件都要进行重定向呢?

示例:./mytest 1> log.txt 2>err.txt

 示例:./mytest > log.txt 2>&1

3.5如何理解“linux下一切皆文件” --以对外设的IO操作为例

  • 不同的外设在进行IO操作时都有自己对应的读写方法,放在struct device里
  • 这些读写方法如何被找到?--由struct operation_func来对读写方法进行管理,该结构体里存在指向对应读写法的函数指针
  • 如何找到struct operation_func?--由struct file来对struct operation_func进行管理,file结构体存在指向struct operation_func的指针,基于struct file之上的被称为虚拟文件系统(VFS)--一切皆文件
  • 当我们打开一个文件的时候,通过进程的pcb数据结构找到struct struct_file,操作系统根据文件描述符的分配规则,在struct struct_file的指针数组中为该文件分配一个fd;当我们要访问一个外设的时候,根据该外设文件fd对应的数组下标内容找到该外设文件的struct file,根据file结构体找到对应的struct operation_func,由于访问的外设的不同,在struct operation_func中根据函数指针找到对应的读写方法,就可以对外设进行访问了

四、文件缓冲区

4.1认识FILE

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

4.2文件缓冲区引入

  • 对比有无fork( )的代码

  • 我们发现 printf fwrite (库函数)都输出了 2 次,而 write 只输出了一次(系统调用),为什么呢?肯定和 fork有关!
  • 再来验证一个现象:
不加'\n'并且在最后close(1)

代码运行的结果是:只有系统调用接口写入的内容被打印出来了 

加上'\n',结果又不一样了

  

4.3文件缓冲区的原理

  • C语言会提供一个缓冲区,我们调用C文件接口写入的数据会被暂存在这个缓冲区内,缓冲区的刷新方式有三种:
  1. 无缓冲:直接刷新,一般我们使用的fflush( )就是无缓冲的刷新方式
  2. 行缓冲:遇到'\n'才刷新,一般对应显示器
  3. 全缓冲:缓冲区满了才刷新,一般对应普通文件的写入
  4. 特殊说明:进程结束的时候会自动刷新缓冲区
  • 在操作系统的内核中也存在一个内核级别的缓冲区,目前认为,只要将数据刷新到了内核,数据就可以到硬件了,内核缓冲区也有自己的刷新方式
  • 为什么要有C层面的缓冲区?
  1. 用户不需要一步一步将数据写入到硬件中,而是可以直接调用C库为我们提供的读写方法,将数据交给库函数来处理,解决用户的效率问题
  2. 我们真正存到文件里的都是一个个的字符,调用C库的读写方法,可以在放入缓冲区之前将我们的数据格式化成字符串,再刷新到内核中进而写入文件,C层面的缓冲区可以配合格式化的工作
  • C为我们提供的缓冲区在FILE结构体里,FILE里面有相关缓冲区的字段和维护信息,FILE属于用户层面,而不属于操作系统
  • 文件写入的过程:
  1. 首先,在文件写入之前,进程会打开一个文件,通过对各种内核数据结构的访问和操作,获得该文件的文件描述符
  2. 如果使用系统调用接口来对文件进行写入,数据直接通过write和fd写入对应的内核级别缓冲区,默认最后都会刷新到硬件中
  3. 如果使用fwrite等库函数来对文件进行写入,首先,在语言层面会malloc出一个FILE结构体,FILE里面有对应的缓冲区信息以及文件的fd,然后内容会先被暂存在C层面的缓冲区,如果是无缓冲,数据直接被刷新到内核中,如果是行缓冲,遇到'\n'就会被刷新到内核中,如果是全缓冲,等缓冲区满了就被刷新到内核中
  4. 由于库函数是对系统调用接口的封装,用户通过write和fd将数据刷新到对应的文件的内核缓冲区内,再由该内核缓冲区刷新到外设

4.4解释现象

  • 为什么不加'\n'并且close(1)的时候,使用库函数写入的内容不会被显示?

  1. 不加'\n',调用库函数写入的数据都会被暂存在C层面的缓冲区

  2. close(1)后,即使进程退出后缓冲区会自动刷新,但是此时已经找不到1号文件的fd了,缓冲区内的数据也无法被写入到内核中,最后也不会显示到显示器上

  3. 加了'\n'即使最后close(1),遇到'\n'缓冲区就会立马将数据刷新到内核中,就会显示到显示器上

  • 为什么fork()之后重定向C接口会被调用两次?
  1. 重定向后,缓冲区的刷新方式会从行缓冲变成全缓冲,也就说,数据要么等到缓冲区满了再被刷新,要么等待进程结束后再刷新,所以我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后
  2. fork( )之后,创建子进程,子进程会继承父进程的内核数据结构对象的内容,父子进程在一开始的时候数据和代码是共享的,缓冲区也属于数据
  3. 进程退出后,要对缓冲区的数据进行统一刷新,刷新就是对数据进行访问写入,此时父子数据会发生写时拷贝,所以当父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据
  4. 由于write没有所谓的缓冲区,write()写入的数据直接在内核中,所以write( )的数据只有一份

4.5总结

  • printf fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。这里所说的缓冲区, 都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区
  • 那这个用户级缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统 调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,说明该缓冲区是二次加上的,由C标准库提供

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/440775.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JS测试框架——Jest

文章目录 安装yarn安装jestvscode支持jest的智能提示创建JS测试用例 安装yarn yarn是meta发布的一款取代npm的包管理工具。 npm install -g yarn查看yarn软件源 yarn config get registry换源 yarn config set registry https://registry.npmmirror.com恢复官方源 yarn co…

中广核CGN25届校招网申SHL测评题库、面试流程、招聘对象,内附人才测评认知能力真题

​中国广核集团校园招聘在线测评攻略&#x1f680; &#x1f393; 校园招聘对象 2024届、2025届海内外全日制应届毕业生&#xff0c;大专、本科、硕士、博士&#xff0c;广核集团等你来&#xff01; &#x1f4c8; 招聘流程 投递简历 简历筛选 在线测评&#xff08;重点来啦…

个人项目简单https服务配置

1.SSL简介 SSL证书是一种数字证书&#xff0c;由受信任的证书颁发机构&#xff08;CA&#xff09;颁发&#xff0c;用于在互联网通信中建立加密链接。SSL代表“安全套接层”&#xff0c;是用于在互联网上创建加密链接的协议。SSL证书的主要目的是确保数据传输的安全性和隐私性…

看Threejs好玩示例,学习创新与技术(LiquidRaymarching)

今天的示例有点超出我的想象&#xff0c;首先会科普下WGSL这种新的着色器脚本&#xff0c;然后说说示例《Liquid Raymarching Scene with Three.js Shading Language | Codrops (tympanus.net)》的技术流程。本示例最终呈现的效果如下。可以看到他跟QQ那个消息拖拽消灭的效果非…

基于STM32的数字温度传感器设计与实现

引言 STM32 是由意法半导体&#xff08;STMicroelectronics&#xff09;开发的基于 ARM Cortex-M 内核的微控制器系列&#xff0c;以其强大的处理能力、丰富的外设接口和低功耗著称&#xff0c;广泛应用于嵌入式系统设计中。在这篇文章中&#xff0c;我们将介绍如何基于 STM32…

考研论坛平台|考研论坛小程序系统|基于java和微信小程序的考研论坛平台小程序设计与实现(源码+数据库+文档)

考研论坛平台小程序 目录 基于java和微信小程序的考研论坛平台小程序设计与实现 一、前言 二、系统功能设计 三、系统实现 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂…

联想电脑怎么开启vt_联想电脑开启vt虚拟化教程(附intel和amd主板开启方法)

最近使用联想电脑的小伙伴们问我&#xff0c;联想电脑怎么开启vt虚拟。大多数可以在Bios中开启vt虚拟化技术&#xff0c;当CPU支持VT-x虚拟化技术&#xff0c;有些电脑会自动开启VT-x虚拟化技术功能。而大部分的电脑则需要在Bios Setup界面中&#xff0c;手动进行设置&#xff…

【Android】Handler消息机制

文章目录 前言概述核心组件概述Android消息机制概述 Android消息机制分析ThreadLocal的工作原理ThreadLocal基础ThreadLocal实现原理 MessageQueueLooperHandler的工作原理总结 前言 本文用于记录Android的消息机制&#xff0c;主要是指Handler的运行机制。部分内容参考自《An…

数据库管理-第248期 23ai:全球分布式数据库-分片数据分布方法(20241006)

数据库管理248期 2024-10-06 数据库管理-第248期 23ai&#xff1a;全球分布式数据库-分片数据分布方法&#xff08;20241006&#xff09;1 系统管理分片2 用户定义分片2.1 分片空间2.2 在用户定义分片配置中添加分片空间2.3 为用户定义分片创建表空间2.4 用户定义分片创建分片表…

使用bert模型进行命名实体识别任务

一、实验内容 本实验使用预训练的 BERT 模型进行命名实体识别&#xff08;NER&#xff09;任务&#xff0c;并且使用 Hugging Face 的 Transformers 库完成模型的训练、验证和测试。最后&#xff0c;使用测试集评估模型性能&#xff0c;计算NER指标。 二、算法介绍 Bert是一种…

Python技巧:如何处理未完成的函数

一、问题的提出 写代码的时候&#xff0c;我们有时候会给某些未完成的函数预留一个空位&#xff0c;等以后有时间再写具体内容。通常&#xff0c;大家会用 pass 或者 ... &#xff08;省略号&#xff09;来占位。这种方法虽然能让代码暂时不报错&#xff0c;但可能在调试的时候…

毕业设计 深度学习水果识别

文章目录 1 前言2 开发简介3 识别原理3.1 传统图像识别原理3.2 深度学习水果识别 4 数据集5 部分关键代码5.1 处理训练集的数据结构5.2 模型网络结构5.3 训练模型 6 识别效果 1 前言 Hi&#xff0c;大家好&#xff0c;这里是丹成学长&#xff0c;今天做一个 基于深度学习的水果…

《如何高效学习》

有道云笔记 第一部分 整体性学习策略 结构 结构就像思想中的一座城市&#xff0c;有很多建筑物&#xff0c;建筑物之间有道路相连&#xff0c;有高大而重要的与其他建筑有上百条路相连&#xff0c;无关紧要的建筑只有少数泥泞的小道与外界相通。 建立良好的知识结构就是绘制…

[ 蓝桥 ·算法双周赛 ] 第 19 场 小白入门赛

&#x1f525;博客介绍&#xff1a; EvLast &#x1f3a5;系列专栏&#xff1a; <<数据结构与算法>> << 算法入门>> << C项目>> &#x1f3a5; 当前专栏: << 算法入门>> 专题 : 帮助小白快速入门算法竞赛 &#x1f44d…

Golang | Leetcode Golang题解之第457题环形数组是否存在循环

题目&#xff1a; 题解&#xff1a; func circularArrayLoop(nums []int) bool {n : len(nums)next : func(cur int) int {return ((curnums[cur])%n n) % n // 保证返回值在 [0,n) 中}for i, num : range nums {if num 0 {continue}slow, fast : i, next(i)// 判断非零且方…

ARM(5)内存管理单元MMU

一、虚拟地址和物理地址 首先&#xff0c;计算机系统的内存被组成一个由M个连续的字节大小组成的数组。每字节都会有一个唯一的物理地址。CPU访问内存最简单的方式就是使用物理地址。如下图&#xff1a; 图 1 物理地址,物理寻址 而现在都是采用的都是虚拟寻址的方法。CPU生成一…

复习HTML(基础)

目录 HTML含义 HTML作用 HTML的常用元素 元素的特点 元素的分类 1 是否嵌套关系 2 是否独占一行 块元素&#xff1a;独占一行 行内元素&#xff1a;共享一行 行内元素与块级元素的转换 3是否有结束标签 常用标签 1 标题标签&#xff1a;有六级 我们用h1 ~h6 表…

二叉树—相关结构

1.相关的结构问题&#xff08;分治递归&#xff09; 1.1节点个数 1.2叶子结点个数 叶子结点&#xff1a;没有孩子的节点 1.3树的高度&#xff08;深度&#xff09; 1.4二叉树第k层的节点个数 1.5二叉树查找值为x的节点 2.二叉树的创建和销毁 2.1二叉树的构建 二叉树遍历_牛客…

数据结构(7.4_3)——B+树

B树的定义&#xff1a; B树的查找&#xff1a; 查找成功时&#xff1a; 查找失败时&#xff1a; B树和B树的比较 总结&#xff1a;

性能测试学习2:常见的性能测试策略(基准测试/负载测试/稳定性测试/压力测试/并发测试)

一.基准测试 1&#xff09;概念 狭义上讲&#xff1a;就是单用户测试。测试环境确定后&#xff0c;对业务模型中的重要业务做单独的测试&#xff0c;获取单用户运行时的各项性能指标。 广义上&#xff1a;是一种测量和评估软件性能指标的活动。可以在某个时刻通过基准测试建立…