趣谈linux操作系统--Linux文件管理笔记

本文详细介绍了文件系统的核心功能,包括以块为单位的存储、索引、缓存管理和文件夹组织。讨论了Linux中如ext3、ext4的文件系统格式,特别关注ext4的Extents特性,优化了大文件的访问。此外,解释了文件系统的系统调用如open、write、read等,并深入剖析了inode数据结构及其在文件权限、时间戳管理中的作用。同时,文章探讨了硬链接和软链接的区别,以及文件系统挂载、打开文件的过程。最后,涉及了文件系统的缓存机制,包括直接I/O和缓存I/O的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述- 文件系统的功能
- 以块为单位的存储组织形式
- 要有索引, 方便查找
- 热点文件应该有缓存
- 可以以文件夹形式组织, 方便管理
- 在内存中维护数据结构, 保存哪些文件被哪些进程打开/使用

  • 文件系统相关命令行
    • 格式化, 组织成一定格式的文件系统; Windows→NTFS, Linux→ext3/ext4
      • fdisk -l 查看分区
      • mkfs.ext3/mkfs.ext4 /dev/… 进行格式化
    • 可建立多个分区, 再分别以不同文件系统进行格式化
      • fdisk /dev/… 打开交互式程序
        • p 打印分区
        • n 新建分区: p primary 建立主分区; e extended 建立扩展分区; 设置大小; w 执行分区修改
      • 再执行 mkfs.ext* 进行格式化
    • 挂载分区到某个目录, 才能正常访问
      • mount /dev/… /目录
      • umount /目录
    • 查看文件类型 ls -l
      • 第一个标识符: - 普通文件; d 文件夹; c 字符设备文件; b 块设备文件; s socket 文件; l 符号链接(软连接)
  • 文件系统相关系统调用
    • open 打开一个文件, 返回文件描述符 fd; 参数 O_CREAT 不存在就创建, O_RDWR 以读写方式打开, O_TRUNC 文件长度截断为 0; 返回成功写入字节数
    • write 写数据, 参数 fd, 数据位置, 写入字节数; 返回成功写入字节数
    • lseek 重新定位读写位置, 参数 fd, 位置, SEEK_SET
    • read 读数据, 参数 fd, 存放位置, 读取字节数; 返回成功读取字节数
    • close 关闭文件
    • stat/lstat 通过文件名获取文件信息; fstat 通过 fd 获取文件信息
    • opendir 打开一个目录, 生成一个目录流 DIR
    • readdir 读取目录流的一个条目, 自动指向下一个条目
    • closedir 关闭目录流
// inode 数据结构
struct ext4_inode {
  __le16  i_mode;    /* File mode 文件的读写权限 i_mode*/
  __le16  i_uid;    /* Low 16 bits of Owner Uid 属于哪个用户*/
  __le32  i_size_lo;  /* Size in bytes */
  __le32  i_atime;  /* Access time ,是最近一次访问文件的时间*/
  __le32  i_ctime;  /* Inode Change time 最近一次更改 inode 的时间*/
  __le32  i_mtime;  /* Modification time 是最近一次更改文件的时间*/
  __le32  i_dtime;  /* Deletion Time */
  __le16  i_gid;    /* Low 16 bits of Group Id 哪个组 */
  __le16  i_links_count;  /* Links count */
  __le32  i_blocks_lo;  /* Blocks count 占用多少个块*/
  __le32  i_flags;  /* File flags */
......
  __le32  i_block[EXT4_N_BLOCKS];/* Pointers to blocks */
  __le32  i_generation;  /* File version (for NFS) */
  __le32  i_file_acl_lo;  /* File ACL */
  __le32  i_size_high;
......
};

区分几个地方。首先,访问了,不代表修改了,也可能只是打开看看,就会改变 access time。其次,修改 inode,有可能修改的是用户和权限,没有修改数据部分,就会改变 change time。只有数据也修改了,才改变 modify time。

ext2 和 ext3 结构
在这里插入图片描述ext4 做了一定的改变引入了一个新的概念,叫做 Extents
在这里插入图片描述

//描述某个节点
struct ext4_extent_header {
  __le16  eh_magic;  /* probably will support different formats */
  __le16  eh_entries;  /* number of valid entries表示这个节点里面有多少项 */
  __le16  eh_max;    /* capacity of store in entries */
  __le16  eh_depth;  /* has tree real underlying blocks? */
  __le32  eh_generation;  /* generation of the tree */
};

eh_entries项分两种

  • 如果是叶子节点,这一项会直接指向硬盘上的连续块的地址,我们称为数据节点 ext4_extent;
  • 如果是分支节点,这一项会指向下一层的分支节点或者叶子节点,我们称为索引节点 ext4_extent_idx。这两种类型的项的大小都是 12 个 byte。

/*
 * This is the extent on-disk structure.
 * It's used at the bottom of the tree.
 */
struct ext4_extent {
  __le32  ee_block;  /* first logical block extent covers */
  __le16  ee_len;    /* number of blocks covered by extent */
  __le16  ee_start_hi;  /* high 16 bits of physical block */
  __le32  ee_start_lo;  /* low 32 bits of physical block */
};
/*
 * This is index on-disk structure.
 * It's used at all the levels except the bottom.
 */
struct ext4_extent_idx {
  __le32  ei_block;  /* index covers logical blocks from 'block' */
  __le32  ei_leaf_lo;  /* pointer to the physical block of the next *
         * level. leaf or next index could be there */
  __le16  ei_leaf_hi;  /* high 16 bits of physical block */
  __u16  ei_unused;
};

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述ln -s 创建的是软链接,不带 -s 创建的是硬链接。它们有什么区别呢?

  • 硬链接与原始文件共用一个 inode 的,但是 inode 是不跨文件系统的,每个文件系统都有自己的 inode 列表,因而硬链接是没有办法跨文件系统的。
  • 软链接相当于重新创建了一个文件。这个文件也有独立的 inode,只不过打开这个文件看里面内容的时候,内容指向另外的一个文件。这就很灵活了。我们可以跨文件系统,甚至目标文件被删除了,链接文件还是在的,只不过指向的文件找不到了而已。
    在这里插入图片描述在这里插入图片描述- 磁盘→盘片→磁道→扇区(每个 512 字节)
  • ext* 定义文件系统的格式
  • inode 与数据块
    • 硬盘分为大小相同的单元→块 ( block ), 大小 4K, 扇区的整数倍, 大小在格式化时可配置
    • 因此, 存放文件时不用分配连续的空间
    • 也因此要为文件建立块索引 + 元数据(名字, 权限, 所属) 信息, 存放于 inode 中
    • inode 还维护三个时间: i_atime 访问时间; i_ctime 更改 inode 时间; i_mtime 更改文件时间
    • 文件分为多个块, 每个块的位置存放在 inode 的 i_block 中, 共 15 项
      • ext2 和 ext3 中, 前 12 项保存块的位置, 若文件较大, 则第十三项指向间接块, 间接块存放剩余数据块的位置; 文件再大, 第 14 项指向两级间接块, 以此类推
      • 但上述, 大文件需要访问多个块才能读取到数据
      • ext4 引入 Extents 概念, 可以用于存放连续的数据块
      • Extents 是树形结构, 每个节点由一个头 ext4_extend_header 来描述节点
        • 节点有多个项, 对于叶子节点: 每项直接指向硬盘上的连续块的地址; 分支节点: 每项指向下一层节点
        • 文件不大: inode 可放下一个头 + 4 个数据项, eh_depth = 0 表示数据节点
        • 文件较大: 除了根节点(存于 inode.i_block 中) , 其他节点都存于一个块中, 4K 能存 340 项, 每项可放 128MB, 总 42.5GB
  • inode 位图与块位图
    • 要保存数据是, 应放在哪? 全扫一遍效率低
    • 用一个块保存 inode 位图, 每一位对应一个 inode, 1→被占用; 同样用一个块保存块位图
    • open 再空文件夹下创建文件: do_sys_open→…→lookup_open 再调用 dir_node→i_op_create(ext4_create) 创建文件夹 inode
      • 调用 ext4_create→…→__ext4_new_inode 读取 inode 位图, 找到下一个空闲 inode
      • 同样用块位图找空闲块
  • 文件系统格式
    • 一个位图只能表示 2^15 个数据块, 即 128MB
    • 一个 inode 位图 + 一个 block 位图, 称为块组, 用数据结构 ext4_group_desc 表示, 里面包含 inode 位图, block 位图和 inode 列表
    • 这些块组描述符构成列表, 另外用超级块 ext4_super_block 描述整个文件系统; 第一个块组前 1k 用于启动引导
    • 文件系统由引导块 + N 个块组组成; 每个块组由: 超级块 + 块组描述符表 + 块位图 + inode 位图 + inode 列表 + 数据块构成
    • 超级块和块组描述符表都是全局信息; 默认超级块和块组描述符表再灭个租客都有备份; 若开启 sparse_super, 则只在固定块组中备份
    • 采用 Meta Block Groups 特性, 避免块组表浪费空间, 或限制文件系统的大小
      • 将块组分成多个组(元块组) 块组描述符表只保存当前元块组中块组的信息, 并在元块组内备份
  • 目录存储格式
    • 目录也是文件, 也有 inode, inode 指向一个块, 块中保存各个文件信息, ext4_dir_entry 包括文件名和 inode, 默认按列表存
    • 第一项 “.” 当前目录; 第二项 “…” 上一级目录
    • 可添加索引, 加快文件查找
      • 需要改变目录块格式, 加入索引树: 用索引项 dx_entry 保存文件名哈希和块的映射, 若该块不是索引, 则里面保存 ext4_dir_enry 列表, 逐项查找
  • 软连接/硬链接的存储
    • 链接即文件的别名: ln -s 创建软链接; ln 创建硬链接
    • 硬链接与原始文件共用一个 inode, 但不能跨文件系统
    • 软链接是一个文件, 有自己的 inode, 该文件内容指向另一个文件, 可跨文件系统

在这里插入图片描述

  • 多层组件统一完成进行读写文件的任务
    • 系统调用 sys_open, sys_read 等
    • 进程维护打开的文件数据结构, 系统维护所有打开的文件数据结构
    • Linux 提供统一的虚拟文件系统接口; 例如 inode, directory entry, mount, 以及对应操作 inode operations等, 因此可以同时支持数十种不同的文件系统
    • vfs 通过设备 I/O 层在通过块设备驱动程序访问硬盘文件系统
    • 通过缓存层加快块设备读写
  • 通过解析系统调用了解内核架构
  • 挂载文件系统 mount
    • 注册文件系统 register_filesystem 后才能挂载
    • 调用链 mount->do_mount->do_new_mount→vfs_kern_mount
    • 首先创建 struct mount
      • 其中 mnt_parent 指向父 fs 的 mount; mnt_parentpoint 指向父 fs 的 dentry
      • 用 dentry 表示目录, 并和目录的 inode 关联
      • mnt_root 指向当前 fs 根目录的 dentry; 还有 vfsmount 指向挂载树 root 和超级块
    • 调用 mount_fs 进行挂载
      • 调用 ext4_fs_type→mount(ext4_mount), 读取超级块到内存
      • 文件和文件夹都有一个 dentry, 用于与 inode 关联, 每个挂载的文件系统都由一个 mount 描述; 每个打开的文件都由 file 结构描述, 其指向 dentry 和 mount.
      • 二层文件系统根目录有两个 dentry, 一个表示挂载点, 另一个是上层 fs 的目录.
  • 打开文件 sys_open
    • 先获取一个未使用的 fd, 其中 task_struct.files.fd_array[] 中每一项指向打开文件的 struct file, 其中 fd 作为下标. 默认 0→stdin, 1→stdout, 2→stderr
    • 调用 do_sys_open->do_flip_open
      • 先初始化 nameidata, 解析文件路径名; 接着调用 path_openat
        • 生成 struct file 结构; 初始化 nameidata, 准备查找
        • link_path_walk 根据路径名逐层查找
        • do_last 获取文件 inode, 初始化 file
      • 查找路径最后一部分对应的 dentry
        • Linux 通过目录项高速缓存 dentry cache(dentry) 提高效率. 由两个数据结构组成
          • 哈希表: dentry_hashtable; 引用变为 0 后加入 lru 链表; dentry 没找到则从 slub 分配; 无法分配则从 lru 中获取; 文件删除释放 dentry;
          • 未使用的 dentry lru 链表; 再次被引用返回哈希表; dentry 过期返回给 slub 分配器
        • do_last 先从缓存查找 dentry, 若没找到在从文件系统中找并创建 dentry, 再赋给 nameidata 的 path.dentry; 最后调用 vfs_open 真正打开文件
        • vfs_open 会调用 f_op->open 即 ext4_file_open, 还将文件信息存入 struct file 中.
      • 许多结构体中都有自己对应的 operation 结构, 方便调用对应的函数进行处理

struct mount {
  struct hlist_node mnt_hash;
  struct mount *mnt_parent;
  struct dentry *mnt_mountpoint;
  struct vfsmount mnt;
  union {
    struct rcu_head mnt_rcu;
    struct llist_node mnt_llist;
  };
  struct list_head mnt_mounts;  /* list of children, anchored here */
  struct list_head mnt_child;  /* and going through their mnt_child */
  struct list_head mnt_instance;  /* mount instance on sb->s_mounts */
  const char *mnt_devname;  /* Name of device e.g. /dev/dsk/hda1 */
  struct list_head mnt_list;
......
} __randomize_layout;


struct vfsmount {
  struct dentry *mnt_root;  /* root of the mounted tree */
  struct super_block *mnt_sb;  /* pointer to superblock */
  int mnt_flags;
} __randomize_layout;

在这里插入图片描述在这里插入图片描述

当文件系统a中的一个文件目录下被挂载另一个文件系统时b时,这一个目录其实在两个文件系统中都存在,这样就需要有两个dentry来表示同一个目录所属两个文件系统。对于a系统,这个目录就是个挂载点,下面挂载文件系统。对于b系统,这个目录是它的根目录。所以mount结构中就同时有两个dentry,一个指向挂载点和,一个指向根目录。虽然在我们看来是同一个东西。

在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述- 系统调用层和虚拟文件系统层
- 调用 read/write 进行读写 → vfs_read/write → __vfs_read/write
- 打开文件时创建 struct file, 其中有 file_operations, 虚拟文件系统调用 operations 中的 read/write

  • ext4 文件系统层
    • 调用到 generic_file_read/write_iter, 其中判断是否需要使用缓存
    • 缓存, 即内存中一块空间, 可分为两类 I/O
      • 缓存 I/O: 默认模式, 读操作先检测缓存区中是否有, 若无则从文件系统读取并缓存; 写操作直接从用户空间赋值到内核缓存中, 再由 OS 决定或用户调用 sync 写回磁盘
      • 直接 I/O: 程序直接访问磁盘, 不经过缓存
    • 直接 I/O 过程:
      • 读: 若设置了 IOCB_DIRECT, 调用 address_space 的 direct_io 直接读取硬盘( 文件与内存页映射) ; 若使用缓存也要调用 address_sapce 进行文件与内存页的映射
      • 写: 若设置了 IOCB_DIRECT, 调用块设备驱动直接写入磁盘
    • 带缓存写过程
      • 在 while 循环中, 找出写入影响的页, 并依次写入, 完成以下四步
        • 每一页调用 write_begin 做准备
        • 将写入内容从用户态拷贝到内核态
        • 调用 write_end 完成写入
        • 查看脏页 (未写入磁盘的缓存) 是否过多, 是否需要写回磁盘
      • write_begin 做准备
        • ext4 是日志文件系统, 通过日志避免断电数据丢失
        • 文件分为元数据和数据, 其操作日志页分开维护
          • Journal 模式下: 写入数据前, 元数据及数据日志必须落盘, 安全但性能差
          • Order 模式下: 只记录元数据日志, 写日志前, 数据必须落盘, 折中
          • Writeback 模式下: 仅记录元数据日志, 数据不用先落盘
        • write_begin 准备日志, 并得到应该写入的缓存页
        • 内核中缓存以页为单位, 打开文件的 file 结构中用 radix tree 维护文件的缓存页
      • iov_iter_copy_from_user_atomic 拷贝内容, kmap_atomic 将缓存页映射到内核虚拟地址; 将拥护他数据拷贝到内核态; kunmap_aotmic 解映射
      • write_end, 先完成日志写入 并将缓存设置为脏页
      • 调用 balance_dirty_pages_ratelimited 若发先脏页超额, 启动一个线程执行回写.
        • 回写任务 delayed_work 挂在 bdi_wq 队列, 若delay 设为 0, 马上执行回写
        • bdi = backing device info 描述块设备信息, 初始化块设备时回初始化 timer, 到时会执行写回函数
      • 另外其他情况也会回写
        • 用户调用 sync 或内存紧张时, 回调用 wakeup_flusher_threads 刷回脏页
        • 脏页时间超过 timer, 及时回写
    • 带缓存读
      • generic_file_buffered_read 从 page cache 中判断是否由缓存页
        • 若没则从文件系统读取并预读并缓存, 再次查找缓存页
        • 若有, 还需判断是否需要预读, 若需要调用 page_cache_async_readahead
        • 最后调用 copy_page_to_user 从内核拷贝到用户空间
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值