利用300行代码实现文件系统

前言

本篇博客是基于下面的博客写成的,有兴趣可以去阅读下面博客
300行代码带你实现一个Linux文件系统

相关知识

文件系统对于用户来说,最重要的就是创建目录,创建文件,打开文件和文件读写

VFS之所有可以将机制大相径庭的完全不同的文件系统对外统一成一个样子,完全就是依靠了它的统一的对POSIX文件调用的接口,该接口的结构看上去是下面的样子:
在这里插入图片描述

Linux内核关于文件系统IO,完整的视图如下所示:

在这里插入图片描述

  • 和POSIX系统调用的接口 即实现open/read/write的操作的接口。
  • 和底层介质的接口 即下接块设备层的接口。
  • 如何管理自身 即何时以及如何操作VFS数据结构inode,dentry,mount等对象。

一个文件系统如果能实现上面三类接口,那它就是个完整的文件系统了

具体分析(4.15版内核)

用大数组定义一块内存,它便是模拟介质,tinyfs的文件格式如下:

// tinyfs.h
#define MAXLEN 8				//表示最大长度为8的字符串
#define MAX_FILES    32			//表示最大文件数量为32
#define MAX_BLOCKSIZE  512		//表示最大块大小为512字节

// 定义每一个目录项的格式
struct dir_entry {
    	char filename[MAXLEN];	//一个字符数组,长度为 MAXLEN,用来存储文件名
    	uint8_t idx;			//用来存储文件的索引
};

// 定义每一个文件的格式。
struct file_blk {
    	uint8_t busy;			//用来标识文件块是否被使用
    	mode_t mode;			//用来表示文件的权限模式
    	uint8_t idx;			//用来存储文件在文件系统中的索引

    	union {					//匿名联合体,包含两个成员变量,但只能使用其中一个成员变量,因为它们共用同一段内存.
        	uint8_t file_size;	//表示文件的大小
        	uint8_t dir_children;	//表示目录的子节点数
    	};
    	char data[0];			//用来存储文件数据
};

// OK,下面的block数组所占据的连续内存就是我的tinyfs的介质,每一个元素代表一个文件。
// struct file_blk block[MAX_FILES+1];

1.模块初始化

static int tinyfs_init(void)
{
    	int ret;

    	memset(block, 0, sizeof(block));
    	ret = register_filesystem(&tinyfs_fs_type);
    	if (ret)
        	printk("register tinyfs failed\n");

    	return ret;
}

模块初始化函数的重点是register_filesystem(&tinyfs_fs_type);我们来看看相关源码

int register_filesystem(struct file_system_type * fs)
{
	int res = 0;
	struct file_system_type ** p;

	BUG_ON(strchr(fs->name, '.'));
	if (fs->next)
		return -EBUSY;
	write_lock(&file_systems_lock);
	p = find_filesystem(fs->name, strlen(fs->name));
	if (*p)
		res = -EBUSY;
	else
		*p = fs;
	write_unlock(&file_systems_lock);
	return res;
}

函数register_filesystem的参数是一个文件类型指针,函数执行时要在内核找相同名字的文件系统,如果不存在相同名字的文件系统,就把tinyfs加入到系统的文件系统链表,如果该文件系统已存在,返回忙

函数find_filesystem的参数一个是待查找的文件系统名称 ,另一个是名称的长度。函数执行时在文件系统类型链表中查找特定名称的文件系统类型。找到匹配,函数返回指向该文件系统类型的指针;没有找到匹配,返回 NULL。其中file_systems是内核中定义的一个全局变量,用来保存所有登记的文件系统。

static struct file_system_type **find_filesystem(const char *name, unsigned len)
{
	struct file_system_type **p;
	for (p = &file_systems; *p; p = &(*p)->next)
		if (strncmp((*p)->name, name, len) == 0 &&
		    !(*p)->name[len])
			break;
	return p;
}

2. 获取空闲块

//tinyfs的介质,每一个元素代表一个文件
struct file_blk block[MAX_FILES+1];
int curr_count = 0; // 全局变量

// 获得一个尚未使用的文件块,保存新创建的文件或者目录
//该函数用于查找block数组中第一个busy属性为0的元素,并返回该元素的下标
static int get_block(void)
{	
	int i;

	// 就是一个遍历,但实现快速。
	for (i = 2; i < MAX_FILES; i++) 
	{//数组下标从2开始遍历是因为该程序将下标0和下标1分别预留给了根目录和当前目录
        	if (!block[i].busy) 
        	{	
     			/*
     			如果当前元素的busy属性为0,则将其busy属性置为1,
     			表示已被占用,并返回该元素的下标
     			*/
            	block[i].busy = 1;
            	return i;
        	}
    	}
    	return -1;					//如果数组中所有元素的busy属性均为1,则返回-1,表示数组已满,无法再分配新的元素
}

函数get_block的功能就是查找未使用的文件块,有空闲的文件块返回其下标。

3.读取目录的实现

static struct inode_operations tinyfs_inode_ops;
// 读取目录的实现
static int tinyfs_iterate(struct file *filp, struct dir_context *dirent)
{
    	loff_t pos;
	filldir_t filldir;
    	struct file_blk *blk;
    	struct dir_entry *entry;
    	/*
struct dir_entry {定义每一个目录项的格式
    char filename[MAXLEN];
    uint8_t idx;
};
struct file_blk {定义每一个文件的格式
    uint8_t busy;
    mode_t mode;
    uint8_t idx;
    union {
        uint8_t file_size;
        uint8_t dir_children;
    };
    char data[0];
};
		*/
    	int i;

    	pos = filp->f_pos;
	filldir = dirent->actor;
    	if (pos)
        	return 0;

    	blk = (struct file_blk *)filp->f_inode->i_private;

    	if (!S_ISDIR(blk->mode)) {
        	return -ENOTDIR;
    	}

    	// 循环获取一个目录的所有文件的文件名
    	entry = (struct dir_entry *)&blk->data[0];
    	for (i = 0; i < blk->dir_children; i++) {
        	(*filldir)(dirent, entry[i].filename, MAXLEN, pos, entry[i].idx, DT_UNKNOWN);
        	filp->f_pos += sizeof(struct dir_entry);
        	pos += sizeof(struct dir_entry);
    	}

    	return 0;
}

函数tinyfs_iterate的参数是文件句柄指针 filp,和一个目录上下文结构指针 dirent,该函数在执行时会循环获取目录中的文件项信息并通过填充函数将其添加到目录流中,以便用户空间程序可以读取目录的内容

(1)下面是关于filldir_t的定义
struct dir_context;
typedef int (*filldir_t)(struct dir_context *, const char *, int, loff_t, u64,
			 unsigned);

filldir_t 是一个函数指针类型,它用于定义目录项填充函数的签名。
其参数:

  • 一个指向 struct dir_context 结构的指针,用于协助填充目录项的信息;
  • 目录项的名称;
  • 目录项的名称长度;
  • 目录项的偏移(loff_t 类型),通常用于文件系统内部的追踪。
  • 目录项的 inode 号(u64 类型),表示目录项关联的 inode。
  • 目录项的文件类型(unsigned 类型),通常是 DT_UNKNOWN 或其他文件类型标识。

目录遍历函数会多次调用这个函数指针,每次遍历一个目录项时都会调用它,用于填充目录项的信息。

(2)具体语句

语句(*filldir)(dirent, entry[i].filename, MAXLEN, pos, entry[i].idx, DT_UNKNOWN);

  1. (*filldir): 调用一个函数,通过 filldir 函数指针执行。filldir 是一个函数指针,指向一个函数,用于填充目录项信息。

  2. (dirent, entry[i].filename, MAXLEN, pos, entry[i].idx, DT_UNKNOWN): 这是函数调用的参数列表,包括以下参数:

    • dirent:这是一个指向 struct dir_context 结构的指针,它表示目录遍历的上下文。函数调用将使用这个上下文来填充目录项信息。
    • entry[i].filename:这是目录项的文件名,通常是一个字符串,用于表示目录中的文件或子目录的名称。
    • MAXLEN:这是一个整数值,表示文件名的最大长度(在.h文件有定义)
    • pos:这是目录项的偏移位置,通常用于文件系统内部的追踪
    • entry[i].idx:这是目录项关联的 inode 号,通常是一个唯一的标识符,表示文件或子目录。
    • DT_UNKNOWN:这是一个表示文件类型的标识符。

通过函数指针 filldir 调用一个函数,该函数将传递给它的参数用于填充目录项信息。这通常在目录遍历中使用,以便将目录中的每个文件项的信息添加到目录流中,以供用户空间程序读取。

4. read实现

// read实现
ssize_t tinyfs_read(struct file * filp, char __user * buf, size_t len, loff_t *ppos)
{	//文件结构体指针 filp,用户缓冲区 buf,要读取的字节数 len,以及文件偏移量 ppos
    	struct file_blk *blk;
    	char *buffer;
/*
struct file_blk {定义每一个文件的格式
    uint8_t busy;
    mode_t mode;
    uint8_t idx;
    union {
        uint8_t file_size;
        uint8_t dir_children;
    };
    char data[0];
};
		*/
    	blk = (struct file_blk *)filp->f_path.dentry->d_inode->i_private;
    	if (*ppos >= blk->file_size)			//检查文件偏移量是否超出文件大小
        	return 0;

    	buffer = (char *)&blk->data[0];			//将 blk 中的数据缓冲区的指针存储在 buffer 变量中
    	len = min((size_t) blk->file_size, len);//将 len 和文件大小中的较小值存储在 len 变量中

    	if (copy_to_user(buf, buffer, len)) {	// copy_to_user 函数:将文件数据从内核空间复制到用户空间缓冲区 buf 中
        	return -EFAULT;
    	}
    	*ppos += len;							//更新文件偏移量 ppos

    	return len;								//返回读取的字节数 len
}

函数tinyfs_read接受以下参数:

  • filp:文件结构体指针,表示要读取的文件。
  • buf:用户空间缓冲区,用于将文件数据复制到用户空间。
  • len:要读取的字节数。
  • ppos:文件偏移位置的指针,用于跟踪读取的位置。

函数在执行时会从虚拟文件系统中读取文件的内容,并将其复制到用户空间缓冲区中。函数会检查文件偏移位置以确保不读取超出文件末尾的数据,并返回实际读取的字节数。

(1)loff_t
//include/linux/types.h
#if defined(__GNUC__)
typedef __kernel_loff_t		loff_t;
#endif

//include/uapi/asm-generic/posix_types.h
typedef long long	__kernel_loff_t;

loff_t 是用于表示文件偏移位置(文件偏移量)的数据类型。文件偏移位置通常用于跟踪文件读取或写入操作的位置,以及文件操作的当前位置。

这里的代码使用了条件编译,以便根据使用的编译器来定义 loff_t 数据类型

#if defined(GNUC):
这是一个条件编译指令,用于检查是否使用GNU编译器(GCC)。如果是,它将定义 loff_t 为 __kernel_loff_t。

typedef __kernel_loff_t loff_t;: 如果正在使用GCC编译器,那么 loff_t 被定义为
__kernel_loff_t。这实际上是在 include/uapi/asm-generic/posix_types.h 文件中定义的 __kernel_loff_t 数据类型。

__kernel_loff_t 是一个long long类型,通常是64位整数,用于表示大文件的偏移位置。这使得Linux内核在处理大文件时能够支持大于2 GB的文件大小

(2)语句

blk = (struct file_blk *)filp->f_path.dentry->d_inode->i_private;语句用于获取文件句柄 filp 对应的文件块结构指针 blk

  • filp:这是一个指向文件结构的指针,表示要进行读取操作的文件。

  • filp->f_path:filp 结构中的 f_path 成员用于存储与文件路径相关的信息。这包括文件的目录项 (dentry) 和与文件相关的 inode。

  • filp->f_path.dentry:dentry 是一个目录项结构,它通常包含了文件名和与文件关联的 inode 号。filp->f_path.dentry 表示文件的目录项。

  • filp->f_path.dentry->d_inode:d_inode 字段是目录项结构中的一个字段,它指向与文件关联的 inode 结构。

  • filp->f_path.dentry->d_inode->i_private:i_private 字段是 inode 结构中的一个字段,通常用于存储文件系统私有数据。在这个上下文中,i_private 存储了文件块结构的指针,即与文件关联的私有数据。

  • struct file_blk *blk:这是一个文件块结构指针,用于存储 i_private 字段中存储的文件块结构的地址

(3)copy_to_user函数
copy_to_user(void __user *to, const void *from, unsigned long n)
{
	if (likely(check_copy_size(from, n, true)))
		n = _copy_to_user(to, from, n);
	return n;
}

函数功能是将内核空间的数据复制到用户空间。

  • to:目标地址(用户空间)
  • from:源地址(内核空间)
  • n:将要拷贝数据的字节数

成功返回0,失败返回没有拷贝成功的数据字节数

5. wirte实现

// write实现
ssize_t tinyfs_write(struct file * filp, const char __user * buf, size_t len, loff_t * ppos)
{
    	struct file_blk *blk;		// 文件块结构体指针
    	char *buffer;				// 缓冲区指针

    	blk = filp->f_path.dentry->d_inode->i_private;	 // 获取文件的私有数据,即文件块结构体指针

    	buffer = (char *)&blk->data[0];		// 将文件块结构体中的数据指针赋值给缓冲区指针
    	buffer += *ppos;					// 将缓冲区指针移动到偏移量位置

    	if (copy_from_user(buffer, buf, len)) {	// 将用户空间的数据拷贝到内核缓冲区
        	return -EFAULT;						// 拷贝失败,返回错误码
    	}
    	*ppos += len;							// 偏移量增加
    	blk->file_size = *ppos;					// 更新文件块结构体中的文件大小

    	return len;								// 返回写入的字节数
}

函数tinyfs_write接受以下参数:

  • filp:文件结构体指针,表示要写入的文件。
  • buf:用户空间缓冲区,包含要写入文件的数据。
  • len:要写入的字节数。
  • ppos:文件偏移位置的指针,用于跟踪写入的位置。

函数在执行时会将数据从用户空间写入到虚拟文件系统中的文件中,并更新文件的大小和偏移位置。

(1)copy_from_user函数
copy_from_user(void *to, const void __user *from, unsigned long n)
{
	if (likely(check_copy_size(to, n, false)))
		n = _copy_from_user(to, from, n);
	return n;
}

将用户空间的数据复制到内核空间。其中

  • to:目标地址(内核空间)
  • from:源地址(用户空间)
  • n:将要拷贝数据的字节数

成功返回0,失败返回没有拷贝成功的数据字节数

6. 定义

const struct file_operations tinyfs_file_operations = {
    	.read = tinyfs_read,
    	.write = tinyfs_write,
};

const struct file_operations tinyfs_dir_operations = {
    	.owner = THIS_MODULE,
    	.iterate = tinyfs_iterate,
};

用于虚拟文件系统 tinyfs 的文件操作结构体的初始化。结构体定义了与文件和目录操作相关的回调函数。:

const struct file_operations tinyfs_file_operations:用于定义与文件操作相关的回调函数的结构体。包含以下字段:

.read = tinyfs_read:将 tinyfs_read 函数分配给文件操作结构体的 .read 字段,在文件读取时将调用 tinyfs_read 函数。

.write = tinyfs_write:将 tinyfs_write 函数分配给文件操作结构体的 .write 字段,在文件写入时将调用 tinyfs_write 函数。

这些字段允许文件系统操作执行相应的读取和写入操作。

const struct file_operations tinyfs_dir_operations:用于定义与目录操作相关的回调函数的结构体。它包含以下字段:

.owner = THIS_MODULE:表示文件操作结构体拥有当前模块的所有权。

.iterate = tinyfs_iterate:将 tinyfs_iterate 函数分配给文件操作结构体的 .iterate 字段,表示在目录遍历时将调用 tinyfs_iterate 函数。

这些字段允许文件系统操作执行相应的目录遍历操作。

这些结构体是文件系统实现的关键部分,它们定义了文件和目录的操作行为。这允许文件系统处理读取、写入和目录遍历等操作,并与内核中的虚拟文件系统接口进行交互。

7. 创建文件的实现

// 创建文件的实现
static int tinyfs_do_create(struct inode *dir, struct dentry *dentry, umode_t mode)
{
    	struct inode *inode;
    	struct super_block *sb;
    	struct dir_entry *entry;
    	struct file_blk *blk, *pblk;
    	int idx;

    	sb = dir->i_sb;

    	if (curr_count >= MAX_FILES) {//检查当前目录下的文件数是否已经达到了最大值
        	return -ENOSPC;			//如果是,则返回错误码-ENOSPC,表示磁盘空间已满
    	}

    	if (!S_ISDIR(mode) && !S_ISREG(mode)) {//检查要创建的是否是文件或是目录
        	return -EINVAL;
    	}

    	inode = new_inode(sb);
    	if (!inode) {
        	return -ENOMEM;
    	}

    	inode->i_sb = sb;
    	inode->i_op = &tinyfs_inode_ops;
    	//inode->i_atime = inode->i_mtime = inode->i_ctime = time(NULL);

    	idx = get_block(); // 获取一个空闲的文件块保存新文件

    	blk = &block[idx];
    	inode->i_ino = idx;
    	blk->mode = mode;
    	curr_count ++;

    	if (S_ISDIR(mode)) {//创建目录
        	blk->dir_children = 0;
        	inode->i_fop = &tinyfs_dir_operations;
    	} else if (S_ISREG(mode)) {//创建文件
        	blk->file_size = 0;
        	inode->i_fop = &tinyfs_file_operations;
    	}

    	inode->i_private = blk;
    	pblk = (struct file_blk *)dir->i_private;
		//在当前目录的文件块中添加新的目录项,记录新文件的inode编号和文件名。
    	entry = (struct dir_entry *)&pblk->data[0];
    	entry += pblk->dir_children;
    	pblk->dir_children ++;

    	entry->idx = idx;
    	strcpy(entry->filename, dentry->d_name.name);

    	//将新的inode和dentry添加到VFS的inode链表中,这是VFS中穿针引线的关键步骤
    	inode_init_owner(inode, dir, mode); 
    	d_add(dentry, inode);
		//返回0表示创建文件或目录成功
    	return 0;
}

函数tinyfs_do_create的参数包括父目录的 inode 结构 dir、新文件的目录项结构 dentry 以及要创建的文件或目录的权限模式 mode。获取父目录的超级块信息 sb。

  1. 创建新文件或目录并初始化其相关属性。确保正确地创建了文件或目录,并将其与文件系统相关的数据结构关联。

函数运行时检查当前目录下的文件数量是否已经达到了最大值 MAX_FILES,用于判断是否创建文件。检查要创建的是文件还是目录。

函数分配一个新的 inode 结构来表示要创建的文件或目录。设置新 inode 的超级块信息为父目录的超级块信息同时确保inode有正确的超级块信息和操作函数。

get_block获取一个空闲的文件块,将新 inode 的索引号设置为获取的文件块的索引号,以确保文件系统中每个文件都有唯一的索引。设置文件块的权限模式为与创建的文件或目录相同。

创建的是目录,设置文件块的相关属性,并将文件操作指针设置为 tinyfs_dir_operations,以便正确处理目录操作。创建的是文件,设置文件块的相关属性,并将文件操作指针设置为 tinyfs_file_operations,以便正确处理文件操作。

  1. 确保了新文件或目录的正确创建和关联,并将其添加到文件系统中

将新创建的 inode 的私有数据指针设置为新文件或目录的文件块结构 blk,以便将 inode 与文件块关联。获取父目录的文件块结构,以便在其中添加新的目录项。

创建新的目录项 entry,包括新文件的 inode 编号和文件名,然后递增目录项的数量。

初始化 inode 所有者,这是 VFS 中的关键步骤,确保文件系统的正确运行。

将新的 inode 和目录项添加到 VFS的 inode 链表中,这是文件系统操作的关键步骤,确保文件系统正常工作。

函数返回 0,表示创建文件或目录成功,如果有任何错误,函数将返回相应的错误码。

(1)new_inode函数
struct inode *new_inode(struct super_block *sb)
{
	struct inode *inode;

	spin_lock_prefetch(&sb->s_inode_list_lock);

	inode = new_inode_pseudo(sb);
	if (inode)
		inode_sb_list_add(inode);
	return inode;
}

在给定的超级块中创建新的 inode,然后将其添加到超级块的 inode 列表中。新的 inode 可以用于表示文件或目录,

(2)inode_init_owner函数
void inode_init_owner(struct inode *inode, const struct inode *dir,
			umode_t mode)
{
	inode->i_uid = current_fsuid();
	if (dir && dir->i_mode & S_ISGID) {
		inode->i_gid = dir->i_gid;
		if (S_ISDIR(mode))
			mode |= S_ISGID;
	} else
		inode->i_gid = current_fsgid();
	inode->i_mode = mode;
}
EXPORT_SYMBOL(inode_init_owner);

inode_init_owner函数接受三个参数。inode 是要初始化的 inode 结构,dir 是指向父目录 inode 结构的指针,mode 是要分配给 inode 的访问权限模式。

inode->i_uid = current_fsuid():设置新 inode 的用户 ID(UID)。使用 current_fsuid() 函数来获取当前用户的有效用户 ID,将其分配给 inode。确保了新 inode 的所有者是当前用户。

if (dir && dir->i_mode & S_ISGID):检查是否传入了父目录的 inode dir 并且父目录的权限标志包含设置组ID位(Set Group ID,S_ISGID)。S_ISGID 用于设置组ID位的宏。

inode->i_gid = dir->i_gid;:如果父目录的权限标志包含设置组ID位,那么新 inode 的组ID(GID)将设置为与父目录相同的组ID。

if (S_ISDIR(mode)) mode |= S_ISGID;:如果新 inode 被设置为目录,将权限模式 mode 中的设置组ID位也设置为组ID位。这确保了新创建的目录将继承父目录的设置组ID位。

else inode->i_gid = current_fsgid();:如果没有设置组ID位或者是一个文件而不是目录,那么新 inode 的组ID将设置为当前用户的有效组ID,通过 current_fsgid() 函数获取。

inode->i_mode = mode;:将 mode 参数中指定的权限模式分配给新 inode 的 i_mode 字段,确定了新 inode 的权限。

函数功能是初始化新创建的文件或目录的一些重要属性,以确保它们具有正确的用户和组所有权,以及适当的访问权限

例子
创建一个名为"my_directory"的新目录(文件夹)

  1. 代码获取当前用户的用户ID(UID)。这是你的唯一标识符,用于确定你是谁。假设你的UID是1000。
  2. 接下来,它检查是否父目录(包含 “my_directory” 的目录)具有设置组ID标志。如果是的话,就将 “my_directory” 的组ID设置为与父目录相同的组ID。这意味着 “my_directory” 将继承其父目录的组属性。
  3. 它设置正确的访问权限模式。由于我们在示例中创建的是目录,所以它会设置适当的访问权限,以确保只有合适的人能够访问 “my_directory”。
  4. 这些属性被应用到 “my_directory”,确保它的所有者、组和权限是正确的。
(3) d_add函数
void d_add(struct dentry *entry, struct inode *inode)
{
	if (inode) {
		security_d_instantiate(entry, inode);
		spin_lock(&inode->i_lock);
	}
	__d_add(entry, inode);
}
EXPORT_SYMBOL(d_add);

d_add 函数,用于将一个目录项(struct dentry)与一个 inode(struct inode)关联并添加到文件系统中,__d_add函数是真正关键,有兴趣可以自己去了解

8.创建文件 & 目录

//创建目录
static int tinyfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
{
    	//其中S_IFDIR是文件类型标志,表示创建的是一个目录类型的文件。mode是文件权限标志,可以通过传递不同的权限标志来设置创建的目录的权限
    	return tinyfs_do_create(dir, dentry, S_IFDIR | mode);
}

//创建文件
static int tinyfs_create(struct inode *dir, struct dentry *dentry, umode_t mode, bool excl)
{
    	//其中mode是文件权限标志,可以通过传递不同的权限标志来设置创建的文件的权限
    	return tinyfs_do_create(dir, dentry, mode);
}

从上面两个函数可以很简单的知道都调用了tinyfs_do_create这个函数

(1)dentry结构

下面是简化的结构,挑出一些重点数据定义

struct dentry {


	struct hlist_bl_node d_hash;	
	struct dentry *d_parent;	
	struct qstr d_name;
	struct inode *d_inode;		

	struct lockref d_lockref;	
	const struct dentry_operations *d_op;
	struct super_block *d_sb;	


	struct list_head d_child;	/* child of parent list */
	struct list_head d_subdirs;	/* our children */

} __randomize_layout;
  • d_inode指向一个inode结构,inode和dentry共同描述了一个普通文件或目录文件
  • d_parent是指针,指向父dentry结构
  • d_hash是链接到dentry cache的hash链表
  • d_name成员保存的是文件或目录的名字,打开一个文件时,根据其名字搜寻目标文件。
  • d _sb指向关联的超级块(super block)的指针。超级块包含了文件系统的整体信息,例如块大小、挂载点和文件系统类型

9.获取指定inode节点

//这是一个用于获取指定inode节点的函数,它接收一个超级块和一个inode号作为参数,并返回一个指向对应inode节点的指针
static struct inode *tinyfs_iget(struct super_block *sb, int idx)
{
    	struct inode *inode;
    	struct file_blk *blk;

    	//为新的inode节点分配内存
    	inode = new_inode(sb);
    	//设置inode节点的索引号和超级块
    	inode->i_ino = idx;
    	inode->i_sb = sb;
    	//设置inode节点的操作函数
    	inode->i_op = &tinyfs_inode_ops;

    	//获取指定索引号的文件块
    	blk = &block[idx];

    	//根据文件块的类型设置inode节点的文件操作函数
    	if (S_ISDIR(blk->mode))
        	inode->i_fop = &tinyfs_dir_operations;
    	else if (S_ISREG(blk->mode))
        	inode->i_fop = &tinyfs_file_operations;

    	//inode->i_atime = inode->i_mtime = inode->i_ctime = time(NULL);
    	//设置inode节点的私有数据为文件块
    	inode->i_private = blk;

    	return inode;
}

函数tinyfs_iget接受一个超级块(sb)和一个索引号(idx)作为参数,并返回一个指向对应 inode 节点的指针。
函数在运行时创建一个新的inode节点并且将其索引号和超级块都设置成函数传递进来的参数,同时设置其对应操作函数,获取与索引号 idx 相关的文件块(struct file_blk)。

根据文件块的类型是文件还是目录设置 inode 节点的文件操作函数,最后将 inode 的私有数据设置为文件块 blk,这将允许 inode 访问与文件相关的数据。函数返回指向创建的 inode 节点的指针。

函数的作用是根据传递的索引号和超级块创建一个 inode 节点返回,并设置相应的操作函数,以便在文件系统操作中使用。

10.查找特定的子目录或文件

struct dentry *tinyfs_lookup(struct inode *parent_inode, struct dentry *child_dentry, unsigned int flags)
{
    	struct super_block *sb = parent_inode->i_sb;
    	struct file_blk *blk;
    	struct dir_entry *entry;
    	int i;

    	blk = (struct file_blk *)parent_inode->i_private;
    	entry = (struct dir_entry *)&blk->data[0];
    	for (i = 0; i < blk->dir_children; i++) {
        	if (!strcmp(entry[i].filename, child_dentry->d_name.name)) {
            		struct inode *inode = tinyfs_iget(sb, entry[i].idx);
            		struct file_blk *inner = (struct file_blk *)inode->i_private;
            		inode_init_owner(inode, parent_inode, inner->mode);
            		d_add(child_dentry, inode);
            		return NULL;
        		}
    	}

    	return NULL;
}

函数tinyfs_lookup接受三个参数:

  • parent_inode:父目录的 inode,这是在该目录中查找子目录或文件。
  • child_dentry:要查找的子目录或文件的目录项(dentry)。
  • flags:额外的标志。

函数在运行时先获取父目录的超级块 sb,以及与父目录关联的文件块 blk。因为在 tinyfs 文件系统中,每个目录都由一个文件块表示,文件块的信息存储在私有数据中。

从父目录的文件块 blk 中获取所有目录项(entry)的列表,这些目录项包括文件或子目录的名称以及相应的索引号。

遍历目录项列表,查找与 child_dentry 中给定的名称匹配的目录项。如果找到匹配的目录项,它会通过调用 tinyfs_iget 函数获取与匹配目录项关联的新的 inode。获取新 inode 关联的文件块 inner。
使用 inode_init_owner 函数初始化新 inode 的拥有者信息(用户 ID 和组 ID)以及文件模式。
调用d_add函数将新的 inode 与 child_dentry 相关联,以便 VFS 可以正确访问它。

tinyfs_lookup 函数允许 VFS 在 tinyfs 文件系统中查找和访问特定的子目录或文件。它在目录中查找匹配的目录项,如果找到,就为 VFS 提供与该目录项相关联的 inode。如果没有找到匹配的目录项,函数返回 NULL,表示未找到。用于处理文件和目录的查找和访问

11.删除目录

int tinyfs_rmdir(struct inode *dir, struct dentry *dentry)
{
    	struct inode *inode = dentry->d_inode;
    	struct file_blk *blk = (struct file_blk *)inode->i_private;

    	blk->busy = 0;
    	return simple_rmdir(dir, dentry);
}

函数tinyfs_rmdir的两个参数inode *dir 表示要删除目录的父目录的 inode 结构。dentry *dentry 表示要删除的目录的目录项 dentry

函数在运行时会获取要删除目录的 inode 结构。同时将目录的 inode 中的私有数据 i_private 转换为 struct file_blk 结构,以便访问目录的文件块信息。

做完这些工作后将目录的文件块的 busy 标志设置为 0,表示该文件块不再被使用,因为目录将被删除。调用 simple_rmdir 函数来执行实际的目录删除操作,确保目录为空并删除相关的目录项。

删除指定的目录。它首先将目录的文件块标记为不再使用,然后调用 simple_rmdir 函数来完成目录的实际删除操作

在 tinyfs 文件系统中,目录和文件均由文件块来表示,因此删除目录时,需要将其文件块的 busy 标志设置为 0 以标记不再使用,并且使用标准的 simple_rmdir 函数来确保目录为空,然后从目录中删除目录项。

(1)simple_rmdir 函数
int simple_rmdir(struct inode *dir, struct dentry *dentry)
{
	if (!simple_empty(dentry))
		return -ENOTEMPTY;

	drop_nlink(d_inode(dentry));
	simple_unlink(dir, dentry);
	drop_nlink(dir);
	return 0;
}
EXPORT_SYMBOL(simple_rmdir);

simple_rmdir 函数用于删除目录,并在删除前检查目录是否为空。如果目录不为空,函数返回错误码;否则,它递减相应的链接计数,执行实际的目录删除操作,然后返回成功。

12. 删除文件(或目录在这个tinyfs文件系统可以这么说)

int tinyfs_unlink(struct inode *dir, struct dentry *dentry)
{
    	int i;
    	struct inode *inode = dentry->d_inode;
    	struct file_blk *blk = (struct file_blk *)inode->i_private;
    	struct file_blk *pblk = (struct file_blk *)dir->i_private;
    	struct dir_entry *entry;

    	// 更新其上层目录
    	entry = (struct dir_entry *)&pblk->data[0];
    	for (i = 0; i < pblk->dir_children; i++) {
        	if (!strcmp(entry[i].filename, dentry->d_name.name)) {
            		int j;
            		for (j = i; j < pblk->dir_children - 1; j++) {
                		memcpy(&entry[j], &entry[j+1], sizeof(struct dir_entry));
            		}
            		pblk->dir_children --;
            		break;
        	}
    	}

    	blk->busy = 0;
    	return simple_unlink(dir, dentry);
}

函数tinyfs_unlink的两个参数inode *dir 表示包含要删除的文件或目录的父目录的 inode 结构。dentry *dentry 表示要删除的文件或目录的目录项 dentry。

函数在执行时获取到要删除的文件或目录的 inode 结构及该 inode 的私有数据中获取文件块的信息,其中包括了文件或目录的元数据。同时从父目录的 inode 中获取该目录的父目录的文件块信息。

设置一个处理目录项的指针。通过遍历父目录中的目录项,找到要删除的文件或目录的目录项。这是通过比较 dentry 的名字与父目录中的目录项的名字来实现的。

一旦找到了要删除的目录项,就会将其从父目录中的目录项数组中删除。这是通过将后续目录项向前移动一个位置来实现的。

更新父目录的目录项数量,将其减少1,表示删除了一个子目录或文件。函数将 blk 的 busy 标志设置为 0,以标记文件或目录不再被使用。

最后调用 simple_unlink 来实际删除文件或目录,并返回删除的结果。

函数用于删除文件或目录,它首先通过遍历父目录找到要删除的目录项,然后从父目录中删除该目录项,更新父目录的目录项数量,标记被删除的文件块不再使用,并最终执行实际的删除操作。

(1)simple_unlink函数
int simple_unlink(struct inode *dir, struct dentry *dentry)
{
	struct inode *inode = d_inode(dentry);

	inode->i_ctime = dir->i_ctime = dir->i_mtime = current_time(inode);
	drop_nlink(inode);
	dput(dentry);
	return 0;
}
EXPORT_SYMBOL(simple_unlink);

simple_unlink 函数用于执行删除操作,它首先更新相关的目录项和 inode 的元数据,然后减少 inode 的链接数,释放目录项的引用,并最终返回操作的结果。

13.inode操作

static struct inode_operations tinyfs_inode_ops = {
    	.create = tinyfs_create,
    	.lookup = tinyfs_lookup,
    	.mkdir = tinyfs_mkdir,
    	.rmdir = tinyfs_rmdir,
    	.unlink = tinyfs_unlink,
};

tinyfs_inode_ops 的结构包含了与文件系统中 inode 操作相关的函数指针。每个函数指针对应于不同的 inode 操作,例如创建文件、查找文件、创建目录、删除目录和删除文件。这些函数指针是文件系统中执行与 inode 相关操作的入口点,它们被用于 VFS(Virtual File System)层。

14. 初始化 tinyfs 文件系统的根目录

int tinyfs_fill_super(struct super_block *sb, void *data, int silent)
{
    	struct inode *root_inode;
    	int mode = S_IFDIR;

    	root_inode = new_inode(sb);
    	root_inode->i_ino = 1;
    	inode_init_owner(root_inode, NULL, mode);
    	root_inode->i_sb = sb;
    	root_inode->i_op = &tinyfs_inode_ops;
    	root_inode->i_fop = &tinyfs_dir_operations;
    	//root_inode->i_atime = root_inode->i_mtime = root_inode->i_ctime = (timespec64)time(NULL);

    	block[1].mode = mode;
    	block[1].dir_children = 0;
    	block[1].idx = 1;
    	block[1].busy = 1;
    	root_inode->i_private = &block[1];

    	sb->s_root = d_make_root(root_inode);
    	curr_count ++;

    	return 0;
}

函数tinyfs_fill_super 接受三个参数,

  • sb 是要填充的超级块,包含了有关文件系统的关键信息。
  • data 通常用于传递额外的文件系统特定的数据,这里没有使用它。
  • silent 是一个标志,通常用于指示是否应禁用错误消息输出。如果 silent 被设置为非零值,表示不会产生错误消息。

函数在执行时创建了根目录的 inode 结构 root_inode这个 inode 表示文件系统的根目录,因此 i_ino 设置为 1,它是超级块中的第一个 inode。

inode_init_owner 函数初始化 root_inode 的权限和所有者信息。inode_init_owner 函数设置了 i_uid、i_gid 以及 i_mode 等属性,以确保正确的权限和所有者信息。确保根目录正确地继承了文件系统的属性。

设置 root_inode 的操作函数:将 tinyfs_inode_ops 结构分配给 root_inode 的 i_op 成员

设置根目录的文件操作函数为 tinyfs_dir_operations,表示根目录是一个目录

初始化根目录文件块:

  1. 设置根目录的文件块属性,包括文件类型(S_IFDIR 表示目录)、
  2. 目录中的子项数量(dir_children),
  3. 索引号(idx)idx 设置为 1,以表示它是超级块中的第一个文件块
  4. 繁忙状态(busy)
  5. 将根目录的私有数据指针设置为指向文件块数组中索引为 1 的文件块。这个私有数据指针用于关联根目录的 inode 节点和文件块,以便文件系统能够访问根目录的数据。

将 root_inode 分配给超级块 sb->s_root,表示它是文件系统的根。在文件系统加载后,根目录将成为文件系统的根。通过递增 curr_count 变量增加文件系统中的当前文件数。

函数在文件系统加载时被调用,用于初始化文件系统的超级块和根目录。完成后,它返回 0,表示初始化成功,文件系统已准备好供使用

(1)d_make_root函数
struct dentry *d_make_root(struct inode *root_inode)
{
	struct dentry *res = NULL;

	if (root_inode) {
		res = __d_alloc(root_inode->i_sb, NULL);
		if (res)
			d_instantiate(res, root_inode);
		else
			iput(root_inode);
	}
	return res;
}
EXPORT_SYMBOL(d_make_root);

d_make_root 函数是用于创建文件系统的根 dentry,并将其与根目录的 inode 相关联的工具函数。这是构建文件系统的重要一步,它建立了文件系统内部的数据结构

15.挂载文件系统

static struct dentry *tinyfs_mount(struct file_system_type *fs_type, int flags, const char *dev_name, void *data)
{
    	return mount_nodev(fs_type, flags, data, tinyfs_fill_super);
}

tinyfs_mount 函数接受下面参数

  • fs_type:指向 struct file_system_type 结构的指针,用于描述文件系统的类型和挂载行为。
  • flags:挂载选项的标志。
  • dev_name:设备名称,通常是 NULL(因为 TinyFS 是无设备的)。
  • data:挂载时传递的数据,通常为空。

将 TinyFS 文件系统挂载到 Linux 内核中的关键部分。它通过调用内核提供的 mount_nodev 函数来执行挂载操作,并返回一个指向根目录的 dentry 指针,这样用户空间的应用程序就可以访问 TinyFS 文件系统

(1)mount_nodev函数
struct dentry *mount_nodev(struct file_system_type *fs_type,
	int flags, void *data,
	int (*fill_super)(struct super_block *, void *, int))
{
	int error;
	struct super_block *s = sget(fs_type, NULL, set_anon_super, flags, NULL);

	if (IS_ERR(s))
		return ERR_CAST(s);

	error = fill_super(s, data, flags & SB_SILENT ? 1 : 0);
	if (error) {
		deactivate_locked_super(s);
		return ERR_PTR(error);
	}
	s->s_flags |= SB_ACTIVE;
	return dget(s->s_root);
}
EXPORT_SYMBOL(mount_nodev);

mount_nodev 函数负责创建超级块、调用填充超级块的函数,并返回根目录的 dentry,以完成文件系统的挂载过程。

16.卸载文件系统

static void tinyfs_kill_superblock(struct super_block *sb)
{
    	kill_anon_super(sb);
}

tinyfs_kill_superblock 函数接受一个指向超级块(struct super_block)的指针作为参数。
这一行代码调用 kill_anon_super 函数,将超级块 sb 标记为匿名超级块并销毁它。匿名超级块是一种无设备文件系统的超级块,通常在内存中创建,而不是从硬盘或其他媒体加载。

在卸载文件系统时,这个函数的目的是清理超级块的资源,释放内存并确保超级块不再被使用。

(1)kill_anon_super函数
void kill_anon_super(struct super_block *sb)
{
	dev_t dev = sb->s_dev;
	generic_shutdown_super(sb);
	free_anon_bdev(dev);
}

EXPORT_SYMBOL(kill_anon_super);

kill_anon_super 函数的作用是销毁匿名超级块,包括释放超级块的资源以及与之关联的匿名设备资源。

17.定义

struct file_system_type tinyfs_fs_type = {
    	.owner = THIS_MODULE,
    	.name = "tinyfs",
    	.mount = tinyfs_mount,
    	.kill_sb = tinyfs_kill_superblock,
};

struct file_system_type 类型的对象 tinyfs_fs_type,它描述了一个文件系统类型的特征,包括文件系统的名称、挂载函数和销毁超级块函数。

  • .owner = THIS_MODULE:指定了文件系统类型对象的所有者。THIS_MODULE 表示将模块自身作为文件系统类型的所有者。

  • .name = "tinyfs":指定了文件系统类型的名称,这里是 “tinyfs”,表示文件系统的名称是 TinyFS。

  • .mount = tinyfs_mount:指定了挂载函数,即在将文件系统挂载到特定挂载点时应调用的函数。tinyfs_mount 函数在挂载时负责创建和初始化超级块,用于管理文件系统的状态。

  • .kill_sb = tinyfs_kill_superblock:指定了销毁超级块函数,即在卸载文件系统时应调用的函数。tinyfs_kill_superblock 函数负责清理和释放超级块的资源。

tinyfs_fs_type 对象用于定义 TinyFS 文件系统类型的特征,包括它的名称以及挂载和卸载文件系统时应执行的操作。它通常会在内核模块初始化时使用,以便 Linux 内核知道如何处理 TinyFS 文件系统。

18.卸载模块

static void tinyfs_exit(void)
{
    	unregister_filesystem(&tinyfs_fs_type);
}

该卸载模块函数调用了unregister_filesystem函数

int unregister_filesystem(struct file_system_type * fs)
{
	struct file_system_type ** tmp;

	write_lock(&file_systems_lock);
	tmp = &file_systems;
	while (*tmp) {
		if (fs == *tmp) {
			*tmp = fs->next;
			fs->next = NULL;
			write_unlock(&file_systems_lock);
			synchronize_rcu();
			return 0;
		}
		tmp = &(*tmp)->next;
	}
	write_unlock(&file_systems_lock);

	return -EINVAL;
}

该函数用于注销(unregister)一个文件系统类型。它从已注册的文件系统类型列表中找到并移除指定的文件系统类型,具体详解可看模块初始化函数

完整代码

// tinyfs.h
#define MAXLEN 8
#define MAX_FILES    32
#define MAX_BLOCKSIZE  512

// 定义每一个目录项的格式
struct dir_entry {
    char filename[MAXLEN];
    uint8_t idx;
};

// 定义每一个文件的格式。
struct file_blk {
    uint8_t busy;
    mode_t mode;
    uint8_t idx;

    union {
        uint8_t file_size;
        uint8_t dir_children;
    };
    char data[0];
};

// OK,下面的block数组所占据的连续内存就是我的tinyfs的介质,每一个元素代表一个文件。
// struct file_blk block[MAX_FILES+1];
// tinyfs.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <uapi/linux/time.h>

#include "tinyfs.h"

struct file_blk block[MAX_FILES+1];
int curr_count = 0; // 全局变量

// 获得一个尚未使用的文件块,保存新创建的文件或者目录
//该函数用于查找block数组中第一个busy属性为0的元素,并返回该元素的下标
static int get_block(void)
{	
	int i;

	// 就是一个遍历,但实现快速。
	for (i = 2; i < MAX_FILES; i++) {//数组下标从2开始遍历是因为该程序将下标0和下标1分别预留给了根目录和当前目录
        	if (!block[i].busy) {	//如果当前元素的busy属性为0,则将其busy属性置为1,表示已被占用,并返回该元素的下标
            	block[i].busy = 1;
            	return i;
        	}
    	}
    	return -1;					//如果数组中所有元素的busy属性均为1,则返回-1,表示数组已满,无法再分配新的元素
}

static struct inode_operations tinyfs_inode_ops;
// 读取目录的实现
static int tinyfs_iterate(struct file *filp, struct dir_context *dirent)
{
    	loff_t pos;
	filldir_t filldir;
    	struct file_blk *blk;
    	struct dir_entry *entry;
    	int i;

    	pos = filp->f_pos;
	filldir = dirent->actor;
    	if (pos)
        	return 0;

    	blk = (struct file_blk *)filp->f_inode->i_private;

    	if (!S_ISDIR(blk->mode)) {
        	return -ENOTDIR;
    	}

    	// 循环获取一个目录的所有文件的文件名
    	entry = (struct dir_entry *)&blk->data[0];
    	for (i = 0; i < blk->dir_children; i++) {
        	(*filldir)(dirent, entry[i].filename, MAXLEN, pos, entry[i].idx, DT_UNKNOWN);
        	filp->f_pos += sizeof(struct dir_entry);
        	pos += sizeof(struct dir_entry);
    	}

    	return 0;
}

// read实现
ssize_t tinyfs_read(struct file * filp, char __user * buf, size_t len, loff_t *ppos)
{	//文件结构体指针 filp,用户缓冲区 buf,要读取的字节数 len,以及文件偏移量 ppos
    	struct file_blk *blk;
    	char *buffer;

    	blk = (struct file_blk *)filp->f_path.dentry->d_inode->i_private;
    	if (*ppos >= blk->file_size)			//检查文件偏移量是否超出文件大小
        	return 0;

    	buffer = (char *)&blk->data[0];			//将 blk 中的数据缓冲区的指针存储在 buffer 变量中
    	len = min((size_t) blk->file_size, len);//将 len 和文件大小中的较小值存储在 len 变量中

    	if (copy_to_user(buf, buffer, len)) {	// copy_to_user 函数:将文件数据从内核空间复制到用户空间缓冲区 buf 中
        	return -EFAULT;
    	}
    	*ppos += len;							//更新文件偏移量 ppos

    	return len;								//返回读取的字节数 len
}

// write实现
ssize_t tinyfs_write(struct file * filp, const char __user * buf, size_t len, loff_t * ppos)
{
    	struct file_blk *blk;		// 文件块结构体指针
    	char *buffer;				// 缓冲区指针

    	blk = filp->f_path.dentry->d_inode->i_private;	 // 获取文件的私有数据,即文件块结构体指针

    	buffer = (char *)&blk->data[0];		// 将文件块结构体中的数据指针赋值给缓冲区指针
    	buffer += *ppos;					// 将缓冲区指针移动到偏移量位置

    	if (copy_from_user(buffer, buf, len)) {	// 将用户空间的数据拷贝到内核缓冲区
        	return -EFAULT;						// 拷贝失败,返回错误码
    	}
    	*ppos += len;							// 偏移量增加
    	blk->file_size = *ppos;					// 更新文件块结构体中的文件大小

    	return len;								// 返回写入的字节数
}

const struct file_operations tinyfs_file_operations = {
    	.read = tinyfs_read,
    	.write = tinyfs_write,
};

const struct file_operations tinyfs_dir_operations = {
    	.owner = THIS_MODULE,
    	.iterate = tinyfs_iterate,
};

// 创建文件的实现
static int tinyfs_do_create(struct inode *dir, struct dentry *dentry, umode_t mode)
{
    	struct inode *inode;
    	struct super_block *sb;
    	struct dir_entry *entry;
    	struct file_blk *blk, *pblk;
    	int idx;

    	sb = dir->i_sb;

    	if (curr_count >= MAX_FILES) {//检查当前目录下的文件数是否已经达到了最大值
        	return -ENOSPC;			//如果是,则返回错误码-ENOSPC,表示磁盘空间已满
    	}

    	if (!S_ISDIR(mode) && !S_ISREG(mode)) {//检查要创建的是否是文件或是目录
        	return -EINVAL;
    	}

    	inode = new_inode(sb);
    	if (!inode) {
        	return -ENOMEM;
    	}

    	inode->i_sb = sb;
    	inode->i_op = &tinyfs_inode_ops;
    	//inode->i_atime = inode->i_mtime = inode->i_ctime = time(NULL);

    	idx = get_block(); // 获取一个空闲的文件块保存新文件

    	blk = &block[idx];
    	inode->i_ino = idx;
    	blk->mode = mode;
    	curr_count ++;

    	if (S_ISDIR(mode)) {//创建目录
        	blk->dir_children = 0;
        	inode->i_fop = &tinyfs_dir_operations;
    	} else if (S_ISREG(mode)) {//创建文件
        	blk->file_size = 0;
        	inode->i_fop = &tinyfs_file_operations;
    	}

    	inode->i_private = blk;
    	pblk = (struct file_blk *)dir->i_private;
		//在当前目录的文件块中添加新的目录项,记录新文件的inode编号和文件名。
    	entry = (struct dir_entry *)&pblk->data[0];
    	entry += pblk->dir_children;
    	pblk->dir_children ++;

    	entry->idx = idx;
    	strcpy(entry->filename, dentry->d_name.name);

    	//将新的inode和dentry添加到VFS的inode链表中,这是VFS中穿针引线的关键步骤
    	inode_init_owner(inode, dir, mode); 
    	d_add(dentry, inode);
		//返回0表示创建文件或目录成功
    	return 0;
}

//创建目录
static int tinyfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
{
    	//其中S_IFDIR是文件类型标志,表示创建的是一个目录类型的文件。mode是文件权限标志,可以通过传递不同的权限标志来设置创建的目录的权限
    	return tinyfs_do_create(dir, dentry, S_IFDIR | mode);
}

//创建文件
static int tinyfs_create(struct inode *dir, struct dentry *dentry, umode_t mode, bool excl)
{
    	//其中mode是文件权限标志,可以通过传递不同的权限标志来设置创建的文件的权限
    	return tinyfs_do_create(dir, dentry, mode);
}

//这是一个用于获取指定inode节点的函数,它接收一个超级块和一个inode号作为参数,并返回一个指向对应inode节点的指针
static struct inode *tinyfs_iget(struct super_block *sb, int idx)
{
    	struct inode *inode;
    	struct file_blk *blk;

    	//为新的inode节点分配内存
    	inode = new_inode(sb);
    	//设置inode节点的索引号和超级块
    	inode->i_ino = idx;
    	inode->i_sb = sb;
    	//设置inode节点的操作函数
    	inode->i_op = &tinyfs_inode_ops;

    	//获取指定索引号的文件块
    	blk = &block[idx];

    	//根据文件块的类型设置inode节点的文件操作函数
    	if (S_ISDIR(blk->mode))
        	inode->i_fop = &tinyfs_dir_operations;
    	else if (S_ISREG(blk->mode))
        	inode->i_fop = &tinyfs_file_operations;

    	//inode->i_atime = inode->i_mtime = inode->i_ctime = time(NULL);
    	//设置inode节点的私有数据为文件块
    	inode->i_private = blk;

    	return inode;
}

struct dentry *tinyfs_lookup(struct inode *parent_inode, struct dentry *child_dentry, unsigned int flags)
{
    	struct super_block *sb = parent_inode->i_sb;
    	struct file_blk *blk;
    	struct dir_entry *entry;
    	int i;

    	blk = (struct file_blk *)parent_inode->i_private;
    	entry = (struct dir_entry *)&blk->data[0];
    	for (i = 0; i < blk->dir_children; i++) {
        	if (!strcmp(entry[i].filename, child_dentry->d_name.name)) {
            		struct inode *inode = tinyfs_iget(sb, entry[i].idx);
            		struct file_blk *inner = (struct file_blk *)inode->i_private;
            		inode_init_owner(inode, parent_inode, inner->mode);
            		d_add(child_dentry, inode);
            		return NULL;
        		}
    	}

    	return NULL;
}

int tinyfs_rmdir(struct inode *dir, struct dentry *dentry)
{
    	struct inode *inode = dentry->d_inode;
    	struct file_blk *blk = (struct file_blk *)inode->i_private;

    	blk->busy = 0;
    	return simple_rmdir(dir, dentry);
}

int tinyfs_unlink(struct inode *dir, struct dentry *dentry)
{
    	int i;
    	struct inode *inode = dentry->d_inode;
    	struct file_blk *blk = (struct file_blk *)inode->i_private;
    	struct file_blk *pblk = (struct file_blk *)dir->i_private;
    	struct dir_entry *entry;

    	// 更新其上层目录
    	entry = (struct dir_entry *)&pblk->data[0];
    	for (i = 0; i < pblk->dir_children; i++) {
        	if (!strcmp(entry[i].filename, dentry->d_name.name)) {
            		int j;
            		for (j = i; j < pblk->dir_children - 1; j++) {
                		memcpy(&entry[j], &entry[j+1], sizeof(struct dir_entry));
            		}
            		pblk->dir_children --;
            		break;
        	}
    	}

    	blk->busy = 0;
    	return simple_unlink(dir, dentry);
}

static struct inode_operations tinyfs_inode_ops = {
    	.create = tinyfs_create,
    	.lookup = tinyfs_lookup,
    	.mkdir = tinyfs_mkdir,
    	.rmdir = tinyfs_rmdir,
    	.unlink = tinyfs_unlink,
};

int tinyfs_fill_super(struct super_block *sb, void *data, int silent)
{
    	struct inode *root_inode;
    	int mode = S_IFDIR;

    	root_inode = new_inode(sb);
    	root_inode->i_ino = 1;
    	inode_init_owner(root_inode, NULL, mode);
    	root_inode->i_sb = sb;
    	root_inode->i_op = &tinyfs_inode_ops;
    	root_inode->i_fop = &tinyfs_dir_operations;
    	//root_inode->i_atime = root_inode->i_mtime = root_inode->i_ctime = (timespec64)time(NULL);

    	block[1].mode = mode;
    	block[1].dir_children = 0;
    	block[1].idx = 1;
    	block[1].busy = 1;
    	root_inode->i_private = &block[1];

    	sb->s_root = d_make_root(root_inode);
    	curr_count ++;

    	return 0;
}

static struct dentry *tinyfs_mount(struct file_system_type *fs_type, int flags, const char *dev_name, void *data)
{
    	return mount_nodev(fs_type, flags, data, tinyfs_fill_super);
}

static void tinyfs_kill_superblock(struct super_block *sb)
{
    	kill_anon_super(sb);
}

struct file_system_type tinyfs_fs_type = {
    	.owner = THIS_MODULE,
    	.name = "tinyfs",
    	.mount = tinyfs_mount,
    	.kill_sb = tinyfs_kill_superblock,
};

static int tinyfs_init(void)
{
    	int ret;

    	memset(block, 0, sizeof(block));
    	ret = register_filesystem(&tinyfs_fs_type);
    	if (ret)
        	printk("register tinyfs failed\n");

    	return ret;
}

static void tinyfs_exit(void)
{
    	unregister_filesystem(&tinyfs_fs_type);
}

module_init(tinyfs_init);
module_exit(tinyfs_exit);

MODULE_LICENSE("GPL");

运行结果
请添加图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言是一种高级编程语言,广泛应用于软件开发和嵌入式系统中。下面是利用C语言的一些方法: 首先,可以使用C语言进软件开发。通过学习C语言的语法和特性,可以编写各种应用程序,如操作系统、游戏、图形界面等。C语言拥有丰富的库函数和强大的控制结构,可以帮助我们实现复杂功能并提高程序的效率。 其次,C语言还可以用于嵌入式系统的开发。嵌入式系统通常是指嵌入到其他设备中的计算机系统,如智能手机、家电、汽车等。C语言的优势在于它可以直接访问硬件,具有高效的执速度和较小的内存占用。因此,使用C语言可以为嵌入式系统开发出高性能和高可靠性的软件。 此外,C语言也可以用于编写网络程序。通过网络编程接口,我们可以使用C语言创建客户端和服务器端应用程序。C语言提供了一些库函数,如套接字库,可以方便地进网络通信。利用C语言编写网络程序,可以实现各种功能,如网页浏览器、邮件客户端、即时通讯等。 最后,C语言还可以用于科学计算。C语言提供了丰富的库函数,如数学库和算法库,可以进科学计算和数据处理。我们可以使用C语言编写数值计算程序、数据分析工具等,以满足科学研究和工程实践的需求。 总之,利用C语言可以进软件开发、嵌入式系统开发、网络编程和科学计算。通过学习和掌握C语言,我们可以开发出高效、可靠和功能丰富的应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值