深入理解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 函数


深入理解Linux虚拟内存管理(九)(中):https://developer.aliyun.com/article/1597911


目录
相关文章
|
9天前
|
安全 Linux Shell
Linux上执行内存中的脚本和程序
【9月更文挑战第3天】在 Linux 系统中,可以通过多种方式执行内存中的脚本和程序:一是使用 `eval` 命令直接执行内存中的脚本内容;二是利用管道将脚本内容传递给 `bash` 解释器执行;三是将编译好的程序复制到 `/dev/shm` 并执行。这些方法虽便捷,但也需谨慎操作以避免安全风险。
|
18天前
|
Linux 调度
深入理解Linux虚拟内存管理(七)(下)
深入理解Linux虚拟内存管理(七)
33 4
|
18天前
|
存储 Linux 索引
深入理解Linux虚拟内存管理(九)(中)
深入理解Linux虚拟内存管理(九)
17 2
|
18天前
|
Linux
深入理解Linux虚拟内存管理(七)(中)
深入理解Linux虚拟内存管理(七)
21 2
|
18天前
|
机器学习/深度学习 消息中间件 Unix
深入理解Linux虚拟内存管理(九)(下)
深入理解Linux虚拟内存管理(九)
15 1
|
18天前
|
Linux
深入理解Linux虚拟内存管理(七)(上)
深入理解Linux虚拟内存管理(七)
24 1
|
16天前
|
缓存 Linux 调度
Linux服务器如何查看CPU占用率、内存占用、带宽占用
Linux服务器如何查看CPU占用率、内存占用、带宽占用
54 0
|
18天前
|
Linux API
深入理解Linux虚拟内存管理(六)(下)
深入理解Linux虚拟内存管理(六)
12 0
|
18天前
|
Linux
深入理解Linux虚拟内存管理(六)(中)
深入理解Linux虚拟内存管理(六)
18 0
|
18天前
|
存储 Linux Serverless
深入理解Linux虚拟内存管理(六)(上)
深入理解Linux虚拟内存管理(六)
15 0