奔跑吧 Linux内核
上QQ阅读APP看书,第一时间看更新

第2章 内存管理

本章思考题

1.在系统启动时,ARM Linux内核如何知道系统中有多大的内存空间?

2.在32bit Linux内核中,用户空间和内核空间的比例通常是3:1,可以修改成2:2吗?

3.物理内存页面如何添加到伙伴系统中,是一页一页添加,还是以2的几次幂来加入呢?

4.内核的一级页表存放在什么地方?内核空间的二级页表又存放在什么地方?

5.用户进程的一级页表存放在什么地方?二级页表又存放在什么地方?

6.在ARM32系统中,页表是如何映射的?在ARM64系统中,页表又是如何映射的?

7.请简述Linux内核在理想情况下页面分配器(page allocator)是如何分配出连续物理页面的。

8.在页面分配器中,如何从分配掩码(gfp_mask)中确定可以从哪些zone中分配内存?

9.页面分配器是按照什么方向来扫描zone的?

10.为用户进程分配物理内存,分配掩码应该选用GFP_KERNEL,还是GFP_HIGHUSER_MOVABLE呢?

11.slab分配器是如何分配和释放小内存块的?

12.slab分配器中有一个着色的概念(cache color),着色有什么作用?

13.slab分配器中的slab对象有没有根据Per-CPU做一些优化?

14.slab增长并导致大量不用的空闲对象,该如何解决?

15.请问kmalloc、vmalloc和malloc之间有什么区别以及实现上的差异?

16.使用用户态的API函数malloc()分配内存时,会马上为其分配物理内存吗?

17.假设不考虑libc的因素,malloc分配100Byte,那么实际上内核是为其分配100Byte吗?

18.假设两个用户进程打印的malloc()分配的虚拟地址是一样的,那么在内核中这两块虚拟内存是否打架了呢?

19.vm_normal_page()函数返回的是什么样页面的struct page数据结构?为什么内存管理代码中需要这个函数?

20.请简述get_user_page()函数的作用和实现流程。

21.请简述follow_page()函数的作用的实现流程。

22.请简述私有映射和共享映射的区别。

23.为什么第二次调用mmap时,Linux内核没有捕捉到地址重叠并返回失败呢?

      #strace捕捉某个app调用mmap的情况
      mmap(0x20000000, 819200, PROT_READ|PROT_WRITE,
      MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x20000000
      …
      mmap(0x20000000, 4096, PROT_READ|PROT_WRITE,
      MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x20000000

24.struct page数据结构中的_count和_mapcount有什么区别?

25.匿名页面和page cache页面有什么区别?

26.struct page数据结构中有一个锁,请问trylock_page()和lock_page()有什么区别?

27.在Linux 2.4.x内核中,如何从一个page找到所有映射该页面的VMA?反向映射可以带来哪些便利?

28.阅读Linux 4.0内核RMAP机制的代码,画出父子进程之间VMA、AVC、anon_vma和page等数据结构之间的关系图。

29.在Linux 2.6.34中,RMAP机制采用了新的实现,在Linux 2.6.33和之前的版本中称为旧版本RMAP机制。那么在旧版本RMAP机制中,如果父进程有1000个子进程,每个子进程都有一个VMA,这个VMA里面有1000个匿名页面,当所有的子进程的VMA同时发生写复制时会是什么情况呢?

30.当page加入lru链表中,被其他线程释放了这个page,那么lru链表如何知道这个page已经被释放了?

31.kswapd内核线程何时会被唤醒?

32.LRU链表如何知道page的活动频繁程度?

33.kswapd按照什么原则来换出页面?

34.kswapd按照什么方向来扫描zone?

35.kswapd以什么标准来退出扫描LRU?

36.手持设备例如Android系统,没有swap分区或者swap文件,kswapd会扫描匿名页面LRU吗?

37.swappiness的含义是什么?kswapd如何计算匿名页面和page cache之间的扫描比重?

38.当系统充斥着大量只访问一次的文件访问(use-one streaming IO)时,kswapd如何来规避这种风暴?

39.在回收page cache时,对于dirty的page cache, kswapd会马上回写吗?

40.内核有哪些页面会被kswapd写回交换分区?

41.ARM32 Linux如何模拟这个Linux版本的L_PTE_YOUNG比特位呢?

42.如何理解Refault Distance算法?

43.请简述匿名页面的生命周期。在什么情况下会产生匿名页面?在什么条件下会释放匿名页面?

44.KSM是基于什么原理来合并页面的?

45.在KSM机制里,合并过程中把page设置成写保护的函数write_protect_page()有这样一个判断:

      if (page_mapcount(page) + 1 + swapped ! = page_count(page)) {
              goto out_unlock;
        }

请问这个判断的依据是什么?

46.如果多个VMA的虚拟页面同时映射了同一个匿名页面,那么此时page->index应该等于多少?

47.为什么Dirty COW小程序可以修改一个只读文件的内容?

48.在Dirty COW内存漏洞中,如果Dirty COW程序没有madviseThread线程,即只有procselfmemThread线程,能否修改foo文件的内容呢?

49.假设在内核空间获取了某个文件对应的page cache页面的struct page数据结构,而对应的VMA属性是只读,那么内核空间是否可以成功修改该文件呢?

50.如果用户进程使用只读属性(PROT_READ)来mmap映射一个文件到用户空间,然后使用memcpy来写这段内存空间,会是什么样的情况?

51.请画出内存管理中常用的数据结构的关系图,如mm_struct、vma、vaddr、page、pfn、pte、zone、paddr和pg_data等,并思考如下转换关系。

❑ 如何由mm数据结构和虚拟地址vaddr找到对应的VMA?

❑ 如何由page和VMA找到虚拟地址vaddr?

❑ 如何由page找到所有映射的VMA?

❑ 如何由VMA和虚拟地址vaddr找出相应的page数据结构?

❑ page和pfn之间的互换。

❑ pfn和paddr之间的互换。

❑ page和pte之间的互换。

❑ zone和page之间的互换。

❑ zone和pg_data之间的互换。

52.请画出在最糟糕的情况下分配若干个连续物理页面的流程图。

53.在Android中新添加了LMK(Low Memory Killer),请描述LMK和OOM Killer之间的关系。

54.请描述一致性DMA映射dma_alloc_coherent()函数在ARM中是如何管理cache一致性的?

55.请描述流式DMA映射dma_map_single()函数在ARM中是如何管理cache一致性的?

56.为什么在Linux 4.8内核中要把基于zone的LRU链表机制迁移到基于Node呢?


很多同学接触Linux的内存管理是从malloc()这个C语言库函数开始的,也是从那时开始就知道了有虚拟内存这个概念,那虚拟内存究竟是什么呢?怎么虚拟?对于只关注上层应用程序编程的同学来说,可能不是太关心这些知识。可是如果不了解一些这方面知识,就很难设计出高效的应用程序。比较早期的操作系统是没有虚拟内存这个概念的,为什么现代操作系统都有虚拟内存这个概念,包括Windows和Linux?要弄明白虚拟内存,你可能需要了解什么是MMU、页表、物理内存、物理页面、建立映射关系、按需分配、缺页中断和写时复制等机制和概念。

当了解MMU时,除了要了解MMU工作原理外,还会接触到Linux内核如何建立页表映射,其中也包括用户空间页表的建立和内核空间页表的建立,以及内核是如何查询页表和修改页表的。

当了解物理内存和物理页面时,会接触到struct pg_data_t、struct zone和struct page等数据结构,这3个数据结构描述了系统中物理内存的组织架构。struct page数据结构除了描述一个4KB大小(或者其他大小)的物理页面外,还包含很多复杂而有趣的成员。

当了解怎么分配物理页面时,会接触到伙伴系统机制和页面分配器(page allocator),页面分配器是内存管理中最复杂的代码之一。

有了物理内存,那怎么和虚拟内存建立映射关系呢?在Linux内核中,描述进程的虚拟内存用struct vm_area_struct数据结构。虚拟内存和物理内存采用建立页表的方法来完成建立映射关系。为什么和进程地址空间建立映射的页面有的叫匿名页面,而有的叫page cache页面呢?

当了解malloc()怎么分配出物理内存时,会接触到缺页中断,缺页中断也是内存管理中最复杂的代码之一。

这时,虚拟内存和物理内存已经建立了映射关系,这是以页为基础的,可是有时内核需要小于一个页面大小的内存,那么slab机制就诞生了。

上面已经建立起虚拟内存和物理内存的基本框图,但是如果用户持续分配和使用内存导致物理内存不足了怎么办?此时页面回收机制和反向映射机制就应运而生了。

虚拟内存和物理内存的映射关系经常是建立后又被解除了,时间长了,系统物理页面布局变得凌乱不堪,碎片化严重,这时内核如果需要分配大块连续内存就会变得很困难,那么内存规整机制(Memory Compaction)就诞生了。

上述是一位笨叔叔学习Linux内核内存管理知识中痛并快乐着的心路历程。

本章主要介绍Linux内核管理中一些基本的知识,包括内存初始化、页表映射过程、内核内存布局图、伙伴系统、slab分配器、vmalloc、VMA操作、malloc、mmap、缺页中断、page引用计数、反向映射、页面回收、匿名页面的宿命、页面迁移、内存规整、KSM、Dirty COW等内容,内存管理包罗万象,本书不可能面面俱到。

本章大部分内容是以ARM Vexpress平台为例来讲述的,如何搭建该实验平台请参考第6.1节。建议读者先阅读第6.1节,并且在Ubuntu 16.04机器上先搭建这样一个简单好用的实验平台,本章列出的一些实验数据可能和读者的数据有些许不同。

除了依照本章列出来的思考题来阅读内存管理代码之外,从用户态的API来深入了解Linux内核的内存管理机制也是一个很好的方法,下面列出常见的用户态内存管理相关的API。

      void *malloc(size_t size);
      void free(void *ptr);

      void *mmap(void *addr, size_t length, int prot, int flags,
                  int fd, off_t offset);
      int munmap(void *addr, size_t length);

      int getpagesize(void);

      int mprotect(const void *addr, size_t len, int prot);

      int mlock(const void *addr, size_t len);
      int munlock(const void *addr, size_t len);

      int madvise(void *addr, size_t length, int advice);
      void *mremap(void *old_address, size_t old_size,
                size_t new_size, int flags, ... /* void *new_address */);

      int remap_file_pages(void *addr, size_t size, int prot,
                  ssize_t pgoff, int flags);

第2.8节讲述malloc()函数在Linux内核的实现,第2.9节讲述mmap()在Linux内核中的实现,第2.17节用到madvise()这个API,相信读者阅读完本章之后会更容易理解这些用户态API的实现。

第2.19节总结了Linux内核内存管理中常用的数据结构之间错综复杂的关系,同时也归纳了内核中常用的内存管理相关的API,相信读者在了解数据结构和API之后对内存管理会有更深刻的理解。

为了行文方便,本章有如下一些约定。

❑ 忽略了对大页面的处理,默认省略了CONFIG_TRANSPARENT_HUGEPAGE的支持。

❑ 默认省略了对锁的讨论,关于锁在内存管理中的应用详见第4.7节。

❑ 对page cache的讨论比较少。

❑ 由于本书的实验对象ARM Vexpress平台不支持NUMA架构,因此为了简化默认,本章忽略了对NUMA相关代码的讨论。

❑ 忽略了对memory cgroup的讨论。