nullLinux内核源代码导读Linux内核源代码导读哈尔滨工业大学(威海)
嵌入式系统实验室
Spring 2012Linux虚拟内存管理Linux虚拟内存管理
1 描述物理内存
2 页
表
关于同志近三年现实表现材料材料类招标技术评分表图表与交易pdf视力表打印pdf用图表说话 pdf
管理
3 进程地址空间1 描述物理内存1 描述物理内存均质存储结构与非均质存储结构
传统的计算机中,整个物理空间都是均匀一致的,CPU访问这个空间中任何一个地址所需时间都是相同的,所以称为“均质存储结构(Uniform Memory Architecture)”,简称UMA
*null假设一台的计算机中具有这样的结构:
在这样的系统中,其物理存储空间虽然连续,“质地”却不一致,CPU访问各内存区域速度不一样,所以称为“非均质存储结构(Non-Uniform Memory Architecture)”,简称NUMA*总 线CPU内存CPU内存CPU内存CPU内存null节点(Node)
在NUMA 结构的系统中,属于同一个CPU节点的存储器模块通常是匀质的,因此也被称为“存储节点” (Memory Node)
在Linux中,节点用一个名为pg_data_t的结构体来表示。所有的节点通过pg_data_t->node_next指针被链入一个叫做pgdat_list的单向链表
UMA结构的计算机只有一个节点
分配一个页面时,Linux采取node-local allocation policy从距离当前CPU最近的节点分配
*null管理区(Zone)
每个节点内的物理内存又按照其地址被划分为几个管理区(zone)。使用一个叫做zone_t的结构体来代表每个管理区
管理区的类型可以是ZONE_DMA,ZONE_NORMAL或ZONE_HIGHMEM
ZONE_DMA:用于DMA
ZONE_NORMAL:可以直接映射到内核的区域
ZONE_HIGHMEM:高端内存,不能被直接映射到内核*nullWith the x86 the zones are:
ZONE_DMA First 16MiB of memory
ZONE_NORMAL 16MiB – 896MiB
ZONE_HIGHMEM 896MiB – End*页面(page)页面(page)系统的内存被划分为固定大小的块,称为页框(page frame)。每个物理页框由一个page(或mem_map_t)结构体表示
内核在初始化时根据物理内存的大小建立起一个page结构体数组mem_map,作为物理页框的仓库
每个page结构在这个数组内的下标就是该物理页框的序号
mem_map数组通常放在ZONE_NORMAL的开始处*Nodes,Zones 和 Pages关系图Nodes,Zones 和 Pages关系图*pagepagepagepagepagepagezone_mem_mapzone_mem_mapzone_mem_mapZONE_DMAZONE_NORMALZONE_HIGHMEMnode_zonespage_data_t节点(Nodes)节点(Nodes)如前所述,一个节点用pg_data_t结构代表。在
声明:
* typedef struct pglist_data {
zone_t node_zones[MAX_NR_ZONES];//节点包括的的管理区
zonelist_t node_zonelists[GFP_ZONEMASK+1];
int nr_zones; //管理区数量
struct page *node_mem_map; //该节点的第一个页面
unsigned long *valid_addr_bitmap; //描述节点内空洞的位图
struct bootmem_data *bdata;
unsigned long node_start_paddr; //节点起始物理地址
unsigned long node_start_mapnr;//在mem_map数组内的偏移
unsigned long node_size; //节点大小,即总页面数
int node_id; //节点编号
struct pglist_data *node_next; //指向下一个节点的指针
} pg_data_t;管理区(Zones)管理区(Zones)一个管理区用zone_t结构代表。在声明:
*typedef struct zone_struct {
spinlock_t lock; //保护该结构的自旋锁
unsigned long free_pages; //空闲页面总数
unsigned long pages_min, pages_low, pages_high;
int need_balance; //是否需要kswaped来平衡内存分配
free_area_t free_area[MAX_ORDER]; //空闲区位图
wait_queue_head_t * wait_table; //等待释放页面的进程哈希表
unsigned long wait_table_size; //上述哈希表大小
unsigned long wait_table_shift;
null续* struct pglist_data *zone_pgdat; //指向所属节点
struct page *zone_mem_map; //管理区第一个页面
unsigned long zone_start_paddr; //管理区起始物理地址
unsigned long zone_start_mapnr; //在mem_map数组内的偏移
char *name; //管理区名称
unsigned long size; //管理区大小,以页面数计
} zone_t;页面(Pages)页面(Pages)每一个物理页框都关联一个page结构体,用来追踪页框的状态。page结构体声明于:*typedef struct page {
struct list_head list;
struct address_space *mapping; //映射到文件时使用
unsigned long index;
struct page *next_hash; //映射到文件时的hash链
atomic_t count; //引用计数
unsigned long flags; //状态标记
struct list_head lru; //LRU队列头
struct page **pprev_hash;
struct buffer_head * buffers;
#if defined(CONFIG_HIGHMEM) || defined(WANT_PAGE_VIRTUAL)
void *virtual; //页面映射到内核空间时的地址
#endif
} mem_map_t;2 页表管理2 页表管理要解决的主要问题:
Linux的地址映射模型?
怎样组织页表来描述映射模型?
怎样用线性地址来找到所属物理页面?
虚拟地址物理地址*2.1 描述页目录2.1 描述页目录每个进程都拥有一个pgd_t结构体数组,占据一个物理页框,叫作全局页目录(Page Global Directory,简称PGD)
在x86上,通过将PGD的地址写入CR3寄存器来加载一个进程的页表
每个pgd_t指向一个中间页目录(Page Middle Directory,简称PMD),这是一个pmd_t结构体数组。每个PMD也占一个物理页框
每个pmd_t又指向一个Page Table Entries(PTE),这是一个pte_t结构体数组,大小同样为一个页框
每个pte_t最终指向包含用户数据的物理页框*null页目录和页表的表项格式:
_PAGE_PRESENT—页面在内存没有被交换出去
_PAGE_PROTNONE-- Page is resident but not accessable
_PAGE_RW-- Set if the page may be written to
_PAGE_USER-- Set if the page is accessible from user space
_PAGE_DIRTY-- Set if the page is written to
_PAGE_ACCESSED-- Set if the page is accessed
线性地址的组成:
*nullPage Table Layout*nullx86的两层映射示意图*123456null例:一段follow_page()代码*pgd_t *pgd;
pmd_t *pmd;
pte_t *ptep, pte;
pgd = pgd_offset(mm, address); //取得全局页目录表项
if (pgd_none(*pgd) || pgd_bad(*pgd))
goto out;
pmd = pmd_offset(pgd, address); //取得中间页目录表项指针
if (pmd_none(*pmd) || pmd_bad(*pmd))
goto out;
ptep = pte_offset(pmd, address); //取得页表项指针
if (!ptep)
goto out;
pte = *ptep; //得到页表项2.2 将地址映射到page2.2 将地址映射到pageLinux需要一种快速的方法来将虚拟地址到物理地址,并将page结构映射到它的物理地址
通过一个全局的mem_map数组来实现
mem_map是个page结构体数组,每个pages对应一个物理页框
每个page结构在这个数组内的下标就是它对应的物理页框的序号
*null将page结构映射到物理地址
在x86结构上是简单的线性映射,只需将物理地址加上3GB就得到其内核虚拟地址
*/* from */
#define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)
/* from */
static inline unsigned long virt_to_phys(volatile void * address)
{
return __pa(address);
}null将page结构映射到物理地址
将物理地址右移PAGE_SHIFT位后当作mem_map数组的下标即可找到对应的page结构
virt_to_page将虚拟地址kaddr作为输入参数,通过__pa()宏将其转为物理地址,再将其右移PAGE_SHIFT位转为数组下标,加上mem_map首地址最终得到对应的page结构*#define virt_to_page(kaddr) (mem_map + (__pa(kaddr)>>PAGE_SHIFT))3 进程地址空间3 进程地址空间现代OS采用虚拟存储器,每个进程都有自己的虚拟地址空间,由OS负责映射到物理内存
地址空间
内核角度看到的地址空间和进程角度看到的不一样
用户空间随着进程切换而改变,内核空间由所有进程共有
*3.1 线性地址空间(Linear Address Space)3.1 线性地址空间(Linear Address Space)从进程的角度看,地址空间是平直的,线性的
从内核角度看,线性地址被分为两部分:用户空间和内核空间
每个进程有自己的用户空间,因此用户空间随着进程的切换而切换(请思考:具体怎样切换?)
内核空间为所有进程共有,不随进程切换改变。实际上这部分是内核代码运行的内存空间
x86系统结构下,用户空间占据0~3GB,内核空间占据3~4GB*null图:内核地址空间*3.2 管理地址空间3.2 管理地址空间在Linux中,由一个mm_struct结构体代表进程的地址空间
进程的task_struct结构中有个叫做mm指针,指向一个mm_struct结构体
每个地址空间由一些页框对齐的内存区域组成,这些区域用vm_area_struct结构体代表
每个vm_area_struct中包含相同保护属性,用于相同目的页面
例如,一个区域用于动态分配内存的堆区,另一个用于装载共享库
*null当备份一个内存区域到文件时,系统会设置该内存区域vm_area_struct结构的vm_file域
此时vm_file->f_dentry->i_mapping指向该区域的address_space结构。address_space含有所有特定文件系统相关信息,用来操作磁盘上的页面*null*表:影响进程地址空间和内存区域的系统调用3.3 进程地址空间描述符3.3 进程地址空间描述符进程的地址空间由一个叫做mm_struct的结构代表
每个进程只有一个mm_struct,同一进程的线程之间则共享mm_struct
一般来说,内核线程不需要mm_struct。其task_struct的mm域总为NULL
*null*struct mm_struct {
struct vm_area_struct * mmap;
rb_root_t mm_rb;
struct vm_area_struct * mmap_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, end_code, start_data, end_data;
unsigned long start_brk, brk, start_stack;
unsigned long arg_start, arg_end, env_start, env_end;
unsigned long rss, total_vm, locked_vm;
unsigned long def_flags;
unsigned long cpu_vm_mask;
unsigned long swap_address;
unsigned dumpable:1;
mm_context_t context;
};null与mm_struct相关的函数*3.4 虚存区域(vm_area_struct)3.4 虚存区域(vm_area_struct)进程的地址空间由若干互不重叠虚存区域组成,不同的虚存区域可能具有不同的访问权限和属性
提供更好的的安全性,灵活性
进程的所有内存区域可在/proc/PID/maps下查看
虚存区域映射到文件时,可以从vm_area_struct的vm_file指针找到对应文件的file结构体
file结构包含一个指向其inode的指针,inode结构则含有指向address_space结构的指针。address_space结构包含含一组文件系统函数指针,这些函数用来读写磁盘上的页面*null*struct vm_area_struct {
struct mm_struct * vm_mm; // 所属的mm_struct结构体
unsigned long vm_start; // 其实地址
unsigned long vm_end; // 结束地址
/* linked list of VM areas per task, sorted by address */
struct vm_area_struct *vm_next; // 链表中下一个vm_area_struct
pgprot_t vm_page_prot; // 页面的保护属性
unsigned long vm_flags; // 内存区域的标志和保护属性
rb_node_t vm_rb; // 红黑树
struct vm_area_struct *vm_next_share;
struct vm_area_struct **vm_pprev_share;
/* Function pointers to deal with this struct. */
struct vm_operations_struct * vm_ops; // 一组操作vm_area的函数
/* Information about our backing store: */
unsigned long vm_pgoff;
struct file * vm_file; // 备份到文件时对应的file结构体
unsigned long vm_raend;
void * vm_private_data; // 私有数据
};vm_flags标志vm_flags标志*null与mmap相关的标志*null*图:与进程地址空间有关的数据结构关系图null当一个虚存区域备份到文件时,关联的address_space结构定义如下:*struct address_space {
struct list_head clean_pages; //干净页面
struct list_head dirty_pages; //脏页面
struct list_head locked_pages; //锁住的页面
unsigned long nrpages; //总页面数
struct address_space_operations *a_ops; //操作函数
struct inode *host; // 备份文件的inode
struct vm_area_struct *i_mmap; //vma
struct vm_area_struct *i_mmap_shared;
spinlock_t i_shared_lock; //保护结构体的自旋锁
int gfp_mask; //__alloc_pages()分配新页面的标志
};null*struct address_space_operations {
int (*writepage)(struct page *);
int (*readpage)(struct file *, struct page *);
int (*sync_page)(struct page *);
/*
* ext3 requires that a successful prepare_write()
* call be followed
* by a commit_write() call - they must be balanced
*/
int (*prepare_write)(struct file *, struct page *, unsigned, unsigned);
int (*commit_write)(struct file *, struct page *, unsigned, unsigned);
/* Unfortunately this kludge is needed for FIBMAP.
* Don’t use it */
int (*bmap)(struct address_space *, long);
int (*flushpage) (struct page *, unsigned long);
int (*releasepage) (struct page *, int);
#define KERNEL_HAS_O_DIRECT
int (*direct_IO)(int, struct inode *, struct kiobuf *, unsigned long, int);
};创建一个虚存区域创建一个虚存区域系统调用mmap()用来在一个进程的地址空间内创建一个新的虚存区域。在x86下该系统调用的服务程序为do_mmap2()。
如果是映射一个文件,do_mmap2()会先检查调用参数对应的struct file,然后取得mm_struct->mmap_sem信号量
下一步,do_mmap2()调用do_map_pgoff()函数,这是创建一个虚存区域的主要函数*nulldo_map_pgoff()函数主要过程:
sanity checks。包括映射文件或外设时对应文件或外设的相关操作函数是否存在,映射区域大小是否页面对齐,是否过大,不能映射到内核空间等等
在线性地址空间找到一块足够大的空闲区域,由get_unmaped_area()函数完成
检查VM标志和文件访问权限
分配一个vm_area_struct并初始化它
将新VMA链入相应链表
调用特定的文件系统或设备mmap函数
更新统计数据并返回
*查找一个已映射的虚存区域查找一个已映射的虚存区域在内核中,查找一个给定线性地址所属的VMA是一个经常用到的操作,由find_vma()等函数实现
首先检查mmap_cache域,这是上次查找调用该函数的结果(访存的局部性原理)
cache中没有则去mm_rb域指向的红黑树中搜索
如果指定的地址没有所属的VMA,则函数会找到一个离该地址最近的VMA返回。因此,调用者必须检查返回的VMA是否包含指定地址*重映射或移动一个虚存区域重映射或移动一个虚存区域Linux提供系统调用mremap()来扩张或缩小一个已映射的虚存区域。*函数调用图:sys_mremap()null若要移动一个虚存区域,do_mremap()首先调用get_unmapped_area()找到一个能包含新设置的容量的虚存区域,然后调用move_area()将旧的VMA移动到新区*函数调用图:move_vma()3.5 页面错误(Page Faulting)3.5 页面错误(Page Faulting)什么是Page Fault?
访问一个不在内存中的页面,例如,页面已被交换到磁盘
访问权限问题,例如试图写一个只读页面
发生页面错误时,CPU通过中断机制自动陷入到内核态对错误进行处理
内核为每种硬件平台提供特定的页面异常处理程序
,do_page_fault()
*