内存级文件原理——Linux

news2024/11/24 15:44:21

目录

进程与文件

Linux下的文件系统

 文件操作,及文件流

 C语言函数

 文件流

 文件描述符        

 系统调用操作

系统调用参数

重定向与文件描述符

输出重定向

输入重定向 


文件=内容+属性

Linux下一切皆文件


进程与文件

         当我们对文件进行操作时,文件必须要被加载到内存中,然后CUP从内存中拿到此文件进行操作,没有打开的文件放在磁盘中存储。

        文件的打开其实也是设计到内部某个进程。无论是系统调用,还是专有库中的函数,都是启动进程来进行打开。进程会自动记录目前启动时的当前路径,平常所说的相对路径就是指相对于当前进程路径下的路径。当我们没有特意说明文件路径在此进程中对文件操作时,默认会在此进程的路径下进行。比如我们使用C语言新建文件使用绝对路径,默认就会在此进程的路径下进行,若是此进程的路径发生改变,新建的文件会在改变后的路径下进行,这就是相对路径的原理

        一个进程可以打开多个文件,那么我们知道系统中存在多个进程,系统中必然存在很多被进程打开的文件OS是一点要管理多个被打开的文件的,如何管理呢,同样是先描述在组织,所以呢内核中一定要有描述被打开文件的结构体,并定义其对象!被打开的文件叫做内存级文件,这也是这篇文章的重点,没打开的我们后续再说,现在我们逐步解开描述被打开文件的结构体的面纱。


Linux下的文件系统

         在Linux系统下,我们可以把一切都看成文件(包括硬件)。Linux系统有一个重要特性,即“一切皆文件”的原则。无论是普通的磁盘文件、目录,还是网络套接字、硬件设备,在Linux中都被抽象为文件。

        我们平常使用高级语言进行底层文件的调用,本质上是封装了系统调用。因为用户不能直接调用系统硬件,本质上是操作系统进行调用的。我们通常使用语言进行调用实际就是操作系统提供了相应的接口供用户使用。比如以C语言为例,C语言的库函数接口fopen、fclose、fread、fwrite 在某种意义上来讲调用的是系统接口open、close、read、write(这些函数运用跟C中的文件操作相似,可用man指令查看文档),只不过对系统调用进行了封装。系统调用接口和库函数的关系如下:

 这里先对linux下一切皆是文件混个眼熟,后面会解释具体原因!!!

 文件操作,及文件流

 C语言函数

fwrite

size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);

返回值:写入了多少个基本单位

用于将二进制数据写入文件

fgets

char *fgets(char *str, int n, FILE *stream);

返回值

成功时:返回指向存储数据的指针 str。

失败或遇到文件结束(EOF):返回 NULL。

fopen

FILE *fopen(const char *filename, const char *mode);

返回值

成功时:返回一个指向文件的 FILE 类型指针。

失败时:返回 NULL,并可通过 perror 或 strerror(errno) 获取错误信息。

fputs

int fputs(const char *str, FILE *stream);

返回值

成功:返回非负值(通常为 0)。

失败:返回 EOF(通常为 -1),表示发生错误。

         这几个函数大家可能都用过,但对于FILE*stream是什么,理解的可能就不透彻了,我们先继续往下看

 文件流

         下面我们来认识一下文件流操作。程序在启动时,默认会打开三个文件流:stdin、stdout、stderr。这三种流的类型都是文件指针FILE*。

  1. stdin:标准输入——默认是键盘设备。计算机系统从此文件流中获取数据信息,即从此文件中读取数据。
  2. stdout:标准输出——默认是显示器设备。将数据输出到此文件流中,即从此文件中输出数据。
  3. stderr:标准错误——默认是显示器设备。用于输出程序或命令的错误信息,与stdout原理相似。

 

         正因有了标准输入输出流操作(I/O设备操作),才能使得程序能够与用户和其他程序进行有效的交互。

        那么问题来了,系统下的所有都是文件,程序系统又是如何找到对应的文件?

        其实每个文件都有一个对应的文件描述符进行标志。文件描述符是一个非负整数,与文件名形成了一种索引关系,使得程序可以通过这个整数来访问和操作对应的文件。

 文件描述符        

        文件描述符的范围是0到N,其中0、1、2是特殊文件的文件描述符:0代表标准输入(stdin),1代表标准输出(stdout),2代表标准错误输出(stderr)。一般情况下,文件描述符从3开始数往后分配。因为内部的文件描述符其实就是存放管理文件结构体(struct file:包含三个方面,第一个是能够通过指针让我们找到文件的属性,第二个是对文件操作的一堆方法,第三个是是所提供的缓冲区。打开一个文件系统内部就会创建一个struct file结构体对文件进行管理)的指针数组 fd_array 的下标,此指针数组每个元素都是一个指向打开文件的结构体指针,而task_struct内部存在一个指针,指向存放此指针数组的结构体(struct files_struct)。

        总的来说文件描述符就是数组的下标,当使用一个文件时就必须找到此文件的文件描述符,通过文件描述符来找到对应的文件。这里的重点在于文件操作符,只要我们拿到文件操作符fd,就能够通过file_struct结构体内部的fd_array数组指针找到对应管理文件的结构体file,对其文件进行操作。

        所以,C标准库中的FILE(文件流:随机读取或写入文件,即与文件操作的底层)其实就是自己封装的一个结构体,里面封装了 stdin、stdout、stderr 的文件描述符0,1,2。之所以系统不直接封装而让语言单独封装是为了保证可移植性。若是系统直接封装,一旦换了平台系统可能就会出问题,导致不可移植。其实不仅仅是流操作,很多有关系统接口也一样,为了保证可移植性,都是在不同语言内部封装不同系统调用的接口和相关的文件接口。

 系统调用操作

         既然已经知道了文件描述符,我们进一步来了解系统调用。上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。而, open close read write lseek 都属于系统提供的接口,为系统调用接口。

#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);

用open打开文件,当文件不存在新建文件时,需要用到第三个参数,设置文件权限,

pathname: 要打开或创建的目标文件

flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。

系统调用参数

 O_RDONLY: 只读打开

 O_WRONLY: 只写打开

 O_RDWR : 读,写打开

 以上三个常量,必须指定一个且只能指定一个

O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限

O_APPEND: 追加写

注意:Mode给定权限,权限比如给0666但是为了不被初始0002影响,设置一个umask(0)掩码写在文件中,就会按照我们给的权限直接设置了

 int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);使用open时,我们传递系统调用参数时候用到了|,这代表按位或运算,为什么这么传参数呢,我们来具体分析:

        倘若你参数是一个变量,那么多个参数就代表多个变量需要传递,这是一个有点浪费空间的举动,因为一个变量就是多个字节;对于参数来说它只代表一个功能,如果你传递了这个参数就代表希望其有这个功能,其实就是有和没有的区别,那你完全可以用1,0这样的数字来传;进一步节省空间,我们可以利用位图的思维来处理这个事情,及按照bit位的1,0来代表有某个参数或没有,在说的直白些就是有某个功能或没有。我们提前对每个bit位约定好如果是1代表具有哪个功能属性,对最后所有的参数取或操作成为一个集中的参数,转换为2进制,位置上有1就就代表有这个参数的传递,在使用的时候用约定好功能绑定的那个比特位和传进来的参数取异为1就执行这个功能;或起来的整体不就可以用很小的内容传递多个参数了,大大节省空间,提升效率!!!!

下边这个图片解释了,用bite位传递参数,并且使用的的实现例子

1<<0= 00001 等价于 1

1<<1=00010 等价于 2

1<<2==00100等价于 4

以此类推

1<<n 表示将数字 1 左移 n 位。

&:

都为 1 时,结果为 1。

只要有一个为 0,结果为 0。

If中只要不是0都可以输出

 linux下一切皆是文件

        现在我们来理解这句话,现在我们来理解这句话,linux下一切皆文件,更准确的说是linux下一切皆struct file,屏蔽底层差异。如何理解呢,先看下面这个图片

        我们将键盘,显示器,磁盘,网卡都描述为stuct,他们都有系统对应停供的系统接口,读操作,写操作,可是键盘没有写,显示器没有写,磁盘的读写也不会和网卡一模一样,但是我们调用的接口都是一样的!!!

         这其实就是上层调用读写任何设备,使用文件对象的函数,指针调用方法,屏蔽硬件底层差异,上层使用统一的接口。所以linux下一切都可以是文件对象,根据各部分实际的不同再来执行不同的操作。对于linux下一切皆文件可能还是不那么清楚,其实这也只是其中的一个,其他的原因也会随着不断深入学习浮出水面

重定向与文件描述符

输出重定向

#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,也就是stdout:标准输出流,所以这时候我们打开的文件fd就变成了1,默认输出到1,而此时1变成了fd,所以本应该打到显示器上的内容被显示在了文件里。

        如果关闭0就是输入重定向,打开的文件就成了0,所以文件里的内容就会被显示在显示屏上,因为默认从0里面获取输入,本来是键盘,变成了文件而已。

        文件描述符会按照最小下标分配,以上程序中关掉了系统下标准输出流的文件描述符1。至于fileno(stdout)的调用,由于stdout是C语言的流,非系统专属,当程序启动时C的stdout就默认打开,因此close(1);只是关闭了与stdout关联的文件描述符,与C的stdout内部文件数据没有关系,fileno(stdout)仍会返回原始的文件描述符值(即1),但是 close是系统操作,它会关闭了底层系统文件描述符表索引值1所指向的 stdout 文件。当我们创建文件log.txt时系统会给此文件分配描述符1。C中的输出都是往文件描述符为1所对应的文件中输出的,即一般情况下都是往标准输出流stdout中输出。这里log.txt的文件描述符为1,进程拿到文件描述符后会自动往文件描述符表fd_array中寻找索引值为1对应的文件中输出,所以就会出现以上输出重定向。输入重定向同理,将文件描述符为0的进行重新指向。

输入重定向 

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
using namespace std;

int main()
{
    close(0);   //将标准输入重定向(键盘)去除
    open("log.txt", O_RDONLY);   //此时文件log.txt的文件描述符为0,即成为了输入流
    int a = 0;
    cin >> a;  //从输入流中读取数据
    cout << a << endl;  
    return 0;
}

输出:123456

         不难发现,以上类似的程序完成重定向功能比较麻烦——先close关闭再open分配。说白了,重定向功能就是分配到指定的文件描述符,而文件描述符对应指定文件的功能,这里我们可直接让指定文件的文件描述符指向对应功能的文件描述符所指向文件的功能即可。比如将文件描述符为3的指向文件描述符为1所对应的系统文件,即指针数组元素之间的浅拷贝fd_array[1]=fd_array[3]。

#include <unistd.h>

int dup2(int oldfd, int newfd);

 

        注意,这里的oldfd会保留到最后,所以oldfd是我们想重定向的文件名,newfd就是我们想替换的文件名1、2之类的。 

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
using namespace std;

int main()
{
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    dup2(fd, 1);
    cout << "fd = " << fd << endl;
    return 0;
}

输出:fd = 3

 补充命令:

 进程在启动的时候会记录的路径是当前路径 命令:cwd,chdir:更改路径

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

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

相关文章

KubeSphere 最佳实战:K8s 构建高可用、高性能 Redis 集群实战指南

首发&#xff1a;运维有术。 本指南将逐步引导您完成以下关键任务&#xff1a; 安装 Redis&#xff1a;使用 StatefulSet 部署 Redis。自动或手动配置 Redis 集群&#xff1a;使用命令行工具初始化 Redis 集群。Redis 性能测试&#xff1a;使用 Redis 自带的 Benchmark 工具进…

apr共享内存

下载&#xff1a; Download - The Apache Portable Runtime Project 编译&#xff1a; 使用cmake-gui生成库&#xff1a; apr-1.lib aprapp-1.lib libapr-1.lib libaprapp-1.lib libapr-1.dll 在Developer PowerShell for VS 2019中&#xff1a; 执行nmake -f Makefile.win来…

Javaweb前端HTML css 整体布局

最后一个是线条颜色 盒子&#xff0c;整体还是300&#xff0c;400

5.STM32之通信接口《精讲》之USART通信---实验串口接收程序

根据上节&#xff0c;我们一已经完成了串口发送程序的代码&#xff0c;并且深入的解析探索了串口的原理&#xff0c;接下来&#xff0c;Whappy小编将带领大家进入串口接收程序的探索与实验&#xff0c;并将结合上一节串口发送一起来完成串口的发送和接收实验。 上来两张图 上图…

借助算力云跑模型

算力平台&#xff1a;FunHPC | 算力简单易用 AI乐趣丛生 该文章只讲述了最基本的使用步骤&#xff08;因为我也不熟练&#xff09;。 【注】&#xff1a;进入平台&#xff0c;注册登录账号后&#xff0c;才能租用。学生认证&#xff0b;实名认证会有免费的算力资源&#xff0…

SpringMVC 执行流程详解

目录 前言1. SpringMVC 的核心组件概述1.1 DispatcherServlet1.2 HandlerMapping1.3 HandlerAdapter1.4 拦截器&#xff08;HandlerInterceptor&#xff09;1.5 ViewResolver 2. SpringMVC 的执行流程详解2.1 接收请求并分发2.2 获取 HandlerExecutionChain2.3 获取 HandlerAda…

安卓应用安装过程学习

声明&#xff1a;此文章来自http://shuwoom.com/?p60的学习记录 启动式安装 public static final IPackageManager main(Context context, Installer installer,boolean factoryTest, boolean onlyCore) {PackageManagerService m new PackageManagerService(context, inst…

如何通过OpenSSL来创建自签名的CA证书?

通过创建自签名CA证书可以让我们在没有商业支持的情况下学习与研究PKI&#xff08;公钥基础设施&#xff09;和SSL/TLS技术&#xff0c;本文将详细介绍如何通过OpenSSL来创建自签名的CA证书。 1. 初衷&#xff1a;为什么需要创建自签名CA证书&#xff1f; 除了开篇引言中提到的…

mac安装Pytest、Allure、brew

安装环境 安装pytest 命令 pip3 install pytest 安装allure 命令&#xff1a;brew install allure 好吧 那我们在安装allure之前 我们先安装brew 安装brew 去了官网复制了命令 还是无法下载 如果你们也和我一样可以用这个方法哦 使用国内的代码仓库来执行brew的安装脚本…

Python中“暂停”(time.sleep?input?)

input函数最是经典&#xff0c;在多种实现中简单粗暴单纯而经济。 (笔记模板由python脚本于2024年11月22日 10:58:38创建&#xff0c;本篇笔记适合比较熟悉python的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大…

刷题——【模板】二维前缀和

前缀和 题目题目链接题解方法一方法二 题目 描述 给你一个 n 行 m 列的矩阵 A &#xff0c;下标从1开始。 接下来有 q 次查询&#xff0c;每次查询输入 4 个参数 x1 , y1 , x2 , y2 请输出以 (x1, y1) 为左上角 , (x2,y2) 为右下角的子矩阵的和&#xff0c; 输入描述&#x…

10 - Clickhouse集群部署以及副本和分片

目 一、副本 1、简介 2、副本写入流程 3、副本配置步骤 3.1、启动zookeeper集群 3.2、在 hallo100 的/etc/clickhouse-server/config.d 目录下创建一个名为metrika.xml 的配置文件,内容如下&#xff1a; 3.3、在 hallo100 的/etc/clickhouse-server/config.xml 中增加如…

Ubuntu24.04LTS设置root用户可远程登录

Ubuntu24.04LTS设置root用户可远程登录 文章目录 Ubuntu24.04LTS设置root用户可远程登录1. 设置root密码2. 设置root用户可远程登录1. 查看ssh服务是否安装2. 安装ssh服务3. 再次查看ssh服务是否安装4. 配置ssh文件5. 重启ssh服务6. root远程登录 1. 设置root密码 Ubuntu安装后…

DMA理论篇

DMA理论篇 简介 传统的数据传输都是需要CPU来实现&#xff0c;从一个地方拷贝到另一个地方&#xff1b;而DMA(Direct Memory Access)则不完全依赖CPU&#xff0c;DMA更新芯片SOC的一个控制器&#xff0c;他可以控制数据从内存中传输到另一个地方(外设、soc其它模块)&#xff…

理解原子变量之三:原子性与memory_order_relaxed

目录 CPU与内存的关系 原子性 典型使用场景 在本系列的第一篇文章理解原子变量之一&#xff1a;从互斥锁到原子变量&#xff0c;最粗浅的认识_原子互斥-CSDN博客&#xff0c;我通过几个实例从感性认识的角度介绍了原子性。本文在第一篇文章的基础上&#xff0c;从理性认识的…

医院信息化与智能化系统(22)

医院信息化与智能化系统(22) 这里只描述对应过程&#xff0c;和可能遇到的问题及解决办法以及对应的参考链接&#xff0c;并不会直接每一步详细配置 如果你想通过文字描述或代码画流程图&#xff0c;可以试试PlantUML&#xff0c;告诉GPT你的文件结构&#xff0c;让他给你对应…

青少年编程等级考试C++一级,硬币反转问题

代码 #include<iostream>using namespace std;bool a[300];int main(){ int n,m; cin >> n >> m; for(int i 1;i < m;i) { for (int j 1;j < n;j) { if( j % i 0) { a[j] !a[j];…

数字化工厂 MES试点方案全解析(二)

生产过程监控与数据采集 在生产线上部署各类传感器、数据采集终端等设备&#xff0c;与 MES 系统相连&#xff0c;实时采集生产数据&#xff0c;如设备运行参数&#xff08;温度、压力、转速等&#xff09;、产品加工数据&#xff08;尺寸、重量、加工时间等&#xff09;、物料…

动态规划子数组系列一>最长湍流子数组

1.题目&#xff1a; 解析&#xff1a; 代码&#xff1a; public int maxTurbulenceSize(int[] arr) {int n arr.length;int[] f new int[n];int[] g new int[n];for(int i 0; i < n; i)f[i] g[i] 1;int ret 1;for(int i 1; i < n-1; i,m. l.kmddsfsdafsd){int…

(十一)Python字符串常用操作

一、访问字符串值 Python访问子字符串变量&#xff0c;可以使用方括号来截取字符串。与列表的索引一样&#xff0c;字符串索引从0开始。 hh"LaoTie 666" hh[2] mm"床前明月光" mm[3] 字符串的索引值可以为负值。若索引值为负数&#xff0c;则表示由字符…