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

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

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

(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);
}

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

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