深入理解Linux虚拟内存管理(九)

news2024/11/24 13:40:00

在这里插入图片描述

系列文章目录


Linux 内核设计与实现
深入理解 Linux 内核
Linux 设备驱动程序
Linux设备驱动开发详解
深入理解Linux虚拟内存管理(一)
深入理解Linux虚拟内存管理(二)
深入理解Linux虚拟内存管理(三)
深入理解Linux虚拟内存管理(四)
深入理解Linux虚拟内存管理(五)
深入理解Linux虚拟内存管理(六)
深入理解Linux虚拟内存管理(七)
深入理解Linux虚拟内存管理(八)
深入理解Linux虚拟内存管理(九)


文章目录

  • 系列文章目录
  • 一、共享内存虚拟文件系统
    • 1、初始化 shmfs
      • (1)init_tmpfs
        • ① ⇐ DECLARE_FSTYPE
        • ② ⟺ register_filesystem
        • ③ ⟺ kern_mount
        • ④ ⇒ shmem_set_size
      • (2)shmem_read_super
        • ① ⇐ SHMEM_SB
        • ② ⇒ shmem_get_inode
        • ③ ⇔ d_alloc_root
      • (3)shmem_set_size
    • 2、在 tmpfs 中创建文件
      • (1)shmem_create
      • (2)shmem_mknod
        • ① ⇒ shmem_get_inode
        • ② ⇔ d_instantiate
      • (3)shmem_get_inode
        • ① ⇔ new_inode
        • ② ⇔ init_special_inode
        • ③ ⇐ shmem_aops、shmem_file_operations
        • ④ ⇐ dcache_dir_ops
    • 3、tmpfs 中的文件操作
      • (1)内存映射
        • ① shmem_mmap
          • ⑴ ⇒ shmem_nopage
      • (2)读取文件
        • ① shmem_file_read
          • ⑴ ⇐ read_descriptor_t
          • ⑵ ⇒ do_shmem_file_read
        • ② do_shmem_file_read
          • ⑴ ⇒ file_read_actor
          • ⑵ ⇒ shmem_getpage
        • ③ file_read_actor
      • (3)写入文件
        • ① shmem_file_write
      • (4)符号链接
        • ① shmem_symlink
        • ② shmem_readlink_inline
        • ③ shmem_follow_link_inline
        • ④ shmem_readlink
      • (5)同步文件
        • ① shmem_sync_file
    • 4、tmpfs 中的索引节点操作
      • (1)截取
        • ① shmem_truncate
        • ② shmem_truncate_indirect
        • ③ shmem_truncate_direct
        • ④ shmem_free_swp
      • (2)链接
        • ① shmem_link
      • (3)解除链接
        • ① shmem_unlink
      • (4)创建目录
        • ① shmem_mkdir
      • (5)移除目录
        • ① shmem_rmdir
        • ② shmem_empty
        • ③ shmem_positive
    • 5、虚拟文件中的缺页中断
      • (1)缺页中断时读取页面
        • ① shmem_nopage
        • ② shmem_getpage
      • (2)定位交换页面
        • ① shmem_alloc_entry
        • ② shmem_swp_entry
    • 6、交换空间交互
      • (1)shmem_writepage
      • (2)shmem_unuse
      • (3)shmem_unuse_inode
      • (4)shmem_find_swp
    • 7、建立共享区
      • (1)shmem_zero_setup
      • (2)shmem_file_setup
    • 8、System V IPC
      • (1)创建一个 SYSV 共享区
        • ① sys_shmget
        • ② newseg
      • (2)附属一个 SYSV 共享区
        • ① sys_shmat
  • 符号


一、共享内存虚拟文件系统

1、初始化 shmfs

(1)init_tmpfs

    这个函数用于注册和挂载 tmpfsshmemfs 文件系统。

// mm/shmem.c
// shm文件系统只有在编译时定义CONFIG_TMPFS的情况下可以挂载。即使没有
// 指定,也会因 fork() 而为匿名共享内存建立tmpfs。
#ifdef CONFIG_TMPFS
/* type "shm" will be tagged obsolete in 2.5 */
// 声明在文件 <linux/fs.h> 中的 DECLARE_FSTYPE(), 声明了 tmpfs_fs_type 是
// struct file_system_type, 并填充了 4 个字段。"tmpfs" 是它的可读名字。shmem_read_super()
// 函数用于为文件系统读取超级块(超级块的细节以及它们如何匹配文件系统已经超出本书范
// 畴)。FS_LITTER 是一个标志位,用于表明应该在 dcache 中维护文件系统树。最后,这个宏
// 设置文件系统的模块所有者成为载入文件系统的模块。
static DECLARE_FSTYPE(shmem_fs_type, "shm", shmem_read_super, FS_LITTER);
static DECLARE_FSTYPE(tmpfs_fs_type, "tmpfs", shmem_read_super, FS_LITTER);
#else
static DECLARE_FSTYPE(tmpfs_fs_type, "tmpfs", shmem_read_super, FS_LITTER|FS_NOMOUNT);
#endif
static struct vfsmount *shm_mnt;

// __init 放置该函数在初始化部分。这意味着,内核启动完毕后,这个函数的代码将被移除。
static int __init init_tmpfs(void)
{
	int error;
	// 注册文件系统为tmpfs_fs_type类型,这已在第1433行进行了声明。如果失
	// 败,跳转到 out3, 返回适当的错误。
	error = register_filesystem(&tmpfs_fs_type);
	if (error) {
		printk(KERN_ERR "Could not register tmpfs\n");
		goto out3;
	}
// 如果在配置时指定了 tmpfs ,那么注册shmem文件系统。如果失败,跳转到
// out2, 解除tmpfs_fs_type的注册并返回错误。
#ifdef CONFIG_TMPFS
	error = register_filesystem(&shmem_fs_type);
	if (error) {
		printk(KERN_ERR "Could not register shm fs\n");
		goto out2;
	}
	// 如果由设备文件系统(devfs)管理/dev/,则创建一个新的shm目录。如果内核没有
	// 使用devfs,则系统管理员必须手工创建该目录。
	devfs_mk_dir(NULL, "shm", NULL);
#endif
	// kern_mount()在内部挂载一个文件系统。换言之,该文件系统被挂载并被激活,但
	// 在VFS中不被任何用户可见。其挂载点是shm_mnt,位于shmem.c文件中,其类型是
	// struct vfsmount。 在后期需要搜索文件系统并卸载这个变量。
	shm_mnt = kern_mount(&tmpfs_fs_type);
	// 确保正确地卸载文件系统, 但是如果没有,则跳转到out1, 解除文件系统的注
	// 册, 并返回错误。
	if (IS_ERR(shm_mnt)) {
		error = PTR_ERR(shm_mnt);
		printk(KERN_ERR "Could not kern_mount tmpfs\n");
		goto out1;
	}

	/* The internal instance should not do size checking */
	// 函数shmem_set_size() (见L. 1. 3小节)用于设置文件系统中创建的块数及索引节
	// 点数的最大值。
	shmem_set_size(SHMEM_SB(shm_mnt->mnt_sb), ULONG_MAX, ULONG_MAX);
	return 0;

out1:
#ifdef CONFIG_TMPFS
	unregister_filesystem(&shmem_fs_type);
out2:
#endif
	unregister_filesystem(&tmpfs_fs_type);
out3:
	shm_mnt = ERR_PTR(error);
	return error;
}
// 在这种情况下,module_init()表明了在载入模块上应当调用init_shmem_fs()以及
// 如何直接编译进内核,在系统启动时调用这个函数。
module_init(init_tmpfs)

① ⇐ DECLARE_FSTYPE

// 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 file_system_type * next;
	struct list_head fs_supers;
};

#define DECLARE_FSTYPE(var,type,read,flags) \
struct file_system_type var = { \
	name:		type, \
	read_super:	read, \
	fs_flags:	flags, \
	owner:		THIS_MODULE, \
}

② ⟺ register_filesystem

// fs/super.c
/**
 *	register_filesystem - register a new filesystem
 *	@fs: the file system structure
 *
 *	Adds the file system passed to the list of file systems the kernel
 *	is aware of for mount and other syscalls. Returns 0 on success,
 *	or a negative errno code on an error.
 *
 *	The &struct file_system_type that is passed is linked into the kernel 
 *	structures and must not be freed until the file system has been
 *	unregistered.
 */
 
int register_filesystem(struct file_system_type * fs)
{
	int res = 0;
	struct file_system_type ** p;

	if (!fs)
		return -EINVAL;
	if (fs->next)
		return -EBUSY;
	INIT_LIST_HEAD(&fs->fs_supers);
	write_lock(&file_systems_lock);
	p = find_filesystem(fs->name);
	if (*p)
		res = -EBUSY;
	else
		*p = fs;
	write_unlock(&file_systems_lock);
	return res;
}

③ ⟺ kern_mount

// fs/super.c
struct vfsmount *kern_mount(struct file_system_type *type)
{
	return do_kern_mount(type->name, 0, (char *)type->name, NULL);
}

④ ⇒ shmem_set_size

    shmem_set_size 函数

(2)shmem_read_super

    这是文件系统提供的回调函数,用于读取超级块。对于普通的文件系统,这可以从磁盘读取细节信息,但是,由于这个文件系统基于 RAM ,相反它产生一个 struct super_block

// mm/shmem.c
// 参数如下所示:
//	sb 是产生的超级块。
//	data 包括了一些参数。
//  silent 在这个函数中未使用。
static struct super_block *shmem_read_super(struct super_block *sb, void *data, int silent)
{
	struct inode *inode;
	struct dentry *root;
	unsigned long blocks, inodes;
	// 设置缺省模式,uid和gid。这些可能覆盖挂载选项中的参数。
	int mode   = S_IRWXUGO | S_ISVTX;
	uid_t uid = current->fsuid;
	gid_t gid = current->fsgid;
	// 每个super_block 都允许具有一个特定文件系统的结构,该结构包括一个称为
	// super_block->u 的联合结构。 宏SHMEM_SB()返回联合结构中所包含的
	// struct shmem_sb_info。
	struct shmem_sb_info *sbinfo = SHMEM_SB(sb);
	struct sysinfo si;

	/*
	 * Per default we only allow half of the physical ram per
	 * tmpfs instance
	 */
	// si_meminfo()产生struct sysinfo,包括了全部内存,可用内存和已用内存的统计数
	// 据。这个函数在arch/i386/mm/init.c文件中进行了定义,它是架构相关的。
	si_meminfo(&si);
	// 缺省情况下,只允许文件系统消耗物理内存的一半。
	blocks = inodes = si.totalram / 2;

// 如果tmpfs可用,这将解析挂载选项,并允许覆盖缺省值。
#ifdef CONFIG_TMPFS
	if (shmem_parse_options(data, &mode, &uid, &gid, &blocks, &inodes))
		return NULL;
#endif
	// 获取锁保护的 sbinfo, 它是 super_block 中的 struct shmem_sb_info。
	spin_lock_init(&sbinfo->stat_lock);
	sbinfo->max_blocks = blocks;
	sbinfo->free_blocks = blocks;
	sbinfo->max_inodes = inodes;
	sbinfo->free_inodes = inodes;
	sb->s_maxbytes = SHMEM_MAX_BYTES;
	sb->s_blocksize = PAGE_CACHE_SIZE;
	sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
	// 产生sb和sbinfo字段。
	sb->s_magic = TMPFS_MAGIC;
	// shmem_ops是超级块结构的函数指针,用于重新挂载文件系统和删除索引节点。
	sb->s_op = &shmem_ops;
	// 这个块分配特定的索引节点,用于表示文件系统的根节点。
	inode = shmem_get_inode(sb, S_IFDIR | mode, 0);
	if (!inode)
		return NULL;
	// 设置新文件系统的根部 uid和 gid。
	inode->i_uid = uid;
	inode->i_gid = gid;
	// 实现在 fs/dcache.c 中
	root = d_alloc_root(inode);
	if (!root) {
		iput(inode);
		return NULL;
	}
	// 设置根索引节点至 super_block 中。
	sb->s_root = root;
	// 返回产生的超级块。
	return sb;
}

① ⇐ SHMEM_SB

// mm/shmem.c
#define SHMEM_SB(sb) (&sb->u.shmem_sb)

// include/linux/fs.h
struct super_block {
	struct list_head	s_list;		/* Keep this first */
	kdev_t			s_dev;
	unsigned long		s_blocksize;
	unsigned char		s_blocksize_bits;
	unsigned char		s_dirt;
	unsigned long long	s_maxbytes;	/* Max file size */
	struct file_system_type	*s_type;
	struct super_operations	*s_op;
	struct dquot_operations	*dq_op;
	struct quotactl_ops	*s_qcop;
	unsigned long		s_flags;
	unsigned long		s_magic;
	struct dentry		*s_root;
	struct rw_semaphore	s_umount;
	struct semaphore	s_lock;
	int			s_count;
	atomic_t		s_active;

	struct list_head	s_dirty;	/* dirty inodes */
	struct list_head	s_locked_inodes;/* inodes being synced */
	struct list_head	s_files;

	struct block_device	*s_bdev;
	struct list_head	s_instances;
	struct quota_info	s_dquot;	/* Diskquota specific options */

	union {
		struct minix_sb_info	minix_sb;
		struct ext2_sb_info	ext2_sb;
		struct ext3_sb_info	ext3_sb;
		struct hpfs_sb_info	hpfs_sb;
		struct ntfs_sb_info	ntfs_sb;
		struct msdos_sb_info	msdos_sb;
		struct isofs_sb_info	isofs_sb;
		struct nfs_sb_info	nfs_sb;
		struct sysv_sb_info	sysv_sb;
		struct affs_sb_info	affs_sb;
		struct ufs_sb_info	ufs_sb;
		struct efs_sb_info	efs_sb;
		struct shmem_sb_info	shmem_sb;
		struct romfs_sb_info	romfs_sb;
		struct smb_sb_info	smbfs_sb;
		struct hfs_sb_info	hfs_sb;
		struct adfs_sb_info	adfs_sb;
		struct qnx4_sb_info	qnx4_sb;
		struct reiserfs_sb_info	reiserfs_sb;
		struct bfs_sb_info	bfs_sb;
		struct udf_sb_info	udf_sb;
		struct ncp_sb_info	ncpfs_sb;
		struct usbdev_sb_info   usbdevfs_sb;
		struct jffs2_sb_info	jffs2_sb;
		struct cramfs_sb_info	cramfs_sb;
		void			*generic_sbp;
	} u;
	/*
	 * The next field is for VFS *only*. No filesystems have any business
	 * even looking at it. You had been warned.
	 */
	struct semaphore s_vfs_rename_sem;	/* Kludge */

	/* The next field is used by knfsd when converting a (inode number based)
	 * file handle into a dentry. As it builds a path in the dcache tree from
	 * the bottom up, there may for a time be a subpath of dentrys which is not
	 * connected to the main tree.  This semaphore ensure that there is only ever
	 * one such free path per filesystem.  Note that unconnected files (or other
	 * non-directories) are allowed, but not unconnected diretories.
	 */
	struct semaphore s_nfsd_free_path_sem;
};

② ⇒ shmem_get_inode

    shmem_get_inode 函数

③ ⇔ d_alloc_root

// fs/dcache.c
/**
 * d_alloc_root - allocate root dentry
 * @root_inode: inode to allocate the root for
 *
 * Allocate a root ("/") dentry for the inode given. The inode is
 * instantiated and returned. %NULL is returned if there is insufficient
 * memory or the inode passed is %NULL.
 */
 
struct dentry * d_alloc_root(struct inode * root_inode)
{
	struct dentry *res = NULL;

	if (root_inode) {
		res = d_alloc(NULL, &(const struct qstr) { "/", 1, 0 });
		if (res) {
			res->d_sb = root_inode->i_sb;
			res->d_parent = res;
			d_instantiate(res, root_inode);
		}
	}
	return res;
}

(3)shmem_set_size

    这个函数更新文件系统中可用块和索引节点的数量。在文件系统挂载和卸载时进行设置。

// mm/shmem.c
// 这些参数描述了文件系统超级块的信息,块的最大数量(max_blocks)和索引节点的
// 最大数量(max_inodes)。
static int shmem_set_size(struct shmem_sb_info *info,
			  unsigned long max_blocks, unsigned long max_inodes)
{
	int error;
	unsigned long blocks, inodes;
	// 锁定超级块信息自旋锁。
	spin_lock(&info->stat_lock);
	// 计算文件系统中当前使用的块数。在初始挂载时,这并不重要,然而,如果重新挂载
	// 文件系统,该函数必须保证新的文件系统不会太小。
	blocks = info->max_blocks - info->free_blocks;
	// 计算当前使用的索引节点数。
	inodes = info->max_inodes - info->free_inodes;
	error = -EINVAL;
	// 如果重新挂载的文件系统没有足够的块存放当前信息,则跳转到out,并返
	// 回-EINVAL。
	if (max_blocks < blocks)
		goto out;
	// 同样地,确保是否有足够的索引节点,否则返回-EINVAL	
	if (max_inodes < inodes)
		goto out;
	// 可以安全地挂载文件系统,因此这里设置error为0表示操作成功。
	error = 0;
	// 设置最大索引节点数和可用节点数。
	info->max_blocks  = max_blocks;
	info->free_blocks = max_blocks - blocks;
	info->max_inodes  = max_inodes;
	info->free_inodes = max_inodes - inodes;
out:
	// 为文件系统超级块信息结构解锁。
	spin_unlock(&info->stat_lock);
	// 成功则返回0,否则返回-EINVAL。
	return error;
}

2、在 tmpfs 中创建文件

(1)shmem_create

    这是创建新文件时位于最顶层的函数。

// mm/shmem.c
// 参数如下所示:
//	dir 是新文件创建时的目录索引节点。
//	entry 是新文件创建时的目录节点。
//	mode 是传递给开放系统调用的标志位。
static int shmem_create(struct inode *dir, struct dentry *dentry, int mode)
{	
	// 调用shmem_mknod() (见L. 2.2小节),并添加S_IFREG标志位到模式标志位,以
	// 此创建一个常规文件。
	return shmem_mknod(dir, dentry, mode | S_IFREG, 0);
}

(2)shmem_mknod

// mm/shmem.c
/*
 * File creation. Allocate an inode, and we're done..
 */
static int shmem_mknod(struct inode *dir, struct dentry *dentry, int mode, int dev)
{
	// 调用shmem_get_inode() (见L. 2. 3小节)创建一个新的索引节点。
	struct inode *inode = shmem_get_inode(dir->i_sb, mode, dev);
	int error = -ENOSPC;
// 如果成功创建索引节点,则更新目录统计数据并实例化新文件。
	if (inode) {
	// 更新目录的大小。
		dir->i_size += BOGO_DIRENT_SIZE;
	// 更新ctime和mtime字段。
		dir->i_ctime = dir->i_mtime = CURRENT_TIME;
	// 实例化索引节点。
		d_instantiate(dentry, inode);
	// 对目录项进行引用,以阻止在页面换出时意外地回收了目录项。
		dget(dentry); /* Extra count - pin the dentry in core */
	// 表明调用成功结束。
		error = 0;
	}
	// 返回成功,否则返回-ENOSPC。
	return error;
}

① ⇒ shmem_get_inode

    shmem_get_inode 函数

② ⇔ d_instantiate

// fs/dcache.c
/**
 * d_instantiate - fill in inode information for a dentry
 * @entry: dentry to complete
 * @inode: inode to attach to this dentry
 *
 * Fill in inode information in the entry.
 *
 * This turns negative dentries into productive full members
 * of society.
 *
 * NOTE! This assumes that the inode count has been incremented
 * (or otherwise set) by the caller to indicate that it is now
 * in use by the dcache.
 */
 
void d_instantiate(struct dentry *entry, struct inode * inode)
{
	if (!list_empty(&entry->d_alias)) BUG();
	spin_lock(&dcache_lock);
	if (inode)
		list_add(&entry->d_alias, &inode->i_dentry);
	entry->d_inode = inode;
	spin_unlock(&dcache_lock);
}

(3)shmem_get_inode

// mm/shmem.c
// 这个函数用于更新空闲索引节点数,用new_inode()分配一个索引节点。
static struct inode *shmem_get_inode(struct super_block *sb, int mode, int dev)
{
	struct inode *inode;
	struct shmem_inode_info *info;
	struct shmem_sb_info *sbinfo = SHMEM_SB(sb);
	// 获取sbinfo自旋锁,因为它即将被更新。
	spin_lock(&sbinfo->stat_lock);
	// 确保有空闲的索引节点,如果没有,则返回NULL。
	if (!sbinfo->free_inodes) {
		spin_unlock(&sbinfo->stat_lock);
		return NULL;
	}
	sbinfo->free_inodes--;
	// 更新空闲索引节点计数并释放锁。
	spin_unlock(&sbinfo->stat_lock);
	// new_inode()处于文件系统层面,并在<linux/fs.h>中声明。它如何运作的详细情
	// 况不在本文档讨论范围内,但其概要内容很简单。它从slab分配器中分配一个索引节点,并
	// 将各字段赋予0,并根据超级块中的信息产生inode->i_sb,inode->i_dev和inode->i_blkbits。
	// 实现在文件 fs/inode.c 中
	inode = new_inode(sb);
// 如果创建成功则填充索引节点各字段。
	if (inode) {
	// 填充基本的索引节点信息。
		inode->i_mode = mode;
		inode->i_uid = current->fsuid;
		inode->i_gid = current->fsgid;
		inode->i_blksize = PAGE_CACHE_SIZE;
		inode->i_blocks = 0;
		inode->i_rdev = NODEV;
		// 设置 address_space_operations 使用 shmem_aops,后者建立函数 shmem_writepage()
		// (见 L. 6. 1小节)用于address_space的页面回写回调函数。
		inode->i_mapping->a_ops = &shmem_aops;
		// 填充更多的基本信息。
		inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
		info = SHMEM_I(inode);
		info->inode = inode;
		// 初始化索引节点的semaphore信号量和自旋锁。
		spin_lock_init(&info->lock);
		// 确定如何根据传入的模式信息填充剩余的字段。
		switch (mode & S_IFMT) {
		default:
		// 在这种情况下,创建特定的索引节点。尤其是在挂载文件系统时和创建根索引节点时。
			init_special_inode(inode, mode, dev);
			break;
		case S_IFREG:
// 为常规文件创建索引节点。这里主要考虑的问题是设置 inode->i_op 和 inode->i_fop
// 字段分别为 shmem_inode_operations 和 shmem_file_operations 。
			inode->i_op = &shmem_inode_operations;
			inode->i_fop = &shmem_file_operations;
			spin_lock(&shmem_ilock);
			list_add_tail(&info->list, &shmem_inodes);
			spin_unlock(&shmem_ilock);
			break;
		case S_IFDIR:
// 为新目录创建索引节点。更新i_nlink和i_size字段以显示增加的文件数量和
// 目录大小。这里主要考虑的问题是设置 inode->i_op 和 inode->i_fop 字段分别为
// shmem_dir_inode_operations 和 dcache_dir_ops 。
			inode->i_nlink++;
			/* Some things misbehave if size == 0 on a directory */
			inode->i_size = 2 * BOGO_DIRENT_SIZE;
			inode->i_op = &shmem_dir_inode_operations;
			inode->i_fop = &dcache_dir_ops;
			break;
		case S_IFLNK:
// 如果链接了文件, 由于它由父函数 shmem_link()操作, 因此它现在什么也不是
			break;
		}
	}
	// 返回新索引节点,如果没有创建则返回NULL。
	return inode;
}

① ⇔ new_inode

// fs/inode.c
/**
 *	new_inode 	- obtain an inode
 *	@sb: superblock
 *
 *	Allocates a new inode for given superblock.
 */
 
struct inode * new_inode(struct super_block *sb)
{
	static unsigned long last_ino;
	struct inode * inode;

	spin_lock_prefetch(&inode_lock);
	
	inode = alloc_inode(sb);
	if (inode) {
		spin_lock(&inode_lock);
		inodes_stat.nr_inodes++;
		list_add(&inode->i_list, &inode_in_use);
		inode->i_ino = ++last_ino;
		inode->i_state = 0;
		spin_unlock(&inode_lock);
	}
	return inode;
}

② ⇔ 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);
		inode->i_cdev = cdget(rdev);
	} else if (S_ISBLK(mode)) {
		inode->i_fop = &def_blk_fops;
		inode->i_rdev = to_kdev_t(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);
}

③ ⇐ shmem_aops、shmem_file_operations

// mm/shmem.c
static struct address_space_operations shmem_aops = {
	removepage:	shmem_removepage,
	writepage:	shmem_writepage,
#ifdef CONFIG_TMPFS
	readpage:	shmem_readpage,
	prepare_write:	shmem_prepare_write,
	commit_write:	shmem_commit_write,
#endif
};

static struct file_operations shmem_file_operations = {
	mmap:		shmem_mmap,
#ifdef CONFIG_TMPFS
	read:		shmem_file_read,
	write:		shmem_file_write,
	fsync:		shmem_sync_file,
#endif
};

static struct inode_operations shmem_inode_operations = {
	truncate:	shmem_truncate,
	setattr:	shmem_notify_change,
};

static struct inode_operations shmem_dir_inode_operations = {
#ifdef CONFIG_TMPFS
	create:		shmem_create,
	lookup:		shmem_lookup,
	link:		shmem_link,
	unlink:		shmem_unlink,
	symlink:	shmem_symlink,
	mkdir:		shmem_mkdir,
	rmdir:		shmem_rmdir,
	mknod:		shmem_mknod,
	rename:		shmem_rename,
#endif
};

④ ⇐ dcache_dir_ops

// fs/readdir.c
struct file_operations dcache_dir_ops = {
	open:		dcache_dir_open,
	release:	dcache_dir_close,
	llseek:		dcache_dir_lseek,
	read:		generic_read_dir,
	readdir:	dcache_readdir,
	fsync:		dcache_dir_fsync,
};

3、tmpfs 中的文件操作

(1)内存映射

    用于映射虚拟文件到内存。惟一要做的改变是更新 VMAvm_operations_struct 字段使用异常的等价 shmfs

① shmem_mmap

// mm/shmem.c
static int shmem_mmap(struct file *file, struct vm_area_struct *vma)
{
	struct vm_operations_struct *ops;
	struct inode *inode = file->f_dentry->d_inode;
	// 操作目前为虚拟文件系统所使用的vm_operations_struct。
	ops = &shmem_vm_ops;
	// 确保索引节点映射的是常规文件。如果不是,则返回-EACCESS。
	if (!S_ISREG(inode->i_mode))
		return -EACCES;
	// 更新索引节点的atime,显示它是否已经被访问。
	UPDATE_ATIME(inode);
	// 更新vma->vm_ops,这样shmem_nopage() 可以用于处理映射中的缺页中断。
	vma->vm_ops = ops;
	return 0;
}
⑴ ⇒ shmem_nopage

    shmem_nopage 函数

(2)读取文件

① shmem_file_read

    这是读取 tmpfs 文件时所调用的位于最顶层的函数。

// mm/shmem.c
// 参数如下所示:
//	filp 是指向被读取文件的指针。
//	buf 是应当填充的缓冲区。
//	count 是应当读取的字节数。
//	ppos 是当前位置。
static ssize_t shmem_file_read(struct file *filp, char *buf, size_t count, loff_t *ppos)
{
	read_descriptor_t desc;
	// 计数不可能为负数。
	if ((ssize_t) count < 0)
		return -EINVAL;
	// access_ok()确保安全地写count数量的字节到用户空间缓冲区。如果不能,
	// 则返回-EFAULT。
	if (!access_ok(VERIFY_WRITE, buf, count))
		return -EFAULT;
	if (!count)
		return 0;
	// 初始化 read_descriptor_t 结构,该结构最终传递给 file_read_actor()
	// (见 L.3.2.3)
	desc.written = 0;
	desc.count = count;
	desc.buf = buf;
	desc.error = 0;
	// 调用do_shmem_file_read()开始执行实际的读操作。
	do_shmem_file_read(filp, ppos, &desc);
	// 返回写到用户空间缓冲区的字节数。
	if (desc.written)
		return desc.written;
	// 如果没有写任何东西, 而返回错误
	return desc.error;
}
⑴ ⇐ read_descriptor_t
// include/linux/fs.h
/*
 * "descriptor" for what we're up to with a read for sendfile().
 * This allows us to use the same read code yet
 * have multiple different users of the data that
 * we read from a file.
 *
 * The simplest case just copies the data to user
 * mode.
 */
typedef struct {
	size_t written;
	size_t count;
	char * buf;
	int error;
} read_descriptor_t;
⑵ ⇒ do_shmem_file_read

    do_shmem_file_read 函数

② do_shmem_file_read

    这个函数通过 shmem_getpage() 找回读取文件所需的页面数,并调 file_read_actor() 复制数据到用户空间。

// mm/shmem.c
static void do_shmem_file_read(struct file *filp, loff_t *ppos, read_descriptor_t *desc)
{
	// 找回 inode 和使用 struct file 的 mapping。
	struct inode *inode = filp->f_dentry->d_inode;
	struct address_space *mapping = inode->i_mapping;
	unsigned long index, offset;
	// index是文件中包含数据的页面的索引。
	index = *ppos >> PAGE_CACHE_SHIFT;
	// offset是在当前被读取页面中的偏移量。
	offset = *ppos & ~PAGE_CACHE_MASK;
// 循环直到读取完请求的字节数。nr 是当前页面中还需要读取的字节数。desc->count
// 初始为需要读取的字节数,并由file_read_actor()(见L. 3. 2. 3)减小。
	for (;;) {
		struct page *page = NULL;
		unsigned long end_index, nr, ret;
	// end_index是文件中最后页面的索引。当到达文件尾部时停止。
		end_index = inode->i_size >> PAGE_CACHE_SHIFT;
// 当到达最后一个页面时,设置nr为当前页面中还需要读取的字节数。如果
// 文件指针在nr后面,则停止,因为没有更多的数据供读取。这有可能发生在文件被截断时的情况。
		if (index > end_index)
			break;
		if (index == end_index) {
			nr = inode->i_size & ~PAGE_CACHE_MASK;
			if (nr <= offset)
				break;
		}
	// shmem_getpage() (见L. 5.1. 2)查找被请求页在页面高速缓存,交换缓存中
	// 的位置。如果错误发生,则记录错误到desc->error并返回。	
		desc->error = shmem_getpage(inode, index, &page, SGP_READ);
		if (desc->error) {
			if (desc->error == -EINVAL)
				desc->error = 0;
			break;
		}

		/*
		 * We must evaluate after, since reads (unlike writes)
		 * are called without i_sem protection against truncate
		 */
	// nr是页面中必须读取的字节数,因此初始化为一个页面的大小,这样就可以读取整
	// 个页面。
		nr = PAGE_CACHE_SIZE;
	// 初始化end_index,它是文件中最后一个页面的索引
		end_index = inode->i_size >> PAGE_CACHE_SHIFT;
// 如果是文件的最后一个页面,则更新nr为页面的字节数。如果nr当前在文
// 件尾部之后(可能发生在文件截取的情况),则释放页面的引用(由 shmem_getpage() 使用),并
// 退出循环。
		if (index == end_index) {
			nr = inode->i_size & ~PAGE_CACHE_MASK;
			if (nr <= offset) {
				page_cache_release(page);
				break;
			}
		}
	// 更新需要读取的字节数。请记得offset是在页面中当前的文件读取处。
		nr -= offset;
// 如果读取的页面不是全局零页面,则需要注意调用 flush_dcache_page() 所引
// 起的别名混淆隐患。如果是第一次读取的页面或者仅仅发生 lseek() (f_reada是0),则用 
// mark_page_accessed()标记页面被访问过。
		if (page != ZERO_PAGE(0)) {
			/*
			 * If users can be writing to this page using arbitrary
			 * virtual addresses, take care about potential aliasing
			 * before reading the page on the kernel side.
			 */
			if (mapping->i_mmap_shared != NULL)
				flush_dcache_page(page);
			/*
			 * Mark the page accessed if we read the
			 * beginning or we just did an lseek.
			 */
			if (!offset || !filp->f_reada)
				mark_page_accessed(page);
		}

		/*
		 * Ok, we have the page, and it's up-to-date, so
		 * now we can copy it to user space...
		 *
		 * The actor routine returns how many bytes were actually used..
		 * NOTE! This may not be the same as how much of a user buffer
		 * we filled up (we may be padding etc), so we can only update
		 * "pos" here (the actor routine has to update the user buffer
		 * pointers and the remaining count).
		 */
	// 调用file_read_actor()(见L. 3. 2. 3)复制数据到用户空间。它返回复制的字节数,
	// 并更新用户缓冲指针及剩下的计数。
		ret = file_read_actor(desc, page, offset, nr);
	// 更新页面中读取的偏移量。
		offset += ret;
	// 如果可能,移动索引至下一个页面。
		index += offset >> PAGE_CACHE_SHIFT;
	// 确保offset是页面中的偏移量。
		offset &= ~PAGE_CACHE_MASK;
	// 释放被复制页面的引用。该引用由shmem_getpage()使用。
		page_cache_release(page);
	// 如果已经读取完请求的字节数,则返回。
		if (ret != nr || !desc->count)
			break;
	}
	// 更新文件指针。
	*ppos = ((loff_t) index << PAGE_CACHE_SHIFT) + offset;
	// 允许文件预读。
	filp->f_reada = 1;
	// 更新inode的访问计数,因为它已经被读取。
	UPDATE_ATIME(inode);
}
⑴ ⇒ file_read_actor

    file_read_actor 函数

⑵ ⇒ shmem_getpage

    shmem_getpage 函数

⇐ ⇒ ⇔ ⇆ ⇒ ⟺
①②③④⑤⑥⑦
⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑿⒀⒁⒂⒃⒄⒅⒆⒇

③ file_read_actor

   

(3)写入文件

① shmem_file_write

   

(4)符号链接

① shmem_symlink

   

② shmem_readlink_inline

   

③ shmem_follow_link_inline

   

④ shmem_readlink

   

(5)同步文件

① shmem_sync_file

   

4、tmpfs 中的索引节点操作

(1)截取

① shmem_truncate

   

② shmem_truncate_indirect

   

③ shmem_truncate_direct

   
①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳

④ shmem_free_swp

   

(2)链接

① shmem_link

   

(3)解除链接

① shmem_unlink

   

(4)创建目录

① shmem_mkdir

   

(5)移除目录

① shmem_rmdir

   

② shmem_empty

   

③ shmem_positive

   

5、虚拟文件中的缺页中断

(1)缺页中断时读取页面

① shmem_nopage

   

② shmem_getpage

   

(2)定位交换页面

① shmem_alloc_entry

   

② shmem_swp_entry

   

6、交换空间交互

(1)shmem_writepage

   

(2)shmem_unuse

   

(3)shmem_unuse_inode

   

(4)shmem_find_swp

   

7、建立共享区

(1)shmem_zero_setup

   

(2)shmem_file_setup

   

8、System V IPC

(1)创建一个 SYSV 共享区

① sys_shmget

   

② newseg

   

(2)附属一个 SYSV 共享区

① sys_shmat

   

符号

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

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/668424.html

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

相关文章

STL容器——map的用法

0、概述 map翻译为映射&#xff0c;map可以将任何基本类型&#xff08;包括STL容器&#xff09;映射到任何基本类型&#xff08;包括STL容器&#xff09;。下面是map、multimap和unordered_map之间的差别。 注意这三种映射的底层实现&#xff0c;他决定了算法的时间复杂度。特…

Java+Demo对接中国移动 云MAS短信发送(http协议详解,新测成功!)

一.登录官网&#xff0c;下载http接入文档&#xff08;随着官网不断更新&#xff0c;可参考官网的文档&#xff09; 官网地址为&#xff1a;云mas业务平台 进入云MAS管理平台&#xff0c;找到 管理-接口管理 的列表页。 (必读&#xff1a;本文对接方式是 java引用jar包&#…

【MYSQL篇】一文弄懂mysql中redo log、binlog

前言 今天想跟大家聊聊关于 mysql 中的两个小的知识点&#xff1a;redo log 和 binlog 。 redo log &#xff1a;InnoDB 存储引擎层方面的日志&#xff0c;所以如果你使用的存储引擎不是 InnoDB 的话&#xff0c;那就根本谈不上 redo log。 binlog &#xff1a; MySQL Serve…

SpringBoot整合模板引擎Thymeleaf(2)

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 概述 Thymeleaf十分类似于JSP中使用的EL表达式。整体而言&#xff0c;Thymeleaf简洁、优雅、高效&#xff1b;非常适合小型项目的快速开发。 Thymeleaf常用标签简述 在此…

多商户商城开发功能清单

电商时代下&#xff0c;传统企业商家急需拓展业务规模&#xff0c;向线上拓展&#xff0c;而多商户小程序造价低&#xff0c;效应高&#xff0c;自然就成为了很多企业商家拓展线上营销渠道的首要选择,那么多商户小程序商城涵盖哪些功能呢?下面小编就来详细为大家解答&#xff…

每日学术速递6.8

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.BundleSDF: Neural 6-DoF Tracking and 3D Reconstruction of Unknown Objects(CVPR 2023) 标题&#xff1a;BundleSDF&#xff1a;未知对象的神经 6-DoF 跟踪和 3D 重建 作者&am…

5年功能测试,“我“一进阶自动化测试拿到了24k的offer...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 什么&#xff1f;…

Linux下工作常用命令

Linux系统下常用的查找文件命令&#xff0c;可以方便地根据文件名、文件类型、文件大小等条件来查找指定文件。以下是一些常用的find示例&#xff1a; 1. 根据文件名查找文件 find / nsme “*.log”目录下查找所有以.log结尾的文件 [rootlocalhost ~]# find / -name "…

vite vs babel+webpack | 创建一个简单的vite项目打包运行

有babel、webpack这些优秀的框架&#xff0c;为什么使用vite? 因为vite编译快&#xff0c;启动快&#xff0c;使用简单&#xff0c;还自带一个热更新重启的服务器&#xff0c;vite能够自动的帮我打包所用到的依赖&#xff0c;有些依赖只有用到才会导入&#xff0c;不用到不会…

卷起来了,阿里最新出品“微服务全阶笔记”,涵盖微服务全部操作

近两年&#xff0c;“大厂裁员”总是凭实力冲上各大媒体头条&#xff0c;身在局中的我们早已习以为常。国内的京东&#xff0c;阿里&#xff0c;腾讯&#xff0c;字节&#xff0c;快手&#xff0c;小米等互联网公司都以不同程度的裁员比例向社会输送人才。大量有大厂经验的卷王…

C++基础(7)——类和对象(继承)

前言 本文主要介绍C中的继承 4.6.1&#xff1a;继承和继承方式&#xff08;公有、保护、私有&#xff09; 4.6.2&#xff1a;继承中的对象模型&#xff0c;sizeof()求子类对象大小 4.6.3&#xff1a;子类继承父类后&#xff0c;两者构造和析构顺序 父类先构造、子类先析构 如…

在vue3中使用pinia完整流程图文

1.准备 使用vite创建好一个vue3项目&#xff0c;开发语言选择ts使用 npm i pinia -s 安装最新版本的pinia 这里我的版本安装的是 2.1.4 2.注册pinia 1.在main中注册pinia import { createApp, createApp } from "vue"; import "./style.css"; import App…

Contrastive Representation Learning 对比表征学习(三)视觉:图像嵌入(1)

原文翻译自这里 视觉&#xff1a;图像嵌入&#xff08;Image Embedding&#xff09; 图像增强 在视觉领域&#xff0c;大多数面向对比表征学习的方案均依赖于通过应用数据增强技术的序列组合来创建样本的噪声形式。而这种增强需要满足保持语义不变的同时极大的改变其视觉外观…

既有内销又有外贸,多样性外贸业务管理解决方案

随着外贸数字化贸易全球化的深入发展&#xff0c;出口、进口、内销业务越来越受到关注。外贸业务是企业在海外市场进行商品贸易&#xff0c;而内销业务是企业在国内市场进行商品贸易。在管理这种业务时&#xff0c;想要实现降本增效&#xff0c;企业需要有一套成熟的管理解决方…

Redis什么是缓存穿透、击穿、雪崩?如何解决

缓存与后端系统 通常后端会采用Mysql等磁盘数据库&#xff0c;可以持久化但是访问慢&#xff0c;高并发时性能差&#xff0c;需要设置Nosql内存型数据库缓存&#xff1a;Redis等 但缓存可能出现&#xff1a;缓存穿透、缓存击穿、缓存雪崩等问题 认识缓存穿透、击穿、雪崩 热点…

Vue2:怎么实现响应式双向绑定?

一、vue2怎么实现双向绑定原理 在Vue2中&#xff0c;双向绑定的实现是通过Vue2的响应式系统和数据绑定机制来完成的。下面是Vue2实现双向绑定的简要原理&#xff1a; 数据劫持&#xff1a;当创建Vue实例时&#xff0c;Vue2会对data选项中的所有属性进行数据劫持。这通过使用Ob…

【单元测试】Junit 4教程(一)--白盒测试方法

目录 1.0 流程图标识 1.1 语句覆盖法&#xff08;C0标准&#xff09; 1.2 判定/分支覆盖法&#xff08;C1标准&#xff09; 1.3 条件覆盖法&#xff08;C2标准&#xff09; 1.4 判定条件覆盖法&#xff08;C1C2标准&#xff09; 1.5 条件组合覆盖法&#xff08;C3标准&am…

【ESP8266】基础AT指令和常用WIF指令

【ESP8266 &#xff08;12F&#xff09;】硬件参数 以及 固件烧录 文章目录 一、常用AT命令1.1 基础1.2 WiFi相关1.21 ATCWMODE&#xff1a;查询/设置 Wi-Fi 模式 (Station/SoftAP/StationSoftAP)1.22 ATCWJAP&#xff1a;连接 AP1.23 ATCWLAP&#xff1a;扫描当前可用的 AP1.2…

容器JVM内存配置最佳实践

背景信息 当您的业务是使用Java开发&#xff0c;且设置的JVM堆空间过小时&#xff0c;程序会出现系统内存不足OOM&#xff08;Out of Memory&#xff09;的问题。事件中心的OOM事件是指系统内存不足时&#xff0c;触发了Linux的内存回收&#xff08;OOM Killer&#xff09;机制…

7D性能工程初级班第一期开班了!

Slogan&#xff1a;领略性能艺术的壮阔、感受性能测试的博大精深 课程大纲见&#xff1a;【7D-RESAR 性能工程初级班大纲】 报名流程 讲师介绍 高楼老师&#xff1a; 性能领域公认的具有匠心的技术专家。架构级性能解决方案资深专家。性能测试调优分析18年经验&#xff0c;…