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

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

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

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

image.png

目录
相关文章
|
1月前
|
安全 Linux Shell
Linux上执行内存中的脚本和程序
【9月更文挑战第3天】在 Linux 系统中,可以通过多种方式执行内存中的脚本和程序:一是使用 `eval` 命令直接执行内存中的脚本内容;二是利用管道将脚本内容传递给 `bash` 解释器执行;三是将编译好的程序复制到 `/dev/shm` 并执行。这些方法虽便捷,但也需谨慎操作以避免安全风险。
106 6
|
1天前
|
Linux C++
Linux c/c++文件虚拟内存映射
这篇文章介绍了在Linux环境下,如何使用虚拟内存映射技术来提高文件读写的速度,并通过C/C++代码示例展示了文件映射的整个流程。
8 0
|
2月前
|
Linux 调度
深入理解Linux虚拟内存管理(七)(下)
深入理解Linux虚拟内存管理(七)
51 4
|
2月前
|
存储 Linux 索引
深入理解Linux虚拟内存管理(九)(中)
深入理解Linux虚拟内存管理(九)
29 2
|
2月前
|
Linux 索引
深入理解Linux虚拟内存管理(九)(上)
深入理解Linux虚拟内存管理(九)
39 2
|
2月前
|
Linux
深入理解Linux虚拟内存管理(七)(中)
深入理解Linux虚拟内存管理(七)
36 2
|
2月前
|
Linux
深入理解Linux虚拟内存管理(七)(上)
深入理解Linux虚拟内存管理(七)
45 1
|
2月前
|
缓存 Linux 调度
Linux服务器如何查看CPU占用率、内存占用、带宽占用
Linux服务器如何查看CPU占用率、内存占用、带宽占用
369 0
|
2月前
|
Linux API
深入理解Linux虚拟内存管理(六)(下)
深入理解Linux虚拟内存管理(六)
16 0
|
2月前
|
Linux
深入理解Linux虚拟内存管理(六)(中)
深入理解Linux虚拟内存管理(六)
22 0