82|0

517

帖子

0

TA的资源

纯净的硅(初级)

楼主
 

《Linux内核深度解析》--文件系统 [复制链接]

本帖最后由 dirty 于 2025-1-24 10:18 编辑

      本篇阅读学习文件系统及使用。

概述

      在Linux 系统中,一切皆文件,除了通常所说的狭义的文件(文本文件和二进制文件)以外,目录、设备、套接字和管道等都是文件。
      文件系统在不同的上下文中有不同的含义。

①在存储设备上组织文件的方法,包括数据结构和访问方法。

②按照某种文件系统类型格式化的一块存储介质。我们常说在某个目录下挂载或卸载文件系统,这里的文件系统就是这种意思。
③内核中负责管理和存储文件的模块,即文件系统模块。

      Linux文件系统架构如下:

用户空间层面

      应用程序可以直接使用内核提供的系统调用访问文件;
①一个存储设备上的文件系统,只有挂载到内存中目录树的某个目录下,进程才能诸问这个文件系统。

②系统调用umount用来卸载某个目录下挂载的文件系统。

③使用 open 打开文件。
④使用close 关闭文件。
⑤使用read 读文件。
⑥使用 write 写文件。
⑦使用lseek 设置文件偏移。

⑧当我们写文件的时候,内核的文件系统模块把数据保存在页缓存中,不会立即写到存储设备。

      应用程序也可以使用 glibc 厍封装的标准 O 流函数访问文件,glibc 库封装的标准 O 流函数如下所示:

①使用 fopen 打开流。
②使用 fclose 关闭流
③使用 fead 读流。
④使用 fwrite 写流。

⑤使用 fseek 设置文件偏移。
⑥使用fwrite可以把数据写到用户空间缓冲区,但不会立即写到内核。我们可以使用fflush 冲刷流,即把写到用户空间缓冲区的数据立即写到内核。

硬件层面
      外部存储设备分为块设备、闪存和NVDIMM 设备3类.

      块设备主要有以下两种:机械硬盘、闪存类块设备。如SSD、eMMC、UFS。

      闪存(Flash Memory)按存储结构分为NAND闪存和NOR闪存。

      NVDIMM (Non-Volatile DIMM,非易失性内存; DIMM 是 Dual-Inline-Memory-Modules的缩写,表示双列直插式存储模块,是内存的一种规格)断电后数据不丢失。在断电的瞬间,超级电容提供电力,把内存中的数据转移到NAND闪存。

内核空间层面

      在内核的目录下可以看到,内核支持多种文件系统类型。为了对用户程序提供统一的文件操作接口。

      为了使不同的文件系统实现能够其存,内核实现了一个抽象层,称为虚拟文件系统(Vitual File System,VFS),也称为虚拟文件系统功换(virnal Filesystem Switch,VFS)。

      文件系统分为以下4种。
①块设备文件系统,存储设备是机械硬盘和固态硬盘等块设备,常用的块设备文件系统是EXT和 btfs(读作|bAtS|)。EXT文件系统是Limux原创的文件系统,目前有3个版本:EXT2、EXT3 和 EXT4。

②闪存文件系统,存储设备是NAND闪存和NOR闪存,常用的闪存文件系统是JFFS2(日志型闪存文件系统版本2,Journalling Flash File System version2)和 UBIFS(无序区块镜像文件系统,Unsorted Block Image File System)。
③内存文件系统,文件在内存中,断电以后文件丢失,常用的内存文件系统是tmpfs,用来创建临时文件。

④伪文件系统,是假的文件系统,只是为了使用虚拟文件系统的编程接口。常用伪文件系统sockfs、proc 文件系统、sysf、hugetlbfs、cgroup、cgroup2

虚拟文件系统的数据结构
      虽然不同文件系统类型的物理结构不同,但是虚拟文件系统定义了一套统一的数据结构。

①超级块。

②虚拟文件系统在内存中把目录组织为一棵树。

③每种文件系统的超级块的格式不同,需要向虚拟文件系统注册文件系统类型fle_system_type,并且实现 mount 方法用来读取和解析超级块。

④索引节点。

⑤目录项。

⑥当进程打开一个文件的时候,虚拟文件系统就会创建文件的一个打开实例:fle结构体,然后在进程的打开文件表中分配一个索引,这个索引称为文件描述符,最后把文件描述符和 fle 结构体的映射添加到打开文件表中。

注册文件系统类型

      函数register flesystem用来注册文件系统类型:

int register filesystem(struct file_system type *fs);

      函数unregister flesystem 用来注销文件系统类型:

int unregister filesystem(struct file_system_type *fs);

      管理员可以执行命令“cat/proc/flesystems”来查看已经注册的文件系统类型。

挂载文件系统

      虚拟文件系统在内存中把目录组织为一棵树。一个文件系统,只有挂载到内存中目树的一个目录下,进程才能访问这个文件系统。

      glibc库封装了挂载文件系统的函数mount:

int mount(const char*dev_name, const char *dir_name,const char *type,unsigned long flags,const void *data);

      glibc库封装了两个卸载文件系统的函数:
①函数umount,对应内核的系统调用oldumount。

int umount(const char *target);

②函数umount2,对应内核的系统调用umount。

int umount2(const char*target,int flags);

打开文件
      进程读写文件之前需要打开文件,得到文件描述符,然后通过文件描述符读写文件。

      内核提供了两个打开文件的系统调用。
①int open(const char *pathname, int flags, mode t mode);

②int openat(int dirfd, const char *pathname, int flags, mode t mode),

      如果打开文件成功,那么返回文件描述符,值大于或等于0;如果打开文件失败,返回负的错误号。

创建文件

      创建不同类型的文件,需要使用不同的命令。

①普通文件:touchFE,这条命今本来用来更新文件的访问时间和修改时间,如果文件不存在,创建文件。
②目录:mkdir DIRECTORY
③符号链接(也称为软链接):In-STARGETLINKNAME或In--symbolC TARGETLINK NAME
④字符或块设备文件:mknod NAME TYPE [MAJOR MINOR]
参数 TYPE:b表示带缓冲区的块设备文件,c表示带缓冲区的字符设备文件,u表示不带缓冲区的字符设备文件,p表示命名管道。
⑤命名管道:mkpipe NAME
⑥命令“In TARGETLINK NAME”用来创建硬链接,给已经存在的文件增加新的名文件的索引节点有一个硬链接计数,如果文件有几个名称,那么硬链接计数是n。

      内核提供了下面这些创建文件的系统调用。
①创建普通文件。

int creat(const char*pathname,mode_t mode);

      也可以使用open和openat创建普通文件

int open(const char *pathname,int flags,mode_t mode);
int openat(int dirfd, const char *pathname, int flags, mode_t mode);

②创建目录。

int mkdir(const char*pathname,mode_t mode);
int mkdirat(int dirfd,const char *pathname, mode_t mode);

③创建符号链接。

int symlink(const char *oldpath,const char *newpath);
int symlinkat(const char *oldpath, int newdirfd, const char *newpath);

④mknod通常用来创建字符设备文件和块设备文件,也可以创建普通文件、命名管道和套接字。

int mknod(const char *pathname,modet mode, dev_t dev);
int mknodat(int dirfd, const char *pathname, mode_t mode, dev_t dev);

⑤link用来创建硬链接,给已经存在的文件增加新的名称。

int link(const char *oldpath,const char*newpath);
int linkat(int olddfd, const char *oldpath, int newdfd, const char *newpath);

      glibc库封装了和上面的系统调用同名的库函数,还封装了创建命名管道的库函数。库函数 mkffo 是通过调用系统调用 mknod 来实现的。

int mkfifo(const char*pathname,mode t mode);

删除文件

      删除文件的命今如下。
①删除任何类型的文件:unlink PILE.
②m FJLE,默认不删除目录,如果使用选项“”“_只”或“_recursive”,可以删除目录和目录的内容。
③删除目录:mdirDIRECTORY.
      内核提供了下面这些刑除文件的系统调用。
①unlink用来删除文件的名称,如果文件的硬链接计数变成0,并且没有进程打开这个文件,那么删除文件。

int unlinx(conet char*pathname);
int unlinrat(int dirfd, const char *pathname, int flags);

②删除目录。

int rmdir(const char *pathname);

设置文件权限

      内核提供了下面这些设置文件权限的系统调用
①int chmod(const char *path, mode t mode),
②int fchmod(int fd, mode t mode);
③int fchmodat(int dfd, const char *filename, mode t mode);

读文件

      进程读文件的方式有3种。
①调用内核提供的读文件的系统调用。
②调用glibc 库封装的读文件的标准 O 流函数。
③创建基于文件的内存映射,把文件的一个区间映射到进程的虚拟地址空国直接读内存。

      内核提供了下面这些读文件的系统调用。
①系统调用read从文件的当前偏移读文件,把数据存放在一个缓冲区.

ssize_t read(int fd,void *buf,size_t count);

②系统调用pread64 从指定偏移开始读文件。

ssize_t pread64(int fd, void *buf, sizet count, off_t offset);

③系统调用readv从文件的当前偏移读文件,把数据存放在多个分散的缓冲区。

ssize_t readv(int fd, const struct iovec *iov, int iovcnt);

④系统调用preadv从指定偏移开始读文件,把数据存放在多个分散的缓冲区

ssize_t preadv(int fd, const struct iovec *iov, int iovcnt, off_t offset);

⑤系统调用preadv2在系统调用preadv 的基础上增加了参数“int flags”。

ssize_t preadv2(int fd, const struct iovec *iov, int iovcnt, off_t offset, int flags);

      其中preadv和preadv2是Linux内核私有的系统调用。

写文件

      进程写文件的方式有3种。
①调用内核提供的写文件的系统调用。
②调用 glibc 库封装的写文件的标准 IO 流函数。
③创建基于文件的内存映射,把文件的一个区间映射到进程的虚拟地址空间,然后直接写内存。

      内核提供了下面这些写文件的系统调用。

①函数 write 从文件的当前偏移写文件,调用进程把要写入的数据存放在一个缓冲区

ssize_t write(int fd,const void *buf,size_t count);

②函数 pwrite64 从指定偏移开始写文件。

ssize_t pwrite64(int fd, const void *buf,size_t count, off_t offset);

③函数 writev从文件的当前偏移写文件,调用进程把要写入的数据存放在多个分散的缓冲区。

ssize_t writev(int fd, const struct iovec *iov, int iovcnt);

④函数pwritev从指定偏移开始写文件,调用进程把要写入的数据存放在多个分散的缓冲区。

ssize_t pwritev(int fd, const struct iovec *iov, int iovont, off_t offset);

⑤函数 pwritev2 在函数 pwritev 的基础上增加了参数“int flags”。

ssize_t pwritev2(int fd, const struct iovec *jov, int iovent, off_t offset, int flags) ;

      其中 pwritev和pwritev2是Linux内核私有的系统调用。

文件回写
      进程写文件时,内核的文件系统模块把数据写到文件的页缓存,没有立即写回到存储设备。文件系统模块会定期把脏页(即数据被修改过的文件页)写回到存储设备,进程也可以调用系统调用把脏页强制写回到存储设备。

      内核提供了下面这些把文件同步到存储设备的系统调用。
①sync把内存中所有修改过的文件元数据和文件数据写回到存储设备。

void sync(void);

②syncfs把文件描述符fd引用的文件所属的文件系统写回到存储设备。

int syncfs(int fd);

③fsyc把文件描述符fd引用的文件修改过的元数据和数据写回到存储设备

int fsync(int fd);

④fdatasync把文件描述符d引用的文件修改过的数据写回到存储设备,还会把检索这些数据需要的元数据写回到存储设备。

int fdatasync(int fd);

⑤Linux 私有的系统调用sync fle range 把文件的一个区间修改过的数据写回到存储设备

int sync_file_range(int fd, off64_t offset, off64_t nbytes, unsigned int flags);

      glibc库针对这些系统调用封装了同名的库函数,还封装了一个把数据从用户空间缓冲区写到内核的标准 I/O 流函数:

int fflush(FILE *stream);

 

      下面以打开、读、写、关闭文件示例代码如下

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main()
{
    char buf_w[30] = "HELLO WORLD!!!\n";
    char buf_r[30] = "HELLO WORLD!!!\n";
    int fd = open("./test", O_RDWR | O_CREAT | O_TRUNC, 0666);
    if (fd < 0) 
    {
        printf("error: test open\n");
        return -1;
    } 
    if( write(fd, buf_w, strlen(buf)) < 0 )
    {
        printf("error: test write\n");
        return -1;
    } 
    if(read(fd, buf, 30) < 0) 
    {
        printf("error: test read\n");
        return -1;
    }
    printf("read fd:%s", buf);
    ret = close(fd);
    if (ret < 0) 
    {
        printf("error: test close\n");
        return -1;
    }
    if(ret = 0) 
    {
        printf("succeed: test close\n");
        return 0;
    }
    return 0;
}

     

      至此,对文件系统进行了梳理学习与使用,对Linux文件系统加深了认识。

点赞 关注
个人签名

保持热爱


回复
举报
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/8 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表