【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.mzph.cn/news/69608.shtml

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

相关文章

STM32 硬件IIC 控制OLED I2C卡死问题

#更新通知&#xff1a;2023-09-06 STM32L151 固件库 使用I2C 太难了&#xff0c;又宕机了&#xff0c;建议不要在固件库版本上尝试硬件IIC 了&#xff0c;一般人真用不了&#xff0c;直接使用软件模拟的&#xff0c;或者不要使用固件库了&#xff0c;用HAL 库吧&#xff0c;据说…

既要炫酷好看,又要出图快?可视化大屏模板了解下!

可视化大屏模板可以在很大程度上满足炫酷好看和出图快的需求。使用模板可以节约制作时间&#xff0c;像奥威BI系统就上线了大量的可视化大屏模板。这些模板实际上都是一张张完整的可视化大屏报表&#xff0c;从数据源到数据分析模型&#xff0c;再到数据可视化图表和智能分析功…

《vue3实战》运用push()方法实现电影评价系统的添加功能

目录 前言 电影评价系统的添加功能是什么&#xff1f; 电影评价系统的添加功能有什么作用&#xff1f; 一、push&#xff08;&#xff09;方法是什么&#xff1f;它有什么作用&#xff1f; 含义&#xff1a; 作用&#xff1a; 二、功能实现 这段是添加开始时点击按钮使…

支持CAN FD的Kvaser PCIEcan 4xCAN v2编码: 73-30130-01414-5如何应用?

这里是引用 Kvaser PCIEcan 4xCAN v2&#xff08;编码: 73-30130-01414-5&#xff09;是一款小巧而先进的多通道实时CAN接口&#xff0c;可发送和接收CAN总线上的标准和扩展CAN消息&#xff0c;时间戳精度高。其与所有使用Kvaser CANlib的应用程序兼容。 主要特性 PCI Express…

spring boot项目上传头像

应用还是验证码使用的原理&#xff1b;但是代码逻辑却有所不同。 逻辑前端传给后端&#xff0c;然后写入本机磁盘去&#xff0c;文件名用uuid避免重复。写完就可以顺带把文件名保存到数据库里。上传就这样子。 怎么取用的&#xff1b;还是通过配置映射的方式&#xff1b;通过sr…

vue3升级了些什么

Vue 3 升级了以下几个方面的内容&#xff1a; 响应式系统&#xff1a;Vue 3 使用了 Proxy 对象来替代 Vue 2 中的 Object.defineProperty&#xff0c;这使得响应式系统更加高效和灵活。Vue 3 的响应式系统可以追踪更细粒度的依赖关系&#xff0c;提供了更好的性能和更细致的响应…

Bootstrap的行、列布局设计(网络系统设计)

目录 00-基础知识01-等宽列布局02-指定某一列的宽度03-根据内容自动改变列的宽度04-五种预定义列宽度 .col、.col-sm-*、.col-md-*、.col-lg-*、.col-xl-*05-不同视口宽度按不同的分列方案划分06-删除列内容的盒模型的外边距07-超过12列怎么办&#xff1f;08-重新排列各列的顺序…

继承(个人学习笔记黑马学习)

1、基本语法 #include <iostream> using namespace std; #include <string>//普通实现页面//Java页面 //class Java { //public: // void header() { // cout << "首页、公开课、登录、注册...(公共头部)" << endl; // } // void footer() …

【精品】NLP自然语言处理学习路线(知识体系)

当前&#xff0c;大规模预训练语言模型的强大对话问答、文本生成能力&#xff0c;将自然语言处理&#xff08;NLP&#xff09;的研究和应用推向了新一轮的热潮。NLP是计算机科学、人工智能和语言学等学科交叉的前沿领域。NLP的应用和研究范围非常的广泛&#xff0c;个人是没有找…

利用GitHub实现域名跳转

利用GitHub实现域名跳转 一、注册一个 github账号 你需要注册一个 github账号,最好取一个有意义的名字&#xff0c;比如姓名全拼&#xff0c;昵称全拼&#xff0c;如果被占用&#xff0c;可以加上有意义的数字. 本文中假设用户名为 UNIT-wuji(也是我的博客名) 地址: https:/…

Git常用命令用法

参考视频&#xff1a;真的是全能保姆 git、github 保姆级教程入门&#xff0c;工作和协作必备技术&#xff0c;github提交pr - pull request_哔哩哔哩_bilibili 1.Git初始化 首先设置名称和邮箱。然后初始化一下&#xff0c;然后就创建了一个空的Git仓库。 PS D:\golang\oth…

node socket.io

装包&#xff1a; yarn add socket.io node后台&#xff1a; const express require(express) const http require(http) const socket require(socket.io) const { getUserInfoByToken } require(../../utils/light/tools)let app express() const server http.createS…

【C++漂流记】结构体的定义和使用、结构体数组、结构体指针、结构体做函数参数以及结构体中const的使用

结构体&#xff08;struct&#xff09;是C语言中一种重要的数据类型&#xff0c;它由一组不同类型的成员组成。结构体可以用来表示一个复杂的数据结构&#xff0c;比如一个学生的信息、一个员工记录或者一个矩形的尺寸等。 结构体定义后&#xff0c;可以声明结构体变量&#xf…

NCCoE发布“向后量子密码学迁移”项目进展情况说明书

近日&#xff0c;NIST下属的国家网络安全中心&#xff08;NCCoE&#xff09;发布了一份向后量子密码学迁移&#xff08;Migration to Post-Quantum Cryptography&#xff09;项目情况说明书。该文档简要概述了向后量子密码学迁移项目的背景、目标、挑战、好处和工作流程&#x…

【HTML5高级第二篇】WebWorker多线程、EventSource事件推送、History历史操作

文章目录 一、多线程1.1 概述1.2 体会多线程1.3 多线程中数据传递和接收 二、事件推送2.1 概述2.2 onmessage 事件 三、history 一、多线程 1.1 概述 前端JS默认按照单线程去执行&#xff0c;一段时间内只能执行一件事情。举个栗子&#xff1a;比方说古代攻城游戏&#xff0c…

Jenkins自动构建(Gitee)

Gitee简介安装JenkinsCLI https://blog.csdn.net/tongxin_tongmeng/article/details/132632743 安装Gitee jenkins-cli install-plugin gitee:1.2.7 # https://plugins.jenkins.io/gitee/releases获取安装命令(稍作变更) JenkinsURL Dashboard-->配置-->Jenkins Locatio…

鸿蒙系列-如何使用好 ArkUI 的 @Reusable?

如何使用好 ArkUI 的 Reusable&#xff1f; OpenHarmony 组件复用机制 在ArkUI中&#xff0c;UI显示的内容均为组件&#xff0c;由框架直接提供的称为 系统组件&#xff0c;由开发者定义的称为 自定义组件。 在进行 UI 界面开发时&#xff0c;通常不是简单的将系统组件进行组合…

NIO原理浅析(三)

epoll 首先认识一下epoll的几个基础函数 int s socket(AF_INET, SOCK_STREAM, 0); bind(s, ...); listen(s, ...);int epfd epoll_create(...) epoll_ctl(epfd, ...); //将所有需要监听的socket添加到epfd中while(1) {int n epoll_wait(...);for(接受到数据的socket) {//处…

Kotlin 环境下解决属性初始化问题

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

react使用hook封装一个tab组件

目录 react使用hook封装一个tab组件Tabbar.jsx使用组件效果 react使用hook封装一个tab组件 Tabbar.jsx import PropsTypes from "prop-types"; import React, { useEffect, useState } from react; export default function Tabbar(props) {const { tabData , cur…