Linux:内存文件 基础io
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、理解 “文件”
- 1.1 狭义理解
- 1.2 ⼴义理解
- 1.3 ⽂件操作的归类认知
- 1.4 系统⻆度
- 二、文件函数接口
- 2.1 使用库函数打开⽂件
- 2.2 c语言文件打开函数的使用
- 2.3 stdin & stdout & stderr
- 2.4 文件的系统调用
- 2.5 ⽂件描述符fd(重要)
- 三、重定向
- 四、缓冲区
- 4.1 缓冲区概念
- 4.1.1 语言层缓冲区
- 4.1.2 系统层缓冲区
- 4.2 语言层缓冲区类型
前言
文件是操作系统中用于存储数据和信息的基本单位。它可以是任何形式的数据集合,包括但不限于文本、图像、音频、视频、程序代码等。在操作系统中,文件通常被组织在目录(或文件夹)结构中,以便于用户和管理员进行查找、访问和管理。
一、理解 “文件”
1.1 狭义理解
- ⽂件在磁盘⾥
- 磁盘是永久性存储介质,因此⽂件在磁盘上的存储是永久性的
- 磁盘是外设(即是输出设备也是输⼊设备)
- 磁盘上的⽂件本质是对⽂件的所有操作,都是对外设的输⼊和输出 简称 IO
1.2 ⼴义理解
- Linux 下⼀切皆⽂件(键盘、显⽰器、⽹卡、磁盘……这些都是抽象化的过程)
1.3 ⽂件操作的归类认知
⽂件操作的归类认知
- 对于 0KB 的空⽂件是占⽤磁盘空间的
- ⽂件是⽂件属性(元数据)和⽂件内容的集合(⽂件 = 属性(元数据)+ 内容)
- 所有的⽂件操作本质是⽂件内容操作和⽂件属性操作
1.4 系统⻆度
文件分为未打开的文件和已经打开的文件,未打开的文件存在于磁盘上,已经打开的文件是由进程(及操作系统内核)在内存上打开的,
- 对⽂件的操作本质是进程对⽂件的操作
- 磁盘的管理者是操作系统
- ⽂件的读写本质不是通过C语⾔/C++的库函数来操作的(这些库函数只是为⽤⼾提供⽅便),⽽
是通过⽂件相关的系统调⽤接⼝来实现的
二、文件函数接口
2.1 使用库函数打开⽂件
#include <stdio.h>
int main()
{
FILE *fp = fopen("myfile", "w");
if(!fp){
printf("fopen error!\n");
}
while(1);
fclose(fp);
return 0;
}
打开⽂件,本质是进程打开,所以,进程知道⾃⼰在哪⾥,即便⽂件不带路径,进程也知道。由此OS就能知道要创建的⽂件放在哪⾥
2.2 c语言文件打开函数的使用
以下主要介绍c语言里的文件打开函数
FILE *fopen(const char *filename, const char *mode);
filename:要打开的文件的名称(包括绝对路径和相对路径,相对路径是根据进程的cwd来进行拼接的)。
mode:文件的打开模式,它是一个字符串,指定了文件的访问类型和用途。
打开模式字符串可以包含以下字符的组合:
- r:以只读方式打开文件。如果文件不存在,则fopen调用失败。
- w:以写入方式打开文件。如果文件不存在,则创建该文件;如果文件已存在,则截断(清空)该文件。
- a:以追加方式打开文件。如果文件不存在,则创建该文件;如果文件已存在,则写入的数据将添加到文件末尾,而不是覆盖原有内容。
- b:用于在Windows平台上以二进制模式打开文件(在Unix和Linux系统上,b标志通常被忽略,因为默认情况下文件是以二进制模式处理的)。
- +:与r、w或a结合使用,以更新模式打开文件(即,允许读写)。
还有 fseek ftell rewind 等文件关闭,读取,写入相关函数,这里不做过多赘述
2.3 stdin & stdout & stderr
• C默认会打开三个输⼊输出流,分别是stdin, stdout , stderr,默认打开这三个文件并不是C语言的特性,是操作系统进程的特性!!
• 仔细观察发现,这三个流的类型都是FILE*, fopen返回值类型,⽂件指针
#include <stdio.h>
extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;
文件其实是在磁盘上的,磁盘是外部设备,访问磁盘文件其实就是访问硬件!因此所有的库只要想访问硬件设备,必须封装系统调用
2.4 文件的系统调用
Linux下的文件打开函数是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);
pathname: 要打开或创建的⽬标⽂件(绝对或相对路径)
flags: 打开⽂件时,可以传⼊多个参数选项,⽤下⾯的⼀个或者多个常量进⾏“或”运算,构成flags。
参数:
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定⼀个且只能指定⼀个
O_CREAT : 若⽂件不存在,则创建它。需要使⽤mode选项,来指明新⽂件的访问权限
O_EXCL:与 O_CREAT 一同使用,如果文件已存在,则调用失败
O_APPEND: 追加写
O_TRUNC:如果文件已存在且为只写或读写方式打开,则将其长度截断为 0。
mode:权限位,受系统的umask影响
返回值:
成功:新打开的⽂件描述符。例如,标准输出(stdout)的文件描述符是1,标准错误(stderr)的文件描述符是2。
失败:-1
2.5 ⽂件描述符fd(重要)
操作系统在访问文件的时候只认文件描述符。文件描述符是一个非负整数,用于标识一个打开的文件或设备
在C标准库中的FILE其实是一个结构体,这个结构体里面一定会有个成员变量为文件描述符,因此stdin里面会有一个名为fileno的变量,值为0
文件描述符的分配规则:
在files_struct数组当中,找到当前没有被使⽤的最⼩的⼀个下标,作为新的⽂件描述符。
由open系统调用的返回值我们可以看出是个整型值,这个整型值被称为文件描述符,本质是一个数组的下标,它意味着当我们想要操作这个文件的时候就可以在系统调用的文件操作函数上使用对应的文件描述符去操作。
当多个进程或用户尝试打开同一个文件时,操作系统会为每个打开操作分配一个独立的文件描述符。这些文件描述符在各自的进程空间中是唯一的,但它们都指向同一个物理文件。这意味着,不同的进程或用户可以通过各自的文件描述符对同一个文件进行读写操作。
多个目标(结构体,进程等)可以指向同一个文件,类似stdout和stderr都指向屏幕显示文件,这个文件用引用计数的方式来记录谁指向的它,关闭stdout的时候我们仍然可以用stderr来向屏幕显示
三、重定向
重定向操作实际上是通过修改进程的文件描述符指针表的指向来实现的。当执行重定向命令时,操作系统会将指定的文件描述符重新指向一个新的文件或设备。
在Linux系统中,dup2 是一个系统调用,用于复制一个现有的文件描述符到指定的新文件描述符位置。如果新的文件描述符已经打开,则 dup2 会先关闭它,然后再进行复制。这个功能在需要重定向标准输入、标准输出或标准错误输出时特别有用。
dup2传入两个文件描述符,然后拷贝两个文件描述符对应的文件指针表中的指针内容
stdout和stderr本质上都是显示器文件,只不过为了更方便显示信息,所以定义两种不同的方式
四、缓冲区
4.1 缓冲区概念
4.1.1 语言层缓冲区
定义与位置:
语言层缓冲区是指在用户空间(即非内核空间)中分配和管理的内存区域,通常由高级编程语言(如C语言的FILE结构体类型,内部就有对应打开文件的缓冲区字段和维护信息)的标准库提供。
在C语言中,当使用标准I/O库(如stdio.h中定义的函数)进行文件操作时,标准I/O库会自动为每个打开的文件流(FILE*)分配一个用户缓冲区,这个缓冲区就是语言层缓冲区。`
用途:
语言层缓冲区用于暂存读取或写入的数据,以减少对系统调用的依赖,从而提高I/O效率。
例如,当使用printf函数输出数据时,数据首先被写入标准输出缓冲区(即语言层缓冲区),然后当缓冲区满或遇到换行符时,数据才会实际输出到屏幕或文件。
4.1.2 系统层缓冲区
定义与位置:
系统层缓冲区是指由操作系统内核在系统空间中分配和管理的内存区域。
当用户程序通过系统调用(如read、write)进行文件I/O操作时,操作系统会在内核中为这些操作分配缓冲区,这就是系统层缓冲区。
用途:
系统层缓冲区用于暂存从磁盘读取的数据或准备写入磁盘的数据,以减少对物理设备的直接访问,提高I/O性能。
在实际的文件I/O操作中,数据通常首先被写入语言层缓冲区,然后由C标准I/O库将数据从语言层缓冲区转移到系统层缓冲区,最终由操作系统内核将数据写入磁盘。
4.2 语言层缓冲区类型
标准I/O提供了3种类型的缓冲区。
- 全缓冲区:这种缓冲⽅式要求填满整个缓冲区后才进⾏I/O系统调⽤操作。对于磁盘⽂件的操作通常使⽤全缓冲的⽅式访问。
- 行缓冲区:在⾏缓冲情况下,当在输⼊和输出中遇到换⾏符时,标准I/O库函数将会执⾏系统调⽤操作。当所操作的流涉及⼀个终端(例如标准输⼊和标准输出),使⽤⾏缓冲⽅式。因为标准I/O库每⾏的缓冲区⻓度是固定的,所以只要填满了缓冲区,即使还没有遇到换⾏符,也会执⾏I/O系统调⽤操作,默认⾏缓冲区的⼤⼩为1024。
- ⽆缓冲区:⽆缓冲区是指标准I/O库不对字符进⾏缓存,直接调⽤系统调⽤。标准出错流stderr通常是不带缓冲区的,这使得出错信息能够尽快地显⽰出来。
除了上述列举的默认刷新⽅式,下列特殊情况也会引发缓冲区的刷新:
1 缓冲区满时;
2 执⾏flush语句;
3 进程退出的时候也会刷新;