achlice
作者achlice2018-02-02 10:56
系统工程师, h3c

Linux内核是如何管理内存的换入换出,以及是如何实现磁盘缓存的?

字数 5423阅读 2747评论 2赞 6

本文作者:唐浩然


1:linux是如何管理内存换入换出的?

内存swap的机制不在介绍,其实从问题来看,楼主是想知道,内存页是怎么与换出到磁盘上的内容一一对应的。答案是通过页表。拿32位系统举例子:

1.jpg

1.jpg

处理器通过页表来把一个虚拟地址转化为实际的物理内存地址。每个进程有属于它自己的一组页表;无论何时发生了进程切换,相应的会发生用户空间的页表切换。mm_struct中有一个pdg域,就指向该进程所使用的页表集。对每一个虚拟页在页表中都有对应的一个页表项(PTE),通常x86下的一页是一个4字节的如下记录(pgd和pte的结构类似,12-31位是page base描述,0-11位是属性描述,不过pgd中的pagebase 指向的pte所在页面的起始地址,而pte的page base指向的物理页面的起始地址。假设下面是一条pte对页的描述记录):

2.jpg

2.jpg

注意第0位P位,P位告诉处理器该虚拟页目前是否在物理内存,P位为1,表示该页在内存中。P位为0页表示不在内存,页不在内存也分下面两种情况:

A:从1位至31位为空:该页不在进程的地址空间,需在请求调页
B:1位至31位至少有一位被至位:说明该页被换出到磁盘。

针对B情况作说明:如果有一位被至位,而PTE的关于该页的条目被以如下方式解读:
既,1-7被解读成一个交换区的区号,而8-31位被解读成页槽索引(page-slot,在swap区中一个大小为4k的页被称为page–slot,也许是为了区别与物理页的page -frame),这样该页就指向了swap区的一个4k的空间。

我们再来看swap区,每个活动的swap区在内存中有一个swap区描述符,系统所有的交换区组成一个链表,交换区结构为swap_info_struct,该结构中有个重要字段 :struct block_device * bdev ,存放交换区的块设备的描述符,就是你格式分成swap的设备/分区。

最后来看磁盘上的swap区是什么组织的:
每个交换区由一组pageslot组成(一般这些页槽在磁盘上连续存放),就是说由一组4k大小的块组成,每块包含一个换出的页,。交换区的第一个page slot(0号页槽)用来永久存放交换区的管理信息,可用来存放换出页的第一个页槽为1号页槽(页槽索引为1)。所以上面描述换出页的页表条目时说“1位至31位至少有一位被至位”,因为0号页槽不能存换出页。

到这里,不知道大家对swap页的标记有没有一个大体的概念?

2:内核读入一个文件时,内核是在哪里记录内存页和磁盘块的对应关系,以便将来把脏数据flush回磁盘?具体来说,是在哪些数据结构里记录这些信息的?

先来看文件在磁盘和内存中的存放方式(ext3举例):
磁盘:文件由inode以及inode指向的具体数据块组成,来看看磁盘的Inode结构的描述(source/fs/ext4/ext3.h 下面为3.14内核的中的,source是指内核代码根目录,下同。内核版本不同,行号可能不同):

/*

256 * Structure of
an inode on the disk
257 */
258 struct ext3_inode {
259__le16  i_mode;         /* File mode */
260__le16  i_uid;          /* Low 16 bits of Owner Uid */
261__le32  i_size;         /* Size in bytes */
262__le32  i_atime;        /* Access time */
263__le32  i_ctime;        /* Creation time */
264__le32  i_mtime;        /* Modification time */
265__le32  i_dtime;        /* Deletion Time */
266__le16  i_gid;          /* Low 16 bits of Group Id */
267__le16  i_links_count;  /* Links count */
268__le32  i_blocks;       /* Blocks count */
269__le32  i_flags;        /* File flags */
……
281__le32  i_block[EXT3_N_BLOCKS];/* Pointers to
blocks */
282__le32  i_generation;   /* File version (for NFS) */
……
312 };

第281行有一个“__le32 i_block[EXT3_N_BLOCKS];”

最前面的le 全称为 little-endian,表示排序方式:低阶字节在高位位置与其对应的是be :big-endian。 如代码注释这个数据指向了具体存放文件数据的块(这里我们不讲间接块)。到此,估计已经了解了文件在磁盘上的存放的管理。

下面来说说文件在内存中的存放,文件在内存中存放由VSF层管理,VFS层在打开文件时在内存中依据磁盘的 ext3_inode而建立VFS的inode结构,VFS的inode与磁盘inode是一一对应的,下面摘出该结构的一些内(同前面的内核版本,文件在source/include/linux/fs.h):

*/
527 struct inode {
528         umode_t                 i_mode;
529         unsigned short          i_opflags;
530         kuid_t                  i_uid;
531         kgid_t                  i_gid;
532         unsigned int            i_flags;
……
560         dev_t                   i_rdev;
561         loff_t                  i_size;
562         struct timespec         i_atime;
563         struct timespec         i_mtime;
564         struct timespec         i_ctime;
565         spinlock_t              i_lock; /* i_blocks, i_bytes, maybe i_size */
566         unsigned short          i_bytes;
567         unsigned int            i_blkbits;
568         blkcnt_t                i_blocks;
…………
593         struct file_lock        *i_flock;
594         struct address_space  i_data;
……
}

是不是发现和磁盘上的inode结构存在相似的地方,但又多了一些内容? 这里与文件映射关系最为密切的是 594行的 struct address_spaces i_data ,该结构内容如下:
(同前面的内核版本,文件在source/include/linux/fs.h):

412 struct address_space {
413    struct inode  *host;     /* owner: inode, block_device */
414    struct radix_tree_root  page_tree;   /* radix tree of all pages */
415    spinlock_t           tree_lock;      /* and lock protecting it */
416    unsigned int     i_mmap_writable;/* count VM_SHARED mappings */
417    struct rb_root   i_mmap;  /* tree of private and shared mappings */
418    struct list_head  i_mmap_nonlinear;/*list VM_NONLINEAR mappings */
……
429 } __attribute__((aligned(sizeof(long))));

需要关注的是414行的 radix_tree_root 结构,描述如下(source/include/linux/radix-tree.h):

64 struct radix_tree_root {
65         unsigned int            height;
66          gfp_t                   gfp_mask;
67         struct radix_tree_node__rcu *rnode;
68 };

Height描述树的高/深度,*rnode指向了其子树,根树与子树的关系:

3.jpg

3.jpg

图中也描述了子树的结构的重要部分slots,slots指针数组指向了具体物理页的页描述符,如果基树树高为1,则slots可以指向[RADIX_TESS_MAP_SIZE]个页面,如下面的结构描述:(source/include/linux/radix-tree.h):

50 struct radix_tree_node {
51         unsigned int    height;      /* Height from the bottom */
52         unsigned int    count;
53         union {
54              struct radix_tree_node *parent; /* Used when ascending tree */
55              struct rcu_headrcu_head;   /* Used when freeing node */
56         };
57         void __rcu      *slots[RADIX_TREE_MAP_SIZE];
58         unsigned long   tags[RADIX_TREE_MAX_TAGS][RADIX_TREE_TAG_LONGS];
59 };

最后再来看看物理页描述符的结构(source/include/linux/mm_types.h,内容和注释太多,贴出来行号会乱掉,我只截了部分内容,且去掉了行号):

struct page {
……
         union {
              struct address_space *mapping; 
              void *s_mem;       /* slab first object */
         };
         union {
               pgoff_tindex; /* Our offset within mapping. */
               void *freelist;      
               bool pfmemalloc;       
                };
….
}

其中两个字段 mapping 和index, 如果该页被用于文件映射,则mapping用于指向文件inode的address_space结构,而index(页索引)呢?index是与文件inode->address_space->radix_tree_root->radix_tree_node->slots[]相关的,我直接载图好了:(且下面载图中最后说的图15-1就是前面已贴出的 根树与子树的关系图):

4.jpg

4.jpg

总结起来就是:VFS从磁盘读取inode,并在内存立对应的vfs的inode,vfs层inode中address_space中radix_tree_root中radix_tree_node中slots指向了内存中存放文件的物理页描述符(找到页描述符就能找到页面)。而页描述符中的mapping指向了 inode的address_space, 同时,页描述符中index指定了页在slots中的具体位置 。

如果觉得我的文章对您有用,请点赞。您的支持将鼓励我继续创作!

6

添加新评论2 条评论

灰色轨迹灰色轨迹技术经理, 城商行
2018-02-06 21:57
学习了
wuwenpinwuwenpin软件开发工程师, 南京
2018-02-05 15:25
学习了
Ctrl+Enter 发表

作者其他文章

相关文章

相关问题

相关资料

X社区推广