内核资料收集
1.内存描述符
与进程地址空间有关的全部信息都包含在一个叫内存描述符(memory descriptor)的数据结构中, 这个结构的类型为mm_struct,
进程描述符的mm字段就指向这个结构. 内存描述符表(不同版本有变化)中的字段(include/linux/mm_types.h):
struct vm_area_struct * mmap 指向线性区对象的链表头
struct rb_root mm_rb 指向线性区对象的红黑树的根
struct vm_area_struct * mmap_cache 指向最后一个引用的线性区对象
unsigned long (*)() get_unmapped_area 在进程地址空间中搜索有效性地址区间的方法
void (*)() unmap_area 释放线性地址区间时调用的方法
unsigned long mmap_base 标识第一个分配的匿名线性区或文件内存映射的线性地址
unsigned long free_area_cache 内核从这个地址开始搜索进程地址空间中线性地址的空闲区间
pgd_t * pgd 指向页全局目录
atomic_t mm_users 次使用计数器
atomic_t mm_count 主使用计数器
int map_count 线性区个数
struct rw_semaphore mmap_sem 线性区读/写信号量
spinlock_t page_table_lock 线性区自旋锁和页表自旋锁
struct list_head mmlist 指向内存区描述符链表中的相邻元素
unsigned long start_code 可执行代码的起始地址
unsigned long end_code 可执行代码的最后地址
unsigned long start_data 已初始化数据的起始地址
unsigned long end_data 已初始化数据的最后地址
unsigned long start_brk 堆起始地址
unsigned long end_brk 堆的当前最后地址
unsigned long start_stack 用户态堆栈的起始地址
unsigned long arg_start 命令行参数起始地址
unsigned long arg_end 命令行参数的最后地址
unsigned long env_start 环境变量起始地址
unsigned long env_end 环境变量的最后地址
unsigned long rss 分配给进程的页框数
unsigned long anon_rss 分配给匿名内存映射的页框数
unsigned long total_vm 进程地址空间的大小(页数)
unsigned long locked_vm "锁住"而不能换出的页的个数
unsigned long shared_vm 共享文件内存映射中的页数
unsigned long exec_vm 可执行内存映射中的页数
unsigned long stack_vm 用户态堆栈中的页数
unsigned long reserved_vm 在保留区中的页数或在特殊线性区中的页数
unsigned long def_flags 线性区默认访问标志
unsigned long nr_ptes this 进程的页表数
unsigned long [] saved_auxv 开始执行ELF程序时使用
unsigned int dumpable 是否可产生内存信息转储标志
cpumask_t cpu_vm_mask 用于懒惰TLB交换的位掩码
mm_context_t context 指向有关特定体系结构信息的表(如x86平台上的LDT)
unsigned long swap_token_time 进程在这个时间将有资格获得交换标记
char recent_pagein 最近发生缺页, 则设置该标记
int core_waiters 正在把进程地址空间的内容卸载到转储文件中的轻量级进程的数量
struct completion * core_startup_done 指向创建内存转储文件时的补充原语
struct completion core_done 创建内存转储文件时使用的补充原语
rwlock_t ioctx_list_lock 用于保护异步I/O上下文链表的锁
struct kioctx * ioctx_list 异步I/O上下文件链表
struct kioctx default_ioctx 默认的异步I/O上下文
unsigned long hiwater_rss 进程拥有的最大页框数
unsigned long hiwater_vm 进程线性区中的最大页数
2. 内存描述符中的一些字段
所有内存描述符存放在一个双向链表中,每个描述符在mmlist字段存放相邻元素的地址. 链表的每一个元素是init_mm的
mmlist字段, init_mm是初始化阶段进程0所使用的内存描述符. mmlist_lock自旋锁保护多处理器系统对链表的同时访问.
mm_user字段存放共享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_count字段的值. 最终
的结果是相同的, 因为mm_users的增加确保了mm_count来变为0,即使拥有这个内存描述符的所有轻量级进程全部死亡.
mm_alloc()函数用来从slab分配器高速缓存中获取一个新的内存描述符. mm_alloc()调用kmem_cache_alloc()来初始化新
的内存描述符, 并把mm_count和mm_users字段都置为1. mm_put()函数递减内存描述符的mm_users字段. 如果该字段变为0,
这个函数就释放局部描述符表/线性区描述符及由内存描述符所引用的页表,并调用mmdrop().后一个函数把mm_count字段减1, 如
果该字段变为0, 就释放mm_struct数据结构.
3. 内核线程的内存描述符
内核线程仅运行在内核态, 它们永远不会访问TASK_SIZE(等于PAGE_OFFSET, x86下通常为0xC0000000)以下的地址. 内核
线程不用线性区, 因而内存描述符的很多字段对内核线程没有意义.
大于TASK_SIZE线性地址的相应页表项都是相同的, 因此一个内核线程到底使用什么样的页表集根本没关系. 为了避免无用的
TLB和高速缓存刷新,内核线程使用一组最近运行的普通进程的页表. 进程描述符用mm和active_mm处理此情况. mm字段指向进程
所拥有的内存描述符, active_mm字段指向进程运行时所使用的内存描述符. 对普通进程而言, 这两个字段存放相同的指针. 但是,
内核线程不拥有内存描述符,因此它们的mm字段总是NULL. 内核线程运行时,它的active_mm字段被初始化为前一个运行进程的
active_mm值.
内核态的进程为高于TASK_SIZE的线性地址修改页表项,那么它也就应当更新系统中所有进程页表集合中的相应表项. 事实上,一旦
内核态的一个进程进行了设置. 映射应该对内核态的其他所有进程都有效.触及所有进程的页表集合是相当费时的操作, 因此,linux采
用延迟方式. 每当一个高端地址必须被重新映射时, 内核就更新根目录在swapper_pg_dir主内存页全局目录中的常规页表集合. 这个
页全局目录由主内存描述符的pgd字段指向,而主内存描述符存放于init_mm变量.
阅读(2929) | 评论(0) | 转发(0) |