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

在这里插入图片描述

系列文章目录


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


文章目录


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

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

(3)inode 结构(1)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

    这个函数用于从页面复制数据到用户空间缓冲区。它最终由多个函数所调用,包括 generic_file_read()shmem_file_read()

// mm/filemap.c
// 参数如下所示:
//	desc是个包含读信息的结构,包括缓冲区和从文件读取的总字节数。
//	page是复制到用户空间且包含文件数据的页面。
//	offset是页面中复制的偏移量。
//	size是从页面读取的字节数。
int file_read_actor(read_descriptor_t * desc, struct page *page, unsigned long offset, 
					unsigned long size)
{
	char *kaddr;
	// count现在是从文件读取的字节数。
	unsigned long left, count = desc->count;
	// 确保读取的字节数不会比请求的多。
	if (size > count)
		size = count;
	// 调用kmap()映射页面到低端内存。见L L 1小节。
	kaddr = kmap(page);
	// 从内核页面复制数据到用户空间缓冲区。定义在文件 include/asm-i386/uaccess.h 中
	left = __copy_to_user(desc->buf, kaddr + offset, size);
	// 解除页面映射。见L 3.1小节。
	kunmap(page);
// 如果所有的字节都没有被复制,那肯定是缓冲区不能访问。这更新size,因
// 此desc->count可以反映读操作中还有多少字节待复制。返回-EFAULT到执行读操作的
// 进程。
	if (left) {
		size -= left;
		desc->error = -EFAULT;
	}
	// 更新desc结构,以表明当前的读状态。
	desc->count = count - size;
	desc->written += size;
	desc->buf += size;
	// 返回写入用户空间缓冲区的字节数。
	return size;
}
⑴ ⇒ kmap

    kmap 函数

⑵ ⇒ kunmap

    kunmap 函数

(3)写入文件

① shmem_file_write

// mm/shmem.c
// 这块是函数导引。
static ssize_t
shmem_file_write(struct file *file, const char *buf, size_t count, loff_t *ppos)
{
	// 获取描述哪些文件被写入的索引节点。
	struct inode	*inode = file->f_dentry->d_inode;
	loff_t		pos;
	unsigned long	written;
	int		err;
	// 如果用户尝试写负的字节数,则返回-EINVAL。
	if ((ssize_t) count < 0)
		return -EINVAL;
	// 如果用户空间缓冲区不可访问,则返回-EINVAL。
	if (!access_ok(VERIFY_READ, buf, count))
		return -EFAULT;
	// 获取保护索引节点的semaphore信号量。
	down(&inode->i_sem);
	// 记录写入开始的位置。
	pos = *ppos;
	// 初始化写入字节为0。
	written = 0;
	// precheck_file_write()执行一系列的检查以确保写操作的正确进行。检查包括以
	// append 模式打开文件时更新pos为文件尾部,并保证系统不会超过进程的限定。
	// 文件实现在 mm/filemap.c 中
	err = precheck_file_write(file, inode, &count, &pos);
	// 如果不能执行写操作,则跳转到out。
	if (err || !count)
		goto out;
	// 如果设置了 SUID 位则清除它。
	remove_suid(inode);
	// 更新索引节点的ctime和mtime。
	inode->i_ctime = inode->i_mtime = CURRENT_TIME;
	
// 循环直至所有的请求都进行了写操作。
	do {
		struct page *page = NULL;
		unsigned long bytes, index, offset;
		char *kaddr;
		int left;
	// 设置offset为当前被写页面内的偏移量。
		offset = (pos & (PAGE_CACHE_SIZE -1)); /* Within page */
	// index是当前被写文件中页面的索引。
		index = pos >> PAGE_CACHE_SHIFT;
	// bytes是当前页面中还需要写入的字节数。
		bytes = PAGE_CACHE_SIZE - offset;
	// 如果bytes表明需要写入比请求(count)更多的字节数,则设置bytes为count。
		if (bytes > count)
			bytes = count;

		/*
		 * We don't hold page lock across copy from user -
		 * what would it guard against? - so no deadlock here.
		 */
	// 定位要写入的页面。SGP_WRITE 标志位表明了如果某个页面还不存在则需
	// 要分配它。如果无法找到某页面或分配页面,则跳出循环。
		err = shmem_getpage(inode, index, &page, SGP_WRITE);
		if (err)
			break;
	// 在再次解除页面映射以前,映射被写入且从用户空间缓冲区复制数据的页面。
		kaddr = kmap(page);
	// 从用户空间缓冲区复制数据到内核页面。定义在文件 include/asm-i386/uaccess.h 中
		left = __copy_from_user(kaddr + offset, buf, bytes);
		kunmap(page);
	// 更新写入的字节数。
		written += bytes;
	// 更新还需要写入的字节数。
		count -= bytes;
	// 更新在文件中的位置。
		pos += bytes;
	// 更新用户空间缓冲区中的指针。
		buf += bytes;
	// 如果文件变大,则更新inode->i_size。
		if (pos > inode->i_size)
			inode->i_size = pos;
	// 刷新dcache以避免别名混淆隐患。
		flush_dcache_page(page);
	// 设置该页面为脏且被引用过。
		SetPageDirty(page);
		SetPageReferenced(page);
	// 释放shmem_getpage()对页面使用的引用。
		page_cache_release(page);
	// 如果所有请求的字节都没有从用户空间缓存区读出,则更新写操作统计数据和
	// 文件中的位置及缓冲区中的位置。
		if (left) {
			pos -= left;
			written -= left;
			err = -EFAULT;
			break;
		}
	} while (count);
	// 更新文件指针。
	*ppos = pos;
	// 如果所有的字节都没有写入,则设置错误返回变量。
	if (written)
		err = written;
out:
	// 释放索引节点semaphore信号量。
	up(&inode->i_sem);
	// 返回成功或者返回还需要写入的字节数。
	return err;
}
⑴ ⇒ shmem_getpage

    shmem_getpage 函数

⑵ ⇒ kmap

    kmap 函数

⑶ ⇒ kunmap

    kunmap 函数

(4)符号链接

① shmem_symlink

    这个函数用于创建符号链接 symname 并决定在什么地方存储该信息。如果链接的名字足够小则存储在索引节点中,否则存储在页面帧当中。

// mm/shmem.c
static int shmem_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
{
// 这块执行基本的全部检查,并为符号链接创建一个新的索引节点。
// 参数symname是所创建链接的名字。
	int error;
	int len;
	struct inode *inode;
	struct page *page = NULL;
	char *kaddr;
	struct shmem_inode_info *info;
	// 计算链接的长度(len)。
	len = strlen(symname) + 1;
	// 如果名字比一个页面长,则返回-ENAMETOOLONG。
	if (len > PAGE_CACHE_SIZE)
		return -ENAMETOOLONG;
	// 分配一个新的inodeo分配失败则返回-ENOSPC
	inode = shmem_get_inode(dir->i_sb, S_IFLNK|S_IRWXUGO, 0);
	if (!inode)
		return -ENOSPC;
	// 获取私有信息结构。
	info = SHMEM_I(inode);
	// 索引节点的大小即链接的长度。
	inode->i_size = len-1;
// 这块用于存储链接信息。
	// 如果名字的长度小于shmem_inode_info所使用的空间,则复制名字到保留
	// 给私有结构的空间中。
	if (len <= sizeof(struct shmem_inode_info)) {
		/* do it inline */
		memcpy(info, symname, len);
	// 设置inode->i_op为shmem_symlink_inline_operations操作,这样可以知道
	// 链接名字在索引节点中。
		inode->i_op = &shmem_symlink_inline_operations;
	} else {
	// 用 shmem_getpage_locked 分配页面。
		error = shmem_getpage(inode, 0, &page, SGP_WRITE);
	// 如果发生错误,则解除索引节点的引用并返回错误。
		if (error) {
			iput(inode);
			return error;
		}
	// 使用shmem_symlink_inode_operations, 这样可以知道链接信息被包括在页面中。
		inode->i_op = &shmem_symlink_inode_operations;
	// shmem_ilock是一个全局锁,用于保护索引节点的全局链表,这个链表是由私有的
	// 信息结构info->list字段链接起来的。
		spin_lock(&shmem_ilock);
	// 添加新的索引节点到全局链表。
		list_add_tail(&info->list, &shmem_inodes);
	// 释放 shmem_ilock 。
		spin_unlock(&shmem_ilock);
	// 映射页面。
		kaddr = kmap(page);
	// 复制链接信息。
		memcpy(kaddr, symname, len);
	// 解除页面映射。
		kunmap(page);
	// 设置页面为脏。
		SetPageDirty(page);
	// 释放对页面的引用。
		page_cache_release(page);
	}
	// 增加目录的大小,因为添加了新的索引节点。BOGO_DIRENT_SIZE 是索引节点的
	// 伪大小,这样 ls 操作看起来更好一些。
	dir->i_size += BOGO_DIRENT_SIZE;
	// 更新 i_ctime 和 i_mtime。
	dir->i_ctime = dir->i_mtime = CURRENT_TIME;
	// 实例化索引节点。
	d_instantiate(dentry, inode);
	dget(dentry);
	// 返回成功。
	return 0;
}
⑴ ⇒ shmem_get_inode

    shmem_get_inode 函数

⑵ ⇒ shmem_getpage

    shmem_getpage 函数

⑶ ⇒ d_instantiate

    d_instantiate 函数

② shmem_readlink_inline

// mm/shmem.c
static int shmem_readlink_inline(struct dentry *dentry, char *buffer, int buflen)
{
	// 链接名字被包括在索引节点中,这样可以将它作为vfs_readlink()的参数传递给 VFS 层。
	// 其实现在文件 fs/namei.c 中
	return vfs_readlink(dentry, buffer, buflen, (const char *)SHMEM_I(dentry->d_inode));
}
③ shmem_follow_link_inline

// mm/shmem.c
static int shmem_follow_link_inline(struct dentry *dentry, struct nameidata *nd)
{
	// 链接名字被包括在索引节点中,这样可以将它作为vfs_followlink()的参数传递给 VFS 层。
	// 其实现在文件 fs/namei.c 中
	return vfs_follow_link(nd, (const char *)SHMEM_I(dentry->d_inode));
}
④ shmem_follow_link

// mm/shmem.c
static int shmem_follow_link(struct dentry *dentry, struct nameidata *nd)
{
	struct page *page = NULL;
	// 由于链接名字在页面中,因此可以调用shmem_getpage()获取页面。
	int res = shmem_getpage(dentry->d_inode, 0, &page, SGP_READ);
	// 如果发生错误,则返回错误。
	if (res)
		return res;
	// 映射页面并作为指针传递给vfs_follow_link()。
	// 其实现在文件 fs/namei.c 中
	res = vfs_follow_link(nd, kmap(page));
	// 解除页面映射。
	kunmap(page);
	mark_page_accessed(page);
	// 解除对页面的引用。	
	page_cache_release(page);
	// 返回成功。
	return res;
}
⑴ ⇒ shmem_getpage

    shmem_getpage 函数

⑤ shmem_readlink

// mm/shmem.c
static int shmem_readlink(struct dentry *dentry, char *buffer, int buflen)
{
	struct page *page = NULL;
	// 链接名字被包括在和symlink关联的页面中,它可以调用shmem_getpage()获取指针。
	int res = shmem_getpage(dentry->d_inode, 0, &page, SGP_READ);
	// 如果发生错误,则返回NULL。
	if (res)
		return res;
	// 调用kmap() 映射页面,并将它作为参数传递给vfs_readlink()。该
	// 链接位于页面首部。
	// 其实现在文件 fs/namei.c 中
	res = vfs_readlink(dentry, buffer, buflen, kmap(page));
	// 解除页面映射。
	kunmap(page);
	// 标记页面被访问过。
	mark_page_accessed(page);
	// 解除调用shmem_getpage()对页面的引用。
	page_cache_release(page);
	// 返回该链接。
	return res;
}
⑴ ⇒ shmem_getpage

    shmem_getpage 函数

(5)同步文件

① shmem_sync_file

    这个函数简单返回 0,因为文件只存在于内存而不需要同步文件到磁盘。

// mm/shmem.c
static int shmem_sync_file(struct file *file, struct dentry *dentry, int datasync)
{
	return 0;
}

4、tmpfs 中的索引节点操作

(1)截取

① shmem_truncate

    当调用这个函数的时候,inode->i_sizevmtruncate() 设置为新大小。这个函数用于创建或移除页面,以此设置文件的大小。

// mm/shmem.c
static void shmem_truncate(struct inode *inode)
{
	// 用SHMEM_I()获取索引节点的私有文件系统信息。
	struct shmem_inode_info *info = SHMEM_I(inode);
	// 获取超级块私有信息。
	struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb);
	unsigned long freed = 0;
	unsigned long index;
	// 更新索引节点的ctime和mtime。
	inode->i_ctime = inode->i_mtime = CURRENT_TIME;
	// 获取文件新的尾部页面的索引。原来的大小存储在info->next_index中。
	index = (inode->i_size + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT;
	// 如果文件扩大了,只需要返回,因为全局。页面将用于表示扩大的区域。
	if (index >= info->next_index)
		return;
	// 获取私有info自旋锁。
	spin_lock(&info->lock);
	// 继续调用shmem_truncate_indirect()直至文件被截取为需要的大小。
	while (index < info->next_index)
		freed += shmem_truncate_indirect(info, index);
	// 如果shmem_info_info结构表示的换出页面多于文件中的页面,这是个bug。	
	BUG_ON(info->swapped > info->next_index);
	// 释放私有info自旋锁。
	spin_unlock(&info->lock);
	// 获取超级块私有info自旋锁。
	spin_lock(&sbinfo->stat_lock);
	// 更新可用空闲块的数量。
	sbinfo->free_blocks += freed;
	// 更新索引节点所使用的块数量。
	inode->i_blocks -= freed*BLOCKS_PER_PAGE;
	// 释放超级块私有info自旋锁。
	spin_unlock(&sbinfo->stat_lock);
}
② shmem_truncate_indirect

    这个函数定位索引节点中最后两次间接访问的块,并调用 shmem_truncate_direct() 截取它。

// mm/shmem.c
/*
 * shmem_truncate_indirect - truncate an inode
 *
 * @info:  the info structure of the inode
 * @index: the index to truncate
 *
 * This function locates the last doubly indirect block and calls
 * then shmem_truncate_direct to do the real work
 */
static inline unsigned long
shmem_truncate_indirect(struct shmem_inode_info *info, unsigned long index)
{
	swp_entry_t ***base;
	unsigned long baseidx, start;
	// len是当前文件中被第2次使用的最后页面。
	unsigned long len = info->next_index;
	unsigned long freed;
// 如果文件很小且所有的项都存储在直接块信息中,则调用shmem_free_swp() 并传递
// info->i_direct 中第一个交换项和项数目给这个函数,供截取之用。
	if (len <= SHMEM_NR_DIRECT) {
		info->next_index = index;
		if (!info->swapped)
			return 0;
		freed = shmem_free_swp(info->i_direct + index,
					info->i_direct + len);
		info->swapped -= freed;
		return freed;
	}
// 被截取的页面位于间接块的某处。这个代码部分用于计算3个变量:base,
// baseidx和len。base是页面的首部,该页面包括将被截取交换项的指针。baseidx是被使用间
// 接块中第一个项的页索引而len是这里将被截取的项数目。
//
// 计算两次间接块的变量。接着设置base到info->i_indirect首部的交换项。而
// info->i_indirect 首部的页面索引baseidx为SHMEM_NR_DIRECT。在这里 len 为文件中页
// 面的数量,因此减去直接块的数量就是剩下页面的数量。	
	if (len <= ENTRIES_PER_PAGEPAGE/2 + SHMEM_NR_DIRECT) {
		len -= SHMEM_NR_DIRECT;
		base = (swp_entry_t ***) &info->i_indirect;
		baseidx = SHMEM_NR_DIRECT;
	} else {
// 如果不是这样,那么这是一个3级索引块,因此必须在计算base,baseidx和len
// 以前遍历下一级。
		len -= ENTRIES_PER_PAGEPAGE/2 + SHMEM_NR_DIRECT;
		BUG_ON(len > ENTRIES_PER_PAGEPAGE*ENTRIES_PER_PAGE/2);
		baseidx = len - 1;
		baseidx -= baseidx % ENTRIES_PER_PAGEPAGE;
		base = (swp_entry_t ***) info->i_indirect +
			ENTRIES_PER_PAGE/2 + baseidx/ENTRIES_PER_PAGEPAGE;
		len -= baseidx;
		baseidx += ENTRIES_PER_PAGEPAGE/2 + SHMEM_NR_DIRECT;
	}
// 如果在截取后文件变大,则更新 next_index 为文件新尾部,并设置start为间接
// 块的首部。
	if (index > baseidx) {
		info->next_index = index;
		start = index - baseidx;
	} else {
// 如果在截取后文件变小,则移动当前文件尾部至将被截取的间接块的首部。
		info->next_index = baseidx;
		start = 0;
	}
	// 如果在base处有块,则调用shmem_truncate_direct()截取其中的页面。
	return *base? shmem_truncate_direct(info, base, start, len): 0;
}
③ shmem_truncate_direct

    这个函数用于循环遍历间接块,并对包含被截取交换向量的每个页面调用 shmem_free_swp

// mm/shmem.c
/*
 * shmem_truncate_direct - free the swap entries of a whole doubly
 *                         indirect block
 *
 * @info:	the info structure of the inode
 * @dir:	pointer to the pointer to the block
 * @start:	offset to start from (in pages)
 * @len:	how many pages are stored in this block
 */
static inline unsigned long
shmem_truncate_direct(struct shmem_inode_info *info, swp_entry_t ***dir, unsigned long start, unsigned long len)
{
	swp_entry_t **last, **ptr;
	unsigned long off, freed_swp, freed = 0;
	// last是被截取间接块中最后一个页面。
	last = *dir + (len + ENTRIES_PER_PAGE - 1) / ENTRIES_PER_PAGE;
	// 如果截取是部分截取而非全页截取,则off是页面中的截取偏移量。
	off = start % ENTRIES_PER_PAGE;
	
// 从dir的startth块开始截取,直至最后一块。
	for (ptr = *dir + start/ENTRIES_PER_PAGE; ptr < last; ptr++, off = 0) {
	// 如果这里没有页面,则继续下一个。
		if (!*ptr)
			continue;
	// 如果info结构表明交换出属于这个索引节点的页面,则调用shmem_free_swp()
	// 释放与该页面相关联的交换槽。如果释放了一个,那么更新infoswapped并增加空闲页面的
	// 计数。
		if (info->swapped) {
			freed_swp = shmem_free_swp(*ptr + off,
						*ptr + ENTRIES_PER_PAGE);
			info->swapped -= freed_swp;
			freed += freed_swp;
		}
	// 如果这不是部分截取,则释放该页面。
		if (!off) {
			freed++;
			free_page((unsigned long) *ptr);
			*ptr = 0;
		}
	}
	// 如果整个间接块现在已释放,则回收该页面。
	if (!start) {
		freed++;
		free_page((unsigned long) *dir);
		*dir = 0;
	}
	// 返回被释放页面的数量。
	return freed;
}
④ shmem_free_swp

    这个函数释放起始项位于 dir 的交换项的 count

// mm/shmem.c
/*
 * shmem_free_swp - free some swap entries in a directory
 *
 * @dir:   pointer to the directory
 * @edir:  pointer after last entry of the directory
 */
static int shmem_free_swp(swp_entry_t *dir, swp_entry_t *edir)
{
	swp_entry_t *ptr;
	int freed = 0;
	// 循环释放每个交换项。
	for (ptr = dir; ptr < edir; ptr++) {
	// 如果存在交换项,则用free_swap_and_cache()释放它并设置交换项为0。它增
	// 加被释放页面的数量。
		if (ptr->val) {
			free_swap_and_cache(*ptr);
			*ptr = (swp_entry_t){0};
			freed++;
		}
	}
	// 返回被释放页面的总数量。
	return freed;
}

(2)链接

① shmem_link

    这个函数创建一个从 dentryold_dentry 的硬链接。

// mm/shmem.c
/*
 * Link a file..
 */
static int shmem_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry)
{
	// 获取与old_dentry对应的索引节点。
	struct inode *inode = old_dentry->d_inode;
	// 如果它链接到一个目录,则返回-EPERM。严格意义上,根目录应当允许硬
	// 链接目录,虽然并不推荐这么处理,但是在文件系统中创建循环时利用诸如find的操作时可
	// 能会找不到路径。所以tmpfs简单地不允许目录硬链接。
	if (S_ISDIR(inode->i_mode))
		return -EPERM;
	// 为新链接增加目录的大小。
	dir->i_size += BOGO_DIRENT_SIZE;
	// 更新目录的mtime和ctime,并更新索引节点的ctime。
	inode->i_ctime = dir->i_ctime = dir->i_mtime = CURRENT_TIME;
	// 增加指向索引节点的链接数量。
	inode->i_nlink++;
	atomic_inc(&inode->i_count);	/* New dentry reference */
	// 调用dget()获取新dentry的额外引用。
	dget(dentry);		/* Extra pinning count for the created dentry */
	// 实例化新目录项。
	d_instantiate(dentry, inode);
	// 返回成功。
	return 0;
}

(3)解除链接

① shmem_unlink

   

// mm/shmem.c
static int shmem_unlink(struct inode *dir, struct dentry *dentry)
{
	// 获取解除链接的dentry的索引节点。
	struct inode *inode = dentry->d_inode;
	// 更新目录索引节点的大小。
	dir->i_size -= BOGO_DIRENT_SIZE;
	// 更新ctime和mtime变量。
	inode->i_ctime = dir->i_ctime = dir->i_mtime = CURRENT_TIME;
	// 减小索引节点的链接数量。
	inode->i_nlink--;
	// 调用dput()减小dentry的引用计数。在引用计数为0时,这个函数也调用iput()清
	// 除索引节点。
	dput(dentry);	/* Undo the count from "create" - this does all the work */
	return 0;
}

(4)创建目录

① shmem_mkdir

// mm/shmem.c
static int shmem_mkdir(struct inode *dir, struct dentry *dentry, int mode)
{
	int error;
	// 调用shmem_mknod() (见L. 2. 2小节)创建一个特定文件。通过指定S_IFDIR标
	// 志位来创建一个目录。
	if ((error = shmem_mknod(dir, dentry, mode | S_IFDIR, 0)))
		return error;
	// 增加父目录的i_nlink字段。
	dir->i_nlink++;
	return 0;
}

(5)移除目录

① shmem_rmdir

// mm/shmem.c
static int shmem_rmdir(struct inode *dir, struct dentry *dentry)
{
	// 用shmem_empty()检查目录是否为空。如果不是,则返回-ENOTEMPTY。
	if (!shmem_empty(dentry))
		return -ENOTEMPTY;
	// 减小父目录的i_nlink字段。
	dir->i_nlink--;
	// 返回 shmem_unlink()(见L. 4. 3. 1)的结果,结果一般是删除该目录。
	return shmem_unlink(dir, dentry);
}
② shmem_empty

    这个函数检查目录是否为空。

// mm/shmem.c
/*
 * Check that a directory is empty (this works
 * for regular files too, they'll just always be
 * considered empty..).
 *
 * Note that an empty directory can still have
 * children, they just all have to be negative..
 */
static int shmem_empty(struct dentry *dentry)
{
	struct list_head *list;
	// dcache_lock虽然保护多个事物,但主要保护dcache的查找,因为这是该函数必须做的工作。
	spin_lock(&dcache_lock);
	list = dentry->d_subdirs.next;
// 循环遍历子目录链表并查找活动目录项,而子目录链表包含了所有的子目录项。如
// 果找到,则表明目录不为空。	
	while (list != &dentry->d_subdirs) {
	// 获取子目录项。
		struct dentry *de = list_entry(list, struct dentry, d_child);
	// 如果目录项有一个相关联的合法索引节点且目前通过hash查找到,则由
	// shmem_positive() (见 L. 4. 5. 3)返回。
	// 因为如果hash查找到,则意味着目录项是活动的,且目录不为空。
		if (shmem_positive(de)) {
	// 如果目录不为空,则释放自旋锁并返回。
			spin_unlock(&dcache_lock);
			return 0;
		}
	// 转到下一个子目录项。
		list = list->next;
	}
	// 目录为空。则释放自旋锁并返回。
	spin_unlock(&dcache_lock);
	return 1;
}
③ shmem_positive

// mm/shmem.c
static inline int shmem_positive(struct dentry *dentry)
{
	// 如果目录项有一个相关联的合法索引节点且目前通过hash查找到,则返回真。
	return dentry->d_inode && !d_unhashed(dentry);
}

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

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

① shmem_nopage

    这是位于最高层的 nopage() 函数,在发生缺页中断时由 do_no_page() 调用。不论该异常是第一次异常还是由后援存储器引起的异常都将调用该函数。

// mm/shmem.c
// 这两个参数表示异常发生的VMA结构和异常发生的地址。
struct page *shmem_nopage(struct vm_area_struct *vma, unsigned long address, int unused)
{
	// 记录发生异常的索引节点。
	struct inode *inode = vma->vm_file->f_dentry->d_inode;
	struct page *page = NULL;
	unsigned long idx;
	int error;
	// 在虚拟文件中将idx作为偏移量以PAGE_SIZE为单位进行计算。
	idx = (address - vma->vm_start) >> PAGE_SHIFT;
	idx += vma->vm_pgoff;
	// 考虑到页面高速缓存中的项大小可能与单个页面的大小不一致,则调整它。但是在
	// 这里没有什么不同。
	idx >>= PAGE_CACHE_SHIFT - PAGE_SHIFT;
	// shmem_getpage()(见 L. 5. 1. 2)用于定位在 idx 的页面。
	error = shmem_getpage(inode, idx, &page, SGP_CACHE);
	// 如果发生错误,则决定是返回一个OOM错误还是返回一个无效异常地址错误。
	if (error)
		return (error == -ENOMEM)? NOPAGE_OOM: NOPAGE_SIGBUS;
	// 标记页面被访问过,这样将移动它到LRU链表首部。
	mark_page_accessed(page);
	// flush_page_to_ram()用于避免dcache别名混淆隐患。
	flush_page_to_ram(page);
	// 返回发生异常的页面。
	return page;
}
② shmem_getpage

// mm/shmem.c
/*
 * shmem_getpage - either get the page from swap or allocate a new one
 *
 * If we allocate a new one we do not mark it dirty. That's up to the
 * vm. If we swap it in we mark it dirty since we also free the swap
 * entry since a page cannot live in both the swap and page cache
 */
// 参数如下所示:
//	inode 是异常发生的索引节点。
//	idx 是异常发生的文件中页面的索引。
//  pagep (如果NULL)将成为异常页面(如果成功)。如果传入一个合法的页面,这
//        个函数要确保它是最新的。
//	sgp 表明这是什么类型的访问,即决定如何定位页面且如何返回。
static int shmem_getpage(struct inode *inode, unsigned long idx, struct page **pagep, 
						enum sgp_type sgp)
{
	struct address_space *mapping = inode->i_mapping;
	// SHMEM_I()返回超级块信息中具有特定文件系统信息的shmem_inode_info。
	struct shmem_inode_info *info = SHMEM_I(inode);
	struct shmem_sb_info *sbinfo;
	struct page *filepage = *pagep;
	struct page *swappage;
	swp_entry_t *entry;
	swp_entry_t swap;
	int error = 0;
	// 确保索引不会超过文件尾部。
	if (idx >= SHMEM_MAX_INDEX)
		return -EFBIG;
	/*
	 * Normally, filepage is NULL on entry, and either found
	 * uptodate immediately, or allocated and zeroed, or read
	 * in under swappage, which is then assigned to filepage.
	 * But shmem_readpage and shmem_prepare_write pass in a locked
	 * filepage, which may be found not uptodate by other callers
	 * too, and may need to be copied from the swappage read in.
	 */
repeat:
	// 如果pagep参数没有传入页面,则尝试定位页面并用find_lock_page()锁住页面。
	// 实现在文件 include/linux/pagemap.h 中
	if (!filepage)
		filepage = find_lock_page(mapping, idx);
	// 如果页面被找到且为最新的,则跳转到done,因为这个函数不需要再做其他更
	// 多的工作。
	if (filepage && Page_Uptodate(filepage))
		goto done;
	// 锁住索引私有信息结构。
	spin_lock(&info->lock);
	// 调用shmem_swp_alloc()搜索交换项得到idx。如果它开始不存在,则分配它。
	entry = shmem_swp_alloc(info, idx, sgp);
	// 如果发生错误,则释放自旋锁并返回错误。
	if (IS_ERR(entry)) {
		spin_unlock(&info->lock);
		error = PTR_ERR(entry);
		goto failed;
	}
	swap = *entry;

// 在这块,页面有一个合法的交换项。首先在交换高速缓存中搜索页面,如果不存在,则从
// 后援存储器读取页面。
//
// 这些代码用于处理合法交换项存在的地方。
	if (swap.val) {
		/* Look it up and read it in.. */
	// 调用lookup_swap_cache() (见K. 2. 4. 1)在交换高速缓存中搜索swappage。
		swappage = lookup_swap_cache(swap);
// 如果页面不在交换高速缓存中,则用read_swap_cache_async()从后援存储器
// 读取页面。在638行,wait_on_page() 一直被调用直至I/O完成。待I/O完成后,释放页面引
// 用,并跳转到repeat标签处获取自旋锁并重新尝试。
		if (!swappage) {
			spin_unlock(&info->lock);
			swapin_readahead(swap);
			swappage = read_swap_cache_async(swap);
			if (!swappage) {
				spin_lock(&info->lock);
				entry = shmem_swp_alloc(info, idx, sgp);
				if (IS_ERR(entry))
					error = PTR_ERR(entry);
				else if (entry->val == swap.val)
					error = -ENOMEM;
				spin_unlock(&info->lock);
				if (error)
					goto failed;
				goto repeat;
			}
			wait_on_page(swappage);
			page_cache_release(swappage);
			goto repeat;
		}
	// 尝试并锁住页面。如果失败,则一直等待直至页面可以上锁,并跳转到repeat
	// 再次尝试。
		/* We have to do this with page locked to prevent races */
		if (TryLockPage(swappage)) {
			spin_unlock(&info->lock);
			wait_on_page(swappage);
			page_cache_release(swappage);
			goto repeat;
		}
	// 如果页面不是最新的,I/O 也由于某种原因失败,则返回错误。
		if (!Page_Uptodate(swappage)) {
			spin_unlock(&info->lock);
			UnlockPage(swappage);
			page_cache_release(swappage);
			error = -EIO;
			goto failed;
		}

// 这里所讨论的是页面在交换高速缓存中的情况。
	// 从交换高速缓存删除页面,这样可以尝试将它添加到页面高速缓存中。
		delete_from_swap_cache(swappage);
// 如果调用者用pagep参数提供页面,则用数据更新swappage的pagep。
		if (filepage) {
			entry->val = 0;
			info->swapped--;
			spin_unlock(&info->lock);
			flush_page_to_ram(swappage);
			copy_highpage(filepage, swappage);
			UnlockPage(swappage);
			page_cache_release(swappage);
			flush_dcache_page(filepage);
			SetPageUptodate(filepage);
			SetPageDirty(filepage);
			swap_free(swap);
		} else if (add_to_page_cache_unique(swappage,
// 如果没有,则尝试并添加swappage到页面高速缓存中。请注意info->swapped
// 已经更新,标记页面为最新,然后调用swap_free()释放交换缓存。
			mapping, idx, page_hash(mapping, idx)) == 0) {
			entry->val = 0;
			info->swapped--;
			spin_unlock(&info->lock);
			filepage = swappage;
			SetPageUptodate(filepage);
			SetPageDirty(filepage);
			swap_free(swap);
		} else {
// 如果添加页面到页面高速缓存失败,则用add_to_swap_cache()添加它到交换
// 缓存。标记页面为最新,接着为页面解锁并跳转到repeat再次尝试。
			if (add_to_swap_cache(swappage, swap) != 0)
				BUG();
			spin_unlock(&info->lock);
			SetPageUptodate(swappage);
			SetPageDirty(swappage);
			UnlockPage(swappage);
			page_cache_release(swappage);
			goto repeat;
		}
	} else if (sgp == SGP_READ && !filepage) {
// 在这块,没有合法的交换项对应idx。如果页面已经读取且pagep为NULL,则在页面高
// 速缓存中定位页面。
	// 调用find_get_page() (见J. 1. 4. 1)在页面高速缓存中查找页面。
		filepage = find_get_page(mapping, idx);
	// 如果找到页面,但不是最新的或者无法上锁,则释放自旋锁并一直等待直至页
	// 面解锁。然后跳转到repeat获取自旋锁并再次尝试。
		if (filepage &&
		    (!Page_Uptodate(filepage) || TryLockPage(filepage))) {
			spin_unlock(&info->lock);
			wait_on_page(filepage);
			page_cache_release(filepage);
			filepage = NULL;
			goto repeat;
		}
	// 释放自旋锁。
		spin_unlock(&info->lock);
	} else {
// 如果没有,则写入一个不在页面高速缓存的页面。当然,需要定位它。
	// 用SHMEM_SB()获取超级块信息。
		sbinfo = SHMEM_SB(inode->i_sb);
	// 获取超级块信息自旋锁。
		spin_lock(&sbinfo->stat_lock);
	// 如果文件系统中没有空闲块,则释放自旋锁,然后设置返回错误为-ENOSPC并
	// 跳转到failed。
		if (sbinfo->free_blocks == 0) {
			spin_unlock(&sbinfo->stat_lock);
			spin_unlock(&info->lock);
			error = -ENOSPC;
			goto failed;
		}
	// 减小可用块的数量。
		sbinfo->free_blocks--;
	// 增加inode的块使用计数。
		inode->i_blocks += BLOCKS_PER_PAGE;
	// 释放超级块私有信息自旋锁。
		spin_unlock(&sbinfo->stat_lock);
// 如果pagep没有提供页面,则分配新页面并为新页面分配交换项。
		if (!filepage) {
	// 释放信息自旋锁,因为page_cache_alloc()可能已经睡眠。
			spin_unlock(&info->lock);
	// 分配新页面。
			filepage = page_cache_alloc(mapping);
			if (!filepage) {
	// 如果分配失败,则调用shmem_free_block()释放块并设置返回错误为
	// -ENOMEM,然后跳转 failed。			
				shmem_free_block(inode);
				error = -ENOMEM;
				goto failed;
			}
	// 重新获取信息自旋锁。
			spin_lock(&info->lock);
	// shmem_swp_entry()为页面定位交换项。如果不存在交换项(或许是对应该页的),
	// 则分配交换项并返回。
			entry = shmem_swp_alloc(info, idx, sgp);
	// 如果没有找到交换项或分配交换项,则进行设置并返回错误。
			if (IS_ERR(entry))
				error = PTR_ERR(entry);
			if (error || entry->val ||
	// 如果没有错误发生,则添加页面到页面高速缓存。
			    add_to_page_cache_unique(filepage,
			    mapping, idx, page_hash(mapping, idx)) != 0) {
	// 如果没有页面被添加到页面高速缓存中(例如,由于进程在竞争中,当释放自旋
	// 锁的同时有另一个进程插入页面),则释放新页面的引用并释放块。
				spin_unlock(&info->lock);
				page_cache_release(filepage);
				shmem_free_block(inode);
				filepage = NULL;
	// 如果错误发生,则跳转到failed报告错误。
				if (error)
					goto failed;
	// 否则,跳转到repeat在页面高速缓存中再次搜索指定的页面。
				goto repeat;
			}
		}
	// 释放信息自旋锁。
		spin_unlock(&info->lock);
	// 对新页面填充0。
		clear_highpage(filepage);
	// 刷新dcache以避免CPU的dcache别名混淆隐患。
		flush_dcache_page(filepage);
	// 标记页面为最新。
		SetPageUptodate(filepage);
	}
done:
	// 如果pagep没有传入页面,则决定返回什么。如果分配的页面用于写操作,则
	// 解锁并返回filepage()否则,调用者为读操作,则返回全局0填充页面。
	if (!*pagep) {
		if (filepage) {
			UnlockPage(filepage);
			*pagep = filepage;
		} else
			*pagep = ZERO_PAGE(0);
	}
	// 返回成功。
	return 0;

// 这是操作失败的执行路径。
failed:
	// 如果页面由该函数分配并存储在filepage中,则为页面解锁并释放对它的引用。
	if (*pagep != filepage) {
		UnlockPage(filepage);
		page_cache_release(filepage);
	}
	// 返回错误代码。
	return error;
}

(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

12.7 建立共享区

    这个函数用于建立一个 VMA ,该 VMA 是由匿名页面后援的共享区域。该函数的调用图如图 12.5 所示。在 mmap()MAP_SHARED 标志位创建匿名页面时调用该函数。

// mm/shmem.c
/*
 * shmem_zero_setup - setup a shared anonymous mapping
 *
 * @vma: the vma to be mmapped is prepared by do_mmap_pgoff
 */
int shmem_zero_setup(struct vm_area_struct *vma)
{
	struct file *file;
	// 计算大小。
	loff_t size = vma->vm_end - vma->vm_start;
	// 调用shmem_file_setup()(见L. 7. 2小节)创建称为dev/zero且指定大小的文件。
	// 可以从代码注释看出名字为什么没必要惟一。
	file = shmem_file_setup("dev/zero", size);
	if (IS_ERR(file))
		return PTR_ERR(file);
	// 如果已经存在文件对应该虚拟区域,则调用fput()释放其引用。
	if (vma->vm_file)
		fput(vma->vm_file);
	// 记录新文件指针。
	vma->vm_file = file;
	// 设置vm_ops,这样在VMA中发生缺页时将调用shmem_nopage()(见L. 5.1. 1)。
	vma->vm_ops = &shmem_vm_ops;
	return 0;
}

(2)shmem_file_setup

    这个函数用于在内部文件系统 shmfs 中创建新文件。由于该文件系统是内部的,所以名字没有必要在每个目录中惟一。因此,由 shmem_zero_setup() 在匿名区域创建的每个文件都称为 “dev/zero” ,而由 shmget() 创建的区域称为 “SYSVNN” ,其中 NN 是个键,它作为第 1 个参数传递给 shmget()

// mm/shmem.c
/*
 * shmem_file_setup - get an unlinked file living in tmpfs
 *
 * @name: name for dentry (to be seen in /proc/<pid>/maps
 * @size: size to be set for the file
 *
 */
// 参数是要创建的文件名和指定的大小。
struct file *shmem_file_setup(char *name, loff_t size)
{
	int error;
	struct file *file;
	struct inode *inode;
	struct dentry *dentry, *root;
	struct qstr this;
	// vm_enough_memory()(M. 1. 1小节)检查以确保有足够的内存供映射。
	int vm_enough_memory(long pages);
	// 如果挂载点有错误,则返回错误。
	if (IS_ERR(shm_mnt))
		return (void *)shm_mnt;
	// 不要创建大于 SHMEM_MAX_BYTES 的文件,SHMEM_MAX_BYTES 在 mm/shmem.c 的文件开始处计算过。
	if (size > SHMEM_MAX_BYTES)
		return ERR_PTR(-EINVAL);
	// 确保有足够的内存供映射。
	if (!vm_enough_memory(VM_ACCT(size)))
		return ERR_PTR(-ENOMEM);
	// 产生struct qstr,它是用于目录节点的字符串类型。
	this.name = name;
	this.len = strlen(name);
	this.hash = 0; /* will go */
	// root用于表示shmfs根目录的目录节点。
	root = shm_mnt->mnt_root;
	// 调用d_alloc()分配新目录项。
	dentry = d_alloc(root, &this);
	// 如果不能分配则返回-ENOMEM。
	if (!dentry)
		return ERR_PTR(-ENOMEM);
	// 从文件表中获取空struct file。如果一个也找不到,则返回-ENFILE,表示文件表溢出。
	error = -ENFILE;
	file = get_empty_filp();
	if (!file)
		goto put_dentry;
	// 创建新索引节点,它是一个常规文件( S_IFREG )且全局可读、可写、可执行。
	// 如果失败,则返回-ENOSPC,表示文件系统中没有空间了。
	error = -ENOSPC;
	inode = shmem_get_inode(root->d_sb, S_IFREG | S_IRWXUGO, 0);
	if (!inode)
		goto close_file;
	// d_instantiate()为目录项填充索引节点信息。在fs/dcache.c中有它的定义
	d_instantiate(dentry, inode);
	// 填充剩下的索引节点和文件信息。
	inode->i_size = size;
	inode->i_nlink = 0;	/* It is unlinked */
	file->f_vfsmnt = mntget(shm_mnt);
	file->f_dentry = dentry;
	file->f_op = &shmem_file_operations;
	file->f_mode = FMODE_WRITE | FMODE_READ;
	// 返回新创建的struct file。
	return file;

close_file:
	// 索引节点不能创建情况下的错误处理路径。put_filp()填充文件表中的struct file 项。
	put_filp(file);
put_dentry:
	// dput()将释放目录项的引用,且销毁它。
	dput(dentry);
	// 返回错误代码。
	return ERR_PTR(error);
}
① ⇒ shmem_get_inode

    shmem_get_inode 函数

② ⇒ d_instantiate

    d_instantiate 函数

8、System V IPC


现在在 Linux 中使用较多的进程间通信方式主要有以下几种:

  • unix 继承:管道、信号
  • system V IPC 对象:共享内存、消息队列、信号灯集
  • 套接字

    进程间通讯方式参考:共享内存_shmgetLinux 进程间通信

(1)创建一个 SYSV 共享区

① sys_shmget

// ipc/shm.c
asmlinkage long sys_shmget (key_t key, size_t size, int shmflg)
{
	struct shmid_kernel *shp;
	int err, id = 0;
	// 获取保护共享内存ID的semaphore信号量。
	down(&shm_ids.sem);
	// 如果指定了 IPC_PRIVATE, 则忽略大部分的标志位,并调用newseg()创建区
	// 域。该标志位用于提供惟一访问共享区的方式,然后Linux并不保证惟一访问方式。
	if (key == IPC_PRIVATE) {
		err = newseg(key, shmflg, size);
	} else if ((id = ipc_findkey(&shm_ids, key)) == -1) {
// 如果不是,则调用ipc_findkey。搜索查看key是否已存在。
	// 如果不是且没有指定IPC_CREAT,则返回-ENOENT。
		if (!(shmflg & IPC_CREAT))
			err = -ENOENT;
		else
	// 如果不是,则调用newseg()创建一个新的区域。
			err = newseg(key, shmflg, size);
	} else if ((shmflg & IPC_CREAT) && (shmflg & IPC_EXCL)) {
	// 如果区域已存在且进程请求先前未创建的新区域,则返回-EEXIST。
		err = -EEXIST;
	} else {
	// 如果不是,则表示访问一个已有的区域,则锁住它,并确保具有必要的权限,然
	// 后调用shm_buildid()建立段标识符,接着再次解锁该区域。段标识符将返回到用户空间。
		shp = shm_lock(id);
		if(shp==NULL)
			BUG();
		if (shp->shm_segsz < size)
			err = -EINVAL;
		else if (ipcperms(&shp->shm_perm, shmflg))
			err = -EACCES;
		else
			err = shm_buildid(id, shp->shm_perm.seq);
		shm_unlock(id);
	}
	// 释放保护ID的semaphore信号量。
	up(&shm_ids.sem);
	// 返回错误或者时段标识符。
	return err;
}
⑴ ⇔ shmget

       #include <sys/ipc.h>
       #include <sys/shm.h>

       int shmget(key_t key, size_t size, int shmflg);
② newseg

    这个函数创建新共享区。

// ipc/shm.c
static int newseg (key_t key, int shmflg, size_t size)
{
// 这块分配段描述符。
	int error;
	struct shmid_kernel *shp;
	// 计算区域占用的页面数。
	int numpages = (size + PAGE_SIZE -1) >> PAGE_SHIFT;
	struct file * file;
	char name[13];
	int id;
	// 确保区域大小不超过范界限。
	if (size < SHMMIN || size > shm_ctlmax)
		return -EINVAL;
	// 确保段所需要的页面数不超过范围界限。
	if (shm_tot + numpages >= shm_ctlall)
		return -ENOSPC;
	// 调用kmalloc() (见H. 4. 2. 1)分配描述符。
	shp = (struct shmid_kernel *) kmalloc (sizeof (*shp), GFP_USER);
	if (!shp)
		return -ENOMEM;
	// 打印shmfs中创建的文件名。名字为SYSVNN,其中NN是区域的key描述符。
	sprintf (name, "SYSV%08x", key);
	// 用 shmem_file_setup() (见 L. 7. 2 小节)在 shmfs 中创建新文件。
	file = shmem_file_setup(name, size);
	// 确保文件创建过程中没有错误发生。
	error = PTR_ERR(file);
	if (IS_ERR(file))
		goto no_file;
	// 缺省情况下,返回的错误表示没有可用的共享内存或请求的大小过于偏大。
	error = -ENOSPC;
	id = shm_addid(shp);
	if(id == -1) 
		goto no_id;
	// 填充段描述符的各个字段。
	shp->shm_perm.key = key;
	shp->shm_flags = (shmflg & S_IRWXUGO);
	shp->shm_cprid = current->pid;
	shp->shm_lprid = 0;
	shp->shm_atim = shp->shm_dtim = 0;
	shp->shm_ctim = CURRENT_TIME;
	shp->shm_segsz = size;
	shp->shm_nattch = 0;
	// 建立段描述符,用于返回给调用者shmget() 。
	shp->id = shm_buildid(id,shp->shm_perm.seq);
	// 设置文件指针和文件操作数据结构。
	shp->shm_file = file;
	file->f_dentry->d_inode->i_ino = shp->id;
	file->f_op = &shm_file_operations;
	// 更新shm_tot为共享段所使用的页面总数。
	shm_tot += numpages;
	shm_unlock (id);
	// 返回描述符。
	return shp->id;

no_id:
	fput(file);
no_file:
	kfree(shp);
	return error;
}
⑴ ⇒ shmem_file_setup

    shmem_file_setup 函数

⑵ ⇔ shm_addid

// ipc/shm.c
static inline int shm_addid(struct shmid_kernel *shp)
{
	return ipc_addid(&shm_ids, &shp->shm_perm, shm_ctlmni+1);
}
⑶ ⇔ ipc_addid

// ipc/util.c
/**
 *	ipc_addid 	-	add an IPC identifier
 *	@ids: IPC identifier set
 *	@new: new IPC permission set
 *	@size: new size limit for the id array
 *
 *	Add an entry 'new' to the IPC arrays. The permissions object is
 *	initialised and the first free entry is set up and the id assigned
 *	is returned. The list is returned in a locked state on success.
 *	On failure the list is not locked and -1 is returned.
 */
int ipc_addid(struct ipc_ids* ids, struct kern_ipc_perm* new, int size)
{
	int id;

	size = grow_ary(ids,size);
	for (id = 0; id < size; id++) {
		if(ids->entries[id].p == NULL)
			goto found;
	}
	return -1;
found:
	ids->in_use++;
	if (id > ids->max_id)
		ids->max_id = id;

	new->cuid = new->uid = current->euid;
	new->gid = new->cgid = current->egid;

	new->seq = ids->seq++;
	if(ids->seq > ids->seq_max)
		ids->seq = 0;

	spin_lock(&ids->ary);
	ids->entries[id].p = new;
	return id;
}
⑷ ⇔ shm_buildid

// ipc/shm.c
#define shm_buildid(id, seq) \
	ipc_buildid(&shm_ids, id, seq)

// ==============================================================
// include/linux/ipc.h
#define IPCMNI 32768  /* <= MAX_INT limit for ipc arrays (including sysctl changes) */

// ==============================================================
// ipc/util.h
#define SEQ_MULTIPLIER	(IPCMNI)

extern inline int ipc_buildid(struct ipc_ids* ids, int id, int seq)
{
	return SEQ_MULTIPLIER*seq + id;
}

(2)附属一个 SYSV 共享区

① sys_shmat

// ipc/shm.c
/*
 * Fix shmaddr, allocate descriptor, map shm, add attach descriptor to lists.
 */
asmlinkage long sys_shmat (int shmid, char *shmaddr, int shmflg, ulong *raddr)
{
// 这部分确保shmat()的参数均合法。
	struct shmid_kernel *shp;
	unsigned long addr;
	unsigned long size;
	struct file * file;
	int    err;
	unsigned long flags;
	unsigned long prot;
	unsigned long o_flags;
	int acc_mode;
	void *user_addr;
	// 不允许出现负标识符,如果出现则返回-EINVAL。
	if (shmid < 0)
		return -EINVAL;
// 如果调用者提供地址,则确保它是ok的。
	if ((addr = (ulong)shmaddr)) {
	// SHMLBA是段边界地址的倍数。在Linux中,它一般是PAGE_SIZE。如果地址不
	// 是和地址对齐的,则检查调用者是否指定了 SHM_RND , 因为它允许改变地址。如果指定了,
	// 则四舍五入地址到最近的页面边界。否则,返回-EINVAL。
		if (addr & (SHMLBA-1)) {
			if (shmflg & SHM_RND)
				addr &= ~(SHMLBA-1);	   /* round down */
			else
				return -EINVAL;
		}
	// 设置VMA所使用的标志位,这样可以创建定长(MAP_FIXED)的共享区域(MAP_SHARED) 。
		flags = MAP_SHARED | MAP_FIXED;
	} else {
// 如果没有提供地址,则确保指定了 SHM_REMAP 且只使用VMA的MAP_SHARED标志位。
// 这意味着do_mmap() (见D. 2. L 1)将找到附属于共享区的合适地址。
		if ((shmflg & SHM_REMAP))
			return -EINVAL;

		flags = MAP_SHARED;
	}

	if (shmflg & SHM_RDONLY) {
		prot = PROT_READ;
		o_flags = O_RDONLY;
		acc_mode = S_IRUGO;
	} else {
		prot = PROT_READ | PROT_WRITE;
		o_flags = O_RDWR;
		acc_mode = S_IRUGO | S_IWUGO;
	}

	/*
	 * We cannot rely on the fs check since SYSV IPC does have an
	 * additional creator id...
	 */
// 这块确保IPC权限合法。
	// shm_lock()锁住和shmid对应的描述符并返回指向描述符的指针。
	shp = shm_lock(shmid);
	// 确保描述符存在。
	if(shp == NULL)
		return -EINVAL;
	// 确保ID和描述符相匹配。
	err = shm_checkid(shp,shmid);
	if (err) {
		shm_unlock(shmid);
		return err;
	}
	// 确保调用者具有正确的权限。
	if (ipcperms(&shp->shm_perm, acc_mode)) {
		shm_unlock(shmid);
		return -EACCES;
	}
	// 获取do_mmap() 所需要的指向struct file的指针。
	file = shp->shm_file;
	// 获取共享区大小,这样do_mmap()就知道创建多大的VMA。
	size = file->f_dentry->d_inode->i_size;
	// 临时增加shm_nattch,它一般用于表示使用该段的VMA数。因为这样可以阻止
	// 段被提前释放。真正的计数器由shm_open()进行增加,shm_open是共享区域中
	// vm_operations_struct 所使用的open()的回调函数。
	shp->shm_nattch++;
	// 释放描述符。
	shm_unlock(shmid);

// 这块作用是调用do_mmap()将区域附属到调用进程。
	// 获取保护mm_struct的semaphore信号量。
	down_write(&current->mm->mmap_sem);
	if (addr && !(shmflg & SHM_REMAP)) {
	// 如果指定了地址,则调用find_vma_intersection() (见D. 3. 1. 3)确保没有VMA
	// 与我们尝试使用的区域相重叠。
		user_addr = ERR_PTR(-EINVAL);
		if (find_vma_intersection(current->mm, addr, addr + size))
			goto invalid;
		/*
		 * If shm segment goes below stack, make sure there is some
		 * space left for the stack to grow (at least 4 pages).
		 */
	// 确保在共享区尾部和栈之间至少有4页大小的空隙。
		if (addr < current->mm->start_stack &&
		    addr > current->mm->start_stack - size - PAGE_SIZE * 5)
			goto invalid;
	}
	// 调用do_mmap()(见D. 2. L 1)分配VMA并映射它到进程地址空间。		
	user_addr = (void*) do_mmap (file, addr, size, prot, flags, 0);

invalid:
	// 释放 mm_struct 的 semaphore 信号量
	up_write(&current->mm->mmap_sem);
	// 释放区域ID的semaphore信号量。
	down (&shm_ids.sem);
	// 锁住段描述符。
	if(!(shp = shm_lock(shmid)))
		BUG();
	// 减小临时shm_nattch计数器。它将会由 vm_ops->open 的回调函数适当地增加。
	shp->shm_nattch--;
	// 如果用户减少到0且指定了 SHM_DEST 标志位,则销毁区域,因为已经不需要它了。
	if(shp->shm_nattch == 0 &&
	   shp->shm_flags & SHM_DEST)
		shm_destroy (shp);
	else
	// 否则,给段解锁。
		shm_unlock(shmid);
	up (&shm_ids.sem);
	// 设置返回给调用者的地址。
	*raddr = (unsigned long) user_addr;
	// 如果发生错误,则设置返回给调用者的错误。
	err = 0;
	if (IS_ERR(user_addr))
		err = PTR_ERR(user_addr);
	// 返回。
	return err;
}
⑴ ⇒ do_mmap

    do_mmap 函数

⑵ ⇒ find_vma_intersection

    find_vma_intersection 函数

符号

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

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)

作者: [爱尔兰] 戈尔曼(Gorm 出版社: 北京航空航天大学出版社 原作名: Understanding the Linux Virtual Memory Manager 内容简介 · · · · · · 深入理解Linux虚拟内存管理,ISBN:9787810777308,作者:(爱尔兰)戈尔曼著;白洛等 作者简介 · · · · · · Mel Gorman曾获得爱尔兰利马瑞克大学的计算机学士和硕士学位。他的研究领域广泛:从网页开发到摄影机的实时显示系统。Mel Gorman认为,即使是最难以攻克的项目也并没有想象中那么艰难。他曾经还担任过系统管理员,主要管理Linux, 也涉及到Solaris和Windows。现今Mel Gorman是都柏林IBM公司的Java程序开发员。 Mel Gorman的大部分技能都来自于他自己在利马瑞克大学的生活经历,与大学里计算机社区的广泛接触,以及实地工作经验这三者的完美结合。是计算机社区使他接触到了Linux,并相信Linux用途广泛,而绝不仅仅只是一个便利的邮箱地址。这个社区还使他对开放源码软件产生了兴趣,尤其是在Linux内核方面。他永远感激利马瑞克大学为他提供的这个平台,使他认识了许多有识之士,并有两年时间来研究VM。 闲暇时Mel Gorman喜欢和女友凯伦呆在一起,或者弹弹吉他(尽管并不擅长),读读手边的书籍,与朋友和家人(他们避免谈及有关VM的话题)一同消磨时光。又或是制订一些可能并无价值的计划(有时仅仅在想象中完成它们)。只要安迪说服他乘坐游艇是个不错的娱乐项目,他也会去尝试。Mel Gorman还在犹豫着是继续创作关于Linux的文章,还是向从前一样在Linux环境下编写程序,因为后者才是他最初的意愿。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值