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

👀樊梓慕:个人主页

 🎥个人专栏:《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 0int _blksize;
#elseint _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 callconst 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 0int _blksize;
#elseint _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.hqwc.cn/news/454425.html

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

相关文章

C++ 类与对象(下)

目录 1. 再谈构造函数 1.1 构造函数体赋值 1.2 初始化列表 1.3 explicit关键字 2. static成员 2.1 概念 2.2 特性 3.友元 3.1友元函数 3.2 友元类 4. 内部类 5.匿名对象 6.拷贝对象时的一些编译器优化 7. 再次理解类和对象 【本节目标】 1. 再谈构造函数 2. Static成员…

动态颗粒背景,适合VUE、HTML前端显示

动态颗粒背景&#xff0c;适合做背景使用&#xff0c;VUE、HTML前端显示直接看效果 废话不多说直接上代码&#xff1b; 一、html 代码部分 <template><div id"login"><div class"container"><div class"login-form"&g…

情人节浪漫礼物指南:精选共享甜蜜时光的情人节礼物推荐

情人节&#xff0c;代表着浪漫和爱意的纪念日&#xff0c;总能激起每个人内心深处的悸动&#xff0c;促使他们渴望与爱侣共度美好时刻。为爱人精心选择一份情人节礼物&#xff0c;不仅是对他们深情的告白&#xff0c;更是将这份爱升华&#xff0c;让它成为两人爱情故事里的宝贵…

Swift 隐藏宝藏:“逆天改命”调整方法重载(function overloading)优先级

概览 在 Swift 语言中有很多隐藏“宝藏”悄悄深埋在不为人知的角落&#xff0c;静静等待着有缘秃头码农们的大力挖掘。 而在这里&#xff0c;我们将介绍 Swift 语言中一个非常有用的秘技&#xff1a;方法重载优先级判断以及如何改变它。 在本篇博文中&#xff0c;您将学到如下…

跟着pink老师前端入门教程-day18

3、CSS3 3D转换 生活中的环境是3D的&#xff0c;照片就是3D物体在2D平面呈现的例子 特点&#xff1a;近大远小&#xff0c;物体后面遮挡不可见 3.1 三维坐标系 三维坐标系其实就是指立体空间&#xff0c;立体空间是由3个轴共同组成的 x轴&#xff1a;水平向右 注意&#x…

服务器和云服务器哪个更安全?

随着云计算技术的不断发展&#xff0c;越来越多的企业开始选择使用云服务器来存储和处理数据。然而&#xff0c;对于一些企业来说&#xff0c;他们可能更倾向于使用传统的服务器。在这种情况下&#xff0c;安全性成为了一个重要的考虑因素。那么&#xff0c;服务器和云服务器哪…

JavaWeb之HTML-CSS --黑马笔记

什么是HTML ? 标记语言&#xff1a;由标签构成的语言。 注意&#xff1a;HTML标签都是预定义好的&#xff0c;HTML代码直接在浏览器中运行&#xff0c;HTML标签由浏览器解析。 什么是CSS ? 开发工具 VS Code --安装文档和安装包都在网盘中 链接&#xff1a;https://p…

基础数学问题整理

最近刷了一些关于基础数学问题的题目&#xff0c;大致是关于组合数、分解质因数还有一些思维题&#xff0c;题目来自洛谷的【数学1】基础数学问题 - 题单 - 洛谷&#xff0c;很多思路还是之前没有见过的&#xff0c;都是简单到一般难度的题目&#xff08;橙、题、绿题&#xff…

解读域用户属性值UserPrincipalName

在Active Directory (AD) 环境中&#xff0c;UserPrincipalName&#xff08;UPN&#xff09;属性是一个用于标识域用户的特殊属性&#xff0c;其解读如下&#xff1a; 全称&#xff1a;User Principal Name用途&#xff1a;UserPrincipalName 是一个基于Internet电子邮件地址格…

【视频编码\VVC】变换编码基础知识及标准设计相关参数

变化编码的基础知识 定义&#xff1a;变换编码是将以空间域像素形式描述的图像转换至变换域&#xff0c;以变换系数的形式加以表示。 大部分图像都包含较多平坦区域和内容变化缓慢的区域&#xff0c;使得图像能量在空间域的分散转换为变换域的相对集中分布&#xff0c;从而达…

爬虫实战--爬取简单文字图片并保存到mongodb数据库

文章目录 前言发现宝藏 前言 为了巩固所学的知识&#xff0c;作者尝试着开始发布一些学习笔记类的博客&#xff0c;方便日后回顾。当然&#xff0c;如果能帮到一些萌新进行新技术的学习那也是极好的。作者菜菜一枚&#xff0c;文章中如果有记录错误&#xff0c;欢迎读者朋友们…

UE中对象创建方法示例和类的理解

对象创建方法示例集 创建Actor示例 //创建一个护甲道具 AProp* armor GetWorld()->SpawnActor<AProp>(pos, rotator); 创建Component示例 UCapsuleComponent* CapsuleComponent CreateDefaultSubobject<UCapsuleComponent>(TEXT("CapsuleComponent&qu…