【Linux】基础IO(一)——文件、上层C库文件操作、底层linux原生IO

【Linux】基础IO(一)------文件、上层C库文件操作、底层linux原生IO

文章目录

  • 【Linux】基础IO(一)------文件、上层C库文件操作、底层linux原生IO
    • [一、 什么是文件?](#一、 什么是文件?)
    • [二、 上层:C 标准库文件操作(stdio)](#二、 上层:C 标准库文件操作(stdio))
      • [1、 进程的 cwd :当前工作目录](#1、 进程的 cwd :当前工作目录)
      • [2、 fopen 六种打开模式](#2、 fopen 六种打开模式)
      • [3、 C 库写文件](#3、 C 库写文件)
      • [4、 C 库读文件](#4、 C 库读文件)
      • [5、 自定义cat 命令(读取文件并打印到屏幕)](#5、 自定义cat 命令(读取文件并打印到屏幕))
      • [6、 标准流:stdin stdout stderr](#6、 标准流:stdin stdout stderr)
    • [三、 底层:Linux 原生系统调用 IO](#三、 底层:Linux 原生系统调用 IO)
      • [1、 open函数](#1、 open函数)
      • [2、 系统调用 write 写文件](#2、 系统调用 write 写文件)
      • [3、 系统调用 read 读文件](#3、 系统调用 read 读文件)
      • [4、 文件描述符 fd](#4、 文件描述符 fd)
        • [(1)什么是 fd?](#(1)什么是 fd?)
        • (2)fd底层原理
        • [(3) FILE* 和 fd 的关系(串联 C 库与系统调用)](#(3) FILE* 和 fd 的关系(串联 C 库与系统调用))
        • [(4) 其他重要问题](#(4) 其他重要问题)

一、 什么是文件?

提到文件 ,我们脑海中第一反应可能是磁盘上的txt、cpp等这类文档,这是我们日常生活中对文件的理解:它们保存在磁盘这种外设硬件上,就算断电,数据也不会丢失我们对磁盘上文档做读写,本质上就是对外设做输入输出,也就是我们常说的 IO(input,output)

但是在Linux中,文件的概念就要宽泛多了,在Linux下一切皆文件

不止普通磁盘文档,键盘、显示器、网卡、管道这些硬件和内核对象,都会被系统统一抽象成 "文件" 。上层程序不需要区分自己操作的是硬盘、键盘还是网络,全部复用同一套 IO 系统接口完成读写,这也是 Linux 设计非常巧妙的地方

搞懂概念之后,我们再来思考一个问题:一个完整的文件,到底由什么东西构成?

文件 = 文件内容(业务数据) + 文件属性(元数据)

文件内容就是我们真正存进去的数据,而文件属性,包含文件大小、访问权限、创建时间、所属用户、inode 编号等等描述信息

也正是因为文件分为内容和属性两部分,所有针对文件的操作,归根到底就只有两大类

  1. 操作文件内容:也就是读写里面的数据
  2. 操作文件属性:比如修改权限、获取文件大小,并不改动文件里面的内容

一个小细节:哪怕是 0KB 的空文件,也会占用磁盘空间。 因为操作系统依然需要磁盘空间,用来存储这份文件属性

二、 上层:C 标准库文件操作(stdio)

了解完文件的基础概念之后,我们先从大家写 C 代码最熟悉的部分入手:C 标准库的文件操作

C 语言为我们提供了一套跨平台的标准 IO 库,头文件就是 stdio.h,它操作文件的核心句柄是 FILE * 文件指针

Windows 叫句柄,Linux 下叫文件描述符fd ,本质是同一个思想。句柄不是指针,不是真正的文件地址,是操作系统给应用程序发的一张编号凭证,程序拿这个数字告诉操作系统:帮我操作这个资源
Linux 中的fd含义,0:标准输入,1:标准输出,2:标准错误,这三个就是最常见的句柄(文件描述符)

不管我们要读还是写文件,有一条硬性规则:使用文件前必须先打开它,我们就得依靠路径 + 文件名找到目标文件

1、 进程的 cwd :当前工作目录

写代码的时候,我们经常见到 fopen("myfile", "w") 这种写法(myfile是要打开的文件名),这里没有写完整的绝对路径,不代表打开文件不需要路径

每一个正在运行的进程,内核内部都会维护一个叫做 cwd(current working directory,当前工作目录)的信息当我们给 open /fopen 传入相对路径文件名时,系统就会以这个 cwd 作为基准去寻找文件(所以写文件名,默认就在当前工作目录读写文件)

cwd知识补充

  1. cwd 和程序所在目录不是一回事

    程序二进制文件放在哪 ≠ cwd 就是哪

    你在 /home/aaa 目录下执行 ./bin/test,程序本体在bin,但进程的 cwd 是 /home/aaa

  2. 修改 cwd 的系统调用是 chdir()

    头文件:#include <unistd.h>

bash 复制代码
int chdir(const char *path);

直接修改当前进程内核里面的 cwd

这里串联上篇文章讲的自定义myshell 的知识:cd 之所以必须做成内建命令,不能 fork 子进程执行,因为子进程修改 cwd 只会影响自己,不会改动父 shell 进程的 cwd

  1. 如果传绝对路径,cwd 直接被忽略
    比如 fopen("/etc/passwd", "r"),直接从根目录开始找,完全不看 cwd 的值
  2. C 库函数 getcwd() 可以拿到进程此刻的 cwd 字符串
    头文件:#include <unistd.h>
bash 复制代码
char *getcwd(char *buf, size_t size);

2、 fopen 六种打开模式

fopen 是 C 标准库函数,作用:打开一个文件,得到文件指针FILE * ,后续读写文件都靠这个指针

头文件:#include <stdio.h>

bash 复制代码
FILE *fopen(const char *filename, const char *mode);
  1. filename :文件名,可以是相对路径或者绝对路径

    相对路径 :以进程的 cwd(当前工作目录)为基准查找文件

    绝对路径:直接从根目录开始找,不受 cwd 影响

  2. mode打开模式字符串,决定读、写、是否新建、是否清空文件

第二个参数模式

模式 含义
r 只读打开;文件必须存在,读写位置在文件开头
r+ 读写打开;文件必须存在,读写位置在文件开头
w 只写打开;不存在则创建,存在直接清空原有内容,读写位置在开头
w+ 读写打开;不存在则创建,存在直接清空原有内容
a 追加只写;不存在则创建,写入永远追加到文件末尾
a+ 追加读写;读取起始在文件开头,写入永远追加到末尾

重点提醒:w 和 w+ 只要打开,就会直接清空旧文件内容,千万不要误操作把重要数据清空

  1. 返回值
    成功 :返回 FILE* 文件指针(这是 C 库封装出来的结构体,不是文件描述符 fd)
    失败:返回 NULL,必须做判空!常见失败原因:文件不存在、权限不足

fopen通常要配套fclose,fclose能:

  1. 刷新用户层缓冲区(fflush)
    fwrite的数据存在 C 库的内存缓冲区,还没调用write系统调用,没真正进内核
    如果不 fclose,程序异常退出,缓冲区残留数据直接丢失,文件内容残缺
  2. 释放 FILE 结构体内存
    fopen 会在堆上分配FILE*结构体,不 fclose 就内存泄漏
  3. 归还底层的文件描述符 fd
    FILE内部封装了 fd,不 close,fd 不会释放,文件描述符泄漏
    进程打开文件数量是有上限,反复 fopen 不 fclose,后面再打开文件会失败

虽然正常结束return 0,操作系统也会回收该进程全部资源,fd就被回收,缓冲区也会刷新。但这个是操作系统收拾的烂摊子,不是代码规范。不能依靠程序退出自动回收,写代码一定要手动fclose

3、 C 库写文件

fwrite

头文件:#include <stdio.h>

bash 复制代码
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
  • 功能把内存里的数据,整块写入到文件中,属于二进制块写入函数

  • ptr:数据源,要写出去的数据的起始地址

  • size:单个数据块的字节大小

  • nmemb:要写多少个这样的数据块

  • stream:FILE* 文件指针,写到哪个文件流

  • 返回值

    返回成功写完的块的个数(nmemb 的个数),不是字节数!

fwrite 是 C 库函数,数据先写到C 库用户缓冲区,不会立刻落到磁盘

等到缓冲区满 / fclose(fp) / fflush(fp),才会底层调用write()系统调用交给内核

接下来写一段实际代码,使用 fopen 打开文件,往里面循环写入字符串

cpp 复制代码
#include<stdio.h>
#include<string.h>

int main()
{
  FILE *fp=fopen("myfile","w");
  if(!fp)
  {
    printf("fopen error!\n");
    return 1;
  }

  const char *msg="hello world\n";
  int count = 5;
  while(count--)
  {
    fwrite(msg,strlen(msg),1,fp);
  }
  fclose(fp);
  return 0;
}

4、 C 库读文件

模式"r"只读,文件必须存在,否则 fopen 直接返回 NULL

fread
头文件:#include <stdio.h>

bash 复制代码
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
  • 作用:从文件里读取一块二进制数据,放到内存缓冲区,和 fwrite 是一对

  • ptr:接收数据的缓冲区,读到的数据存到这里

  • size:单个元素的字节大小

  • nmemb:想要读取多少个元素

  • stream:文件指针 FILE*

  • 返回值

    返回实际读到的元素个数(不是字节数!)

    正常读到数据:返回读到的元素数量,小于等于 nmemb

    读到文件末尾 / 发生错误:返回 0

feof

bash 复制代码
int feof(FILE *stream);

作用:判断上一次文件读取操作,是不是已经读到文件末尾了

返回值

  • 非 0:已经到达文件末尾
  • 0:还没到文件末尾

写完之后,我们再以读模式打开文件,把文件内容读取到内存缓冲区打印出来

c 复制代码
#include<stdio.h>
#include<string.h>

int main()
{
  //只读模式打开myfile文件
  FILE* fp=fopen("myfile","r");
  if(!fp)
  {
    printf("fopen error\n");
    return 1;
  }

  //读取缓冲区,每次最多读1024字节
  char buf[1024]; 
  while(1)
  {
    //一共读取sizeof(buf)个元素
    //s:实际读到的字节数量
    size_t s = fread(buf,1,sizeof(buf),fp);
    if(s>0)
    {
      //fread读来的是二进制字节,不会自动补字符串结束符'\0'
      //手动在有效数据末尾加上 \0,才能用printf打印
      buf[s]='\0';
      printf("%s",buf);
    }

    //检测是否到达文件末尾
    if(feof(fp))
    {
      break;
    }
  }
  fclose(fp);
  return 0;
}

5、 自定义cat 命令(读取文件并打印到屏幕)

Linux中有一个cat命令,可以读取指定文件,把文件内容打印到标准输出屏幕

我们可以利用 C 库 IO,自己实现一个极简版本的 cat:

cpp 复制代码
#include<stdio.h>
#include<string.h>

int main(int argc,char *argv[])
{
  //判断命令行参数,程序执行格式:./a.out 文件名
  if(argc!=2)
  {
    printf("argv error!\n");
    return 1;
  }

  // 只读模式打开传入的文件名字 argv[1]
	FILE* fp=fopen(argv[1],"r");
	if(!fp)
	{	
		printf("fopen error!\n");
		return 2;
	}

  char buf[1024];
  while(1)
  {
        // 每次最多读1024字节
        size_t s = fread(buf, 1, sizeof(buf), fp);
        if (s > 0)
        {
            buf[s] = '\0'; // 手动补字符串结束符
            printf("%s", buf);
        }
        // 上一次IO碰到文件末尾,退出死循环
        if (feof(fp))
        {
            break;
        }
  }

	fclose(fp);
	return 0;
}

6、 标准流:stdin stdout stderr

我们写printf打印到显示器,并没有调用 fopen 打开显示器,为什么可以直接输出?

当一个 C 程序启动的时候,C 标准库会默认帮我们打开三个FILE*类型的文件流,不需要程序员手动 fopen

cpp 复制代码
extern FILE *stdin;   // 标准输入,对应键盘
extern FILE *stdout;  // 标准输出,对应显示器
extern FILE *stderr;  // 标准错误,对应显示器

也就是说,显示器、键盘,在 C 库层面同样被当成文件。我们完全可以把之前的文件输出函数,直接作用到 stdout 上,实现打印效果

fprintf

头文件:#include <stdio.h>

bash 复制代码
int fprintf(FILE *stream, const char *format, ...);

作用格式化输出,把拼接好的字符串,写到你指定的文件流 stream

printf = fprintf 的特殊版本,固定输出到 stdout(屏幕)

cpp 复制代码
#include<stdio.h>
#include<string.h>

int main()
{
  const char *msg="hello fwrite\n";
  //使用fwrite往标准输出流stdout写数据
  fwrite(msg,strlen(msg),1,stdout);
  printf("hello printf\n");
  //fprintf指定输出流为stdout,打印字符串
  fprintf(stdout,"hello fprintf\n");
  return 0;
}

到这里,我们熟悉的 C 标准库 IO 就看完了

前面我们学习了 C 标准库的fopen/fprintf/fclose,这些都是用户态库函数

真正和操作系统内核打交道的,是 Linux 提供的系统调用:open / write / read / close

三、 底层:Linux 原生系统调用 IO

C 语言标准库文件函数,它只是用户态的封装层,真正向 Linux 操作系统发起文件操作,依靠的是一组系统调用

C 库函数(上层) Linux 底层系统调用 作用
fopen() open() 打开文件,获取操作凭证
fread() read() 读取文件数据
fwrite() / fprintf() write() 写入文件数据
fclose() close() 关闭文件,释放资源

C语言标准库文件函数: 内部封装了系统调用返回的文件描述符 fd,还自带用户态缓冲区

系统调用 :操作系统内核提供接口,没有用户缓冲区,直接和内核打交道

操作流程:我们调用fwrite数据先放到 C 库缓冲区,满足条件后,内部自动调用write系统调用交给内核

1、 open函数

想要操作文件,第一步依旧是打开文件。Linux 系统调用open,用来向内核申请打开一个文件

头文件必须包含:

cpp 复制代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

open 有两种函数原型:

cpp 复制代码
// 不需要创建文件,两个参数版本
int open(const char *pathname, int flags);

// 需要创建文件时,第三个mode参数指定新建文件的权限
int open(const char *pathname, int flags, mode_t mode);

返回值

  • open函数的返回值我们称为fd,fd是文件描述符,一个普通 int 整数
  • 作用相当于操作系统给你的文件编号。后面 read、write、close,就传这个编号,告诉内核操作哪个文件
  • open成功:返回一个非负整数(>=0)
  • open失败:返回 -1

第三个参数mode,代表期望新建文件的权限仅在O_CREAT标志存在时生效必须使用八进制书写

受进程 umask 掩码影响,磁盘上真实权限不等于 mode

当文件不存在 ,需要新建文件时,mode 指定这个新文件的初始权限

文件已经存在的情况下,mode 完全不起作用,不会修改旧文件权限

【linux】Shell 原理与用户权限解析-CSDN博客具体可以看这篇文章,详细讲了权限问题

第二个参数flags 常用宏

flags 是整数,采用位图标记设计,多个功能通过 |(按位或)叠加
必选三选一(只能选一个)

  • O_RDONLY:只读打开
  • O_WRONLY:只写打开
  • O_RDWR:读写打开

可选附加标志,可以多个叠加

  • O_CREAT:文件不存在,则创建该文件;文件已存在,无效果
  • O_TRUNC:打开文件的时候,把原有文件内容全部清空
  • O_APPEND:追加模式,所有写入都往文件末尾添加

可能会有人疑惑:多个标志为什么用|,不能用逗号隔开?

这里就要理解位图标记的设计思想:每一个宏,占用二进制里面独立的一个 bit 位

cpp 复制代码
#include <stdio.h>
#define ONE 0001
#define TWO 0002
#define THREE 0004

void func(int flags)
{
    if (flags & ONE) printf("flags has ONE! ");
    if (flags & TWO) printf("flags has TWO! ");
    if (flags & THREE) printf("flags has THREE! ");
    printf("\n");
}

int main()
{
    func(ONE);
    func(ONE | TWO);
    func(ONE | TWO | THREE);
    return 0;
}

原理

  1. 每一个功能宏,独占二进制里面独立的一个 bit,互相不干扰
  2. 按位或 |:用来组合多个开关标志
  3. 按位与 &用来校验某个标志是否被开启
    open的O_XXX系列宏,和示例ONE/TWO/THREE设计思想完全一致。内核内部就是通过&运算,判断传入的 flags 开启了哪些功能

举个对应关系C 库fopen("w"),底层等价调用
open("myfile", O_WRONLY | O_CREAT | O_TRUNC, 0666);

w模式的创建、清空行为,就是O_CREAT与O_TRUNC提供的

2、 系统调用 write 写文件

write是系统调用,没有用户态缓冲区,调用就直接向内核提交 IO 请求

bash 复制代码
ssize_t write(int fd, const void *buf, size_t count);
  1. fd:文件描述符,open 返回的整数凭证
  2. buf:要写入的数据缓冲区
  3. count:希望写入多少字节
  4. 返回值:成功返回实际写入字节数,出错返回-1
cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    umask(0);  // 修改文件掩码,方便观察新建文件权限
    int fd = open("myfile", O_WRONLY | O_CREAT | O_TRUNC, 0664);
    if (fd < 0)
    {
        perror("open");
        return 1;
    }

    const char *msg = "hello world!\n";
    int count = 5;
    int len = strlen(msg);
    while (count--)
    {
        write(fd, msg, len);
    }

    close(fd);
    return 0;
}

看ls -l输出,可以看到文件权限是 -rw-rw-r--,正好对应0664

write 和 C库 fwrite 对比

项目 库函数 fwrite 系统调用 write
缓冲区 有 C 库用户缓冲区 无用户缓冲区,直接交给内核
格式化 配合 fprintf 可以格式化字符串 只能搬运原始字节,不会格式化
头文件 stdio.h unistd.h
凭证 FILE* 文件流指针 int 类型文件描述符 fd

3、 系统调用 read 读文件

bash 复制代码
ssize_t read(int fd, void *buf, size_t count);
  1. fd:文件描述符
  2. buf:接收读取数据的缓冲区
  3. count:期望读取的字节数
  4. 返回值:
    返回正数(>0):返回值 = 本次实际读到的字节数
    0:已经读到文件末尾 EOF,没有更多数据可读
    -1:发生错误

同样,read读到的是原始二进制,不会自动补\0,当做字符串打印,需要手动添加结束标记

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    int fd = open("myfile", O_RDONLY);
    if (fd < 0)
    {
        perror("open");
        return 1;
    }

    char buf[1024];
    ssize_t s;
    // sizeof(buf)-1,预留一个位置手动放'\0'
    while ((s = read(fd, buf, sizeof(buf)-1)) > 0)
    {
        buf[s] = '\0';
        printf("%s", buf);
    }

    close(fd);
    return 0;
}

4、 文件描述符 fd

(1)什么是 fd?

调用open打开文件成功之后,返回一个小小的整数,这个整数就是文件描述符 fd (file descriptor)

Windows 叫做句柄 Handle;Linux 叫做文件描述符 fd,二者是等价,是内核给到用户程序访问资源的凭证

进程刚启动的时候,内核会默认预先打开 3 个 fd

  • fd = 0:标准输入 stdin,对应键盘
  • fd = 1:标准输出 stdout,对应显示器
  • fd = 2:标准错误 stderr,对应显示器

也就是说,我们不需要 open,就可以直接使用 0、1、2 这三个整数,直接用系统调用读写控制台

cpp 复制代码
#include<unistd.h>
#include<string.h>

int main()
{
  const char* str="直接使用fd=1输出到屏幕\n";
  write(1,str,strlen(str));
  return 0;
}
(2)fd底层原理

每一个进程在内核里,都有自己的进程 PCB------task_struct

task_struct里面有成员 * files,指向一张文件描述符表files_struct,这张表本质就是数组file * fd_array\[\]

而fd 文件描述符,就是fd_array\[\]这个数组的下标

具体流程:

  1. 我们在代码调用open(),要打开磁盘上的文件

    内核会

    (1)在磁盘找到对应文件,在内核创建一个file内核对象,里面保存读写偏移、状态标记,关联磁盘的 inode 元数据

    (2) 在当前进程files_struct的fd_array\[\]数组里,找一个空闲下标(比如 3),把刚才新建内核file对象的指针填进fd_array3

    (3)将数组下标3返回给我们的用户程序,这个数字就是 fd

    进程刚启动的时候,数组下标 0、1、2 就已经填好了:fd=0标准输入、fd=1标准输出、fd=2标准错误

  2. 接下来做读写调用系统调用read(fd, ...)、write(fd, ...),只把这个下标数字传给内核

  3. 内核拿到 fd 下标,去当前进程专属的fd_array\[\]数组(文件描述符表),按下标取出内核file*指针,找到内核真实的文件对象,执行真正读写

注意:我们用户层拿到的仅仅只是一个整数下标 fd,fd 本身不是文件,不存文件内容;真正文件状态、读写位置全部保存在内核的file对象里面

打开文件使用完毕,一定要执行close(fd)

close的作用把这个下标归还内核,释放内核里面对应的文件对象资源

如果忘记close(fd),就会产生文件描述符泄漏,每个进程fd_array\[\]数组大小有限,允许打开 fd 有上限。不停open不关闭,空闲下标会被全部占光,之后再调用open直接失败

(3) FILE* 和 fd 的关系(串联 C 库与系统调用)

上面说的是内核、系统调用层面的知识。用户直接操作整数 fd 写代码不够方便,于是 C 标准库在用户空间做了一层封装,也就是FILE*结构体结构体内部就保存这个 fd 整数,还额外带上 C 库的用户缓冲区

我们现在有两套文件操作方式

  1. 系统调用open/read/write/close靠fd 整数操作文件,内核提供接口
  2. C 标准库fopen/fread/fwrite/fclose,靠FILE*指针操作文件,语言库提供接口

调用层级

  • fopen()底层会调用系统调用open()拿到 fd把 fd 封装进 FILE 结构体,返回 FILE * 指针给用户
  • fread / fwrite并不会立刻去调用系统调用,先读写 C 库的用户缓冲区等到缓冲区满 / 满足刷新条件,才在底层调用read/write系统调用,真正和内核打交道
  • fclose():首先把缓冲区残留的数据刷新出去,再在内部调用close(fd)归还文件描述符

注意 :C 库 IO 是对系统调用 IO 的上层封装,多了一层用户缓冲区。read/write系统调用本身,没有用户缓冲区

用户缓冲区 = C 库在你的应用程序内存里,开辟的一块普通数组内存,属于用户空间,不是内核内存

如果要往文件写很多小段文字

  • 没有缓冲区(直接 write 系统调用):写一小段,就要跑一趟操作系统,频繁来回,开销大、慢
  • 有用户缓冲区(FILE * )
  1. 你调用fprintf,数据先写到程序自己内存的缓冲区数组里,不碰操作系统
  2. 不断攒数据,等缓冲区装满 / 遇到换行 /fclose/fflush,才一次性把一大块数据调用write交给内核写入文件
    目的:减少系统调用的次数,提高 IO 效率。系统调用是有开销的,能少跑就少跑

这里只是简单讲一下,后边的文章会着重展开说

(4) 其他重要问题

C++ 为什么还有 ifstream/ofstream,C++ 不是兼容 C 吗?

C++ 完全可以直接使用 C 的 fopen 系列函数。新增文件流是面向对象封装

  1. RAII:构造打开文件,析构自动关闭,避免忘记 close 资源泄漏
  2. 运算符重载 >> <<,自动识别数据类型,不用手动格式化字符串
  3. 底层依旧封装系统调用,只是上层面向对象封装

既然有 open/write 系统调用,为什么还要 C 标准库 IO?

  1. 跨平台兼容:open 是 Linux 专属系统调用,Windows 没有这套接口(Windows 使用 CreateProcess、CreateFile)。C 标准库 IO 由标准规定,在不同操作系统下,库内部适配对应系统调用,一套代码可以在 Linux、Windows 编译运行
  2. 用户缓冲区优化:C stdio 库在用户层维护缓冲区,多次 fprintf 数据会先写到缓冲区,攒够数据再一次性调用 write再写进内核,减少用户态和内核态切换,提升 IO 效率
  3. 封装简化:write 只能写入原始字节,printf 封装数字、字符串格式化逻辑,开发更简单

打开文件到底做了什么?

磁盘速度很慢,打开文件时,操作系统会把磁盘上文件的内容、属性加载到内核内存,生成 struct file 对象;后续进程读写,本质是 CPU 访问内存中的文件数据,减少磁盘交互

相关推荐
小五传输1 小时前
聚焦卫健数字化建设 文件传输服务器守护跨机构敏感医疗数据交换
大数据·运维·安全
机器人梦想家1 小时前
Debian 12/13 关闭开机图形界面(仅保留命令行)
运维·debian
Fcy6481 小时前
Linux下 应⽤层⾃定义协议与序列化(JSON方法)
linux·json
遇见小修修1 小时前
佛山工厂打印机频繁漏粉、页面脏污、机身飞粉?零基础排查+长效解决教程
运维·打印机维修·打印机故障·专业维修
koi77u1 小时前
嵌入式学习————————TCP并发服务器(1)
linux·学习
上学的小垃圾9 小时前
基于docker安装MySQL(openEuler系统)
linux·mysql·docker·容器
736c10 小时前
C语言-结构体 08
c语言
736c10 小时前
C语言-数组 07
c语言·开发语言
一直C11 小时前
Linux网络编程|HTTP协议详解(报文、请求方法、状态码)
linux·http·wireshark·vim·visual studio