Linux 内核源代码情景分析(四)

news2024/10/7 10:25:45

在这里插入图片描述

系列文章目录


Linux 内核设计与实现
深入理解 Linux 内核
Linux 设备驱动程序
Linux设备驱动开发详解
深入理解Linux虚拟内存管理
Linux 内核源代码情景分析(一)
Linux 内核源代码情景分析(二)
Linux 内核源代码情景分析(三)
Linux 内核源代码情景分析(四)


文章目录

  • 系列文章目录
  • 5.4 文件系统的安装和拆卸
    • 1、sys_mount
      • (1)mount flags
      • (2)do_mount
        • ① file_system_type
        • ② get_fs_type
        • ③ 文件系统定义
        • ④ get_sb_bdev
          • ⑴ ext2_read_inode
          • ⑵ init_special_inode
  • To be continued


5.4 文件系统的安装和拆卸

    在一个块设备(见本书下册 “设备驱动” 一章)上按一定的格式建立起文件系统的时候,或者系统引导之初,设备上的文件和节点都还是不可访问的。也就是说,还不能按一定的路径名访问其中特定的节点或文件(虽然作为 “设备” 是可访问的)。只有把它 “安装” 到计算机系统的文件系统中某个节点上,才能使设备上的文件和节点成为可访问的。经过安装以后,设备上的 “文件系统” 就成为整个文件系统的一部分,或者说一个子系统。一般而言,文件系统的结构就好像一棵倒立的树,不过由于可能存在着的节点间的 “连接” 和 “符号连接” 而并不一定是严格的图论意义上的 “树” 。最初时, 整个系统中只有一个节点,那就是整个文件系统的 “根” 节点 “/” ,这个节点存在于内存中,而不在任何具体的设备上。系统在初始化时将一个 “根设备” 安装到节点 “/” 上,这个设备上的文件系统就成了整个系统中原始的、基本的文件系统(所以才称为根设备)。此后,就可以由超级用户进程通过系统调用 mount() 把其他的子系统安装到已经存在于文件系统中的空闲节点上,使整个文件系统得以扩展,当不再需要使用某个子系统时,或者在关闭系统之前,则通过系统调用 umount() 把已经安装的设备逐个 “拆卸” 下来。

    系统调用 mount() 将一个可访问的块设备安装到一个可访问的节点上。所谓 “可访问” 是指该节点或文件已经存在于已安装的文件系统中,可以通过路径名寻访。Unix(以及Linux)将设备看作一种特殊的文件,并在文件系统中有代表着具体设备的节点,称为 “设备文件” ,通常都在目录 “/dev” 中。例如 IDE 硬盘上的第一个分区就是 /dev/hda1 。 每个设备文件实际上只是一个索引节点,节点中提供了设备的 “设备号” ,由 “主设备号” 和 “次设备号” 两部分构成。其中主设备号指明了设备的种类,或者更确切地说是指明了应该使用哪一组驱动程序。同一个物理的设备,如果有两组不同的驱动程序,在逻辑上就被视作两种不同的设备而在文件系统中有两个不同的 “设备文件”。次设备号则指明该设备是同种设备中的第几个。所以,只要找到代表着某个设备的索引节点,就知道该怎样读/写这个设备了。 既然是一个 “可访问” 的块设备,那为什么还要安装呢?答案是在安装之前可访问的只是这个设备, 通常是作为一个线性的无结构的字节流来访问的,称为 “原始设备” (raw device);而设备上的文件系统则是不可访问的。经过安装以后,设备上的文件系统就成为可访问的了。

    读者也许已经想到了一个问题,那就是:系统调用 mount() 要求被安装的块设备在安装之前就是可访问的,那根设备怎么办?在安装根设备之前,系统中只有一个节点,根本就不存在可访问的块设备啊。是的,根设备不能通过系统调用 mount() 来安装。事实上,根据情况的不同,内核中有三个函数是用于设备安装的,那就是 sys_mount()mount_root() 以及 kem_mount() 。我们先来看 sys_mount(),这就是系统调用 mount() 在内核中的实现,其代码在 fs/super.c 中。

1、sys_mount

// fs/super.c
// 参数dev_name为待安装设备的路径名;dir_name则是安装点(空闲目录节点)的路径名;type是
// 表示文件系统类型(即格式)的字符串,如 “ext2"、 “iso9660” 等。此外,flags为安装模式,有关的
// 标志位定义于 include/linux/fs.h
//
// 最后,指针 data 指向用于安装的附加信息,由不同文件系统的驱动程序自行加以解释,所以其类
// 型为 void 指针
asmlinkage long sys_mount(char * dev_name, char * dir_name, char * type,
			  unsigned long flags, void * data)
{
	int retval;
	unsigned long data_page;
	unsigned long type_page;
	unsigned long dev_page;
	char *dir_page;

// 代码中通过 getname() 和 copy_mount_options() 将字符串形式或结构形式的参数值从用户空间复制
// 到系统空间。这些参数值的长度均以一个页面为限,但是 getname() 在复制时遇到字符串结尾符 “\0” 
// 就停止,并返回指向该字符串的指针;而 copy_mount_options() 则拷贝整个页面(确切地说是
// PAGE_SIZE - 1 个字节),并且返回页面的起始地址。然后,就是这个操作的主体 do_mount() 了
// 我们分段来看 (super.c)
	retval = copy_mount_options (type, &type_page);
	if (retval < 0)
		return retval;

	dir_page = getname(dir_name);
	retval = PTR_ERR(dir_page);
	if (IS_ERR(dir_page))
		goto out1;

	retval = copy_mount_options (dev_name, &dev_page);
	if (retval < 0)
		goto out2;

	retval = copy_mount_options (data, &data_page);
	if (retval < 0)
		goto out3;

	lock_kernel();
	retval = do_mount((char*)dev_page, dir_page, (char*)type_page,
			  flags, (void*)data_page);
	unlock_kernel();
	free_page(data_page);

out3:
	free_page(dev_page);
out2:
	putname(dir_page);
out1:
	free_page(type_page);
	return retval;
}

(1)mount flags

// include/linux/fs.h
/*
 * These are the fs-independent mount-flags: up to 32 flags are supported
 */
#define MS_RDONLY	 1	/* Mount read-only */
#define MS_NOSUID	 2	/* Ignore suid and sgid bits */
#define MS_NODEV	 4	/* Disallow access to device special files */
#define MS_NOEXEC	 8	/* Disallow program execution */
#define MS_SYNCHRONOUS	16	/* Writes are synced at once */
#define MS_REMOUNT	32	/* Alter flags of a mounted FS */
#define MS_MANDLOCK	64	/* Allow mandatory locks on an FS */
#define MS_NOATIME	1024	/* Do not update access times. */
#define MS_NODIRATIME	2048	/* Do not update directory access times */
#define MS_BIND		4096

    例如,如果 MS_NOSUID 标志为 1,则整个系统中所有可执行文件的 suid 标志位就都不起作用了。 但是,正如原作者的注释所说,这些标志位并不是对所有文件系统都有效的。所有的标志位都在低 16 位中,而高 16 位则用作 “magic number” 。

(2)do_mount

// fs/super.c
/*
 * Flags is a 16-bit value that allows up to 16 non-fs dependent flags to
 * be given to the mount() call (ie: read-only, no-dev, no-suid etc).
 *
 * data is a (void *) that can point to any structure up to
 * PAGE_SIZE-1 bytes, which can contain arbitrary fs-dependent
 * information (or be NULL).
 *
 * NOTE! As pre-0.97 versions of mount() didn't use this setup, the
 * flags used to have a special 16-bit magic number in the high word:
 * 0xC0ED. If this magic number is present, the high word is discarded.
 */
// 首先是对参数的检验。例如对于安装节点名就要求指针dir_name不为0,并且字符串的第一个字
// 符不为0,即不是空字符串,并且字符串的长度不超过一个页面。这里的 memchr() 在指定长度的缓冲
// 区中寻找指定的字符(这里是0),如果找不到就返回 0。对设备名dev_name的检验很有趣:如果
// dev_name 为非 0,则字符串的长度不得长于一个页面(实际上 copy_mount_options() 保证了这一点,因
// 为它拷贝 PAGE_SIZE—1 个字节),可是 dev_name 为0却是允许的。这似乎不可思议,下面读者将会
// 看到,在特殊情况下这确实是允许的。
long do_mount(char * dev_name, char * dir_name, char *type_page,
		  unsigned long flags, void *data_page)
{
	struct file_system_type * fstype;
	struct nameidata nd;
	struct vfsmount *mnt = NULL;
	struct super_block *sb;
	int retval = 0;

	/* Discard magic */
	if ((flags & MS_MGC_MSK) == MS_MGC_VAL)
		flags &= ~MS_MGC_MSK;
 
	/* Basic sanity checks */

	if (!dir_name || !*dir_name || !memchr(dir_name, 0, PAGE_SIZE))
		return -EINVAL;
	if (dev_name && !memchr(dev_name, 0, PAGE_SIZE))
		return -EINVAL;

	/* OK, looks good, now let's see what do they want */

	/* just change the flags? - capabilities are checked in do_remount() */
// 如果调用参数中的 MS_REMOUNT 标志位为1,就表示所要求的只是改变一个原已安装的设备的
// 安装方式。例如,原来是按 “只读” 方式来安装的,而现在要改为 “可写” 方式;或者原来的MS_NOSUID
// 标志位为0,而现在要改变成1,等等。所以这种操作称为 “重安装”。函数 do_remount() 的代码也在
// super.c 中,读者可以在阅读了 do_mount() 的 “主流” 以后回过来自己读一下这个 “支流” 的代码。
// 另一个分支是对特殊设备如 /dev/loopback 等 “回接” 设备的处理。这种设备是特殊的,其实并不
// 是一种设备,而是一种机制。从系统的角度来看,它似乎是一种设备,但实际上它只是提供了一条
//  “loopback” (回接)到某个可访问普通文件或块设备的手段。举例来说,系统的管理人员可以通过实
// 用程序 losetup,实际上是系统调用 ioctl(),建立起 /dev/loop0 与一个普通文件 /blkfile
// 之间的联系,或者说将 /dev/loop0  “回接” 到 /blkfile,从而将这个文件当作一个块设备来使用:
// losetup -e des /dev/loop0 /blkfile
// 这里的可选项 -e des 表示在通过 /dev/loop0 读写作为虚拟块设备的 /blkfile 时要对内容加密,而加
// 密的算法则为DES (一种加密/解密标准)。也可以使用比较简单的加密算法XOR,此时可选项即为
//  "-e xor"。如果不加密就不用 -e 可选项。回接以后,通过 /dev/loop0 访问的文件 /blkfile 就作为
// 一个 “块设备” 来使用了,所以也要加以格式化:
// mkfs -t ext2 /dev/loop0 100
// 参数 -t ext2 表示按 Ext2 格式化,也可以改用其他文件系统的格式。参数100表示该设备的大小为
// 100个记录块。当然,文件 /blkfile 原来的大小要是够,并且其原来的内容就丢失了,所以一般可以先
// 建立起一个是够大的空文件:
// dd if=/dev/zero of=/blkfile bs=lk count=100
// 回接的对象并不非得是一个普通文件,也可以是一个常规的块设备文件如/dev/hda2等。但是,以
// 普通文件为回接对象给我们提供了将它格式化成一个文件系统并加以安装的手段。我们在回接时采用
// 了加密,所以格式化以后的文件系统映象是加了密的,然后,就可以把这个虚拟的块设备安装到文件
// 系统中了:
// mount -t ext2 /dev/loop0 /mnt
// 从此以后,就跟一般己安装的子系统一样了,只是在我们这个例子中对这个子系统的读/写都加了密。
// 回接的对象还可以是一个已经安装的块设备。例如,/dev/hdal 已经安装在根节点/上,我们仍可
// 以把它作为回接的对象。此时当然不能再加密,也不能再格式化了,但是还可以通过 /dev/loop0 再安装
// 一次(在另外一个节点上),例如把它安装成 “只读” 方式。如果回忆一下,一个进程(例如某种网络
// 服务进程)可以通过系统调用设置自己的 “根” 目录,就不难想像这种 “回接” 设备对子系统安全性
// 可能有用处了。通常在/dev目录中有 /dev/loop0 和 /dev/loopl 两个回接设备文件,需要的话可以
// 通过 mknod 再创建,其上设备号为7。
// 对通过回接设备的安装,以前在mount命令行中有个 “-o loop” 可选项,现在则改成将命令行中
// 的文件类型加上一种 "bind",即 "-t bind",表示所安装的设备是个 “捆绑” 到另一个对象上的回接设
// 备。所以,如果flags中的 MS_BIND 标志位为 1 (见代码中的第1341行,),就调用 do_loopback()
// 来完成回接设备的安装。我们暂且跳过它继续往下读(super.c)。
	if (flags & MS_REMOUNT)
		return do_remount(dir_name, flags & ~MS_REMOUNT,
				  (char *) data_page);

	/* "mount --bind"? Equivalent to older "mount -t bind" */
	/* No capabilities? What if users do thousands of these? */
	if (flags & MS_BIND)
		return do_loopback(dev_name, dir_name);

	/* For the rest we need the type */

	if (!type_page || !memchr(type_page, 0, PAGE_SIZE))
		return -EINVAL;

#if 0	/* Can be deleted again. Introduced in patch-2.3.99-pre6 */
	/* loopback mount? This is special - requires fewer capabilities */
	if (strcmp(type_page, "bind")==0)
		return do_loopback(dev_name, dir_name);
#endif

	/* for the rest we _really_ need capabilities... */
// 进一步的操作需要系统管理员的权限,所以先检查当前进程是否具有此项授权。一般超级用户进
// 程都是有这种授权的。
	if (!capable(CAP_SYS_ADMIN))
		return -EPERM;

	/* ... filesystem driver... */
// 系统支持的每一种文件系统都有一个file_system_type数据结构,定义于include/linux/fs.h:
	fstype = get_fs_type(type_page);
	if (!fstype)		
		return -ENODEV;

	/* ... and mountpoint. Do the lookup first to force automounting. */
// 回到 do_mount() 的代码中。找到了给定文件系统类型的数据结构以后,就要寻找代表安装点的
// dentry 数据结构了。通过path_init() 和path_walk() 寻找目标节点的过程以前已经讲过,就不重复了。
// 找到了安装点的dentry结构(在nameidata结构nd中有个dentry指针)以后,要把待安装设备的 “超级
// 块” 读进来并根据超级块中的信息在内存中建立起相应的super_block数据结构。但是,这里因具体文
// 件系统的不同而有几种情形要区别对待:
// (1)有些虚拟的文件系统(如pipe、共享内存区等),要由内核通过 kern_mount() 安装,而根本
// 不允许由用户进程通过系统调用 mount() 来安装。这样的文件系统类型在其 fs_flag 中的
// FS_NOMOUNT 标志位为1。虚拟文件系统类型的 “设备” 其实没有超级块,所以只是按特
// 定的内容初始化,或者说生成一个 super_block 结构。对于这种文件系统类型,系统调用 mount()
//  时应出错返回.
// (2) 一般的文件系统类型要求有物理的设备作为其物质基础,在其 fs_flag 中的
// FS_REQUIRES_DEV 标志位为 1,这些就是 “正常” 的文件系统类型,如ext2、minix、ufs
// 等等。对于这些文件系统类型,通过 get_sb_bdev() 从待安装设备上读入其超级块。
// (3)有些虚拟文件系统在安装了同类型中的第一个 “设备” ,从而创建了超级块的 super_block 数
// 据结构以后,再安装同一类型中的其他设备时就共享已经存在的 super_block 结构,而不再有
// 其自己的超级块结构。此时相应 file_system_type 结构的 fs_flags 中的 FS_SINGLE 标志位为 1,
// 表示整个文件系统类型只有一个超级块,而不像一般的文件系统类型那样每个具体的设备上
// 都有一个超级块。
//(4)还有些文件系统类型的 fs_flags 中的 FS_NOMOUNT 标志位、FS_REQUIRE_DEV 标志位以
// 及 FS_SINGLE 标志位全都为0,所以不属于上述三种情形中的任何一种。这些所谓 “文件系
// 统” 其实也是虚拟的,通常只是用来实现某种机制或者规程,所以根本就没有 “设备” 。对
// 于这样的 “文件系统类型” 都是通过 get_sb_nodev() 来生成一个 super_block 结构的。
// 总之,每种文件系统类型都有个 file_system_type 结构,而结构中的 fs_flags 则由各种标志位组成,
// 这些标志位表明了具体文件系统类型的特性,也决定着这种文件系统的安装过程。内核代码中提供了
// 两个用来建立 file_system_type 数据结构的宏操作,其定义在 fs.h 中
	if (path_init(dir_name,
		      LOOKUP_FOLLOW|LOOKUP_POSITIVE|LOOKUP_DIRECTORY, &nd))
		retval = path_walk(dir_name, &nd);
	if (retval)
		goto fs_out;

	/* get superblock, locks mount_sem on success */
// 以后读者会看一到,flags 中的 FS_STNGLE 标志位有着很重要的作用。我们在这里只关心常规文件
// 系统的安装,所以只阅读 get_sb_bdev() 的代码,以后我们会结合其他章节,如进程间通信和设备驱动,
// 再来阅读 get_sb_single() 等函数的代码。顺便提一下,这里 get_sb_single() 和 get_sb_nodev()
// 都不使用参数 dev_name,所以它可以是 NULL。这个函数的代码也在 fs/super.c中,我们分段阅读。
	if (fstype->fs_flags & FS_NOMOUNT)
		sb = ERR_PTR(-EINVAL);
	else if (fstype->fs_flags & FS_REQUIRES_DEV)
		sb = get_sb_bdev(fstype, dev_name, flags, data_page);
	else if (fstype->fs_flags & FS_SINGLE)
		sb = get_sb_single(fstype, flags, data_page);
	else
		sb = get_sb_nodev(fstype, flags, data_page);

	retval = PTR_ERR(sb);
	if (IS_ERR(sb))
		goto dput_out;

	/* Something was mounted here while we slept */
	while(d_mountpoint(nd.dentry) && follow_down(&nd.mnt, &nd.dentry))
		;

	/* Refuse the same filesystem on the same mount point */
	retval = -EBUSY;
	if (nd.mnt && nd.mnt->mnt_sb == sb
	    	   && nd.mnt->mnt_root == nd.dentry)
		goto fail;

	retval = -ENOENT;
	if (!nd.dentry->d_inode)
		goto fail;
	down(&nd.dentry->d_inode->i_zombie);
	if (!IS_DEADDIR(nd.dentry->d_inode)) {
		retval = -ENOMEM;
		mnt = add_vfsmnt(&nd, sb->s_root, dev_name);
	}
	up(&nd.dentry->d_inode->i_zombie);
	if (!mnt)
		goto fail;
	retval = 0;
unlock_out:
	up(&mount_sem);
dput_out:
	path_release(&nd);
fs_out:
	put_filesystem(fstype);
	return retval;

fail:
	if (list_empty(&sb->s_mounts))
		kill_super(sb, 0);
	goto unlock_out;
}

① file_system_type

// include/linux/fs.h
struct file_system_type {
	const char *name;
	int fs_flags;
	struct super_block *(*read_super) (struct super_block *, void *, int);
	struct module *owner;
	struct vfsmount *kern_mnt; /* For kernel mount, if it's FS_SINGLE fs */
	struct file_system_type * next;
};

    结构中的 fs_flags 指明了具体文件系统的一些特性,有关的标志位定义见文件fs.h:

// include/linux/fs.h
/* public flags for file_system_type */
#define FS_REQUIRES_DEV 1 
#define FS_NO_DCACHE	2 /* Only dcache the necessary things. */
#define FS_NO_PRELIM	4 /* prevent preloading of dentries, even if
			   * FS_NO_DCACHE is not set.
			   */
#define FS_SINGLE	8 /*
			   * Filesystem that can have only one superblock;
			   * kernel-wide vfsmnt is placed in ->kern_mnt by
			   * kern_mount() which must be called _after_
			   * register_filesystem().
			   */
#define FS_NOMOUNT	16 /* Never mount from userland */
#define FS_LITTER	32 /* Keeps the tree in dcache */
#define FS_ODD_RENAME	32768	/* Temporary stuff; will go away as soon
				  * as nfs_rename() will be cleaned up
				  */

    对这些标志他的意义和作用我们将随着代码解释的进展加以说明。

    结构中有个函数指针 read_super,各种文件系统通过这个指针提供用来读入其超级块的函数,因为不同文件系统的超级块也是不同的。显然,这个数据结构也是从虚拟文件系统 VFS 进入具体文件系统的一个转接点。同时,每种文件系统还有个字符串形式的文件系统类型名。

    安装文件系统时要说明文件系统的类型,例如系统命令 mount 就有个可选项 “-t” 用于类型名。 文件系统的类型名以字符串的形式复制到 type_page 中,现在就用来比对、寻找其 file_system_type 数据结构。

    函数 get_fs_type() 根据具体文件系统的类型名在内核中找到相应的 file_system_type 结构,有关的代码在 super.c 中。

② get_fs_type

// fs/super.c
struct file_system_type *get_fs_type(const char *name)
{
	struct file_system_type *fs;
	
	read_lock(&file_systems_lock);
	fs = *(find_filesystem(name));
	if (fs && !try_inc_mod_count(fs->owner))
		fs = NULL;
	read_unlock(&file_systems_lock);
	if (!fs && (request_module(name) == 0)) {
		read_lock(&file_systems_lock);
		fs = *(find_filesystem(name));
		if (fs && !try_inc_mod_count(fs->owner))
			fs = NULL;
		read_unlock(&file_systems_lock);
	}
	return fs;
}

// =======================================================================
static struct file_system_type **find_filesystem(const char *name)
{
	struct file_system_type **p;
	for (p=&file_systems; *p; p=&(*p)->next)
		if (strcmp((*p)->name,name) == 0)
			break;
	return p;
}

    内核中有一个 file_system_type 结构队列,叫做 file_systems,队列中的每个数据结构都代表着一种文件系统。系统初始化时将内核支持的各种文件系统的 file_system_type 数据结构通过一个函数 register_filesystem() 挂入这个队列,这个过程称为文件系统的注册。除此之外,对有些文件系统的支持可以通过 “可安装模块” 的方式来实现。在装入这些模块时,也会将相应的数据结构注册挂入该队列中。

    函数 find_filesystem() 则扫描 file_systems 队列,找到所需文件系统类型的数据结构。在 file_system_type 结构中有一个指针 owner,如果结构所代表的文件系统类型是通过可安装模块实现的, 则该指针指向代表着具体模块的 module 结构。找到了 file_system_type结构以后,要调用 try_inc_mod_count() 看看该文件系统是否由可安装模块实现,是的话就要递增相应 module 结构中的共享计数,因为现在这个模块多了一个使用者。

    要是在 file_systems 队列中找不到所需的文件系统类型怎么办呢?那就通过request_module() 试试能否(在已安装的文件系统中)找到用来实现所需文件系统类型的可安装模块,并将其装入内核;如果成功的话就再去 file_systems 队列中找一遍。如果装入所需的可安装模块失败,或者装入以后还是找不到相应的 file_system_type 结构,那就说明 Linux 系统不支持所要求的文件系统类型。有关模块的装入可参考 “设备驱动” 一章。

③ 文件系统定义

// include/linux/fs.h
#define DECLARE_FSTYPE(var,type,read,flags) \
struct file_system_type var = { \
	name:		type, \
	read_super:	read, \
	fs_flags:	flags, \
	owner:		THIS_MODULE, \
}

#define DECLARE_FSTYPE_DEV(var,type,read) \
	DECLARE_FSTYPE(var,type,read,FS_REQUIRES_DEV)

// =====================================================================
// fs/ext2/super.c
static DECLARE_FSTYPE_DEV(ext2_fs_type, "ext2", ext2_read_super);

// =====================================================================
// fs/umsdos/inode.c
static DECLARE_FSTYPE_DEV(umsdos_fs_type, "umsdos", UMSDOS_read_super);

// =====================================================================
// fs/pipe.c
static DECLARE_FSTYPE(pipe_fs_type, "pipefs", pipefs_read_super,
	FS_NOMOUNT|FS_SINGLE);

④ get_sb_bdev

// fs/super.c
static struct super_block *get_sb_bdev(struct file_system_type *fs_type,
	char *dev_name, int flags, void * data)
{
	struct inode *inode;
	struct block_device *bdev;
	struct block_device_operations *bdops;
	struct super_block * sb;
	struct nameidata nd;
	kdev_t dev;
	int error = 0;
	/* What device it is? */
// 对于常规的文件系统,参数 dev_name 必须是一个有效的路径名。同样,这里也是通过 path_init()
// 和 path_walk() 找到目标节点,即相应设备文件的 dentry 结构以及 inode 结构。当然,找到的
// inode 结构必须是代表着一个块设备,其 i_mode 中的 S_IFBLK 标志位必须为 1,否则就错了。
// 宏操作 S_ISBLK()  定义于 include/linux/stat.h:
//
// 设备文件的 inode 结构是在 path_walk() 中根据从已经安装的磁盘上(或其他已安装的文件系统中)
// 读入的索引节点建立的。对于Ext2文件系统,我们在 “从路径名到目标节点”  一节中阅读 path_walk() 
// 的代码时曾在它所辗转调用的 ext2_read_inode() 中看到这么一段代码:
	if (!dev_name || !*dev_name)
		return ERR_PTR(-EINVAL);
	if (path_init(dev_name, LOOKUP_FOLLOW|LOOKUP_POSITIVE, &nd))
		error = path_walk(dev_name, &nd);
	if (error)
		return ERR_PTR(error);
	inode = nd.dentry->d_inode;
	error = -ENOTBLK;
	if (!S_ISBLK(inode->i_mode))
		goto out;
	error = -EACCES;
	if (IS_NODEV(inode))
		goto out;
	bdev = inode->i_bdev;
	bdops = devfs_get_ops ( devfs_get_handle_from_inode (inode) );
	if (bdops) bdev->bd_op = bdops;
	/* Done with lookups, semaphore down */
	down(&mount_sem);
	dev = to_kdev_t(bdev->bd_dev);
	sb = get_super(dev);
	if (sb) {
		if (fs_type == sb->s_type &&
		    ((flags ^ sb->s_flags) & MS_RDONLY) == 0) {
			path_release(&nd);
			return sb;
		}
	} else {
		mode_t mode = FMODE_READ; /* we always need it ;-) */
		if (!(flags & MS_RDONLY))
			mode |= FMODE_WRITE;
		error = blkdev_get(bdev, mode, 0, BDEV_FS);
		if (error)
			goto out;
		check_disk_change(dev);
		error = -EACCES;
		if (!(flags & MS_RDONLY) && is_read_only(dev))
			goto out1;
		error = -EINVAL;
		sb = read_super(dev, bdev, fs_type, flags, data, 0);
		if (sb) {
			get_filesystem(fs_type);
			path_release(&nd);
			return sb;
		}
out1:
		blkdev_put(bdev, BDEV_FS);
	}
out:
	path_release(&nd);
	up(&mount_sem);
	return ERR_PTR(error);
}
⑴ ext2_read_inode
// fs/ext2/inode.c
	if (inode->i_ino == EXT2_ACL_IDX_INO ||
	    inode->i_ino == EXT2_ACL_DATA_INO)
		/* Nothing to do */ ;
	else if (S_ISREG(inode->i_mode)) {
		inode->i_op = &ext2_file_inode_operations;
		inode->i_fop = &ext2_file_operations;
		inode->i_mapping->a_ops = &ext2_aops;
	} else if (S_ISDIR(inode->i_mode)) {
		inode->i_op = &ext2_dir_inode_operations;
		inode->i_fop = &ext2_dir_operations;
	} else if (S_ISLNK(inode->i_mode)) {
		if (!inode->i_blocks)
			inode->i_op = &ext2_fast_symlink_inode_operations;
		else {
			inode->i_op = &page_symlink_inode_operations;
			inode->i_mapping->a_ops = &ext2_aops;
		}
	} else 
		init_special_inode(inode, inode->i_mode,
				   le32_to_cpu(raw_inode->i_block[0]));

    由于设备文件既不是常规文件,也不是目录,更不是符号连接,所以必然会调用 init_special_inode(), 其代码在 fs/devices.c 中

⑵ init_special_inode
// fs/devices.c
void init_special_inode(struct inode *inode, umode_t mode, int rdev)
{
	inode->i_mode = mode;
	if (S_ISCHR(mode)) {
		inode->i_fop = &def_chr_fops;
		inode->i_rdev = to_kdev_t(rdev);
	} else if (S_ISBLK(mode)) {
		inode->i_fop = &def_blk_fops;
		inode->i_rdev = to_kdev_t(rdev);
		inode->i_bdev = bdget(rdev);
	} else if (S_ISFIFO(mode))
		inode->i_fop = &def_fifo_fops;
	else if (S_ISSOCK(mode))
		inode->i_fop = &bad_sock_fops;
	else
		printk(KERN_DEBUG "init_special_inode: bogus imode (%o)\n", mode);
}

    以前说过,在 inode 数据结构中有两个设备号。一个是索引节点所在设备的号码i_dev,另一个是索引节点所代表的设备的号码 i_rdev 。可是,如果看一下存储在设备上的索引节点 ext2_inode 数据结构, 就可以发现里面一个专门用于设备号的字段也没有。首先,既然索引节点存储在某个设备上,当然就不需要再在里面说明存储在哪个设备上了。再说,一个索引节点如果代表着一个设备,那就不需要记录跟文件的物理信息有关的数据了,从而可以利用这些空间来记录所代表设备的设备号。事实上,当索引节点代表着设备时,其 ext2_inode 数据结构中的数组 i_block[] 空着没用,所以就将 i_block[0] 用于设备号。这个设备号在这里的 init_special_node() 中经过 to_kdev_t() 加以格式转换以后就变成 inode 结构中的 i_rdev。此外,对于块设备还要使 inode 结构中的指针 i_bdev 指向一个 block_device 结构。具体的数据结构由 bdget() 根据设备号寻找或创建,详见 “设备驱动” 一章中有关的内容。

To be continued

   
⇐ ⇒ ⇔ ⇆ ⇒ ⟺
①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳㉑㉒㉓㉔㉕㉖㉗㉘㉙㉚㉛㉜㉝㉞㉟㊱㊲㊳㊴㊵㊶㊷㊸㊹㊺㊻㊼㊽㊾㊿
⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑿⒀⒁⒂⒃⒄⒅⒆⒇
➊➋➌➍➎➏➐➑➒➓⓫⓬⓭⓮⓯⓰⓱⓲⓳⓴
⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵
ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ
ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏ
🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩

123

y = x 2 + z 3 y = x^2 + z_3 y=x2+z3

y = x 2 + z 3 + a b + b a y = x^2 + z_3 + \frac {a}{b} + \sqrt[a]{b} y=x2+z3+ba+ab

y = x 2 + z 3 (1) y = x^2 + z^3 \tag{1} y=x2+z3(1)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/723190.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

开源项目推荐 【SkyEyeSystem】

大家好&#xff0c;今天向大家推荐一个开源项目——SkyEyeSystem。 这是一个基于Spring Boot的全网热点爬虫项目&#xff0c;旨在提供全面而准确的全网热搜数据。 关于项目 SkyEyeSystem通过定时任务间隔10min爬取全网热搜数据。目前包括的平台有&#xff1a; 微博热搜B站热…

Huawei Cloud EulerOS 安装 MySQL8.0

EulerOS 安装 MySQL8.0 安装MySQL配置文件 安装MySQL 当创建一个基于EulerOS的服务器时&#xff0c;MySQL是一个常见且强大的数据库管理系统选择。在此博客中&#xff0c;我将向您展示如何在EulerOS上安装MySQL 8.0。 步骤1&#xff1a;更新系统 在开始之前&#xff0c;让我…

【FATE】联邦学习 optimizer在FATE的自定义trainer中被改变

起因 使用torch的optimizer添加了2组parameter&#xff0c;传参进入FATE的trainer后&#xff0c;optimizer被改变&#xff0c;且FATE框架无提示。 代码差不多是下面这样&#xff1a; # optimizer中加入2组优化参数&#xff08;param&#xff09; optimizer torch.optim.SGD…

滑动窗口 /【模板】单调队列

day1 滑动窗口 /【模板】单调队列 题目描述 有一个长为 n n n 的序列 a a a&#xff0c;以及一个大小为 k k k 的窗口。现在这个从左边开始向右滑动&#xff0c;每次滑动一个单位&#xff0c;求出每次滑动后窗口中的最大值和最小值。 例如&#xff1a; The array is [ …

mysql redis区别

一、.redis和mysql的区别总结 &#xff08;1&#xff09;类型上 从类型上来说&#xff0c;mysql是关系型数据库&#xff0c;redis是缓存数据库 &#xff08;2&#xff09;作用上 mysql用于持久化的存储数据到硬盘&#xff0c;功能强大&#xff0c;但是速度较慢 redis用于存储使…

NSS [NSSCTF 2022 Spring Recruit]ezgame

NSS [NSSCTF 2022 Spring Recruit]ezgame 前端小游戏&#xff0c;乐。

【雕爷学编程】Arduino动手做(136)---0.91寸OLED液晶屏模块3

0.91寸OLED模块引脚说明 GND ------ 地线 VCC ------ 电源 &#xff08;因为模块内部自带稳压&#xff0c;所以3.3~5V供电都是ok的&#xff09; SDA ------ I2C 数据线&#xff08;接A4&#xff09; SCL ------ I2C 时钟线&#xff08;接A5&#xff09; &#xff08;可以…

Pairwise 提高测试造数据的困难

Pairwise是L. L. Thurstone(29 May1887 – 30 September 1955)在1927年首先提出来的。他是美国的一位心理统计学家。Pairwise也正是基于数学统计和对传统的正交分析法进行优化后得到的产物。 下图是另一个项目结构 (造一个csv转测试用例) selenium的挑战者 - playwright简析 搭…

代码随想录二刷day44 | 动态规划之 完全背包 518. 零钱兑换 II 377. 组合总和 Ⅳ

day44 完全背包基础知识问题描述举个栗子 518. 零钱兑换 II1.确定dp数组以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺序5.举例推导dp数组 377. 组合总和 Ⅳ1.确定dp数组以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺序5.举例来推导dp数组 完全背…

Android之OkHttp框架的分析

Okhttp是Android开发常用的一个网络请求框架&#xff0c;下面将按照自己的理解将okhttp分为三条主线进行分析。 文章目录 使用方式OkHttp第一条主线&#xff1a;请求发送到哪里去了&#xff1f;OkHttp第二条主线&#xff1a;请求是如何被消费的&#xff1f;OkHttp第三条主线&a…

376. 摆动序列

如果连续数字之间的差严格地在正数和负数之间交替&#xff0c;则数字序列称为 摆动序列 。第一个差&#xff08;如果存在的话&#xff09;可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。 例如&#xff0c; [1, 7, 4, 9, 2, 5] 是一个 摆动序列 &…

机器学习 day24(多类分类模型)

多类分类 多类分类问题仍然是分类问题&#xff0c;所以预测y的可能结果是少量的&#xff0c;而不是无穷多个&#xff0c;且对于多类分类它&#xff1e;2 如上图&#xff1a;左侧为二分类&#xff0c;右侧为多分类&#xff0c;可以通过决策边界来划分区域

EasyExcel导出横向动态表头

需求&#xff1a;导出如下所示excel文档&#xff0c;红框内为动态表头 mysql表内数据格式如下图所示&#xff0c;由于某些特殊原因不能横向展示&#xff0c;仅能纵向展示&#xff0c;所以一个vin对应的数据可能有很多此时导出的时候上图所示的样式更便于人员观看。 依赖项 <…

VS多处理器编译提高编译速度

VS多处理器编译提高编译速度 开启多处理器编译能够提升编译速度&#xff0c;特别是当工程巨大时候&#xff0c;编译速度往往很慢&#xff0c;打开多处理器编译效果明显&#xff0c;下面给出设置和对比 开启多处理器编译 关闭多处理器编译

RabbitMQ系列(8)--实现RabbitMQ队列持久化及消息持久化

概念&#xff1a;在上一章文章中我们演示了消费者宕机的情况下消息没有被消费成功后会重新入队&#xff0c;然后再被消费&#xff0c;但如何保障RabbitMQ服务停掉的情况下&#xff0c;生产者发过来的消息不会丢失&#xff0c;这时候我们为了消息不会丢失就需要将队列和消息都标…

提高计算能力的五种方法

一旦你投入时间&#xff0c;就会 不舍得 轻易放手。 一开始可能会很浪费时间&#xff0c;但是当你练熟之后&#xff0c;效果显而易见。 二次函数&#xff1a;

php对称加密AES加密解密

AES-128-ECB和AES-256-CBC是两种常见的AES加密模式&#xff0c;它们在加密方式和安全性上有以下区别&#xff1a; 加密方式&#xff1a; AES-128-ECB&#xff1a;ECB&#xff08;Electronic Codebook&#xff09;模式是最简单的AES加密模式&#xff0c;它将数据分成固定大小的…

CSS 自定义提示(重写 title 属性)

前言 CSS 原生 title 属性太丑&#xff0c;需要重写 效果 改造 HTML 代码第2行&#xff0c;tip-label 自定义属性 <div class"tools"><div class"btn tip" v-for"item of list" :key"item.icon" :tip-label"item.l…

允许Traceroute探测漏洞和ICMP timestamp请求响应漏洞解决方法

目录 服务器检测出了漏洞需要修改 1.允许Traceroute探测漏洞解决方法 2、ICMP timestamp请求响应漏洞 服务器检测出了漏洞需要修改 1.允许Traceroute探测漏洞解决方法 详细描述 本插件使用Traceroute探测来获取扫描器与远程主机之间的路由信息。攻击者也可以利用这些信息来…

Sentinel持久化实战

前言 Sentinel有pull&#xff08;拉&#xff09;模式&#xff0c;和push&#xff08;推&#xff09;模式。本文是使用reids实现pull模式。 通过SPI机制引入自己的类 在项目的 resources > META-INF > services下创建新文件&#xff0c;文件名如下&#xff0c;内容是自…