专栏首页原创分享虚拟文件系统源码解析之初始化(基于linux1.2.13)

虚拟文件系统源码解析之初始化(基于linux1.2.13)

从main函数开始,直到虚拟文件系统的初始化,路径是init()->setup()->syssetup();sys_setup主要是注册了虚拟文件系统下面所有的文件系统。然后挂载根文件系统。下面是初始化代码。

asmlinkage int sys_setup(void)
{
	static int callable = 1;

	if (!callable)
		return -1;
	callable = 0;

	device_setup();

#ifdef CONFIG_MINIX_FS
	register_filesystem(&(struct file_system_type)
		{minix_read_super, "minix", 1, NULL});
#endif

#ifdef CONFIG_EXT_FS
	register_filesystem(&(struct file_system_type)
		{ext_read_super, "ext", 1, NULL});
#endif
......
mount_root();
}

下面先看一下基本的数据结构。

struct file_system_type {
	struct super_block *(*read_super) (struct super_block *, void *, int);
	char *name;
	int requires_dev;
	struct file_system_type * next;
};

这是一个具体文件系统在虚拟文件系统注册时的表示结构。然后看一下注册文件系统的函数。

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

	if (!fs)
		return -EINVAL;
	if (fs->next)
		return -EBUSY;
	// tmp是二级指针,指向文件系统链表的头指针的地址
	tmp = &file_systems;
	// 遍历链表,直到尾部,插入新的节点
	while (*tmp) {
		// 判断是否已经注册了该文件系统
		if (strcmp((*tmp)->name, fs->name) == 0)
			return -EBUSY;
		// 指向当前节点的next域的地址,*tmp得到下一个被比较的节点
		tmp = &(*tmp)->next;
	}
	// 利用二级指针指针修改next域的内容,不需要使用->next = fs的形式
	*tmp = fs;
	return 0;
}

就是把一个file_system_type结构体插入一个链表中。注册文件系统其实只是构建一个单链表。接着看挂载根文件系统。这里大致分析一下流程。有时间再详细说。

void mount_root(void)
{
	struct file_system_type * fs_type;
	struct super_block * sb;
	struct inode * inode, d_inode;
	struct file filp;
	int retval;
	// 清空超级块数组
	memset(super_blocks, 0, sizeof(super_blocks));
#ifdef CONFIG_BLK_DEV_FD
	if (MAJOR(ROOT_DEV) == FLOPPY_MAJOR) {
		printk(KERN_NOTICE "VFS: Insert root floppy and press ENTER\n");
		wait_for_keypress();
	}
#endif

	memset(&filp, 0, sizeof(filp));
	memset(&d_inode, 0, sizeof(d_inode));
	// 根设备号
	d_inode.i_rdev = ROOT_DEV;
	filp.f_inode = &d_inode;
	// 只读方式挂载
	if ( root_mountflags & MS_RDONLY)
		filp.f_mode = 1; /* read only */
	else
		filp.f_mode = 3; /* read write */
	// 暂时忽略
	retval = blkdev_open(&d_inode, &filp);
	if(retval == -EROFS){
		root_mountflags |= MS_RDONLY;
		filp.f_mode = 1;
		retval = blkdev_open(&d_inode, &filp);
	}

	for (fs_type = file_systems ; fs_type ; fs_type = fs_type->next) {
		if(retval)
			break;
		// 没有关联到设备则不需要往下执行,有些文件系统是没有对应的底层设备的
		if (!fs_type->requires_dev)
			continue;
		// 读根设备的超级块,设备的第一扇区是分区表,接着是超级块
		sb = read_super(ROOT_DEV,fs_type->name,root_mountflags,NULL,1);
		// 读取成功
		if (sb) {
			// 根节点
			inode = sb->s_mounted;
			inode->i_count += 3 ;	/* NOTE! it is logically used 4 times, not 1 */
			sb->s_covered = inode;
			sb->s_flags = root_mountflags;
			// 当前进程(init进程)的根目录和工作目录设置为根节点
			current->fs->pwd = inode;
			current->fs->root = inode;
			printk ("VFS: Mounted root (%s filesystem)%s.\n",
				fs_type->name,
				(sb->s_flags & MS_RDONLY) ? " readonly" : "");
			// 直接返回,即第一个读取成功的文件系统成为根文件系统
			return;
		}
	}
	panic("VFS: Unable to mount root fs on %02x:%02x",
		MAJOR(ROOT_DEV), MINOR(ROOT_DEV));
}

读取根设备的超级块内容,如果成功,则成为根文件系统。并设置当前init进程的工作目录和根目录是根文件系统的根节点对应的inode。我们看看怎么读取超级块的。

// 读设备对应的超级块
static struct super_block * read_super(dev_t dev,char *name,int flags,
				       void *data, int silent)
{
	struct super_block * s;
	struct file_system_type *type;

	if (!dev)
		return NULL;
	check_disk_change(dev);
	// 有则直接返回,初始化的时候还没有
	s = get_super(dev);
	if (s)
		return s;
	// 否则根据name在文件系统链表中(在系统初始化时建立的)找到对应的文件系统节点,里面有一个read_super函数
	if (!(type = get_fs_type(name))) {
		printk("VFS: on device %d/%d: get_fs_type(%s) failed\n",
						MAJOR(dev), MINOR(dev), name);
		return NULL;
	}
	// 在超级块数组中找到一个slot
	for (s = 0+super_blocks ;; s++) {
		if (s >= NR_SUPER+super_blocks)
			return NULL;
		if (!s->s_dev)
			break;
	}
	// 赋值给超级块节点的字段
	s->s_dev = dev;
	s->s_flags = flags;
	// 调底层的文件系统到硬盘去读取超级块内容,比如ext文件系统,ext2文件系统等等都定义了该函数。
	if (!type->read_super(s,data, silent)) {
		s->s_dev = 0;
		return NULL;
	}
	s->s_dev = dev;
	s->s_covered = NULL;
	s->s_rd_only = 0;
	s->s_dirt = 0;
	s->s_type = type;
	return s;
}

主要是获取一个超级块结构体,然后调底层文件系统的read_super。然后设置超级块的属性。这里分析一下ext文件系统的read_super。

struct super_block *ext_read_super(struct super_block *s,void *data,
				   int silent)
{
	struct buffer_head *bh;
	struct ext_super_block *es;
	int dev = s->s_dev,block;

	lock_super(s);
	set_blocksize(dev, BLOCK_SIZE);
	// 读取设备的内容,即超级块的内容
	if (!(bh = bread(dev, 1, BLOCK_SIZE))) {
		s->s_dev=0;
		unlock_super(s);
		printk("EXT-fs: unable to read superblock\n");
		return NULL;
	}
	// 文件系统的一些属性
	es = (struct ext_super_block *) bh->b_data;
	s->s_blocksize = 1024;
	s->s_blocksize_bits = 10;
	s->u.ext_sb.s_ninodes = es->s_ninodes;
	s->u.ext_sb.s_nzones = es->s_nzones;
	s->u.ext_sb.s_firstdatazone = es->s_firstdatazone;
	s->u.ext_sb.s_log_zone_size = es->s_log_zone_size;
	s->u.ext_sb.s_max_size = es->s_max_size;
	s->s_magic = es->s_magic;
	s->u.ext_sb.s_firstfreeblocknumber = es->s_firstfreeblock;
	s->u.ext_sb.s_freeblockscount = es->s_freeblockscount;
	s->u.ext_sb.s_firstfreeinodenumber = es->s_firstfreeinode;
	s->u.ext_sb.s_freeinodescount = es->s_freeinodescount;
	brelse(bh);
	if (s->s_magic != EXT_SUPER_MAGIC) {
		s->s_dev = 0;
		unlock_super(s);
		if (!silent)
			printk("VFS: Can't find an extfs filesystem on dev 0x%04x.\n",
				   dev);
		return NULL;
	}
	if (!s->u.ext_sb.s_firstfreeblocknumber)
		s->u.ext_sb.s_firstfreeblock = NULL;
	else
		if (!(s->u.ext_sb.s_firstfreeblock = bread(dev,
			s->u.ext_sb.s_firstfreeblocknumber, BLOCK_SIZE))) {
			printk("ext_read_super: unable to read first free block\n");
			s->s_dev = 0;
			unlock_super(s);
			return NULL;
		}
	if (!s->u.ext_sb.s_firstfreeinodenumber)
		s->u.ext_sb.s_firstfreeinodeblock = NULL;
	else {
		block = 2 + (s->u.ext_sb.s_firstfreeinodenumber - 1) / EXT_INODES_PER_BLOCK;
		if (!(s->u.ext_sb.s_firstfreeinodeblock = bread(dev, block, BLOCK_SIZE))) {
			printk("ext_read_super: unable to read first free inode block\n");
			brelse(s->u.ext_sb.s_firstfreeblock);
			s->s_dev = 0;
			unlock_super (s);
			return NULL;
		}
	}
	unlock_super(s);
	/* set up enough so that it can read an inode */
	s->s_dev = dev;
	// 操作函数集
	s->s_op = &ext_sops;
	// 读取根节点
	if (!(s->s_mounted = iget(s,EXT_ROOT_INO))) {
		s->s_dev=0;
		printk("EXT-fs: get root inode failed\n");
		return NULL;
	}
	return s;
}

主要工作是读取硬盘的超级块信息到内存,最后读取根节点对应的inode。虚拟文件系统初始化就分析到这,下一篇分析虚拟文件系统的使用。

本文分享自微信公众号 - 编程杂技(theanarkh),作者:theanarkh

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-12-14

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 网卡收到一个数据包的时候,是如何传给应用层的(上)

    这里以3c501网卡为例,每个设备对应一个device的结构体,下面代码即对3c501网卡的数据结构进行初始化,包括发送函数,注册中断回调,mac头长度等。

    theanarkh
  • 深入浅出文件系统原理之根文件系统挂载(基于linux0.11)

    看完文件系统的基础数据结构。我们接着解析的根文件系统的挂载,因为这是文件系统被使用的起点。根文件系统的挂载是在操作系统初始化的时候进行的。对应的函数是mount...

    theanarkh
  • 系统调用之mmap源码分析(基于linux1.2.13)

    mmap涉及到操作系统底层很多知识,目前粗略介绍一下大概的逻辑,等深入理解后再继续。操作系统用vma链表管理内存,mmap就是申请一个新的vma供进程使用。可以...

    theanarkh
  • 逻辑卷管理器LVM

    版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/gon...

    魏晓蕾
  • SCOM 2012 R2监控Microsoft Azure服务(2)配置Azure监控

    上一篇文章介绍了如何添加Azure管理包,并配置Azure订阅进行管理。但配置完成后,SCOM还无法对Azure的云、存储、虚拟机进行监控,那么本章内容就会进行...

    李珣
  • 编程小白 | 每日一练(9)

    这道理放在编程上也一并受用。在编程方面有着天赋异禀的人毕竟是少数,我们大多数人想要从编程小白进阶到高手,需要经历的是日积月累的学习,那么如何学习呢?当然是每天都...

    闫小林
  • 201403-1

    试题编号: 201403-1 试题名称: 相反数 时间限制: 1.0s 内存限制: 256.0MB 问题描述: 问题描述 ...

    用户4492257
  • HDU 1711 Number Sequence(KMP裸题,板子题,有坑点)

    Number Sequence Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 32768/3...

    Angel_Kitty
  • uva------(11464)Even Parity

    D Even Parity Input: Standard Input Output: Standard Output We...

    Gxjun
  • 腾讯云MongoDB多机房部署场景下就近访问原理详解

    提示:公众号展示代码会自动折行,建议横屏阅读 多机房容灾是存储系统领域非常重要的课题。本文将从内核代码层面,介绍腾讯云MongoDB数据库系统(CMongo)...

    腾讯数据库技术

扫码关注云+社区

领取腾讯云代金券