【文件描述符|重定向|缓冲区】(一)

简介: 【文件描述符|重定向|缓冲区】(一)

1 C语言文件操作的回顾

这块博主在讲解C语言时就已经做了很详细的讲解,这里就不详细讲了,直接给出代码。

写操作:

#include<stdio.h>    
#include<stdlib.h>    
#include<errno.h>    
#define LOG "log.txt"    
int main()    
{    
  FILE* fw=fopen("LOG","w");    
  if(fw==NULL)    
  {    
    perror("fopen:");    
    exit(1);                                                                                                                                
  }    
  const char* str="hello file\n";    
  fputs(str,fw);    
  fputs(str,fw);    
  fputs(str,fw);    
  fclose(fw);    
  return 0;    
}    

读操作:

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<errno.h>
  4 
  5 #define LOG "log.txt"
  6 int main()
  7 {
  8   FILE* fr=fopen("LOG","r");
  9   if(fr==NULL)                                                                                                                          
 10   {
 11     perror("fopen:");
 12     exit(1);
 13   }
 14 
 15   char buffer[256];
 16   fgets(buffer,sizeof(buffer),fr);
 17   printf("%s\n",buffer);
 18   fclose(fr);
 19   return 0;
 20 }

除了用上面的方法外C语言我们还可以用fprintf/fscanf;fwrite/fread等等。输出到显示器有哪些方法呢?除了用printf外,我们还可以用fprintf参数给stdout,这是由于Linux下一切皆文件的准则,至于为啥我们在后面会给出解释。

在C语言我们知道C会默认打开三个流:标准输入(stdin),标准输出(stdout),标准错误(stderr),而这三个流的返回指针都是FILE*类型。

打开文件的方式:

r Open text file for reading.

The stream is positioned at the beginning of the file.

r+ Open for reading and writing.

The stream is positioned at the beginning of the file.

w Truncate(缩短) file to zero length or create text file for writing.

The stream is positioned at the beginning of the file.

w+ Open for reading and writing.

The file is created if it does not exist, otherwise it is truncated.

The stream is positioned at the beginning of the file.

a Open for appending (writing at end of file).

The file is created if it does not exist.

The stream is positioned at the end of the file.

a+ Open for reading and appending (writing at end of file).

The file is created if it does not exist. The initial file position

for reading is at the beginning of the file,

but output is always appended to the end of the file

如上,是我们之前学的文件相关操作。还有 fseek ftell rewind 的函数,有兴趣的老哥可以取移步博主讲解文件操作那里。


2 系统文件I/O

上面我们介绍的是语言层面的文件操作,但是系统层面的该如何操作呢?我们一个一个来介绍。

2.1 open

我们可以通过man手册查询:

e30a91d17549456aa8dedc9ccca5bdf7.png

我们来看看第一个参数:就是要打开文件的名字;第二个参数:这个要重点讲解。

我们翻到手册后面:

46b840a88eb74a4dbf0131e5c0008c77.png

这里面出现了一堆宏,究竟是什么鬼呢?其实open的第二个参数是一个位图结构,里面每一个比特位对应这一个宏,如何通过比特位将宏联系起来可以参考这种方式:

#define ONE 0x1
#define TWO 0x2
#define THREE 0x4
#define FOUR 0x8
#define FIVE 0x10
// 0000 0000 0000 0000 0000 0000 0000 0000
void Print(int flags)
{
    if(flags & ONE) printf("hello 1\n"); //充当不同的行为
    if(flags & TWO) printf("hello 2\n");
    if(flags & THREE) printf("hello 3\n");
    if(flags & FOUR) printf("hello 4\n");
    if(flags & FIVE) printf("hello 5\n");
}
int main()
{
    printf("--------------------------\n");
    Print(ONE);
    printf("--------------------------\n");
    Print(TWO);
    printf("--------------------------\n");
    Print(FOUR);
    printf("--------------------------\n");
    Print(ONE|TWO);
    printf("--------------------------\n");
    Print(ONE|TWO|THREE);
    printf("--------------------------\n");
    Print(ONE|TWO|THREE|FOUR|FIVE);
    printf("--------------------------\n");
    return 0;
}

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_TRUNC:清空文件
 O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
 O_APPEND: 追加写
返回值:
 成功:新打开的文件描述符
 失败:-1

若要使用mode选项,这里文件的权限还要受umask的影响,如果想要自己在参数中设置的就是文件权限,可以直接用umask(0)来设置文件的默认掩码。

2.2 write

6cced82aa8e646c4be2abc2e9042ee56.png

我们可以来试试:

    //C库
    fprintf(stdout, "hello fprintf\n");
    //系统调用
    const char *msg = "hello write\n";
    write(1, msg, strlen(msg)); //+1?

这里问一下大家,这里strlen(msg)是否要+1?我们想想如果是要拷贝\0的话那的确是要加,但是别忘了现在我们是系统调用接口,我们并不想要\0进入到文件中,换一种说法就是在给文件中根本就不认识什么\0,因为\0是由C语言给我们提供的,系统根本就不认识,那上面还有个\n呢?\n文件系统是可以识别到的能够自动换行。所以这里就不要加上\0了。

2.3 read


5d717aa039544bd896434acc601a6118.png

我们可以来试试:

char buffer[1024];
    // 这里我们无法做到按行读取,我们是整体读取的。
    ssize_t n = read(fd, buffer, sizeof(buffer)-1); //使用系统接口来进行IO的时候,一定要注意\0问题
    if(n > 0)
    {
        buffer[n] = '\0';
        printf("%s\n", buffer);
    }
这里还是有一样的问题,我们将文件中的内容读到字符串中由于文件中是没有\0的,所以需要我们自己手动增加\0.(当然你调用C语言的接口是不会出现这些问题的)

2.4 close

这个很简单,大家自己看看文档就懂了。

f38443c389d04bfb80e6830106bf4ce1.png

之前我们讲了C语言会默认打开3个文件流(stdin,stdout,stderr),那么我们是不是就可以合理猜测我们继续打开文件会与我们默认打开的文件之间有什么关系?

我们上手来验证验证:

  int fd1=open(LOG,O_WRONLY | O_CREAT ,0666);
 27   int fd2=open(LOG,O_WRONLY | O_CREAT ,0666);
 28   int fd3=open(LOG,O_WRONLY | O_CREAT ,0666);
 29   int fd4=open(LOG,O_WRONLY | O_CREAT ,0666);
 30   int fd5=open(LOG,O_WRONLY | O_CREAT ,0666);
 31 
 32   printf("%d %d %d %d %d\n",fd1,fd2,fd3,fd4,fd5);                                                                                       
 33   close(fd1);
 34   close(fd2);
 35   close(fd3);
 36   close(fd4);
 37   close(fd5);

运行结果:

2caea43c9fe04f78a69d5a7f73c394e5.png

我们发现打印的文件描述符是从3开始打印的,并没有从下标0开始打印,这也正好解释了系统默认给我们打开了三个文件,这三个文件的描述符恰好是0 1 2。

那如果在打开新的文件之前我们关闭标准输入和标准错误会发生什么呢?😝😝

    fclose(stdin);//close(0)
    fclose(stderr);//close(2)    
    int fd1=open(LOG,O_WRONLY | O_CREAT ,0666);
    int fd2=open(LOG,O_WRONLY | O_CREAT ,0666);                                                                                          
    int fd3=open(LOG,O_WRONLY | O_CREAT ,0666);
    int fd4=open(LOG,O_WRONLY | O_CREAT ,0666);                                                                                             
    int fd5=open(LOG,O_WRONLY | O_CREAT ,0666);
    printf("%d %d %d %d %d\n",fd1,fd2,fd3,fd4,fd5);
    close(fd1);
    close(fd2);
    close(fd3);
    close(fd4);
    close(fd5);

运行结果:

3edaa61c8df848bf8e8c162f357b1e10.png

不难发现此时新打开的文件描述符已经从0开始了。

文件描述符的分配规则是:在文件描述符的表中最小的没有被使用的数组下标分配给新文件。

2.5文件描述符fd

通过对open函数的学习,我们知道了文件描述符就是一个小整数.

0 & 1 & 2

Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2.

0,1,2对应的物理设备一般是:键盘,显示器,显示器

😝😝😝😝谈谈你对文件描述符的理解?

在进程中每打开一个文件,都会创建有相应的文件描述信息struct file,这个描述信息被添加在pcb的struct files_struct中,以数组的形式进行管理,随即向用户返回数组的下标作为文件描述符,用于操作文件

3 各种问题的引入

1 如何理解C语言文件操作和系统调用?

通过上面对C语言文件操作的回顾以及对系统文件调用方法的学习,我们不难知道其实C语言的文件操作必定是封装了系统调用的,还记得在给大家讲解进程时给大家看的这样一张图吗?

c0f81cd61e4f4f64b06c403bdda3d067.png

我们调用的库函数其实是对系统调用的封装,是为了方便用户使用而进行的二次开发。不仅仅是C语言包括C++/java/python等语言只要想在Linux平台下跑,进行文件操作时必定封装了系统调用。

2 文件操作时第一步是打开文件,为什么要打开文件呢?文件没有被操作时在什么位置?被操作时又在什么位置?文件load到内存load的是文件内容还是属性?

打开文件的目的是将文件load到内存,文件没有被操作时应该在磁盘,操作时应该在内存中,文件load到内存操作系统为了高效会将文件属性load到内存中。

3 是谁打开文件的?

很显然是OS打开文件的(OS是系统的管理者),那么问题来了是谁让OS打开文件的呢?

是不是因为我们创建了一个进程,让进程请求OS帮助我们打开文件,而进程打开文件很显然不只是打开一个文件,那么如何将这些文件管理起来呢?答案是先描述再组织。

操作系统管理所有文件时是通过file结构体来进行管理的,而进程为了管理文件是在进程的task_struck中又增加了一个files_struct来管理本进程中的文件,具体关系如下图所示:

30f98ca631ad4967908b2b5e9b25cce5.png

其中files_struct结构体又指向了一个指针数组,指针数组中存放着进程所管理文件的地址,通过下标索引就能够轻易的找到文件,对文件进行操作。

而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来

描述目标文件。于是就有了file结构体表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进

程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数

组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件

描述符,就可以找到对应的文件。

所以此时我们就能够更加清晰的解释为啥在上面我们关闭标准输入和标准错误后文件描述符发生的改变,本质就是0下标映射的标准输入文件和2下标映射的标准错误文件指向发生了改变。

这样通过映射关系建立连接还有一个好处就是将进程管理和文件管理进行了解耦合,将进程管理与文件管理分别管理了起来。


目录
相关文章
|
SQL 关系型数据库 分布式数据库
基于PolarDB的图分析:银行金融领域图分析实践
本文介绍了如何使用阿里云PolarDB PostgreSQL版及其图数据库引擎(兼容Apache AGE,A Graph Extension)进行图数据分析,特别针对金融交易欺诈检测场景。PolarDB PostgreSQL版支持图数据的高效处理和查询,包括Cypher查询语言的使用。文章详细描述了从数据准备、图结构创建到具体查询示例的过程,展示了如何通过图查询发现欺诈交易的关联关系,计算交易间的Jaccard相似度,从而进行欺诈预警。
基于PolarDB的图分析:银行金融领域图分析实践
|
人工智能 运维 安全
华讯网络携手阿里云,助力企业数智化转型
上海华讯网络系统有限公司荣获阿里云「Landing Zone生态合作伙伴授牌认证」。华讯网络与阿里云共建的《Landing Zone联合解决方案》是华讯网络为阿里云客户定制的上云方案。Landing Zone通过行业最佳实践为企业创建可管理、可扩展、安全合规的上云基础架构环境,帮助企业客户上好云、用好云、管好云。
|
资源调度 JavaScript 前端开发
2020你应该有一个自己的组件
2020你应该有一个自己的组件
164 0
|
Shell 网络安全 数据安全/隐私保护
HA-Avengers靶机渗透
HA-Avengers靶机渗透
|
API
[学习报告]《LeetCode零基础指南》(第六讲) C排序API
[学习报告]《LeetCode零基础指南》(第六讲) C排序API
232 0
[学习报告]《LeetCode零基础指南》(第六讲) C排序API
|
14天前
|
人工智能 数据可视化 安全
王炸组合!阿里云 OpenClaw X 飞书 CLI,开启 Agent 基建狂潮!(附带免费使用6个月服务器)
本文详解如何用阿里云Lighthouse一键部署OpenClaw,结合飞书CLI等工具,让AI真正“动手”——自动群发、生成科研日报、整理知识库。核心理念:未来软件应为AI而生,CLI即AI的“手脚”,实现高效、安全、可控的智能自动化。
34776 39
王炸组合!阿里云 OpenClaw X 飞书 CLI,开启 Agent 基建狂潮!(附带免费使用6个月服务器)
|
8天前
|
人工智能 自然语言处理 安全
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)
本文介绍了Claude Code终端AI助手的使用指南,主要内容包括:1)常用命令如版本查看、项目启动和更新;2)三种工作模式切换及界面说明;3)核心功能指令速查表,包含初始化、压缩对话、清除历史等操作;4)详细解析了/init、/help、/clear、/compact、/memory等关键命令的使用场景和语法。文章通过丰富的界面截图和场景示例,帮助开发者快速掌握如何通过命令行和交互界面高效使用Claude Code进行项目开发,特别强调了CLAUDE.md文件作为项目知识库的核心作用。
9045 27
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)
|
4天前
|
人工智能 JavaScript Ubuntu
低成本搭建AIP自动化写作系统:Hermes保姆级使用教程,长文和逐步实操贴图
我带着怀疑的态度,深度使用了几天,聚焦微信公众号AIP自动化写作场景,写出来的几篇文章,几乎没有什么修改,至少合乎我本人的意愿,而且排版风格,也越来越完善,同样是起码过得了我自己这一关。 这个其实OpenClaw早可以实现了,但是目前我觉得最大的区别是,Hermes会自主总结提炼,并更新你的写作技能。 相信就冲这一点,就值得一试。 这篇帖子主要就Hermes部署使用,作一个非常详细的介绍,几乎一步一贴图。 关于Hermes,无论你赞成哪种声音,我希望都是你自己动手行动过,发自内心的选择!
1823 18
下一篇
开通oss服务