深入理解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. 不缓冲:直接调用系统接口
相关推荐
用户9718356334662 小时前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪3 小时前
linux 拷贝文件或目录到指定的位置
linux
大树8819 小时前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠19 小时前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush419 小时前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行52020 小时前
Linux 11 动态监控指令top
linux
小宇宙Zz20 小时前
Maven依赖冲突
java·服务器·maven
不会C语言的男孩21 小时前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言
闪闪发亮的小星星21 小时前
高斯光以及高斯光公式解释
笔记
古城小栈21 小时前
Unix 与 Linux 异同小叙
linux·服务器·unix