【Linux】文件周边002之初步理解文件管理(打开的文件)

news2024/10/7 14:21:36

👀樊梓慕:个人主页

 🎥个人专栏:《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C++》《Linux》《算法》

🌝每一个不曾起舞的日子,都是对生命的辜负


目录

前言

1.(打开的)文件管理

2.重定向

2.1dup2系统调用

3.如何理解Linux下一切皆文件

4.C语言中的FILE结构体

4.1FILE中的文件描述符

4.2FILE中的缓冲区


前言

本篇文章博主将会讲解『 Linux系统是如何管理文件的』,『 进程与文件之间是如何联系的』,『 重定向』以及『 C语言中的FILE结构体』的相关内容。


欢迎大家📂收藏📂以便未来做题时可以快速找到思路,巧妙的方法可以事半功倍。

=========================================================================

GITEE相关代码:🌟fanfei_c的仓库🌟

=========================================================================


1.(打开的)文件管理

 一定时间段内,系统中存在多个进程,每个进程可能打开多个文件,那么操作系统是如何管理『 打开的』文件的呢?

我们之前学习进程时,了解到操作系统对进程的管理是『 先描述,再组织』,核心是PCB-task_struct,那么对于文件来说,肯定也会存在这样一个结构体用来描述文件,所以进程与文件之间的联系就变成了struct  task_struct与struct XXX的联系。

这是我们的猜想,让我们进入Linux内核一探究竟:

果然如我们所料,task_struct内部有一个结构体指针,指向的就是文件结构体。

然后我们来分析files_struct:

上篇文章我们提到在进程中,文件描述符是标识不同文件的标识符,每个文件都拥有自己的文件描述符,文件描述符的分配规则为当前没有被使用的最小的下标作为新的文件描述符。

那我们来简要画一下文件管理的概念图:

所以我们只需要知道文件描述符fd,就可以通过该下标索引到对应的文件流上。


2.重定向

观察:如果利用close关闭标准输出流,printf的内容会被写入到哪?

int main()
{
    close(1);//关闭标准输出流
    open("log.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
    printf("hello linux\n");
}   

所以我们得到文件描述符的分配规则:

在files_struct数组中,找到当前『 没有被使用』的最小下标,作为新的文件描述符。 

这样好像就完成了重定向的目的,但是是不是有点太挫了,有没有什么看起来更专业的方式来实现重定向呢? 

试想:如果我们将文件描述符表中的内容『 直接作替换』,是不是就完成了重定向的目的呢?

2.1dup2系统调用

将oldfd索引内容拷贝给newfd索引内容。

int main()
{
    int fd = open("log.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
    dup2(fd, 1);
    printf("hello linux\n");
}

也实现了重定向的功能。


3.如何理解Linux下一切皆文件

 既然说Linux下一切皆文件,那么硬件设备也都是文件。

但是硬件设备的操作方法一定是不一样的。

那文件会操作不同的硬件,如何屏蔽硬件差异呢?

系统的设计一定是要通用的,不然每新出一个硬件,系统底层的文件结构体都要重新设计么?

 所以这里利用的是『 函数指针』的方式来访问不同的硬件操作方法,无需关心底层是什么硬件设备。


4.C语言中的FILE结构体

这部分我们主要研究一下语言层面上是如何对文件管理进行设计的。

之前我们说 C标准库中的文件IO接口一定『 封装了系统调用』,所以才能利用fopen()、fputs()等函数对文件进行操作。

4.1FILE中的文件描述符

之前我们学习C语言时,fopen函数的返回值就是FILE的指针fp,那现在我们学习了系统层面上的文件管理,了解到『 文件描述符fd』才是唯一标识不同文件的属性值,所以C语言中的FILE结构体中一定也封装了文件描述符fd。

我们来看一看C的源码:

typedef struct _IO_FILE FILE;

struct _IO_FILE {
	int _flags;       /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

	//缓冲区相关
	/* The following pointers correspond to the C++ streambuf protocol. */
	/* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
	char* _IO_read_ptr;   /* Current read pointer */
	char* _IO_read_end;   /* End of get area. */
	char* _IO_read_base;  /* Start of putback+get area. */
	char* _IO_write_base; /* Start of put area. */
	char* _IO_write_ptr;  /* Current put pointer. */
	char* _IO_write_end;  /* End of put area. */
	char* _IO_buf_base;   /* Start of reserve area. */
	char* _IO_buf_end;    /* End of reserve area. */
	/* The following fields are used to support backing up and undo. */
	char *_IO_save_base; /* Pointer to start of non-current get area. */
	char *_IO_backup_base;  /* Pointer to first valid character of backup area */
	char *_IO_save_end; /* Pointer to end of non-current get area. */

	struct _IO_marker *_markers;

	struct _IO_FILE *_chain;

	int _fileno; //封装的文件描述符
#if 0
	int _blksize;
#else
	int _flags2;
#endif
	_IO_off_t _old_offset; /* This used to be _offset but it's too small.  */

#define __HAVE_COLUMN /* temporary */
	/* 1+column number of pbase(); 0 is unknown. */
	unsigned short _cur_column;
	signed char _vtable_offset;
	char _shortbuf[1];

	/*  char* _save_gptr;  char* _save_egptr; */

	_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

所以C语言中FILE结构体中对应的文件描述符叫做_fileno。

所以fopen是如何工作的呢?

  • fopen函数在上层为用户申请FILE结构体变量,并返回该结构体的地址(FILE*),在底层通过系统调用接口『 open』打开对应的文件,得到『 文件描述符fd』,并把fd填充到『 FILE结构体』当中的『 _fileno』变量中,至此便完成了文件的打开操作。
  • C语言当中的其他文件操作函数,比如fread、fwrite、fputs、fgets等,都是根据我们传入的文件指针找到对应的FILE结构体,然后在FILE结构体当中找到文件描述符,最后通过文件描述符对文件进行的一系列操作。

4.2FILE中的缓冲区

来段代码研究一下:

#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main()
{
    // 使用system call
    const char *s1 = "hello write\n";
    write(1, s1, strlen(s1));

    // 使用C语言接口
    const char *s2 = "hello fprintf\n";
    fprintf(stdout, "%s", s2);

    const char *s3 = "hello fwrite\n";
    fwrite(s3, strlen(s3), 1, stdout);


    fork();
    return 0;
}

当我们直接运行时:

可以看到write、fprintf、fwrite函数都成功将对应内容输出到了显示器上。 

可当我们将程序的结果重定向到log.txt文件当中后:

输出的结果就不一样了,C语言函数fprintf和fwrite执行了两次,系统调用write执行了一次,为什么呢?

这就与『 语言层面』上的『 缓冲区』有关了。

首先,缓冲策略有以下几种:

  • 无缓冲。
  • 行缓冲。(常见的对显示器进行刷新数据)——遇到\n刷新
  • 全缓冲。(常见的对磁盘文件写入数据)——写满缓冲区才刷新

当我们直接运行程序时,由于都是对显示器进行输出数据,所以属于行缓冲,行缓冲遇到\n数据就都刷新出来了,也就是说此时缓冲区中无内容,创建子进程不会发生修改,不会发生写时拷贝,所以子进程结束也不会有新的内容被刷新出来。

而重定向到文件中,属于全缓冲,全缓冲缓冲区写满才刷新,所以对于C语言函数fprintf和fwrite来说,数据被写入到了C语言自带的缓冲区当中,之后当我们使用fork函数创建子进程时,由于进程间具有独立性(之后当父进程或是子进程对要刷新缓冲区内容时,本质就是对父子进程共享的数据进行了修改),此时就需要对数据进行写时拷贝,也就是说父子各一份缓冲区,当父子进程结束时,都刷新缓冲区,所以重定向到log.txt文件当中printf和puts函数打印的数据就有两份。但由于write函数是系统调用接口,系统调用接口没有语言层面上的缓冲区(系统中当然也有缓冲区,但不受我们控制,我们可以将write函数看作是没有缓冲区的),因此write函数打印的数据就只打印了一份。

注意我们这里研究的缓冲区都是语言层面上(用户级)缓冲区, 操作系统也当然会提供相关内核级缓冲区,不过不在我们探讨的范围之内。

一个文件一个缓冲区,缓冲区在文件结构体FILE内部做管理。 

typedef struct _IO_FILE FILE;

struct _IO_FILE {
	int _flags;       /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

	//缓冲区相关
	/* The following pointers correspond to the C++ streambuf protocol. */
	/* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
	char* _IO_read_ptr;   /* Current read pointer */
	char* _IO_read_end;   /* End of get area. */
	char* _IO_read_base;  /* Start of putback+get area. */
	char* _IO_write_base; /* Start of put area. */
	char* _IO_write_ptr;  /* Current put pointer. */
	char* _IO_write_end;  /* End of put area. */
	char* _IO_buf_base;   /* Start of reserve area. */
	char* _IO_buf_end;    /* End of reserve area. */
	/* The following fields are used to support backing up and undo. */
	char *_IO_save_base; /* Pointer to start of non-current get area. */
	char *_IO_backup_base;  /* Pointer to first valid character of backup area */
	char *_IO_save_end; /* Pointer to end of non-current get area. */

	struct _IO_marker *_markers;

	struct _IO_FILE *_chain;

	int _fileno; //封装的文件描述符
#if 0
	int _blksize;
#else
	int _flags2;
#endif
	_IO_off_t _old_offset; /* This used to be _offset but it's too small.  */

#define __HAVE_COLUMN /* temporary */
	/* 1+column number of pbase(); 0 is unknown. */
	unsigned short _cur_column;
	signed char _vtable_offset;
	char _shortbuf[1];

	/*  char* _save_gptr;  char* _save_egptr; */

	_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

tips:你知道格式化输入、格式化输出在哪里发生么?

比如:

printf("%d",12345);

实际上就是在用户级的缓冲区上发生的,比如向显示器打印一个整型数据12345,实际上显示器显示的是字符1,字符2,字符3,字符4,字符5,所以整形数据在送到内核级缓冲区之前,首先要在用户级缓冲区上被转化为字符。


=========================================================================

如果你对该系列文章有兴趣的话,欢迎持续关注博主动态,博主会持续输出优质内容

🍎博主很需要大家的支持,你的支持是我创作的不竭动力🍎

🌟~ 点赞收藏+关注 ~🌟

=========================================================================

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PMP资料怎么学?PMP备考经验分享

PMP考试前大家大多都是提前备考个一两个月&#xff0c;但是有些朋友喜欢“不走寻常路”&#xff0c;并不打算去考PMP认证&#xff0c;想要单纯了解PMP&#xff0c;不管要不要考证&#xff0c;即使是仅仅学习了解一下我个人都非常支持&#xff0c;因为专业的基础的确能提高工作效…

【Linux系统 02】Shell脚本

目录 一、Shell概述 二、输入输出 三、分支控制 1. 表达式 2. if 分支 3. case 分支 四、循环控制 1. for 循环 2. while 循环 3. select 循环 五、函数 一、Shell概述 Shell是Linux系统连接用户和操作系统的外壳程序&#xff0c;将用户的输入和请求选择性传递给操…

Unity笔记:相机移动

基础知识 鼠标输入 在Unity中&#xff0c;开发者在“Edit” > “Project Settings” > “Input Manager”中设置输入&#xff0c;如下图所示&#xff1a; 在设置了Mouse X后&#xff0c;Input.GetAxis("Mouse X")返回的是鼠标在X轴上的增量值。这意味着它会…

考勤|基于Springboot的大学生考勤系统设计与实现(源码+数据库+文档)

大学生考勤系统目录 目录 基于Springboot的大学生考勤系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、系统登录注册 2、管理员功能模块 3、教师功能模块 4、学生功能模块 四、数据库设计 1、实体ER图 2、具体的表设计如下所示&#xff1a; 五、核心代码…

【CSS + ElementUI】更改 el-carousel 指示器样式且隐藏左右箭头

需求 前三条数据以走马灯形式展现&#xff0c;指示器 hover 时可以切换到对应内容 实现 <template><div v-loading"latestLoading"><div class"upload-first" v-show"latestThreeList.length > 0"><el-carousel ind…

@所有人 您需要的 幻兽帕鲁服务器搭建教程 已上线

所有人 您需要的 幻兽帕鲁服务器搭建教程 已上线 幻兽帕鲁一键购买及部署体验购买及部署购买云服务器ECS部署幻兽帕鲁 创建账户并登录Steam其他操作更新服务器修改游戏参数其他操作释放资源 一直拖到今天才来写这篇幻兽帕鲁服务器搭建教程&#xff0c;确实是因为前段时间有事耽…

【Rust】——rust前言与安装rust

&#x1f383;个人专栏&#xff1a; &#x1f42c; 算法设计与分析&#xff1a;算法设计与分析_IT闫的博客-CSDN博客 &#x1f433;Java基础&#xff1a;Java基础_IT闫的博客-CSDN博客 &#x1f40b;c语言&#xff1a;c语言_IT闫的博客-CSDN博客 &#x1f41f;MySQL&#xff1a…

机器学习系列5-特征组合、简化正则化

1.特征组合 1.1特征组合&#xff1a;编码非线性规律 我们做出如下假设&#xff1a;蓝点代表生病的树。橙色的点代表健康的树。 您可以绘制一条直线将生病的树与健康的树清晰地分开吗&#xff1f;不可以。这是一个非线性问题。您绘制的任何线条都无法很好地预测树的健康状况…

R语言学习case12:ggplot 置信区间(多线型)

接上文&#xff1a;多条曲线 R语言学习case11&#xff1a;ggplot 置信区间&#xff08;包含多子图&#xff09; 在ggplot2中&#xff0c;每个geom函数都接受一个映射参数。然而&#xff0c;并非每个美学属性都适用于每个geom。你可以设置点的形状&#xff0c;但不能设置线的“…

群晖NAS开启FTP服务结合内网穿透实现公网远程访问本地服务

⛳️ 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 文章目录 ⛳️ 推荐1. 群晖安装Cpolar2. 创建FTP公网地址3. 开启群晖FTP服务4. 群晖FTP远程连接5. 固定FTP公网地址6. 固定FTP…

产品经理学习-产品运营《如何策划一场活动》

互联网活动怎么玩 最常听到的有&#xff1a; 注册有奖、拉新有奖 签到积分 秒杀、大促、神券 和过去相比&#xff0c;现在活动的特征变化&#xff1a; 线上化、形式丰富、覆盖人群广、即时性、效果可控 什么是活动运营 通过策划不同形式的活动&#xff0c;进行有效的资源和…

LFU缓存(Leetcode460)

例题&#xff1a; 分析&#xff1a; 这道题可以用两个哈希表来实现&#xff0c;一个hash表&#xff08;kvMap&#xff09;用来存储节点&#xff0c;另一个hash表&#xff08;freqMap&#xff09;用来存储双向链表&#xff0c;链表的头节点代表最近使用的元素&#xff0c;离头节…

SpringBoot注解--02---常用注解汇总

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1.SpringBoot 配置启动注解SpringBootApplication 2.Bean处理注解2.1 依赖注入AutoWired、Qualifier、Resource 2.2 类被 Spring 容器创建&#xff0c;管理 iocComp…

基于ESP8266 开发板(MCU)遥控小车

遥控小车 ​ 遥控界面 ​ 【项目源码】 第一版ESP8266 https://github.com/liyinchigithub/esp8266_car_webServerhttps://github.com/liyinchigithub/esp8266_car_webServer 第二版ESP32 GitHub - liyinchigithub/esp32-wroom-car: 嵌入式单片机 ESP32 Arduino 遥控小车&a…

第16届大广赛命题详情它来啦!

“中国大学生创造力”全国大学生广告艺术竞赛&#xff08;以下简称&#xff1a;广播竞赛&#xff09;作为高水平三维生产教育一体化、科学教育一体化竞争平台&#xff0c;坚持高地位&#xff0c;基于大模式&#xff0c;在19年的发展过程中&#xff0c;坚持道德培养人才的基础&a…

MySQL温故篇(一)SQL语句基础

一、SQL语句基础 1、SQL语言分类 DDL&#xff1a;数据定义语言 DCL&#xff1a;数据控制语言 DML&#xff1a;数据操作语言 DQL&#xff1a;数据的查询语言 2、数据类型 3、字符类型 char(11) &#xff1a; 定长 的字符串类型,在存储字符串时&#xff0c;最大字符长度11个&a…

TypeError: wave.ensureState is not a function 水球图引入报错问题

TypeError: wave.ensureState is not a function 水球图引入报错问题 什么问题&#xff1f; 版本问题 echarts4.x 版本 适用于 echarts-liquidfill2.x.x版本 echarts5.x 版本 适用于 echarts-liquidfill3.x.x版本 完美解决

使用PDFBox实现pdf转其他图片格式

最近在做一个小项目&#xff0c;项目中有一个功能要把pdf格式的图片转换为其它格式&#xff0c;接下来看看用pdfbox来如何实现吧。 首先导入pdfbox相关依赖&#xff1a; <dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox</a…

【FPGA】快速学习路径

FPGA学习教程、功利式学习路径、以找工作为目的&#xff0c;早日入门FPGA_哔哩哔哩_bilibili

解决git切换分支导致代码丢失的问题

问题描述&#xff1a; 最近写项目时&#xff0c;我在主分支&#xff08;master分支&#xff09;上面写的代码&#xff0c;但是我没有提交&#xff08;Commit&#xff09;到Git上。但是又碰到一个新的需求&#xff0c;所以需要去新建一个分支&#xff0c;当我切换到新建的分支&a…