Linux中的文件

文件

文件

我们学习文件操作之前,我们通过我们之前学习的知识来认识一下文件

  • 文件 = 内容 + 属性,对文件的操作,就是对文件内容和属性的操作
  • 当文件没有被操作的时候,文件会在磁盘
  • 当我们对文件操作的时候,文件被加载到内存中,因为冯诺依曼体系,cpu只在内存中交互
  • 当我们对文件进行操作的时候,文件要提前被加载到内存之中
  • 当我们对文件操作的时候,至少加载了文件的属性(通过我们之前的知识的推导)
  • 当我们操作文件的时候,内存中已经有大量的其他的文件的属性
  • 所以打开文件的本质就是将文件的属性加载到内存之中,操作系统内部需要管理再内存中的大量文件,怎么管理? - - - 先描述,在组织
  • 描述:构建在内存中的被打开的文件的结构体
  • 这和我们学的进程一样,操作系统要对被打开的文件创建对应的内核数据结构
  • 文件可以被分为:被打开的文件(内存),未被打开的文件(磁盘)
  • 文件被打开,是用户创建了一个进程,让操作系统来打开的
  • 我们的文件操作都是进程和被打开的文件的关系

语言文件操作

python,java,c/c++,php,go等语言都有文件操作,他们的文件操作都不一样 - - - 但实质上都是封装了系统文件操作

我们学习文件之前,我们通过复习一下我们之前学的c语言的文件操作来再学习一下语言文件操作

“w”

在这里插入图片描述
在这里插入图片描述
我们成功写入了,我们知道”w“是覆盖式写入,如果文件不存在会创建文件,文件存在会覆盖文件原有的内容再进行写入
在这里插入图片描述
我们可以通过fprintf函数在文件中写入,也可以写到显示器上
在这里插入图片描述
在这里插入图片描述
我们同样可以通过snprintf先接受我们想输进文件的内容到字符串数组中,然后再写入文件中

“a”

“a”:在文件的末尾以追加的方式写入,文件不存在会自动创建文件,文件存在不会覆盖文件原来的内容
在这里插入图片描述

在这里插入图片描述

系统文件操作

open

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
#include <unistd.h>
int close(int fd);
  • pathname:要打开的文件的路径名。
  • flags:打开文件的标志,用于指定文件的访问模式和行为。
  • mode:仅在创建新文件时使用,指定新文件的访问权限。

flags是选项,是用位图的方式,利用flags的32个比特位来设置选项,选项每一个都是宏,mode就是设置权限
打开模式(flags参数):

  • O_RDONLY:只读方式打开文件。
  • O_WRONLY:只写方式打开文件。
  • O_RDWR:读写方式打开文件。
  • O_CREAT:如果文件不存在,则创建新文件。
  • O_TRUNC:如果文件存在且为写入模式打开,则截断文件(清空文件内容)。
  • O_APPEND:在文件末尾追加数据,而不是覆盖已有内容。
  • O_EXCL:与O_CREAT一起使用,确保创建文件时文件不存在,避免文件覆盖。
  • 其他标志可用于控制文件的行为,如非阻塞打开(O_NONBLOCK)和同步写入(O_SYNC)等。

O_WRONLYWR就是write,ONLY就是only,就是以写的方式打开
在这里插入图片描述
在这里插入图片描述
可以看到只有O_WRONLY模式下,没有文件,并不会自动创建

在这里插入图片描述
在这里插入图片描述
我们这时用读和写权限去创建并打开文件,可以看到是成功了,但是文件时红色的,并且权限是乱码

这是因为创建文件时要有权限的,我们创建文件一般不用这个两个参数的open,我们用三个参数的,第三个参数设置权限

在这里插入图片描述

在这里插入图片描述
我们看到文件成功创建,并且权限设置好了

但是还是有点问题,权限我们设置的是0666,但是权限是0664,这是因为umask掩码的影响

在这里插入图片描述
在这里插入图片描述

现在我们就设置好了权限

这就跟我们c语言写的fopen("file.text", "w");是差不多的效果了

c标准库就是就是封装调用了这些系统接口

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
可以看到我们的程序没有任何问题,按照我们的想法执行了

在这里插入图片描述

现在我们改一下write,多传一个’\0’进去
在这里插入图片描述

在这里插入图片描述

我们外面看没有任何区别,但是我们vim进去看发现有乱码了

因为’\0’是c语言的规定,不是文件的规定,所有我们不应该加上这个’\0’

O_TRNC
在这里插入图片描述
在这里插入图片描述
我们看到我们文件内容并没有被覆盖,每次写入都是默认从开头开始写,不会将文件原有的所有内容覆盖

我们就要加上O_TURNC选项,将文件清空
在这里插入图片描述
在这里插入图片描述
现在我们加的这些选项,就是和"w"一样了

O_APPEND追加

在这里插入图片描述
在这里插入图片描述
现在我们带了这些选项就是"a"追加写入的底层实现

O_RDONLYRD就是read
read是按个读取的,如果要按行读取,那就读一个判断一下是不是’\n’
在这里插入图片描述
在这里插入图片描述
这就是系统文件读取

文件描述符

我们看上面那张图,可以发现,我们的文件成功打开了之后的返回值是3,这时候我们会想,为什么是3,为什么不是0,1,2呢?现在我就来解释一下

任何一个进程,在启动的时候,都会默认打开当前进程的三个文件:标准输入(c:stdin,c++:cin)、标准输出(c:stdout,c++:cout)、标准错误(c:stderr,c++:cerr)0就是给了标准输入,1就是标准输出,2就是标准错误,之后就按打开的文件的顺序给3,4,5,6…,从这我们就可以知道文件描述符本质就是数组下标

文件描述符(File Descriptor)是操作系统中用于标识打开文件或其他I/O资源的整数值。它是在程序中管理和操作文件的重要概念。文件描述符是一个非负整数,通常表示为整数值。

在大多数操作系统中,包括Linux和Unix,文件描述符被用于表示打开的文件、套接字(socket)、管道(pipe)等I/O资源。每个打开的文件或资源都被分配一个唯一的文件描述符。通过文件描述符,程序可以对文件或资源进行读取、写入、关闭等操作。

文件描述符的特点:

  1. 非负整数:文件描述符是一个非负整数,通常从0开始递增。标准输入(stdin)、标准输出(stdout)和标准错误(stderr)分别使用文件描述符0、1和2。

  2. 数值范围:文件描述符的具体数值范围取决于操作系统。通常情况下,文件描述符的最大值由系统限制,可以使用系统常量如OPEN_MAX(在头文件<limits.h>中定义)来获取最大文件描述符数。

  3. 关联打开的资源:文件描述符与打开的文件、套接字或其他I/O资源相关联。通过文件描述符,操作系统可以追踪和管理这些资源。不同的文件描述符可以指向同一个文件或资源。

  4. 使用系统调用:文件描述符的创建和操作通常通过系统调用完成,如opensocketpipe等。系统调用返回文件描述符,以便程序对文件或资源进行进一步的操作。

  5. 标准I/O函数:文件描述符可以与标准I/O函数(如readwriteclose等)一起使用,用于进行读取、写入和关闭操作。标准I/O函数通过文件描述符与对应的文件或资源进行交互。

文件描述符是操作系统中用于标识打开文件或其他I/O资源的整数值。它是管理和操作文件的重要概念,通过文件描述符,程序可以对文件或资源进行读取、写入和关闭等操作。文件描述符是非负整数,与打开的文件或资源关联。

进程中创建的所有文件都会同样在内存中创建一个数据结构来管理文件,比如说链表,每个文件都通过链表的结点管理,只要找到了内存中的这个数据结构就可以找到文件的内容
在这里插入图片描述

操作系统下文件描述符中前三个放的是标准输入、输出、错误,之后就放的是进程创建的文件,这就是操作系统内部做文件管理时映射的关系

内存中每个文件对应的数据结构会带一个缓冲区,我们使用write函数向文件中写入,其实是写在缓冲区中,所以IO类的read,write函数本质都是拷贝函数,什么时候从缓冲区将内容写入到磁盘中的文件里是由操作系统决定

在这里插入图片描述
在这里插入图片描述

文件描述符会将最小的没有被使用的分配给文件

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

重定向

我们知道printf是向标准输出(fd为1)中打印,所以我们可以做点有意思的事情

在这里插入图片描述
在这里插入图片描述
我们可以看到本来应该打印到屏幕上的字,现在打印到文件中了,这就是输出重定向

我们将标准输出关掉了,并且在它的位置放上了我们自己的文件,这样做,上层不知道,也不关心,上层只认文件描述符,不管文件描述符对应的是谁

重定向的原理:在上层无法知道的情况下,在操作系统内部,更改进程对应的文件描述符表中,特定下标的指向

我们改一下文件的内容

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这就是输入重定向
:
在这里插入图片描述
这是追加重定向

dup2
#include <unistd.h>
int dup2(int oldfd, int newfd);
  • dup2函数的作用是将一个已存在的文件描述符 oldfd 复制到另一个文件描述符 newfd 上。如果 newfd 已经打开,则会先关闭它,然后将 oldfd 复制到 newfd 上。

  • dup2函数的返回值为新的文件描述符,如果复制成功,则返回值与 newfd 相同;如果复制失败,则返回-1,并设置相应的错误码。

在这里插入图片描述
在这里插入图片描述
这就实现了常规打印和错误打印分别打印到不同文件,这样可以方便我们排查错误

在这里插入图片描述
第一个是输出重定向(将log.text的地址放到1中),第二个是输出和错误重定向(&1是将1的地址取出来放到2中)
在这里插入图片描述
也可以这样将输出放到一个文件,错误放到另一个文件
我们一般不这样用,一般用函数dup2

dup2(fd,1);//这样就可以将1(标准输出流)的位置放入我们自己打开的文件的文件描述符

深刻理解缓冲区

在这里插入图片描述
在这里插入图片描述
结果很正常,我们加一句代码

在这里插入图片描述
在这里插入图片描述
这时就不对劲了,打印到文件中,fprintf多打印了一句

我们先引入一个概念

c语言库中也有缓冲区
在这里插入图片描述
刷新策略

  • 无缓冲,有内容就立马刷新
  • 行缓冲,遇到了换行字符就将换行字符之前的包括换行字符全部刷新
  • 全缓冲,缓冲区满了再刷新
  • 显示器的刷新策略:行缓冲
  • 普通文件的刷新策略:全缓冲
  • 为什么c库中也有缓冲区呢? - - 因为进程如果要自己将内容写入操作系统的缓冲区要调用系统接口,这样更耗时间,如将内容写入c库中的缓冲区,然后让c库来将内容写入操作系统的缓冲区可以节省时间
  • c库中的缓冲区在哪? - - 当我们进行fopen打开文件的时候,会得到FILE结构体,缓冲区就在FILE结构体中

现在我们来分析一下为什么有这样的现象:
write是系统调用,我们直接将内容写入操作系统的缓冲区里了

而fprintf是将内容写入c库中的缓冲区,由c库根据它的刷新策略选择打印进操作系统的缓冲区

我们如果是向显示器打印,刷新策略是行缓冲,我们打印的消息都是有换行字符的,所以向显示器打印的时候,在fork之前我们的缓冲区中的内容都已经刷新了,所以fork就没有什么作用

而我们重定向打印到文件中,write依旧是写给操作系统,fprintf写到c库的缓冲区中,刷新策略现在是全缓冲了,所以,我们写的内容完全不能将缓冲区写满,fork之前c库中的缓冲区还有内容,所以fork之后,父进程和子进程都会刷新缓冲区,然后父进程或者子进程先刷新,另一个发生写时拷贝,再刷新一次缓冲区,就会有两个fprintf打印了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值