【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 -> 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.mfbz.cn/a/101473.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【小沐学Unity3d】3ds Max 骨骼动画制作(CAT、Character Studio、Biped、骨骼对象)

文章目录 1、简介2、 CAT2.1 加载 CATRig 预设库2.2 从头开始创建 CATRig 3、character studio3.1 基本描述3.2 Biped3.3 Physique 4、骨骼系统4.1 创建方法4.2 简单示例 结语 1、简介 官网地址&#xff1a; https://help.autodesk.com/view/3DSMAX/2018/CHS https://help.aut…

11.物联网lwip,网卡原理

一。LWIP协议栈内存管理 1.LWIP内存管理方案 &#xff08;1&#xff09;堆heap 1.灰色为已使用内存 2.黑色为未使用内存 3.紫色为使用后内存 按照某种算法&#xff0c;把数据放在内存块中 &#xff08;2&#xff09;池pool 设置内存池&#xff0c;设置成大小相同的内存块。 2…

element-plus指定el-date-picker的弹出框位置

此处记录一下,通过popper-options指定popper出现的位置

【051】基于Vue、Springboot电商管理系统(含源码、详细论文、数据库)

基于Vue、Springboot、Mysql的前后端分离的电商管理系统&#xff0c;不仅功能完善&#xff0c;还有详细课设报告供查看&#xff0c;这不收藏起来&#xff0c;源码和论文获取见文末结尾 部分报告内容如下&#xff08;省略图片&#xff09; c 目录 1 引言 4 1.…

【数学建模】清风数模正课5 相关性分析

相关系数 相关性分析的关键是计算相关系数&#xff0c;在本节课中将会介绍两种常用的相关系数&#xff1a;皮尔逊相关系数&#xff08;Pearson&#xff09;和斯皮尔曼相关系数&#xff08;Spearman&#xff09;。 它们可以用来衡量两个变量间相关性的大小&#xff0c;对于不同…

深入探讨梯度下降:优化机器学习的关键步骤(一)

文章目录 &#x1f340;引言&#x1f340;什么是梯度下降&#xff1f;&#x1f340;损失函数&#x1f340;梯度(gradient)&#x1f340;梯度下降的工作原理&#x1f340;梯度下降的变种&#x1f340;随机梯度下降&#xff08;SGD&#xff09;&#x1f340;批量梯度下降&#xf…

Java“牵手”京东商品列表数据,关键词搜索京东商品数据接口,京东API申请指南

京东商城是一个网上购物平台&#xff0c;售卖各类商品&#xff0c;包括服装、鞋类、家居用品、美妆产品、电子产品等。要获取京东商品列表和商品详情页面数据&#xff0c;您可以通过开放平台的接口或者直接访问京东商城的网页来获取商品详情信息。以下是两种常用方法的介绍&…

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;修改系…

docker安装jenkins

运行jenkins docker run -d \--name jenkins \ --hostname jenkins \-u root \-p 29090:8080 \--restart always \-v D:\springcloud\学习\jekins\jenkins\jks_home:/var/jenkins_home \ jenkins/jenkins获取root登录密码 密码在jekins_home/secrets/initalAdminPassword文件…

设计模式—原型模式(Prototype)

目录 一、什么是原型模式&#xff1f; 二、原型模式具有什么优缺点吗&#xff1f; 三、有什么缺点&#xff1f; 四、什么时候用原型模式&#xff1f; 五、代码展示 ①、简历代码初步实现 ②、原型模式 ③、简历的原型实现 ④、深复制 ⑤、浅复制 一、什么是原型模式&…

Ubuntu学习---跟着绍发学linux课程记录(第二部分)

文章目录 7 文件权限7.1 文件的权限7.2 修改文件权限7.3 修改文件的属主 8、可执行脚本8.2Shell脚本8.3python脚本的创建 9Shell9.1Shell中的变量9.2 环境变量9.3用户环境变量 学习链接: Ubuntu 21.04乌班图 Linux使用教程_60集Linux课程 所有资料在 http://afanihao.cn/java …

单调递增的数字【贪心算法】

单调递增的数字 当且仅当每个相邻位数上的数字 x 和 y 满足 x < y 时&#xff0c;我们称这个整数是单调递增的。 给定一个整数 n &#xff0c;返回 小于或等于 n 的最大数字&#xff0c;且数字呈 单调递增 。 public class Solution {public int monotoneIncreasingDigits…

stm32---用外部中断实现红外接收器

一、红外遥控的原理 红外遥控是一种无线、非接触控制技术&#xff0c;具有抗干扰能力强&#xff0c;信息传 输可靠&#xff0c;功耗低&#xff0c;成本低&#xff0c;易实现等显著优点&#xff0c;被诸多电子设备特别是 家用电器广泛采用&#xff0c;并越来越多的应用到计算机系…

【USRP】调制解调系列6:16APSK、32APSK 、基于labview的实现

APSK APSK是&#xff0c;与传统方型星座QAM&#xff08;如16QAM、64QAM&#xff09;相比&#xff0c;其分布呈中心向外沿半径发散&#xff0c;所以又名星型QAM。与QAM相比&#xff0c;APSK便于实现变速率调制&#xff0c;因而很适合目前根据信道及业务需要分级传输的情况。当然…

音频——I2S DSP 模式(五)

I2S 基本概念飞利浦(I2S)标准模式左(MSB)对齐标准模式右(LSB)对齐标准模式DSP 模式TDM 模式 文章目录 DSP formatDSP A时序图逻辑分析仪抓包 DSP B时序图逻辑分析仪抓包 DSP format DSP/PCMmode 分为 Mode-A 和 Mode-B 共 2 种模式。不同芯⽚有的称为 PCM mode 有的称为 DSP m…

Qt —UDP通信QUdpSocket 简介 +案例

1. UDP通信概述 UDP是无连接、不可靠、面向数据报&#xff08;datagram&#xff09;的协议&#xff0c;可以应用于对可靠性要求不高的场合。与TCP通信不同&#xff0c;UDP通信无需预先建立持久的socket连接&#xff0c;UDP每次发送数据报都需要指定目标地址和端口。 QUdpSocket…

在访问一个网页时弹出的浏览器窗口,如何用selenium 网页自动化解决?

相信大家在使用selenium做网页自动化时&#xff0c;会遇到如下这样的一个场景&#xff1a; 在你使用get访问某一个网址时&#xff0c;会在页面中弹出如上图所示的弹出框。 首先想到是利用Alert类来处理它。 然而&#xff0c;很不幸&#xff0c;Alert类处理的结果就是没有结果…

flink on yarn with kerberos 边缘提交

flink on yarn 带kerberos 远程提交 实现 flink kerberos 配置 先使用ugi进行一次认证正常提交 import com.google.common.io.Files; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FileUtils; import org.apache.flink.client.cli.CliFrontend; import o…

Matlab(数值微积分)

目录 1.多项式微分与积分 1.1 微分 1.2 多项式微分 1.3 如何正确的使用Matlab? 1.3.1 Matlab表达多项式 1.3.2 polyval() 多项式求值 1.3.3 polyder()多项式微分 1.4 多项式积分 1.4.1 如何正确表达 1.4.2 polyint() 多项式积分 2.数值的微分与积分 2.1 数值微分 2…

django.core.exceptions.AppRegistryNotReady: Apps aren‘t loaded yet.

运行django测试用例报错django.core.exceptions.AppRegistryNotReady: Apps arent loaded yet. 解决&#xff1a;在测试文件上方加上 django.setup() django.setup()是Django框架中的一个函数。它用于在非Django环境下使用Django的各种功能、模型和设置。 在常规的Django应用…