11 ext2文件系统IO流程:读写文件read/write

ext2使用page页面缓存来完成对文件的读写。这些页面的管理是通过inode的字段i_mapping来完成,也就是地址空间。所以在创建inode时,要指定i_mapping的操作表a_ops,帮助地址空间完成页面操作。

参考:014 Linux文件系统数据结构详解:地址空间struct address_space

地址空间操作表a_ops中,需要指定读page、写page等多种页面操作的函数指针,但是具体的块操作(读取、写入)、buffer操作、VM页面操作,其实文件系统不用太关注,因为Linux内核提供了大量公共函数(参考:buffer.c和mpage.c),文件系统可以直接调用完成读取和写入。但是,文件系统需要提供块的映射方法,帮助完成文件系统逻辑块号(就是在文件中的偏移量)到实际块设备块号映射,最后填充到buffer_head中。

// inode.c

const struct address_space_operations ext2_aops = {
	.set_page_dirty		= __set_page_dirty_buffers,
	.readpage		= ext2_readpage,
	.readahead		= ext2_readahead,
	.writepage		= ext2_writepage,
	.write_begin		= ext2_write_begin,
	.write_end		= ext2_write_end,
	.bmap			= ext2_bmap,
	.direct_IO		= ext2_direct_IO,
	.writepages		= ext2_writepages,
	.migratepage		= buffer_migrate_page,
	.is_partially_uptodate	= block_is_partially_uptodate,
	.error_remove_page	= generic_error_remove_page,
};
继续阅读“11 ext2文件系统IO流程:读写文件read/write”

10 ext2文件系统IO流程:inode的创建、写入、读取、删除、释放子流程

在Linux文件系统中,inode就代表一个磁盘上的文件,所有磁盘文件的操作,最终都落到inode上去处理,inode的生命周期管理非常重要。但是inode的创建、写入、读取和删除,通常不是独立的流程,它是裹挟在其它的大IO流程中,比如创建文件。但是在其它流程中,inode的处理的介绍几乎是一笔带过,所以,有必要针对inode管理,总结一篇详实的材料。本文还是以ext2文件系统为例,来讲述inode生命周期的管理。

首先,你要知道inode管理的操作函数,都是在超级块结构体里声明,因为inode自己的操作表,都是和文件操作相关。好,我们一起来看一下:

// super.c

static const struct super_operations ext2_sops = {
    .alloc_inode    = ext2_alloc_inode,
    .free_inode     = ext2_free_in_core_inode,
    .write_inode    = ext2_write_inode,
    .evict_inode    = ext2_evict_inode,
    .put_super      = ext2_put_super,
    .sync_fs        = ext2_sync_fs,
    .freeze_fs      = ext2_freeze,
    .unfreeze_fs    = ext2_unfreeze,
    .statfs         = ext2_statfs,
    .remount_fs     = ext2_remount,
    .show_options   = ext2_show_options,
};
继续阅读“10 ext2文件系统IO流程:inode的创建、写入、读取、删除、释放子流程”

09 ext2文件系统IO流程:删除目录rmdir

上一篇说到ext2文件系统删除文件是逻辑删除,对应文件系统的unlink操作,其实就是解除磁盘目录项与inode关系,同时删除目录项(也是逻辑删除,与前一项合并)。那么,同样删除目录跟删除文件类似,ext2也是做了一次unlink操作。详细的删除目录流程如下:

  • ① 检查目录是否为空(除了.和..之外,是否还有其它文件),如果目录不空有文件,返回-ENOTEMPTY,无法删除(相关函数:ext2_empty_dir())
  • ② 如果目录为空,调用ext2_unlink()函数解除link
ext2删除目录流程
ext2删除目录流程
继续阅读“09 ext2文件系统IO流程:删除目录rmdir”

08 ext2文件系统IO流程:删除文件unlink

前面几篇博客讲了创建文件、创建目录、查找文件,接下来说一下ext2文件系统是如何删除文件,ext2文件系统删除文件,并没有在物理设备上擦除文件,只是做了一个unlink操作,所谓的逻辑删除。unlink的具体实现,就是解除目录项与inode关系,这样目录项就不会关联到inode,就无法查找到文件,达到了删除的目的。unlink的具体的流程如下:

  • ① 根据文件名和父目录信息,到磁盘上查找目录项(ext2_find_entry),记录对应的页面page和页地址page_addr
  • ② 解除目录项与inode连接,然后删除目录项(逻辑删除,其实是与前一个目录项合并)
  • ③ 修改inode->i_ctime,递减inode的硬链接数i->nlink,同时标记脏inode
ext2 unlink操作
ext2 unlink操作
继续阅读“08 ext2文件系统IO流程:删除文件unlink”

07 ext2文件系统IO流程:文件查找lookup

在打开文件、列举文件或者stat()操作时,用户端给的是文件路径信息,内核文件系统需要根据路径,查找到对应的inode,这样才能生成file对象,才能进行后续的读写等操作。所以,文件查找是一个基础能力,是其他文件操作的前置条件。

所谓查找文件,就是找到路径对应的dentry项,通过dentry项找到对应inode信息,有了inode才能对文件进行其他操作。文件查找时,优先在dcache缓存中查找,如果没有找到,需要到磁盘上进行遍历。

在dcache中查找,是VFS层面的基本动作,dcache如果没有找到,VFS就会调用具体文件系统的lookup()函数,到磁盘上进行遍历。以ext2文件系统为例,lookup具体流程如下:

  • ① 根据父目录inode、当前文件名,查找对应的目录项,最终查找到对应的ino(ext2_inode_by_name)
  • ② 根据ino,从磁盘上读取inode信息(ext2_iget)
  • ③ 连接dentry和inode,处理目录项的别名(d_splice_alias)
ext2文件lookup流程
ext2文件lookup流程
继续阅读“07 ext2文件系统IO流程:文件查找lookup”

06 ext2文件系统IO流程:创建目录mkdir

前一篇介绍了在目录下创建文件,我接下来们继续ext2主IO流程:创建目录。不论是创建文件还是创建目录,都是基于父目录,两者有很多相似之处,都要新建inode和目录项,然后建立inode与dentry的关系。但是创建目录操作,在写入目录项时,要多写两个目录项.(当前目录)和..(上层目录),详细流程如下:

  • 新建inode,并标记dirty,待内核同步
  • 设置操作表:i_op、i_fop、i_mapping->a_ops
  • 新建.和..两个目录项,inode指向新建的inode,并持久化
  • 在父目录的页缓存中,寻找一个空间,新建目录的目录项,inode指向新建的inode,并持久化(ext2_add_link)
  • 建立缓存dentry与inode关系
ext2创建目录
ext2创建目录
继续阅读“06 ext2文件系统IO流程:创建目录mkdir”

05 ext2文件系统IO流程:创建文件create

创建文件是文件系统的基本操作,之前在介绍Linux文件系统VFS时,说过创建文件会调用inode操作表中的create()函数,那create函数具体应该如何实现呢?

文件系统在创建文件时,VFS会调用父目录目录的create()函数,在这个函数中要完成具体的文件创建,以ext2文件系统为例,ext2注册的创建文件的函数指针是ext2_create,在ext2文件系统创建文件,一般包括以下几个步骤:

  • 新建一个inode对象
  • 设置inode的文件操作表
  • 标记inode为脏,等待写入到磁盘
  • 在父目录的页缓存中,写入目录项数据,指向新建的inode
  • 建立缓存dentry和inode关系
ext2创建文件
ext2创建文件
继续阅读“05 ext2文件系统IO流程:创建文件create”

04 ext2文件系统格式化

要使用ext2文件系统,要先在设备上创建文件系统,也就是对设备进行格式化。常见的格式化工具有e2fsprog的mkefs,还有busybox的mkfs.ext2。接下来就以busybox的工具为例,介绍具体的格式化流程。

ext2格式化
ext2格式化
继续阅读“04 ext2文件系统格式化”

03 ext2文件系统物理结构剖析

在说ext2物理结构之前,要先认祖归宗。

首先,大部分现代文件系统的祖先都能追溯到BSD FFS(Fast File System 1983年 BSD 4.2版本),所以对文件系统的介绍不可能忽略它。从40年后今天来看,BSD FFS的设计理念还有很多值得学习的地方,对于现代文件系统可以说是产生的深远的影响。在它的设计框架里,有一个超级块,一个块位图,一个inode位图和一些预分配的inode表。这种设计可在许多现代文件系统里找到影子。

4.2BSD (August 1983) would take over two years to implement and contained several major overhauls. Before its official release came three intermediate versions: 4.1a from April 1982[13] incorporated a modified version of BBN’s preliminary TCP/IP implementation; 4.1b from June 1982 included the new Berkeley Fast File System, implemented by Marshall Kirk McKusick; and 4.1c in April 1983 was an interim release during the last few months of 4.2BSD’s development. Back at Bell Labs, 4.1cBSD became the basis of the 8th Edition of Research Unix, and a commercially supported version was available from mt Xinu.

From: History of the Berkeley Software Distribution – Wikipedia

继续阅读“03 ext2文件系统物理结构剖析”

02 ext2如何构造超级块super_block

上一篇介绍了ext2注册文件系统注册的流程,其中提到在注册文件系统时,给VFS传递了file_system_type结构体,在这个结构体中,包含了挂载函数指针ext2_mount(),用于挂载的回调操作。

在ext2_mount()函数实现中,调用了VFS的mount_bdev()来挂载块设备,这个mount_bdev()函数最后一个参数,是一个函数指针,VFS使用此函数完成超级块的构造和填充,而超级块是文件系统核心数据结构,它的构造是构建文件系统主要流程,本文就重点梳理一下超级块的构造,以下是mount_bdev()函数原型:

dentry *mount_bdev(struct file_system_type *fs_type,
	int flags, const char *dev_name, void *data,
	int (*fill_super)(struct super_block *, void *, int));
继续阅读“02 ext2如何构造超级块super_block”