【Linux】文件

Linux 文件

  • 什么叫文件
  • C语言视角下文件的操作
    • 文件的打开与关闭
    • 文件的写操作
    • 文件的读操作 & cat命令模拟实现
  • 文件操作的系统接口
    • open & close
    • write
    • read
  • 文件描述符
  • 进程与文件的关系
  • 重定向问题
  • Linux下一切皆文件的认识
  • 文件缓冲区
    • 缓冲区的刷新策略
  • stuout & stderr

什么叫文件

狭义的文件:普通的磁盘文件
广义的文件:几乎所有的外设,都可以称作文件
站在系统的角度,那些能够被读取(input),或者能够被写出(output)的设备就叫做文件。

C语言视角下文件的操作

文件的打开与关闭

在这里插入图片描述
path是文件的路径,mode的选择如下。
在这里插入图片描述

// 此时是打开(或创建)当前路径下的文件 log.txt
FILE* pf = fopen("log.txt", "w");

所谓当前路径,准确来说是:当一个进程运行起来的时候,这个进程所处的工作路径。
在这里插入图片描述
w的方式打开,如果文件存在,会将文件清空(是在对文件读写操作之前);如果文件不存在,则会创建。
这里可以get到一个tips,就是如何将文件清空。
这里给出更直接的使用方法:> yourfile>表示输入重定向。
在这里插入图片描述
a方式打开,是为了open for appending,与其对应的有>>追加重定向。
文件的关闭使用fclose,传入文件指针就可以了。
在这里插入图片描述

文件的写操作

文件的写操作有多种,下面代码中给出部分示例。

const char* s[] = {"hello fwrite\n","hello fprintf\n","hello fputs\n"};fwrite(s[0], strlen(s[0]), 1, pf);
fprintf(pf, "%s", s[1]);
fputs(s[2], pf);

在这里插入图片描述

文件的读操作 & cat命令模拟实现

下面再以读的方式r模拟实现cat命令
读取信息的就用fgets函数,
在这里插入图片描述
在这里插入图片描述

void Test1(int argc, char* argv[])
{if(argc != 2){printf("argv error!");exit(1);}FILE* pf = fopen(argv[1], "r");if(pf == NULL){perror("fopen");exit(2);}char line[64] = {0};while(fgets(line, sizeof(line), pf)){fprintf(stdout, "%s", line);}fclose(pf);
}

在这里插入图片描述

文件操作的系统接口

访问文件本质其实是进程通过调用接口访问,下面就来学习一下文件的调用接口。

open & close

在这里插入图片描述
open的接口使用比较复杂。
如果想实现w方式打开,需要如下的传参。

/*
* 宏定义,代表一个 bit 数据
* O_WRONLY 代表只写
* O_CREAT 代表创建文件
* O_TRUNC 代表清空文件
*/
int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC)

因为对于是否只写,是否创建,是否清空,这种非此即彼的选择很符合二进制位0与1的选择,open接口内部会对传输的flags做位数据的判断来决定是否执行对应操作,这样可以简化接口的传参和节省空间。
上面O_*的传参其实是宏定义,每一个这样的定义都对应一个bit位上的数据。
在这里插入图片描述
参数mode是完成对文件权限属性的设置,用来设置文件权限的。

int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666); // rw-rw-rw-

在这里插入图片描述
上面对文件权限的设置应该是-rw-rw-rw-,可创建之后为什么是-rw-rw-r--呢?
这和权限掩码umask有关。
在这里插入图片描述

umask(0);
int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666); // -rw-rw-rw-

在这里插入图片描述
umask设置0后,就看到了预期的-rw-rw-rw-了。
文件关闭传入文件描述符(file descriptor)就行了。
在这里插入图片描述
对于文件描述符,open函数创建文件成功就会返回这个文件的文件描述符,这里我们接收就好。

write

write是用于文件写入的操作。
在这里插入图片描述
在这里插入图片描述

void Test2()
{int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd < 0){perror("open");exit(1);}// 写操作const char* s = "hello world\n";write(fd, s, strlen(s));close(fd);
}

在这里插入图片描述

read

read是用于文件的读取操作。
在这里插入图片描述
在这里插入图片描述

void test3()
{// O_RDONLY 只读int fd = open("./log.txt", O_RDONLY);if(fd < 0){perror("open");exit(1);}// 读操作char buffer[64] = {0};read(fd, buffer, sizeof(buffer));// 打印读取结果printf("%s", buffer);
}

在这里插入图片描述

文件描述符

上面对文件的操作都用到了一个东西,叫做文件描述符fd,下面就来了解一下fd是什么样一个东西吧。

void Test4()
{int fd1 = open("log1.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);assert(!(fd1 < 0));printf("open success, fd1: %d\n", fd1);int fd2 = open("log2.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);assert(!(fd1 < 0));printf("open success, fd2: %d\n", fd2);int fd3 = open("log3.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);assert(!(fd1 < 0));printf("open success, fd3: %d\n", fd3);int fd4 = open("log4.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);assert(!(fd1 < 0));printf("open success, fd4: %d\n", fd4);close(fd1);close(fd2);close(fd3);close(fd4);
}

在这里插入图片描述
为什么我们所创建的文件的文件描述符fd是从 3 开始的呢?为什么不从 0 ,从 1 开始呢?
这是因为在运行C/C++程序是,会默认打开三个文件流:
stdin 标准输入;stdout 标准输出;stderr 标准错误。
在这里插入图片描述
这三个流的类型都是FILE*的,FILE*类型是结构体指针类型(FILE结构体是C标准库提供的),文件描述符fd也只是这个结构体中的一个成员变量。而默认打开的这三个文件,它们已经占据了0,1,2三个fd的值。
在这里插入图片描述

进程与文件的关系

文件大致可以分成两类:

  1. 磁盘文件(没有被打开,文件=内容+属性)
  2. 内存文件(进程在内存中打开)

进程要访问文件,必须先打开文件。一个进程可以打开多个文件(文件要被访问,必须是先加载到内存中)。
当多个进程都要访问文件时,系统中会存在大量的被打开的文件。
面对如此之多的被打开的文件,操作系统就要采用先描述,再组织的方式将这些被打开的文件进行管理。
Linux内核中,面对每一个被打开的文件都要构建一份struct file结构体(包含了一个被打开的文件的几乎所有的内容,不仅仅包含属性)
通过创建struct file对象来充当一个被打开的文件,并通过双链表的结构组织起来。
在这里插入图片描述
上面进程与文件的关系在内核代码中的体现如下图。
在这里插入图片描述
每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包含的一个指针数组,数组中每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件。
在这里插入图片描述

重定向问题

void Test5()        
{                                close(1);                              // fd的分配规则是:优先分配最小的,没有被占用的文件描述符 int fd = open("./log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);if(fd < 0)   {                                    perror("open");          exit(1);}          // 应该往显示器(标准输出)上写入的// 但是都写入到了 log.txt// 这是什么呢?-> 输出重定向      printf("fd: %d\n", fd);fprintf(stdout, "hello fprintf\n");const char* s = "hello fwrite\n";fwrite(s,strlen(s), 1, stdout);fflush(stdout);close(fd);   
}  

代码中首先关闭了fd为1的文件stdout,体现在底层就是,文件描述符表中下标为1的元素不再指向标准输出流了。
这时立即创建了log.txt文件,而fd的分配规则是:优先分配最小的,没有被占用的文件描述符。于是给log.txt分配文件描述符fd是1。
然后再调用文件写入操作的一些接口向stdout写入。默认是向fd为1的文件进行写入。
这时就将本应显示到显示器上的内容写入到了log.txt中。
在这里插入图片描述
同理,可以演示输入重定向的问题。

void Test6()    
{    close(0);    int fd = open("./log.txt", O_RDONLY);    if(fd < 0)    {    perror("open");    exit(1);    }    printf("fd: %d\n", fd);    // 输入重定向// 本来从键盘读取数据,变成从log.txt读取    char buffer[64] = {0};    fgets(buffer, sizeof buffer, stdin);    // 将读取的信息进行打印printf("%s", buffer);    close(fd);    
}    

在这里插入图片描述
从上面的式样中可以看出,重定向问题本质其实是在操作系统内部,更改fd对应的内容的指向。
但是上面的重定向操作需要需要先手动关闭默认打开的文件流,这里介绍一个dup2函数来更好的完成重定向操作。
在这里插入图片描述
在这里插入图片描述
dup2接口中oldfdnewfd的拷贝不是单纯的int的拷贝,反应在底层上是文件描述符所对应的数组元素中file*指针的拷贝。
oldfd拷贝给newfd,最后newfd所指向的内容是要和oldfd一样的。
这里利用dup2接口将>重定向和>>重定向的功能模拟实现一下。

void Test7()
{if(argc != 2){exit(1);}// 输入重定向int fd = open("./log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);// 追加重定向//int fd = open("./log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);if(fd <  0){perror("open");exit(2);}dup2(fd, 1);fprintf(stdout, "%s\n", argv[1]);close(fd);
}

在这里插入图片描述

Linux下一切皆文件的认识

LInux下一切皆文件,是体现在Linux操作系统的软件设计层面上的设计哲学。
当操作系统面对底层不同的硬件,一定对应的是不同的操作方法。
但是对于硬件这种设备都是外设,所以面对每一个设备的核心访问方法,都可以是 read/write(I/O),但是基于不同的设备可以有自己不同的 read/write,即在代码实现上是有差别的。
而且有些设备只能read或者只能write,对应实现的功能也会又所差异(比如键盘只能read)。
但是操作系统将所有这些设备都看待成struct file结构体,每一个设备都对应一个这样的struct file,结构体里面包含了这些设备的属性和方法(方法其实是函数指针)。
当操作系统上层要调用哪个设备,就可以通过找到对应的struct file,然后使用对应的操作方法使用设备了。
也就是说,在操作系统以上,就可以用一切皆文件的视角看待这些不同的设备了。
在这里插入图片描述
下面是内核代码中文件操作方法的函数指针。
在这里插入图片描述

文件缓冲区

缓冲区,简单说就是一段内存空间。它是由语言层提供的。它的存在可以提高整机效率,主要还是为了提高用户的响应速度(是一种写回模式)。

缓冲区的刷新策略

一般情况下有三种刷新策略:

  1. 立即刷新:写入缓冲区就立即刷新。
  2. 行刷新:写完一行内容再刷新。
  3. 满刷新:缓冲区写满再刷新。

特殊情况也会刷新:

  1. 用户强制刷新(fflush)。
  2. 进程退出。

其实,所有的设备,永远都倾向于全缓冲策略(缓冲区满了才刷新),这样做可以更少次地对外设进行访问,提高效率(和外部设备进行IO的时候,预备IO的过程是最费时间的)。
一般而言,行缓冲的设备文件有显示器,全缓冲的设备文件有磁盘文件。显示器的行刷新策略也是对效率和用户体验的折中处理。

void Test8()
{// C语言提供printf("hello printf\n");fprintf(stdout, "hello fprintf\n");const char* s1 = "hello fputs\n";fputs(s1, stdout);// OS提供const char* s2 = "hello write\n";write(1, s2, strlen(s2));// 调用fork时,上面的函数已经被执行完了,但并不代表数据已经刷新了fork();
}

在这里插入图片描述
Test8程序在执行向显示器打印时输出4行文本,向普通文件(磁盘上)打印却变成输出7行。这是为什么呢?
其实很明显是fork()的原因,那fork()又是如何作用出现这种情况呢?
细心观察发现,打印的内容中,系统接口的打印始终是一次,C语言接口相比会多打印一次。(从这里也可以猜到缓冲区是语言层提供的)
其实,如果是向显示器打印,刷新策略是行刷新,那么最后执行fork的时候,之前的函数一定是执行完了,且数据已经被刷新了(行刷新)的,此时fork就无意义了。
当程序重定向后,向磁盘文件打印,刷新策略隐形地变成了满刷新,此时fork的时候,之前的函数一定是执行完了,但数据还没有刷新(在当前进程对应的C标准库中的缓冲区),这部分数据是属于父进程的数据的。而fork之后,程序就要退出了,同时会强制将缓冲区的数据进行刷新。而父子进程的数据被刷新势必会影响到另一方,所以发生写时拷贝,父子进程各刷新一份数据到文件,就出现C接口的打印出现两份的情况了。

在这里插入图片描述
缓冲区是语言层提供的,封装的struct FILE结构体内部就有所打开文件对应的语言层的缓冲区结构。
下面通过Test9可以对上面的解释做一个验证。

void Test9()
{// C语言提供printf("hello printf\n");fprintf(stdout, "hello fprintf\n");const char* s1 = "hello fputs\n";fputs(s1, stdout);// OS提供const char* s2 = "hello write\n";write(1, s2, strlen(s2));// fork之前,进行强制刷新fflush(stdout);// 调用fork时,上面的函数已经被执行完了,但并不代表数据已经刷新了fork();
}

在这里插入图片描述

stuout & stderr

stdout对应的文件描述符是1,stderr对应的文件描述符是2。
它们对应的都是显示器文件,但却是不同的。可以认为,同一个显示器文件,被打开了两次。

void Test10()
{// stdout -> 1printf("hello printf 1\n");fprintf(stdout, "hello fprintf 1\n");const char* s1 = "hello write 1\n";write(1, s1, strlen(s1));std::cout << "hello cout 1" << std::endl;// stderr -> 2    perror("hello perror 2");const char* s2 = "hello write 2\n";write(2, s2, strlen(s2));std::cerr << "hello cerr 2" << std::endl;
}

在这里插入图片描述
从这里发现,重定向的是1号文件描述符对应的文件。
所以可以通过重定向把标准输入和标准输出的内容输入到不同的文件中进行分开查看。
![在这里插入图片描述](https://img-blog.csdnimg.cn/e6143fe26eaa4f91b5604c946427517b.png =x150x)
一般而言,如果程序运行有,可能有错误的话,建议使用stderr,或者cerr来打印。如果是常规文本内容,建议使用coutstdout打印。
当然也是可以将标准输入和标准输出的内容都输入到一个文件中的。
在这里插入图片描述
打印的内容中,有一个细节是perror的打印包含有一段信息,其实就是错误码errno对应的错误信息。
这里根据perror的功能可以模拟实现一个进行理解。

void my_perror(const char* msg)                                                                           
{    fprintf(stderr, "%s: %s\n", msg, strerror(errno));
}  

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

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

相关文章

windows笔记本远程连接如何打开任务管理器?

参考素材&#xff1a; https://jingyan.baidu.com/article/8275fc86a97f5207a03cf6cd.html https://www.anyviewer.cn/how-to/ctrl-alt-delete-remote-desktop-6540.html 网上查了很多方法&#xff0c;都说ctrlaltend可以解决这个问题。 但是笔记本键盘上没有end键。 继续查了一…

【MATLAB第71期】基于MATLAB的Abcboost自适应决策树多输入单输出回归预测及多分类预测模型(更新中)

【MATLAB第71期】基于MATLAB的Abcboost自适应决策树多输入单输出回归预测及多分类预测模型&#xff08;更新中&#xff09; 一、效果展示&#xff08;多分类预测&#xff09; 二、效果展示&#xff08;回归预测&#xff09; 三、代码获取 CSDN后台私信回复“71期”即可获取下…

c++:QT day2 信号和槽

1.多态&#xff1a; 静态多态&#xff1a;函数的重载 动态多态&#xff1a;程序运行 多态的实现:父类的指针或引用&#xff0c;指向或初始化子类的对象&#xff0c;调用子类对父类重写的函数&#xff0c;进而展开子类的功能 2.虚函数&#xff1a;用virtua关键字修饰的函数是虚函…

【算法】leetcode 105 从前序与中序遍历序列构造二叉树

题目 输入某二叉树的前序遍历和中序遍历的结果&#xff0c;请构建该二叉树并返回其根节点。 假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 示例 1: Input: preorder [3,9,20,15,7], inorder [9,3,15,20,7] Output: [3,9,20,null,null,15,7]示例 2: Input: pr…

SpringBoot自定义消息总线

一、前言 在现代的分布式系统中&#xff0c;消息传递已成为一个非常流行的模式。它使得系统内的不同部分可以松耦合地通信&#xff0c;从而实现更高效、更可靠的应用程序。本博客将介绍SpringBoot如何提供简单易用的消息传递机制&#xff0c;并展示如何自定义消息总线以满足特定…

kafka详解二

kafka详解二 1、 offset 1.1 offset介绍 老版本 Consumer 的位移管理是依托于 Apache ZooKeeper 的&#xff0c;它会自动或手动地将位移数据提交到 ZooKeeper 中保存。当 Consumer 重启后&#xff0c;它能自动从 ZooKeeper 中读取位移数据&#xff0c;从而在上次消费截止的地…

【探索SpringCloud】服务发现-Nacos服务端数据结构和模型

前言 上一文中&#xff0c;我们从官方的图示了解到Nacos的服务数据结构。但我关心的是&#xff0c;Nacos2.x不是重构了吗&#xff1f;怎么还是这种数据结构&#xff1f;我推测&#xff0c;必然是为了对Nacos1.x的兼容&#xff0c;实际存储应该不是这样的。于是&#xff0c;沿着…

vue的第2篇 第一个vue程序

一 环境的搭建 1.1常见前端开发ide 1.2 安装vs.code 1.下载地址&#xff1a;Visual Studio Code - Code Editing. Redefined 2.进行安装 1.2.1 vscode的中文插件安装 1.在搜索框输入“chinese” 2.安装完成重启&#xff0c;如下变成中文 1.2.2 修改工作区的颜色 选中[浅色]…

opencv 提取选中区域内指定hsv颜色的水印

基于《QT 插件化图像算法研究平台》做的功能插件。提取选中区域内指定hsv颜色的水印。 《QT 插件化图像算法研究平台》有个HSV COLOR PICK功能&#xff0c;可以很直观、方便地分析出水印 的hsv颜色&#xff0c;比如, 蓝色&#xff1a;100,180,0,255,100,255。 然后利用 opencv …

Django(10)-项目实战-对发布会管理系统进行测试并获取测试覆盖率

在发布会签到系统中使用django开发了发布会签到系统&#xff0c; 本文对该系统进行测试。 django.test django.test是Django框架中的一个模块&#xff0c;提供了用于编写和运行测试的工具和类。 django.test模块包含了一些用于测试的类和函数&#xff0c;如&#xff1a; Tes…

每日一题 1372二叉树中的最长交错路径

题目 给你一棵以 root 为根的二叉树&#xff0c;二叉树中的交错路径定义如下&#xff1a; 选择二叉树中 任意 节点和一个方向&#xff08;左或者右&#xff09;。如果前进方向为右&#xff0c;那么移动到当前节点的的右子节点&#xff0c;否则移动到它的左子节点。改变前进方…

vscode 清除全部的console.log

在放页面的大文件夹view上面右键点击在文件夹中查找 console.log.*$ 注意&#xff1a;要选择使用正则匹配 替换为 " " (空字符串)

跨模态可信感知

文章目录 跨模态可信感知综述摘要引言跨协议通信模式PCP网络架构 跨模态可信感知跨模态可信感知的概念跨模态可信感知的热点研究场景目前存在的挑战可能改进的方案 参考文献 跨模态可信感知综述 摘要 随着人工智能相关理论和技术的崛起&#xff0c;通信和感知领域的研究引入了…

ELK日志收集系统(四十九)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 一、概述 二、组件 1. elasticsearch 2. logstash 2.1 工作过程 2.2 INPUT 2.3 FILETER 2.4 OUTPUTS 3. kibana 三、架构类型 3.1 ELK 3.2 ELKK 3.3 ELFK 3.5 EF…

VScode远程连接主机

一、前期准备 1、Windows安装VSCode&#xff1b; 2、在VSCode中安装PHP Debug插件&#xff1b; 3、安装好Docker 4、在容器中安装Xdebug ①写一个展现phpinfo的php文件 <?php phpinfo(); ?>②在浏览器上打开该文件 ③复制所有信息丢到Xdebug: Installation instr…

【C进阶】深度剖析数据在内存中的存储

目录 一、数据类型的介绍 1.类型的意义&#xff1a; 2.类型的基本分类 二、整形在内存中的存储 1.原码 反码 补码 2.大小端介绍 3.练习 三、浮点型在内存中的存储 1.一个例子 2.浮点数存储规则 一、数据类型的介绍 前面我们已经学习了基本的内置类型以及他们所占存储…

封装(个人学习笔记黑马学习)

1、格式 #include <iostream> using namespace std;const double PI 3.14;//设计一个圆类&#xff0c;求圆的周长 class Circle {//访问权限//公共权限 public://属性//半径int m_r;//行为//获取圆的周长double calculateZC() {return 2 * PI * m_r;} };int main() {//通…

Linux 学习笔记(1)——系统基本配置与开关机命令

目录 0、起步 0-1&#xff09;命令使用指引 0-2&#xff09;查看历史的命令记录 0-3&#xff09;清空窗口内容 0-4&#xff09;获取本机的内网 IP 地址 0-5&#xff09;获取本机的公网ip地址 0-6&#xff09;在window的命令行窗口中远程连接linux 0-7&#xff09;修改系…

VScode 国内下载源 以及 nvm版本控制器下载与使用

VScode 国内下载源 进入官网 https://code.visualstudio.com/ 点击下载 复制下载链接到新的浏览器标签 将地址中的/stable前的az764295.vo.msecnd.net换成vscode.cdn.azure.cn&#xff0c;再回车就会直接在下载列表啦。 参考大神博客 2.使用nvm 对 node 和npm进行版本控制…

ARM编程模型-内存空间和数据

ARM属于RISC体系&#xff0c;许多指令单周期指令&#xff0c;是32位读取/存储架构&#xff0c;对内存访问是32位&#xff0c;Load and store的架构&#xff0c;只有寄存器对内存&#xff0c;不能内存对内存存储&#xff0c;CPU通过寄存器对内存进行读写操作。 ARM的寻址空间是线…