Linux操作系统下对文件的管理

news2025/1/19 8:26:57

文件的初步理解

C语言中我们了解过一些有关文件处理的函数,如:fopen、fclose、fread……这些函数其实都是封装了系统调用接口,从而利于我们直接使用。


认识文件 

了解文件之前我们必须知道:文件=内容+属性。文件的属性标识着文件的时间、权限、大小、拥有者…… 而对于文件的内容就是文件中的存储数据。其实归结到一起,文件的内容和属性其实都是数据。


文件访问与管理

就拿我们磁盘上的文件来说,我们要想访问一个文件的话,就要先将文件双击也就是打开文件,然后再操作文件。所以我们要知道:访问文件前要先打开文件。而C语言中我们可以通过fopen函数来打开一个文件,但是访问结束别忘了关闭文件:fclose。对于我们用C语言去调用fopen打开文件的过程其实是进程通过操作系统帮助我们打开的这个文件(操作系统为我们提供了系统调用接口),因为只有当我们的程序加载到内存中运行起来才会调用fopen函数去打开文件。所以说我们访问文件本质上其实是进程通过操作系统帮我们访问文件的。

我们的文件创建了以后其实是存在磁盘上的,而操作系统是对文件能够进行管理的,此时由冯诺依曼体系结构,我们知道这之中必定要借助内存,所以当我们进行文件访问时,被访问的文件是通过操作系统从磁盘加载到内存中,然后被操作系统进行管理,而管理的本质是:先描述再组织。所以就像操作系统管理进程时会为每一个进程创建PCB一样,管理打开的文件时也会在内核中创建一个struct file的结构,其中存储文件的所有数据。但是管理文件却有一点不同的,因为基于上段内容我们知道是进程来访问文件的。每一个进程访问的有可能是多个文件,所以综上我们可以知道:操作系统可以借助进程PCB去管理该进程所访问的文件。

所以我们基于上面的分析就又可以推测出进程PCB中应该也存放着一个叫做:struct file* array[]的指针数组,将每个进程所打开的文件管理起来。 


C文件接口的使用

int main()
{
    FILE* pf = fopen("bite.txt","w");//w方式打开,不存在就创建文件,且清空数据
    if(NULL==pf)
    {
        cout<<"文件打开失败"<<endl;
        return 1;
    }
    fputs("Linux so uneasy\n",pf);
    fputs("Linux so uneasy\n",pf);

    fclose(pf);

    return 0;
}

这里想说的就是,当我们实现好makefile以后,执行完make指令之后是不会创建bite.txt这个文件的,此时只是会生成exe可执行程序,并没有执行该程序,也就是说该程序还没有变成进程,所以我们无法创建文件,也就是说,本质上是进程通过操作系统帮助我们打开这个文件。

此时打开的方式我们要知道常用的两种:w && a

w和a在打卡不存在的文时都会创建文件,但是 w方式就是在每次打开文件的时候都会对文件所有内容进行自动的清除,而a方式并不会清楚文件内容而是进行追加方式打开文件。

而且注意w打开方式其实对应的就是输出重定向(>);而a的打开方式对应的是追加重定向(>>)。

还有一些其他的打开方式:

r:只读的方式打开文件。(文件必须存在)

r+:读写的方式打开文件,可以写文件,打开文件后进行写入操作时,会覆盖原有位置的内容。(文件必须存在)

w+:读写的方式打开文件,其余就像w一样。

a+:以可读写的方式打开文本文件,其他与a一样。

 C语言的函数其实都是封装了系统调用接口的,因为进程是通过操作系统来打开文件的,操作系统是进行软硬件管理的一款软件,而且操作系统是不允许我们直接对硬件资源进行访问操作的,所以操作系统会为我们提供系统调用接口供我们进行硬件访问。


认识系统调用接口

文件打开:

以上是文档,以下是文档的内容解释说明: 

  此时系统调用open的第一个参数是指文件名,而第二个参数就是文件打开方式,但是不同于C接口。这里的flag是一个整数,但是其中包含了32个比特位的数据的,所以实质上是通过对应比特位来标志着不同的打开文件方式的。其实底层就是多个if语句分别进行判断该比特位上数据是否为1,为1则表明数据有效,满足该打开方式。

 但是当我们新建文件时,最好给该文件赋予权限,否则的话就可能会是乱码式的权限。别忘了umask权限掩码,但是可以直接函数调用umask(num)函数,设置权限掩码为num。

int main()
{
    int fd = open("test.txt",O_WRONLY|O_CREAT,0666); 
    if(fd==-1)
    {
        cout<<"调用失败"<<endl;
    }

    close(fd); 

    return 0;
}

其实类比上C接口

w文件打开方式就相当于 open("test.txt",O_WRONLY|O_CREAT|O_,TRUNC0666);

a文件打开方式就相当于是open("test.txt",O_WRONLY|O_APPEND,0666);  


文件写入: 

对于像文件中写入数据,我们有write系统调用接口。

int main()
{
    int fd = open("test.txt",O_WRONLY|O_CREAT,0666); 
    if(fd==-1)
    {
        cout<<"调用失败"<<endl;
    }
    write(fd,"haha",4);

    close(fd); 

    return 0;
}

此时需要注意的就是,这个count参数是你的指针buf的长度-1(不算上\0)其实\0是C语言规定的,但是我们操作的是文本文件内容,所以不需要关注\0。


 文件打开的返回值:(文件描述符的认识)

int main()
{
    int fd1 = open("test.txt",O_WRONLY|O_CREAT,0666);
    int fd2 = open("test.txt",O_WRONLY|O_CREAT,0666);
    int fd3 = open("test.txt",O_WRONLY|O_CREAT,0666);
    cout<<fd1<<" "<<fd2<<" "<<fd3<<endl;

    close(fd1); 
    close(fd2); 
    close(fd3); 

    return 0;
}

首先我们知道文件打开的返回值是整型,而且经过验证发现文件依次打开后的返回值是有顺序的,而且在上文中对文件的访问与管理有了初步的认识,文件是被操作系统管理起来的,而管理自然就会创建文件的描述结构体对象,而每个进程通过操作系统打开的不仅仅只有一个文件,就像我们这里打开了三个文件(虽然都是同一个文件),而这些文件的返回值不就恰恰说明了文件存放顺序嘛,而存放在哪里呢,是一个文件类型的指针数组,而返回值就是下标。其实操作系统访问文件时访问的就是下标。


进程会默认打开文件 :

但是为什么创建的文件不是对应着0、1、2描述符的呢,其实每个进程在运行的时候都会默认打开标准输入stdin(键盘)、标准输出stdout(显示器)、标准错误stderr(显示器)。

 这里C语言提供的FILE实际上是一个结构体,其中就封装了文件描述符的值,因为文件描述符就是文件的标识,操作系统对指定文件的操作实际上就是根据文件描述符进行区分文件的。


文件描述符的理解

分配文件描述符

我们前面在连续创建文件时,可以看出文件描述符是依次从3开始进行分配的。其中的本质就是进程文件描述表的下标位置,该表中存放的就是指向文件的指针。但是如果我们进行文件关闭,并再次创建文件,此时的文件描述符是如何呢?

test_1:

int main()
{
    int fd1=open("test.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
    cout<<"fd1 = "<<fd1<<endl;
    close(fd1);

    int fd2=open("test.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
    cout<<"fd2 = "<<fd2<<endl;
    close(fd2);

    return 0;
}


test_2:

int main()
{
    close(0);//关闭stdin文件
    int fd2=open("test.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
    cout<<"fd2 = "<<fd2<<endl;
    close(fd2);

    return 0;
}

 所以就可以说明文件描述符指向的文件被关闭以后,该文件所存在文件描述符表中的数据也被清除了,而下一次再次创建文件时,新文件的文件描述符就从最低位开始依次向空的位置处填充。


 认识read系统调用

 该系统调用函数是从文件描述符fd中读取文件数据并放到buf中,而count就是buf指针内部的空间大小,返回值是实际读取数据的个数。

int main()
{
    char buf[501];
    ssize_t sz=read(0,buf,500);//sz是实际字符个数
    buf[sz]=0;//末尾\0用于打印
    if(sz>0)
    {
        cout<<buf<<endl;
    }

    return 0;
}

 我们的这段代码就是从标准输入中读取数据,然后打印出来,标准输入对应的就是键盘,所以我们从键盘中输入什么数据,最终就会打印什么数据,别忘了最后还有一个回车键。(如果不想要最后的回车键就直接将 buf[sz-1]=0;进行设置)

回顾write系统调用

这里write系统调用需要我们注意的就是,文件中存的字符信息和我们语言中的字符串是不同的,我们语言中都会在字符串结尾+\0但是文件并不需要。以至于文件的写入都会将字符串的长度传过去。

int main()
{
    char buf[501];
    ssize_t sz=read(0,buf,500);
    buf[sz]=0;
    if(sz>0)
    {
       // cout<<buf<<endl;
       write(1,buf,strlen(buf));//1->标准输出(显示器)
    }

    return 0;
}

 


 数据读写本质

在我们进行数据的读写时,操作系统都会将文件数据从磁盘加载到内存的文件缓冲区中。所以我们读写本质就是将内核的缓冲区数据拷贝(拷回)到当前临时对象,也就是进行数据的来回的拷贝

 

重定向

重定向主要讲讲输入输出两种,对应着键盘和显示器,所以自然就和文件联系起来了,而我们对具体文件的操作本质上都是通过文件描述符表的指向的的而不是C语言中的FILE*指针变量名的。

输出重定向

int main()
{
    close(1);//stdout
    open("test.txt",O_WRONLY|O_TRUNC|O_CREAT,0666);//

    printf("haha\n");

    return 0;
}

 对于以上现象其实也挺好解释的,我们知道进程运行时会默认打开3个文件,所以我们C语言调用printf函数时就是默认向显示器中进行打印数据,但是我们代码中关闭了该文件,且紧接着创建test.txt这个文件,所以根据文件描述符分配规则:寻找最小的且,没有被使用的位置所以,此时自然文件描述符1对应的就是test.txt这个文件,而文件的本质区分就是文件描述符,所以此时打印的数据就进入了test.txt当中。

分析图如下:

输出重定向 

了解了输入重定向以后,输出重定向就好理解多了。

int main()
{
    close(0);//stdin
    open("test.txt",O_RDONLY,0666);//

    char buf[30];
    ssize_t sz = read(0,buf,30);
    buf[sz]=0;
    cout<<buf<<endl;

    return 0;
}

 


 所以基于上面的测试可以得出:重定向本质其实就是修改文件描述符表特定数组下标里的内容。

重定向系统调用接口

对于以上的两种重定向方式都属于原理层的使用,实际上,我们是可以直接调用接口实现重定向的。dmp2

 翻译理解后就是:oldfd才是重定向之后的文件,也就是保留下来的文件 其功能就是将文件描述符表中oldfd下标里的内容拷贝到newfd里。也就是说明此时有两个下标里的内容指向的是同一个文件,所以对于这同一个文件会采用引用计数的方式避免关闭文件时会同时关闭的情况。


int main()
{
    int fd = open("a.txt",O_RDWR|O_CREAT|O_TRUNC,0666);
    int fdd = open("b.txt",O_RDWR|O_CREAT|O_TRUNC,0666);
    write(fdd,"abcabc",6);

    dup2(fd,1);//fd输出重定向
    dup2(fdd,0);//fdd输入重定向
    //将fdd中的数据输出到fd中
    
    char buf[1000];
    rewind(stdin);//将位置指针移动到文件开头
    //fseek(fp, 0, SEEK_SET);//将位置指针fp移动到文件开头
    ssize_t sz = read(0,buf,1000);//从sdin中读取数据
    buf[sz]=0;
    cout<<buf<<endl;//输出到stdout里

    close(fd);
    close(fdd);

    return 0;
}

 

 以上代码是通过系统调用接口函数同时实现输入与输出重定向,其实也没什么,但是有一点就是:文件每当进行一次读写后,该指针自动指向下一次读写的位置。 当文件刚打开或创建时,该指针指向文件的开始位置。!!!!!!!!!!!

也就是说如果没有rewind函数将文件位置指针移到文件开头的位置的话,那么后面的read函数始终无法从0号标识符下读取数据,因为此时的位置指针已经指向文件最后的位置,所以实际读取到的有效数据始终为0,因此必须将位置指针回位。


认知stderr标准错误

stderr标准错误对应的也是显示器,就是向显示器里打印,但是我们已经有了标准输出stdout为什么还要标准错误stderr呢,stdout也是向显示器中打印的啊。

int main()
{
    fprintf(stdout,"hello stdout\n");
    fprintf(stderr,"hello stderr\n");

    return 0;
}

 这里我们分别打印数据到标准输出和标准错误中,对应的也就是显示器,所以执行可执行程序的时候就会向显示器中都打印出来。

我们可以重定向输出:

我们知道 > 是输出重定向,就是将本该在标准输出里的内容输出到文件tmp.txt中,但是我们此时的标准错误并没有重定向,所以stderr打印到了显示器上,而我们打印tmp.txt中的文件就是:

 

 当我们想将标准输入和标准输出同时重定向的话就可以:

其中的2文件描述符对应的就是标准错误,此时的1(标准错误)已经重定向成了tmp.txt了,所以stderr也重定向到了tmp.txt了


所以基于上面的内容我们可以了解到标准输出和标准错误虽然都是对应的显示器,但是我们可以将两者重定向打印到不同的文件中进行区分。

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

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

相关文章

混沌映射初始化种群与随机初始化种群初始种群分布图对比

自行切换混沌映射&#xff0c;代码如下&#xff1a; Lb -1; % 搜索空间下界 Ub 1; % 搜索空间上界N_iter 500; % 最大迭代次数 N 30; % 种群个数 dim 2; % 种群维度 Z zeros(N, dim);% 随机生成一个d维向量 Z(1, :) rand(1, dim);% 利用logistic生成N个向量 for i…

【51单片机系列】矩阵按键介绍

本节实现的功能是&#xff1a;通过开发板上的矩阵键盘控制静态数码管显示对应的键值0-F。 文章目录 一、矩阵按键介绍二、硬件设计三、软件设计 一、矩阵按键介绍 独立键盘与单片机连接时&#xff0c;每一个按键都需要单片机的一个I/O口。若某单片机系统需要较多按键&#xff…

QT----Visual Studio输入中文报错,常量中有换行符

问题描述 在VS中写qt时发现在标题中输入了中文直接把报错无法运行 解决方法1 修改文件的编码方式。在VS菜单栏 工具->自定义-》命令-》选择文件-》添加高级保存选项命令。 双加选中添加中文的文件&#xff0c;文件-》高级保存选项-》修改utf-8就可以运行了 解决方法2…

用C语言的循环嵌套打印菱形

这是用循环弄出来的&#xff0c;看的时候可以列一个表&#xff0c;将空格&#xff0c;要打印的*数&#xff0c;行数对应出来&#xff0c;因为里面涉及到一些数学公式&#xff0c;写出来的话方便观察&#xff01; #define _CRT_SECURE_NO_WARNINGS 1//C语言中用scanf可能会警告&…

【K8S】Hello World

文章目录 1 搭建本地测试环境1.1 安装 docker和 Colima1.2 安装 minikube1.3 启动minikube1.4 安装 kubectl1.5 注册 docker hub镜像仓库 2 k8s核心资源概念2.1 Pod2.2 Deployment2.3 Service2.4 Ingress 参考资料 1 搭建本地测试环境 本文以 mac os为例 1.1 安装 docker和 C…

Day53力扣打卡

打卡记录 重新规划路线&#xff08;dfs&#xff09; 链接 class Solution:def dfs(self, x: int, parent: int, e: List[List[List[int]]]) -> int:res 0for edge in e[x]:if edge[0] parent:continueres edge[1] self.dfs(edge[0], x, e)return resdef minReorder(se…

Android 11.0 MTK Camera2 设置默认拍照尺寸功能实现

1.前言 在11.0的系统rom定制化开发中,在mtk平台的camera2关于拍照的一些功能修改中,在一些平台默认需要设置最大的分辨率 来作为拍照的分辨率,所以就需要了解拍照尺寸设置流程,然后来实现相关的功能 如图: 2.MTK Camera2 设置默认拍照尺寸功能实现的核心类 \vendor\me…

Terraform实战(二)-terraform创建阿里云资源

1 初始化环境 1.1 创建初始文件夹 $ cd /data $ mkdir terraform $ mkdir aliyun terraform作为terraform的配置文件夹&#xff0c;内部的每一个.tf&#xff0c;.tfvars文件都会被加载。 1.2 配置provider 创建providers.tf文件&#xff0c;配置provider依赖。 provider…

AUTOSAR 入门

前言 AUTOSAR是什么Vector DaVinci 工具功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个注脚注释也是必…

nodejs微信小程序+python+PHP的智能停车系统-计算机毕业设计推荐django

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性&#xff1a;…

Leetcode刷题笔记题解(C++):165. 比较版本号

思路&#xff1a;利用小数点来分开求和&#xff0c;判断和是否相等来推断两个版本号是否一致 class Solution { public:int compareVersion(string version1, string version2) {int i0;//记录version1的字符下标int j0;//记录version2的字符下标int num10;//保存version1的和…

【Android】查看keystore的公钥和私钥

前言&#xff1a; 查看前准备好.keystore文件&#xff0c;安装并配置openssl、keytool。文件路径中不要有中文。 一、查看keystore的公钥&#xff1a; 1.从keystore中获取MD5证书 keytool -list -v -keystore gamekeyold.keystore 2.导出公钥文件 keytool -export -alias …

在线学习平台-班级模块

在线学习平台-项目搭建 在线学习平台-需求分析 若依的基本使用 通过分析可知,班级模块的结构会比较简单,可以先从班级模块入手 1.先在domain里写上班级里的属性 快捷方式: 时区可以这里找,时区和数据库名之间要加一个 ? 右键需要的数据库模型,便可直接生成 生成的实体类不…

智能优化算法应用:基于蛇优化算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于蛇优化算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于蛇优化算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.蛇优化算法4.实验参数设定5.算法结果6.参考文献7.…

服务器配置免密SSH

在当今互联网时代&#xff0c;远程工作和网络安全已成为信息技术领域的热点话题。无论是管理远程服务器、维护网络设备还是简单地从家中连接到办公室&#xff0c;安全始终是首要考虑的因素。这就是为什么 SSH&#xff08;Secure Shell&#xff09;成为了网络专业人士的首选工具…

Python---类的综合案例

1、需求分析 设计一个Game类 属性&#xff1a; 定义一个类属性top_score记录游戏的历史最高分 定义一个实例属性player_name记录当前游戏的玩家姓名 方法&#xff1a; 静态方法show_help显示游戏帮助信息 类方法show_top_score显示历史最高分 实例方法start_game开始当前…

20231207给NanoPC-T4(RK3399)开发板刷Android12的挖掘机方案的LOG

20231207给NanoPC-T4(RK3399)开发板刷Android12的挖掘机方案的LOG 2023/12/7 23:50 SDK&#xff1a;rk356x_android12_220722.tgz 只修改DTS的DTC部分就【直接】可以跑NanoPC-T4 参考资料&#xff1a; http://www.friendlyelec.com.cn/agent.asp http://www.friendlyelec.com.c…

JS的变量提升ES6基础

JS的变量提升&ES6基础 变量var关键字var声明作用域实例一实例二多个变量 变量提升 let关键字暂时性死区全局声明for循环中使用let const关键字 变量 ECMAScript变量时松散类型的&#xff0c;意思是变量可以用于保存任何类型的数据。 声明变量&#xff1a;var 、const、let …

NumPy学习:NumPy(Numerical Python)基础(一)

1.什么是NumPy NumPy 是 Python 中用于科学计算的基础包。 它是一个 Python 库&#xff0c;提供多维数组对象&#xff0c; 各种派生对象&#xff08;例如掩码数组和矩阵&#xff09;&#xff0c;以及 用于对阵列进行快速操作的各种例程&#xff0c;包括 数学、逻辑、形状操作、…

AI助力智慧农业,基于YOLOv8全系列模型【n/s/m/l/x】开发构建不同参数量级的识别系统

智慧农业随着数字化信息化浪潮的演变有了新的定义&#xff0c;在前面的系列博文中&#xff0c;我们从一些现实世界里面的所见所想所感进行了很多对应的实践&#xff0c;感兴趣的话可以自行移步阅读即可&#xff1a; 《自建数据集&#xff0c;基于YOLOv7开发构建农田场景下杂草…