【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
的值。
进程与文件的关系
文件大致可以分成两类:
- 磁盘文件(没有被打开,文件=内容+属性)
- 内存文件(进程在内存中打开)
进程要访问文件,必须先打开文件。一个进程可以打开多个文件(文件要被访问,必须是先加载到内存中)。
当多个进程都要访问文件时,系统中会存在大量的被打开的文件。
面对如此之多的被打开的文件,操作系统就要采用先描述,再组织的方式将这些被打开的文件进行管理。
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
接口中oldfd
和newfd
的拷贝不是单纯的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
,然后使用对应的操作方法使用设备了。
也就是说,在操作系统以上,就可以用一切皆文件的视角看待这些不同的设备了。
下面是内核代码中文件操作方法的函数指针。
文件缓冲区
缓冲区,简单说就是一段内存空间。它是由语言层提供的。它的存在可以提高整机效率,主要还是为了提高用户的响应速度(是一种写回模式)。
缓冲区的刷新策略
一般情况下有三种刷新策略:
- 立即刷新:写入缓冲区就立即刷新。
- 行刷新:写完一行内容再刷新。
- 满刷新:缓冲区写满再刷新。
特殊情况也会刷新:
- 用户强制刷新(fflush)。
- 进程退出。
其实,所有的设备,永远都倾向于全缓冲策略(缓冲区满了才刷新),这样做可以更少次地对外设进行访问,提高效率(和外部设备进行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号文件描述符对应的文件。
所以可以通过重定向把标准输入和标准输出的内容输入到不同的文件中进行分开查看。

一般而言,如果程序运行有,可能有错误的话,建议使用stderr
,或者cerr
来打印。如果是常规文本内容,建议使用cout
,stdout
打印。
当然也是可以将标准输入和标准输出的内容都输入到一个文件中的。
打印的内容中,有一个细节是perror
的打印包含有一段信息,其实就是错误码errno
对应的错误信息。
这里根据perror
的功能可以模拟实现一个进行理解。
void my_perror(const char* msg)
{ fprintf(stderr, "%s: %s\n", msg, strerror(errno));
}
相关文章:

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

Android OTA 相关工具(六) 使用 lpmake 打包生成 super.img
我在 《Android 动态分区详解(二) 核心模块和相关工具介绍》 介绍过 lpmake 工具,这款工具用于将多个分区镜像打包生成一个 Android 专用的动态分区镜像,一般称为 super.img。Android 编译时,系统会自动调用 lpmake 并传入相关参数来生成 sup…...
信创环境 Phytium S2500 虚拟机最大内存规格测试
在 ARM 架构中,"IPA" 通常指的是 "Instruction Set Architecture"(指令集架构),arm环境的虚拟机支持的最大内存规格与母机上内存多少无关,由arm本身的ipa size决定,ipa size 可以理解为虚拟机的物理地址空间,kernel5.4.32中ipa默认是44bits(16T si…...

新建工程——第一个S32DS工程
之前的"测试开发板"章节 测试开发板——第一个AutoSAR程序,使用了一个 demo 工程,不管是裸机程序还是 AutoSAR 程序,那都是别人已经创建好的工程。本节来介绍如何来创建自己的工程,本节介绍如何创建一个 S32DS 的工程,点亮开发板上的 LED 我们从官方提供的例程…...

基于Open3D的点云处理16-特征点匹配
点云配准 将点云数据统一到一个世界坐标系的过程称之为点云配准或者点云拼接。(registration/align) 点云配准的过程其实就是找到同名点对;即找到在点云中处在真实世界同一位置的点。 常见的点云配准算法: ICP、Color ICP、Trimed-ICP 算法…...

设计模式—简单工厂
目录 一、前言 二、简单工厂模式 1、计算器例子 2、优化后版本 3、结合面向对象进行优化(封装) 3.1、Operation运算类 3.2、客户端 4、利用面向对象三大特性(继承和多态) 4.1、Operation类 4.2、加法类 4.3、减法类 4…...

真机安装Linux Centos7
准备工具: 8G左右U盘最新版UltraISOCentOS7光盘镜像 操作步骤 下载镜像 地址:http://isoredirect.centos.org/centos/7/isos/x86_64/ 安装刻录工具UltraISO,刻录镜像到U盘 ① 选择ISO镜像文件 ② 写入磁盘镜像,在这里选择你的U盘…...

ceph peering机制-状态机
本章介绍ceph中比较复杂的模块: Peering机制。该过程保障PG内各个副本之间数据的一致性,并实现PG的各种状态的维护和转换。本章首先介绍boost库的statechart状态机基本知识,Ceph使用它来管理PG的状态转换。其次介绍PG的创建过程以及相应的状…...
Java | File类
目录: File类File类中常用的方法:boolean exists( ) :判断此 文件/目录 是否存在boolean createNewFile( ) :创建一个文件boolean mkdir( ) :创建 “单层” 目录/文件夹boolean mkdirs( ) :创建 “多层” 目…...
数学建模之灰色预测
灰色预测(Grey Forecasting)是一种用于时间序列数据分析和预测的方法,通常用于处理具有较少历史数据的情况或者数据不够充分的情况。它是一种非常简单但有效的方法,基于灰色系统理论,用来估计未来的趋势。 以下是灰色…...
03_nodejd_npm install报错
npm install报错 npm ERR! code ERESOLVE npm ERR! ERESOLVE unable to resolve dependency tree npm ERR! npm ERR! While resolving: 5kcrm11.0.0 npm ERR! Found: vue2.5.17 npm ERR! node_modules/vue npm ERR! vue"2.5.17" from the root project npm ERR! np…...

three.js(二):webpack + three.js + ts
用webpackts 开发 three.js 项目 webpack 依旧是主流的模块打包工具;ts和three.js 是绝配,three.js本身就是用ts写的,ts可以为three 项目提前做好规则约束,使项目的开发更加顺畅。 1.创建一个目录,初始化 npm mkdir demo cd de…...

最小二乘法处理线性回归
最小二乘法是一种数学优化技术,用于查找最适合一组数据点的函数。 该方法主要用于线性回归分析,当然,也可用于非线性问题。 开始之前,我们先理解一下什么是回归。 回归:回归是一种监督学习算法,用于建模和…...
ModbusCRC16校验 示例代码
作者: Herman Ye Galbot Auromix 测试环境: Ubuntu20.04 更新日期: 2023/08/30 注1: Auromix 是一个机器人爱好者开源组织。 注2: 本文在更新日期经过测试,确认有效。 笔者出于学习交流目的, 给…...

一不留神就掉坑
乘除顺序问题 在据卡特兰数[1]公式,解决leetcode-96 不同的二叉搜索树[2]时,遇到一个非常诡异的问题, package mainimport "fmt"func main() { for i : 0; i < 40; i { fmt.Printf("第%d个卡特兰数为:%d\n", i, numTrees(i)) }}func numTrees(n int) i…...

Redis数据类型(list\set\zset)
"maybe its why" List类型 列表类型是⽤来存储多个有序的字符串,列表中的每个字符串称为元素(element),⼀个列表最多可以存储个2^32 - 1个元素。在Redis中,可以对列表两端插⼊(push)…...
TongWeb安装以及集成
TongWeb 安装步骤 静默安装 获取linux可执行安装包 如: Install_TWx.x.x.x_Enterprise_Linux.bin 创建安装所需配置文件 install.properties 内容如下 [root@node5 tongweb]# cat install.properties INSTALL_UI=silent USER_INSTALL_DIR=/home/tongweb SILENT_JDK_HOME=/jd…...

ScreenToGif-动图制作软件实用操作
ScreenToGif官网:ScreenToGif ⭕第一步:启动页面 ⭕第二步:选项 🥝录像机-捕获频率选择手动-播放延迟1000ms(可以任意) ⭕第三步:录像机开始录屏 🥝我们调整录屏的大小后,打开画图,…...

sqlibs安装及复现
sqlibs安装 安装phpstudy后,到github上获取sqlibs源码 sqli-labs项目地址—Github获取:GitHub - Audi-1/sqli-labs: SQLI labs to test error based, Blind boolean based, Time based. 在phpstudy本地文件中的Apache目录中解压上方下载的源码。 将sq…...
OpenAI 创始人 Sam Altman 博客有一篇 10 年前的文章
OpenAI 创始人 Sam Altman 博客有一篇 10 年前的文章《Advice for ambitious 19 year olds》,给 19 岁年轻人的建议,从 #参考答案 看到,非常适合我们🤣年轻人,顺便用 GPT4 重新翻译了下全文。 太长不读纯摘要版本如下&…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...

剑指offer20_链表中环的入口节点
链表中环的入口节点 给定一个链表,若其中包含环,则输出环的入口节点。 若其中不包含环,则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...

GitFlow 工作模式(详解)
今天再学项目的过程中遇到使用gitflow模式管理代码,因此进行学习并且发布关于gitflow的一些思考 Git与GitFlow模式 我们在写代码的时候通常会进行网上保存,无论是github还是gittee,都是一种基于git去保存代码的形式,这样保存代码…...
tomcat入门
1 tomcat 是什么 apache开发的web服务器可以为java web程序提供运行环境tomcat是一款高效,稳定,易于使用的web服务器tomcathttp服务器Servlet服务器 2 tomcat 目录介绍 -bin #存放tomcat的脚本 -conf #存放tomcat的配置文件 ---catalina.policy #to…...

MySQL:分区的基本使用
目录 一、什么是分区二、有什么作用三、分类四、创建分区五、删除分区 一、什么是分区 MySQL 分区(Partitioning)是一种将单张表的数据逻辑上拆分成多个物理部分的技术。这些物理部分(分区)可以独立存储、管理和优化,…...

Golang——7、包与接口详解
包与接口详解 1、Golang包详解1.1、Golang中包的定义和介绍1.2、Golang包管理工具go mod1.3、Golang中自定义包1.4、Golang中使用第三包1.5、init函数 2、接口详解2.1、接口的定义2.2、空接口2.3、类型断言2.4、结构体值接收者和指针接收者实现接口的区别2.5、一个结构体实现多…...
API网关Kong的鉴权与限流:高并发场景下的核心实践
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 引言 在微服务架构中,API网关承担着流量调度、安全防护和协议转换的核心职责。作为云原生时代的代表性网关,Kong凭借其插件化架构…...
《Offer来了:Java面试核心知识点精讲》大纲
文章目录 一、《Offer来了:Java面试核心知识点精讲》的典型大纲框架Java基础并发编程JVM原理数据库与缓存分布式架构系统设计二、《Offer来了:Java面试核心知识点精讲(原理篇)》技术文章大纲核心主题:Java基础原理与面试高频考点Java虚拟机(JVM)原理Java并发编程原理Jav…...

CVE-2023-25194源码分析与漏洞复现(Kafka JNDI注入)
漏洞概述 漏洞名称:Apache Kafka Connect JNDI注入导致的远程代码执行漏洞 CVE编号:CVE-2023-25194 CVSS评分:8.8 影响版本:Apache Kafka 2.3.0 - 3.3.2 修复版本:≥ 3.4.0 漏洞类型:反序列化导致的远程代…...