Contents
  1. 1. 简介
  2. 2. 内存描述符
  3. 3. 内核线程的内存描述符

简介

内核中获得动态内存的方式:__get_free_pages()或alloc_pages()从分区页框分配器中获得页框,kmem_cache_alloc()或kmalloc()使用slab分配器为专用或通用对象分配块,vmalloc()或vmalloc_32()获得一块非连续的内存区。如果所请求的内存区得以满足,这些函数都返回一个页描述符地址或线性地址(即所分配动态内存区的起始地址)。

使用以上简单方法的原因:

内核是操作系统中优先级最高的成分 

内核信任自己。 


当给用户态进程分配内存时:

进程对动态内存的请求被认为是不紧迫的,内核总是尽量推迟给用户态进程分配动态内存。 

由于用户进程是不可信任的,因此 ,内核必须能随时准备捕获用户态进程引起的所有寻址错误。 

进程的地址空间(address space)由允许进程使用的全部线性地址组成。每个进程所看到的线性地址集合是不同的,一个进程所使用的地址与另外一个进程所使用的地址之间没有什么关系。后面我们会看到,内核可以通过增加或删除某些线性地址区间来动态修改进程的地址空间。

内核通过所谓线性区的资源来表示线性地址区间,线性区是由起始线性地址、长度和一些访问权限来描述的。为了效率起见,起始地址和线性区的长度都必须是4096的倍数,以便每个线性区所识别的数据完全填满分配给它的页框。

我们会在“缺页异常处理程序”博文中看到,确定一个进程当前所拥有的线性区(即进程的地址空间)是内核的基本任务,因为这可以让缺页异常处理程序有效地区分引发这个异常处理程序的两种不同类型的无效线性地址:

  • 由编程错误引发的无效线性地址。

  • 由缺页引发的无效线性地址;即使这个线性地址属于进程的地址空间,但是对应于这个地址的页框仍然有待分配。

从进程的观点来看,后一种地址不是无效的,内核要利用这种缺页以实现请求调页:内核通过提供页框来处理这种缺页,并让进程继续执行。

内存描述符

与进程地址空间有关的全部信息都包含在一个叫做内存描述符(memory descriptor)的数据结构中,这个结构的类型为mm_struct,进程描述符的mm字段就指向这个结构。

所有的内存描述符存放在一个双向链表中。每个描述符在mmlist段存放链表相邻元素的地址。链表的第一个元素是init_mm的mmlist字段,init_mm是初始化阶段进程0所使用的内存描述符。mmlist_lock自旋锁保护多处理器系统对链表的同时访问(同样是位于include/linux/Sched.h):

extern spinlock_t mmlist_lock;

mm_users字段存放共享mm_struct数据结构的轻进程的个数。mm_count字段是内存描述符的主是使用计数器,在mm_users次使用计数器中的所有用户在mm_count中只作为一个单位。每当mm_count递减时,内核都要检查它是否变为0,如果是,就要解除这个内存描述符,因为不再有用户使用它。

我们用一个例子来解释mm_users和mm_count之间的不同。如果一个内存描述符由两个轻量级进程共享。它的mm_users字段通常存放的值为2,而mm_count字段存放的值为1(两个所有者进程算作一个)。

如果把内存描述符暂时借给一个内核线程,那么,内核就增加mm_count。这样,即使两个轻量级进程都死亡,且mm_users字段变为0,这个内存描述符也不被释放,直到内核线程使用完为止,因为mm_count字段仍然大于0。

但是,如果内核想确保内存描述符在一个长操作的中间不被释放,那么,就应该增加mm_users字段而不是mm_coont字段的值。最终的结果是相同的,因为mm_users的增加确保了mm_count不变为0,即使拥有这个内存描述符的所有轻进程全部死亡。

内核线程的内存描述符

内核线程仅运行在内核态,因此,它们永远不会访问低于TASK_SIZE(等于PAGE_OFFSET,通常为0xc0000000,即768MB)的地址。与普通进程相反,内核线程不用线性区(vm_area_struct),因此,内存描述符的很多字段对内核线程是没有意义的。

因为大于TASK_SIZE线性地址的相应页表项都应该总是相同的,因此,一个内核线程到底使用什么样的页表集根本就没有什么关系。为了避免无用的TLB和高速缓存刷新,内核线程使用一组最近运行的普通进程的页表。所以,我们在每个进程描述符中包含了两种内存描述符的指针:mm和active_mm。

进程描述符中的mm字段指向进程所拥有的内存描述符,而active_mm字段指向进程运行时所使用的内存描述符。对于普通进程而言,这两个字段存放相同的指针。但是,内核线程不拥有任何内存描述符,因此,它们的mm字段总是为NULL。当内核线程得以运行时,他的active_mm字段被初始化为前一个运行进程的active_mm值。



本文章参考自《深入理解linux内核》 。

Contents
  1. 1. 简介
  2. 2. 内存描述符
  3. 3. 内核线程的内存描述符