【Linux】文件

news2025/1/27 13:12:38

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 -> 1
    printf("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.coloradmin.cn/o/965237.html

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

相关文章

学信网学历电子注册备案表 下载方法

如果你需要成人高考的专升本 那么就会需要 学信网学历电子注册备案表 全称叫 教育部学历证书电子注册备案表 是学信网依托全国高等教育学生信息数据库&#xff0c;对学生的学历信息提供的在线验证报告&#xff0c;是我们验证学历真伪的一份报告 学历电子注册备案表在 考研、考…

shell脚本中时间的编写规范20230902

背景&#xff1a;经常写shell&#xff0c;但是很多种时间格式规范真是记不住哈&#xff0c;&#x1f604;&#xff0c;索性记录一下 一、 获取-年 下面的这两种写法都成 year$(date "%Y") yeardate "%Y"echo -e "测试输出 年: ${year}"输出结…

springboot web开发springmvc自动配置原理

前言 我们也知道springboot启用springmvc基本不用做什么配置可以很方便就使用了但是不了解原理,开发过程中遇到点问题估计就比较头疼,不管了解的深不深入,先巴拉一番再说… 下面我们先看看官网…我的版本是2.3.2版本,发现官网改动也比较大…不同版本自己巴拉下吧,结构虽然变化…

PMD代码检查:过长的变量名字(LongVariable)

https://docs.pmd-code.org/pmd-doc-6.55.0/pmd_rules_java_codestyle.html#longvariable 属性、正式变量、局部变量的名字如果太长&#xff0c;会使代码难理解。例如&#xff0c;下面的代码报违反项&#xff1a; 可以通过属性minimum配置报违反规则的最小长度&#xff08;默…

qt.qpa.plugin:找不到Qt平台插件“wayland“|| (下载插件)Ubuntu上解决方案

相信大家也都知道这个地方应该做什么&#xff0c;当然是下载这个qt平台的插件wayland,但是很多人可能不知道怎么下载这个插件。 那么我现在要说的这个方法就是针对这种的。 sudo apt install qtwayland5完事儿了奥兄弟们。 看看效果 正常了奥。

总结ADX指标交易的好处

股神巴菲特从一个穷小子变成世界富豪&#xff0c;而闻名世界。anzo capital昂首资本以为这辈子再也不会和巴菲特产生任何交集&#xff0c;直到我看了巴菲特的发家史&#xff0c;才发现原来我们都使用过ADX指标盈利过&#xff0c;下面anzo capital昂首资本就总结一下使用ADX指…

如何大批量、低成本获取精准客户

身为一个企业或广告投放的决策人&#xff0c;当你在做每一次营销决策的时候&#xff0c;一定会回答一个问题&#xff1a;如何把产品卖给对的人&#xff1f; 目前经济形势下&#xff0c;消费活跃&#xff0c;但是获客却越来越难&#xff0c;在大数据弥漫的今天&#xff0c;我们仿…

leetcode 1365. 有多少小于当前数字的数字

2023.9.2 本题直观的解法就是双层for循环暴力求解&#xff1a; 暴力解&#xff1a; class Solution { public:vector<int> smallerNumbersThanCurrent(vector<int>& nums) {vector<int> ans;for(int i0; i<nums.size(); i){int temp 0;//比当前元素…

如何确认linux的包管理器是yum还是apt,确认之后安装其他程序的时候就需要注意安装命令

打开终端 输入apt&#xff0c;下图中提示未找到命令&#xff0c;则基本上包管理工具就是用yum的 输入yum&#xff0c;我们看到有打印信息&#xff0c;则说明包管理工具是yum的&#xff0c;离线安装命令使用rpm

kafka详解二

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

中国地方ZF数据开放指数(省域指数、城市指数)

“中国开放数林指数”是我国首个专注于评估政府数据开放水平的专业指数&#xff0c;由复旦大学数字与移动治理实验室制作出品。开放数据&#xff0c;蔚然成林&#xff0c; “开放数林”意喻我国政府数据开放利用的生态体系&#xff0c;一棵棵地方开放“数木”由最初的丛然并生、…

postgres源码解析54 Brin Index--1

Brin Index简介 brin index是Block range Index的缩写&#xff0c;顾名思义该索引是指块范围索引&#xff0c;该索引适合在超大表中进行过滤性扫描。基本的思路是追踪heap页域的最大值与最小值&#xff0c;用于过滤不符合条件的数据块。以下图为例&#xff0c;右边的堆表包含三…

ARM编程模型-寄存器组

Cortex A系列ARM处理器共有40个32位寄存器,其中33个为通用寄存器,7个为状态寄存器。usr模式和sys模式共用同一组寄存器。 通用寄存器包括R0~R15,可以分为3类: 未分组寄存器R0~R7分组寄存器R8~R14、R13(SP) 、R14(LR)程序计数器PC(R15)、R8_fiq-R12_fir为快中断独有 在不同模…

VITIS编译启动文件

source /home/wj/xilinx/PetaLinux/2021.1/tool/settings.sh source /home/wj/xilinx/Vitis/2021.1/settings64.sh source /home/wj/xilinx/Vivado/2021.1/settings64.sh 0 层 eng/home/wj/ETHERCAT_DW/meta-adi/meta-adi-core 1 层 /home/wj/ETHERCAT_DW/meta-adi/meta-ad…

【算法竞赛宝典】插入排序

【算法竞赛宝典】插入排序 题目描述伪代码代码展示代码讲解 题目描述 伪代码 代码展示 //插入排序数,请自己根据例程修改 #include <iostream>using namespace std;int main() {int a[11] {1, 4, 6, 9, 13, 16, 19, 28, 40, 100};int temp1, temp2, number, end, i, j…

go学习part20(3)反射细节

1.Set函数的细节 错误示范 正确示范:传指针再调用Elem() 2.反射的高级用法 1&#xff09;方法调用&#xff08;传参和返回值都是Value&#xff09; 方法顺序i是按照方法名字的顺序来计算&#xff0c;比较ascii码 2)结构体字段数 3)value.Field&#xff08;i&#xff09;获取…

MPI之虚拟进程拓扑

什么是虚拟进程拓扑 在很多并行应用进程中&#xff0c;进程的线性排列不能充分的反映进程间在逻辑上的通信模型&#xff0c;通常由问题几何和所用的算法决定&#xff0c;进程经常被排列成二维或者三维网络形式的拓扑模型而通常用一个图来描述逻辑进程排列&#xff0c;此种逻辑…

百度搜索清理大量低质量网站

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 据部分站长爆料&#xff1a;百度大规模删低质量网站的百度资源站长平台权限&#xff0c;很多网站都被删除了百度站长资源平台后台权限&#xff0c;以前在百度后台添加的网站大量被删除&#xff01;…

在Mac上安装及使用 dubbo-go

目录 dubbo-go 是什么dubbo-go 快速入门环境安装&#xff08;Mac 系统&#xff09;安装 Go语言环境安装 序列化工具protoc安装 dubbogo-cli 以及相关插件解决报错&#xff1a;zsh: command not found: protoc-gen-go&#xff0c;而其他两个工具都能正常输出版本号信息。 完成依…

[贪心] 拼接最小数

这道题思路并不难&#xff0c;我主要想学习其一些对于字符串的处理。 代码如下&#xff1a; #include <iostream> #include <string> #include <algorithm> using namespace std;const int MAXN 10000; string nums[MAXN];bool cmp(string a, string b) {…