深入理解linux中的文件(上)

1.前置知识:

(1)文章 = 内容 + 属性

(2)访问文件之前,都必须打开它(打开文件,等价于把文件加载到内存中)

如果不打开文件,文件就在磁盘中

(3)谁会去访问一个文件,进程。进程被加载启动之后,运行到fopen,才会打开一个文件

(4)手绘的进程和文件系统之间的交互图**( 必看!!!)**:

2.C语言fopen函数:

#include <stdio.h>

FILE *fopen(const char *path, const char *mode);
  • path: 指向你想要打开的文件路径的字符串。
  • mode: 字符串,指定文件的打开模式。

打开模式

mode 参数决定了文件是如何被打开的。常见的模式有:

  • "r": 只读方式打开文本文件。文件必须存在。
  • "w": 只写方式打开文本文件。如果文件存在则将其截断为零长度;如果文件不存在,则创建新文件。
  • "a": 追加方式打开文本文件。如果文件存在,则在文件末尾添加数据;如果文件不存在,则创建新文件。
  • "rb", "wb", "ab": 分别对应上面的二进制文件版本。
  • "r+", "w+", "a+": 对应的读写版本(既可读也可写)。
  • "rb+", "wb+", "ab+": 读写模式下的二进制文件版本。

3.系统级接口open:

open系统级接口,我们熟知的fopen是C语言的语言级接口,fopen底层封装的就是open

#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
  • pathname: 指向你想要打开或创建的文件路径的字符串。
  • flags: 这个参数可以包含多个标志的按位或组合,用于指定文件的打开方式(例如:只读、只写、读写等)。
  • mode: 当创建新文件时(通过使用了 O_CREAT 标志),这个参数指定了新文件的权限模式。

常见标志

  • O_RDONLY: 只读方式打开文件。
  • O_WRONLY: 只写方式打开文件。
  • O_RDWR: 读写方式打开文件。
  • O_CREAT: 如果指定的文件不存在,则创建之。
  • O_TRUNC: 如果文件存在并且以写方式或者读写方式打开,则将其长度截断为0。
  • O_APPEND: 每次写操作前都会将文件指针移动到文件末尾。

返回值

成功时,open 函数返回一个新的文件描述符 ;失败时返回 -1 并设置 errno 来指示错误类型。

4.文件描述符(open函数的返回值)

操作系统中,只认识 "文件描述符" :<0,代表打开文件失败

0 1 2 分别代表 键盘文件(标准输入)显示器文件(标准输出)显示器文件(标准错误流)

接下来打开的文件顺序是从3号开始

每一个进程,执行到open,创建struct file结构体,然后在file_struct数组中一个位置连接这个struct_file结构体,就会把数组的下标作为返回值fd1, 放回给进程。(fd的分配规则,最小的,没有被使用的fd!)

  1. 创建struct file结构体

  2. 数组链接结构体

  1. 把数组下标返回给进程
  1. C语言对操作系统中的文件操作进行了两个封装:

1.接口封装 fopen(C语言级接口) -> open(系统级接口)

2.类型封装 FILE (结构体,里面肯定包含文件描述符)-> int (文件描述符,下标)

  1. 为什么语言层面还要进行封装:

1.方便用户操作 (open需要使用各种标识O_RDONLY... 而fopen使用打开模式"r")

2.不用考虑平台的切换,提高语言的可移植性(linux windows的open不同,但fopen一样)

7.理解struct file结构体:

最重要的三个部分:1.inode结构体指针 2.文件操作的结构体指针 3.文件内核级缓冲区指针

(1)inode结构体

当文件从磁盘加载到内存的时候,这个inode表就要被创建。

在磁盘中:文件 = 属性 + 内容。 inode中的数据,就是拷贝磁盘中文件的属性。

当我讲那些没有被打开的文件时,我还会重谈inode表中的索引指针!

(2)文件操作表:

保存文件操作的函数指针。进程中的write(),会调用struct file -> f_op ->write ->写入内核级缓冲区

(由操作系统决定什么时候将内核级缓冲区中的数据写入到磁盘。系统级接口 fsync ( fd )可以刷新内核级缓冲区)

(3) 文件内核级缓冲区:

文件内核级缓冲区完全由操作系统管理,旨在提供高效、可靠的文件I/O操作,同时尽量减少用户空间应用对此过程的干预需求。这种设计使得大多数应用程序无需关心底层存储细节,即可获得良好的性能表现。

  1. 输入输出重定向:

close(1);

file1.txt;

printf("hello");

fflush(stdout); //一定要执行这个操作,才会把内容写入到file1.txt中,也不显示到显示器上。

//如果没有执行fflush,内容不在file1.txt中,也不在显示器上显示。

首先,我们先不管fflush,假设他会写入到file1.txt中,这是为什么呢?

因为,close(1)会把显示器文件关闭,然后打开file1.txt是返回最小的,没有被使用的fd,那就是1了。这样子printf只认识fd==1的,就会写入到file1.txt文件中。

然后为什么需要fflush(stdout);stdout其实就是fd = 1;fflush是刷新语言级别的缓冲区! (这里引入一个新概念,语言级别缓冲区)

输出重定向 int dup2(int oldfd,int newfd ); //但是这里有认知偏差,如果要把1覆盖, dup2(fd,1);

在数组中,把新的地址,浅拷贝到原先的地址,当上层使用文件描述符(下标)的时候,就会重定向到目标文件!

(dup2还会把多余的指向目标文件的指针进行清除,没人指向的那一个,一般会自动关闭!)

oldfd 和 newfd都是 文件描述符。
你也可以不使用fflush来刷新,而是使用fclose来刷新,因为fclose不但封装了close系统调用,而且还封装了fflush。

那么为什么close不能自动刷新呢?因为fflush是刷新语言级缓冲区,而close是系统级调用,语言级缓冲区还在系统调用之上,close根本就看不到语言级缓冲区。

9.语言级别缓冲区:

因为在写入或者读取的时候,不断访问内核级缓冲区(调用系统调用),会有明显的消耗。所以在语言层面,还有一个语言级别的缓冲区。当我们printf的时候,只是写入到语言级缓冲区,还需要使用fflush写入到内核级缓冲区中。

语言级别缓冲区的三种刷新方式:

  1. 显示器文件: 行刷新 ,遇到 \n 刷新
  2. 写入磁盘文件(普通文件):缓冲区写满再刷新
  3. 不缓冲:直接调用系统接口
相关推荐
稚辉君.MCA_P8_Java2 分钟前
SpringAI 人工智能
大数据·linux·人工智能·分布式·spring
时差freebright33 分钟前
【Linux系统】信号:信号保存 / 信号处理、内核态 / 用户态、操作系统运行原理(中断)
linux·运维·信号处理
利明的博客1 小时前
【流媒体】搭建流媒体服务器
运维·服务器
YYYYYY020201 小时前
ue5笔记(2)
笔记·ue5
成都被卷死的程序员1 小时前
从0开始,来看看怎么去linux排查Java程序故障
java·linux·运维·服务器
和小潘一起学AI1 小时前
机器学习--2.多元线性回归
笔记·算法·机器学习
打鱼又晒网2 小时前
Linux网络 | 网络层IP报文解析、认识网段划分与IP地址
linux·网络·tcp/ip
✿ ༺ ོIT技术༻2 小时前
Linux:宏观搭建网络体系
linux·服务器·网络
苏-言2 小时前
Linux环境下的Java项目部署技巧:Nginx 详解
linux·运维·服务器
Vincent_Zhang2332 小时前
Word List 2
笔记