Understand Swap in & mem_map
2010年10月19日
Understand Swap in & mem_map
mm/memory.c PageReserved
这1000多行代码,有绝大多数函数定义为static,意味着这个模块只提供了少量
的对外接口. 这几个接口是我们理解此模块的一个重要线索.
第一部分 :全局量的仔细解释
I)首先是几个全局变量,下面给出其具体含义:
+================================================= =====================
/*mem_map 中最高地址页面的map number,可以使用的ram全在其中*/
unsigned long max_mapnr;
/*物理内存页面总数(当然包括highmem在内)*/
unsigned long num_physpages;
/*从虚拟地址 high_memory 开始不再是线性映射区
*如果全部可以线性映射其值为物理内存顶端之虚拟地址
*/
void * high_memory;
struct page *highmem_start_page; /*高端物理内存的起始 page(struct page)*/
+================================================= =====================
II) i386下的 mem_map
另外一个重要的全局数组:
==========================================
mem_map_t * mem_map; /*page 结构的总数组*/
==========================================
这个数组存放有全部的struct page结构(i386 not numa),每一个物理内存页面
都对应一个page 机构,存储于此数组.下面来看看i386体系下如何初始化mem_map:
setup.S->asmlinkage void __init start_kernel(void) (init/main.c)
|
+-->setup_arch ---> 处理e820内存报告
--> 关于内存的提示信息
---> 初始化bootmem (init_bootmem)
---> paging_init
+
| +--> pagetable_init(含fix map,vmalloc init)
\ / +--> load cr3
. +--> kmap_init
. +--> free_area_init(zone-buddy初始化)
函数paging_init如此调用free_area_init(zones_size);参数zones_size给出
每一个zone(DMA,NORMAL,HIGH)的大小.再看:
void __init free_area_init(unsigned long *zones_size)
{
free_area_init_core(0, &contig_page_data, &mem_map, zones_size,
0, 0, 0);
}
对于i386此种"连续"内存结构,使用了一个pg_data_t类型的变量:contig_page_data
注意第三个参数是&mem_map.
void __init free_area_init_core(int nid, pg_data_t *pgdat,
struct page **gmap, unsigned long *zones_size ,
unsigned long zone_start_paddr, unsigned long *zholes_size,
struct page *lmem_map)
{
/* pgdat 是contig_page_data, gmap是&mem_map, lmem_map是 0 */
.....
if (lmem_map == (struct page *)0) {
//非numa体系此值为0, 分配mem map数组
lmem_map = (struct page *) alloc_bootmem_node(pgdat, map_size);
lmem_map = (struct page *)(PAGE_OFFSET +
MAP_ALIGN((unsigned long)lmem_map - PAGE_OFFSET));
}
/*mem_map指向刚分配的内存,即是contig_page_data.node_mem_map*/
*gmap = pgdat->node_mem_map = lmem_map;
pgdat->node_size = totalpages;
pgdat->node_start_paddr = zone_start_paddr;
pgdat->node_start_mapnr = (lmem_map - mem_map);
/*从0号物理内存页开始*/
}
NUMA下的 mem_map
III)NUMA下的 mem_map
而对于NUMA类型的体系,mem_map已经失去了其存在的意义,不过2.4处理的
不够好,代码凌乱,可读性不强.以arm体系为例:(arch/arm/mm/init.c)
void __init paging_init(struct meminfo *mi, struct machine_desc *mdesc)
{
.........
for (node = 0; node node_boot_start, zhole_size);
}
......
}
这里调用的free_area_init_node定义于mm/numa.c
void __init free_area_init_node(int nid, pg_data_t *pgdat, struct page *pmap,
unsigned long *zones_size, unsigned long zone_start_paddr,
unsigned long *zholes_size)
{
int i, size = 0;
struct page *discard;
if (mem_map == (mem_map_t *)NULL)
mem_map = (mem_map_t *)PAGE_OFFSET; /*仅为mem_map指定了一个值,其指向
*的地址并没有存放struct page
*/
free_area_init_core(nid, pgdat, &discard, zones_size, zone_start_paddr,
zholes_size, pmap);
pgdat->node_id = nid;
.......
}
void __init free_area_init_core(int nid, pg_data_t *pgdat,
struct page **gmap, unsigned long *zones_size ,
unsigned long zone_start_paddr, unsigned long *zholes_size,
struct page *lmem_map)
{
/* pgdat 是NODE_pgdat, gmap是&discard, lmem_map还是 0 */
.....
if (lmem_map == (struct page *)0) {
//numa体系此值亦为0, 分配mem map数组
lmem_map = (struct page *) alloc_bootmem_node(pgdat, map_size);
lmem_map = (struct page *)(PAGE_OFFSET +
MAP_ALIGN((unsigned long)lmem_map - PAGE_OFFSET));
}
/* *gmap再numa下不指向mem_map,被丢弃不用*/
*gmap = pgdat->node_mem_map = lmem_map;
pgdat->node_size = totalpages;
pgdat->node_start_paddr = zone_start_paddr;
pgdat->node_start_mapnr = (lmem_map - mem_map);
/*node_start_mapnr再numa下失去其意义*/
/*变成了lmem_map这个地址到PAGE_OFFSET有多少个页面*/
offset = lmem_map - mem_map; /*减去常量PAGE_OFFSET*/
for (j = 0; j offset = offset; /*失去意义*/
....
zone->zone_mem_map = mem_map + offset; /*又加上常量PAGE_OFFSET所以
*zone_mem_map确实有意义
*/
zone->zone_start_mapnr = offset; /*失去意义*/
...
offset += size;
}
}
从上面的分析,很明显2.4还没有处理好NUMA和discon mem.在NUMA体系下,mem_map
已经失去存在的意义,只是一个跳板.arm的vir_to_page宏就没有使用这个mem_map.
而ia64-sn的使用方法就巨绕:
*******include/asm-ia64/page.h 定义
# define virt_to_page(kaddr) (mem_map + platform_map_nr(kaddr))
mem_map是一个常量PAGE_OFFSET???
*******include/asm-ia64/machvec_sn1.h定义
#define platform_map_nr sn1_map_nr
*******arch/ia64/sn/sn1/setup.c定义
sn1_map_nr (unsigned long addr)
{
#ifdef CONFIG_DISCONTIGMEM
return MAP_NR_SN1(addr);
#else
return MAP_NR_DENSE(addr);
#endif
}
*******include/asm-ia64/sn/mmzone.h定义
#define NODE_DATA(n) (&((plat_node_data + (n))->gendata))
#define NODE_MEM_MAP(nid) (NODE_DATA((nid))->node_mem_map)
#define LOCAL_MAP_NR(kvaddr) \
(((unsigned long)(kvaddr)-LOCAL_BASE_ADDR((kvaddr))) >> PAGE_SHIFT)
#define MAP_NR_SN1(kaddr) (LOCAL_MAP_NR((kaddr)) + \
(((unsigned long)ADDR_TO_MAPBASE((kaddr)) - PAGE_OFFSET) / \
sizeof(mem_map_t)))
仔细分析MAP_NR_SN1就知道其种缘由了:
mem_map + (((unsigned long)ADDR_TO_MAPBASE((kaddr)) - PAGE_OFFSET) / \
sizeof(mem_map_t))
就定位到了pgdat->node_mem_map,而node_mem_map了在numa中依然有效,指向真
实的"mem_map".
pgdat->node_start_mapnr也失去了意义,不过在kernel2.4中大概只有mips64使
用这个值,并且类似ia64的vir_to_page,见include/asm-mips64/pgtable.h
#define mips64_pte_pagenr(x) \
(PLAT_NODE_DATA_STARTNR(PHYSADDR_TO_NID(pte_val(x) )) + \
PLAT_NODE_DATA_LOCALNR(pte_val(x), PHYSADDR_TO_NID(pte_val(x))))
#define pte_page(x) (mem_map+mips64_pte_pagenr(x))
使用这个变量的时候最终还是加上了mem_map!!!
不过从另外一个角度理解mem_map也可以:这紧紧是一个虚拟的值,借助于这个变
量也简化了一些设计,比如使用zone->offset的地方,如:mm/page_alloc.c
static void __free_pages_ok (struct page *page, unsigned long order)
{
.........
base = mem_map + zone->offset;
page_idx = page - base;
....
}
本来zone->offset减去了mem_map,现在加上mem_map,还是指向这个zone所属的
pgdat->zone_mem_map. 虽然失去其本意,也达到了目的,必是2.4向NUMA转化的过
渡性代码.
在kernel2.6,numa体系下,根本不再有mem_map这个全局变量,
pgdat->node_start_mapnr也时刻保持其原意.zone->offset更是消失不见了,直接
换成了指针.一切都变得clean&clear.
注:看64bit代码时突然想到两个64bit值之差赋值给long型的变量是否有问题,后来
参考了:
>
指出在linux的64bit环境中, long是64bit的!!!和windows不同啊.
Page Reserved详解
第二部分 本模块对外接口详解
I)clear_page_tables : 释放一个进程指定范围内的页表,以及关联的pmd,对应
的pgd entry被清空,pgd本身没有被释放.至于页表中是否还有未断开
影射的物理页面,此函数未做检查.(所以调用之前要先调用
zap_page_range). 对于这个函数和其涉及的其他函数请自己阅读.
II)copy_page_range
从一个进程的mm拷贝指定的vm area到另一个进程,拷贝内容包括:为新进程指
定的这段空间分配pmd页面,分配page table页面.从src进程的mm拷贝pte到新分配
的页表.增加copy的pte所引用页面的引用计数,并且处理pte是swap entry的情况(
增加swap page的引用计数). 对于vm area属性是可写但无VM_SHARE属性的情况,将
新进程的pte置写保护,等到新进程写此页面的时候进行COW. 如果vm area具有共享
属性(那当然就是容许在进程间共享此页面)将新设置的pte的dirty位清除:新进程
还未曾访问此页面.
通过上面的分析阅读copy_page_range应该不是问题了.注意在copy_page_range
的时候就为COW设置了伏笔:共享一个page,但是不容许写.
III)zap_page_range:
从一个进程指定的地址开始释放指定大小的虚拟地址空间内
所有已映射页面. clear_page_tables仅释放"cpu meta data",而此函数
仅释放"user used data pages".如果页面被交换到磁盘,则释放磁盘页面.
此函数相关子函数:
zap_pmd_range zap_pte_range free_pte.
都是对mm进行遍历,只有free_pte还有些"业务"逻辑.
static inline int free_pte(pte_t pte)
{
if (pte_present(pte)) {
struct page *page = pte_page(pte);
if ((!VALID_PAGE(page)) || PageReserved(page))
return 0;
/*
* free_page() used to be able to clear swap cache
* entries. We may now have to do it manually.
*/
if (pte_dirty(pte) && page->mapping)
set_page_dirty(page);
free_page_and_swap_cache(page);
return 1;
}
swap_free(pte_to_swp_entry(pte));
return 0;
}
PageReserved检查page是否就有PG_reserved属性.
到底什么是PG_reserved页面
这里说明一下: 其实非常简单,内核不认为PG_reserved属性的页面是"ram"页
面.从而不会在"任何"地方使用这个物理页面. 这里的任何地方不是指真正的所有
的内核程序,而是指不知道PG_reserved的页面是什么页面的代码.
从另外一种意义上说,就是内核的某个地方保留了这个page,不得再做他用.
首先, 参考arch/i386/mm/init.c的分析,函数mem_init,使用函数free_all_bootmem
清除所有未分配出去的页面的PG_reserved位.仔细阅读一下函数对bootmem的初始
化就可以发现:所有非ram页面,在bootmem中已经标记为已分配,PG_reserved置位.
所以free_all_bootmem也没有将非ram页面的PG_reserved标识清除掉.而对于high
mem, mem_init 将非ram页面的PG_reserved位设置上.这就是PG_reserved的第一
个含义:非ram页面.
然后看setup_arch对reserve_bootmem的调用,可以知道,(0, PAGE_SIZE)被保
留,这样不会有"任何"代码可以分配这段内存. 这就是第二个含义:被内核保留用于
特殊用途.
关于zap_page_range不再做其他说明.
user kiobuf 和 remap, zeromap
IV)user kiobuf
在2.6内核,对应的函数叫做get_user_pages,用于AIO.这里的user kiobuf提供
这样一种服务:
从内核读取数据直接映射到用户空间,减少了copy,可以"大大的"提高效率.不过
看来2.4想用于raw io.到时候再仔细分析,这里先看看user kiobuf是如何建立和使
用的.
int map_user_kiobuf(int rw, struct kiobuf *iobuf, unsigned long va, size_t len)
{
.....
int datain = (rw == READ);
/* Make sure the iobuf is not already mapped somewhere. */
if (iobuf->nr_pages)
return -EINVAL;
mm = current->mm;
dprintk ("map_user_kiobuf: begin\n");
//准备扫描指定的区间(遍历ptr到end之间涉及的vma)
ptr = va & PAGE_MASK;
end = (va + len + PAGE_SIZE - 1) & PAGE_MASK;
err = expand_kiobuf(iobuf, (end - ptr) >> PAGE_SHIFT);
if (err)
return err;
down(&mm->mmap_sem);
iobuf->locked = 0;
iobuf->offset = va & ~PAGE_MASK;
iobuf->length = len;
i = 0;
/*
* First of all, try to fault in all of the necessary pages
*/
while (ptr = vma->vm_end) {
/*指定的虚拟地址va必须有一个vam区间对应*/
vma = find_vma(current->mm, ptr);
if (!vma)
goto out_unlock;
if (vma->vm_start > ptr) {
/*必须使用堆栈(fix me)做io buf*/
if (!(vma->vm_flags & VM_GROWSDOWN))
goto out_unlock;
/*先对vma进行调整*/
if (expand_stack(vma, ptr))
goto out_unlock;
}
/*检查一下访问权限*/
if (((datain) && (!(vma->vm_flags & VM_WRITE))) ||
(!(vma->vm_flags & VM_READ))) {
err = -EACCES;
goto out_unlock;
}
}
/*为涉及到的空间影射物理内存页面*/
if (handle_mm_fault(current->mm, vma, ptr, datain) page_table_lock);
/*记录页面的page 结构到 iobuf中,以便后用*/
map = follow_page(ptr);
if (!map) {
spin_unlock(&mm->page_table_lock);
dprintk (KERN_ERR "Missing page in map_user_kiobuf\n");
goto out_unlock;
}
map = get_page_map(map);
if (map) {
flush_dcache_page(map);
/*增加引用计数,以免被swap out对应unmap_kiobuf*/
atomic_inc(&map->count);
} else
printk (KERN_INFO "Mapped page missing [%d]\n", i);
spin_unlock(&mm->page_table_lock);
iobuf->maplist[i] = map;
iobuf->nr_pages = ++i;
ptr += PAGE_SIZE;
}
up(&mm->mmap_sem);
.......
}
与此相关的其他几个函数unmap_kiobuf,lock_kiovec,unlock_kiovec个人认为
极为简单,就不再分析.
V)zeromap_page_range remap_page_range
int zeromap_page_range(unsigned long address, unsigned long size,
pgprot_t prot)
相关函数zeromap_pmd_range,zeromap_pte_range,forget_pte.是一个遍历
当前进程的页面映射表的函数.将指定地址空间的每一项pte都指向zero page页
(一个4k的全0页面),并将pte置写保护,以后可以进行COW.
如果某个pte已经映射了一个页面,就将其释放. 不过这种情况应该极少出现
一般调用此函数做zero map时都已经撤销了映射.
值得注意的是,这个不涉及vma.没有加锁.
int remap_page_range(unsigned long from, unsigned long phys_addr,
unsigned long size, pgprot_t prot)
相关函数remap_pmd_range,remap_pte_range. 作用是将phy_addr开始的连续
物理页面映射到虚存from, 大小为size(page align).这些物理页面都不能是ram
页面.只负责mem map不能管理的页面,和PageReserved的页面.主要应用于驱动将
设备内存映射到内核的一段虚存. 看看下面的函数就知道了:
static remap_pte_range(pte,address, size, phys_addr, pgprot_t prot)
{
......
do {
struct page *page;
pte_t oldpage;
oldpage = ptep_get_and_clear(pte);
page = virt_to_page(__va(phys_addr));
/*只负责映射保留页面和非ram页面*/
if ((!VALID_PAGE(page)) || PageReserved(page))
set_pte(pte, mk_pte_phys(phys_addr, prot));
/*同时负责拆除老的映射页面*/
forget_pte(oldpage);
.....
pte++;
} while (address && (address do_sys_truncate->do_truncate()
{ ......
newattrs.ia_valid = ATTR_SIZE | ATTR_CTIME;
error = notify_change(dentry, &newattrs);
....
}
notify_change(){
......
lock_kernel();
if (inode->i_op && inode->i_op->setattr) //ext2 未设置此op
error = inode->i_op->setattr(dentry, attr);
else {
//所以对ext2来讲执行此公共流程
error = inode_change_ok(inode, attr);
if (!error)
inode_setattr(inode, attr);
}
unlock_kernel();
.......
}
inode_setattr(){
......
if (ia_valid & ATTR_SIZE)
vmtruncate(inode, attr->ia_size);
......
mark_inode_dirty(inode);
}
可见, vmtruncate是其核心. 对于一个文件在进行truncate时要考虑文件已经
使用mmap映射到内存的情况. 将所有的映射一并truncate.此外还要处理page cache
保证page cache不再含有相关数据.最后才是文件本身进行truncate. vmtruncate
即此服务.
void vmtruncate(struct inode * inode, loff_t offset)
{
unsigned long partial, pgoff;
struct address_space *mapping = inode->i_mapping;
unsigned long limit;
if (inode->i_size i_size = offset;
/* 清除page cache中相关的缓冲数据 */
truncate_inode_pages(mapping, offset);
spin_lock(&mapping->i_shared_lock);
/*检查是否存在此文件的mapping*/
if (!mapping->i_mmap && !mapping->i_mmap_shared)
goto out_unlock;
pgoff = (offset + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT;
partial = (unsigned long)offset & (PAGE_CACHE_SIZE - 1);
/*truncate maping*/
if (mapping->i_mmap != NULL)
/*
*始释放指定虚拟地址空间内所有已映射页面.(not clear cpu meta data)
*/
vmtruncate_list(mapping->i_mmap, pgoff, partial);
if (mapping->i_mmap_shared != NULL)
vmtruncate_list(mapping->i_mmap_shared, pgoff, partial);
out_unlock:
spin_unlock(&mapping->i_shared_lock);
/* this should go into ->truncate */
inode->i_size = offset;
/*最后对文件进行truncate,ext2 参考ext2_truncate */
if (inode->i_op && inode->i_op->truncate)
inode->i_op->truncate(inode);
return;
do_expand:
limit = current->rlim[RLIMIT_FSIZE].rlim_cur;
if (limit != RLIM_INFINITY) {
if (inode->i_size >= limit) {
send_sig(SIGXFSZ, current, 0);
goto out;
}
if (offset > limit) {
send_sig(SIGXFSZ, current, 0);
offset = limit;
}
}
inode->i_size = offset;
if (inode->i_op && inode->i_op->truncate)
inode->i_op->truncate(inode);
out:
return;
}
vmtruncate_list就是将truncate语义指定的范围内的已映射页面umap.这里不
再分析.(就是调用zap_page_range)
清除page cache在分析filemap.c的时候已经分析过了,对于文件本身的操作等
到分析buffer.c再议.
Understanding Swap in
VII)Understanding Swap in
接口函数: swapin_readahead,handle_mm_fault,make_pages_presen t.
我们从handle_mm_fault谈起,在分析fault.c的时候提到过一个mm fault产生的
几种情形,首先是进入到do_page_fault的时候
/*
* 有三种情况下执行流到达此函数:
* 1. pmd, pgt, pte 有一个为空, 即未建立映射或已经撤销.(nomal page
cache)
* 2. 页面不在内存(not present)被内核交换到了磁盘(swap space).
* 3. 权限不正确. (内核已经授权或者根本不容许那样访问)
*
* 说明:
* 情况1)涉及的页面或者没有建立映射或者属于nomal address map(like mmap)
* 一般要到page cache中试图寻找.
* try_to_swap_out处理nomal address map的时候,把相应的pte置成0.
*
* 情况2.涉及页面属于swapper_space管理. try_to_swap_out 不会把pte 置0,
而是讲pte置为相应的swp_entry_t并将present位置0.
*
* 再转交下一级函数处理时, 所有非法操作都在这个函数中处理掉了.所谓非法
* 是用户企图进行一次内核(vma)不容许的操作,如vma无写属性,而进程进行了写操
* 作.
*/
asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long error_code)
{
.........
/*
* 如果处时指令流抵达此地,说明异常点在一个完好的vma中,
* 并且符合OS 赋予用户的权限.
*/
switch (handle_mm_fault(mm, vma, address, write)) {
case 1:
tsk->min_flt++;
break;
case 2:
tsk->maj_flt++;
break;
case 0:
goto do_sigbus;
default:
goto out_of_memory;
}
........
}
上面的分析给出了handle_mm_fault(mm, vma, address, write)执行时所面临的
条件"说明异常点在一个完好的vma中,并且符合OS 赋予用户的权限":vma是经过了
扩展(expand stack)或修改,用户的这次操作应该得到相应的服务.
int handle_mm_fault(struct mm_struct *mm, struct vm_area_struct * vma,
unsigned long address, int write_access)
{
int ret = -1;
pgd_t *pgd;
pmd_t *pmd;
pgd = pgd_offset(mm, address);
pmd = pmd_alloc(pgd, address); /*查找或者分配pmd page*/
if (pmd) {
pte_t * pte = pte_alloc(pmd, address); /*查找或者分配page table*/
if (pte)
ret = handle_pte_fault(mm, vma, address, write_access, pte);
}
return ret;
}
可见在发生页面异常的时候,容许进程被调度,和中断有所不同.这是合理的:可
以看作进程请求内核处理mm fault,就像一个系统调用.
static inline int handle_pte_fault(struct mm_struct *mm,
struct vm_area_struct * vma, unsigned long address,
int write_access, pte_t * pte)
{
pte_t entry;
/*
* We need the page table lock to synchronize with kswapd
* and the SMP-safe atomic PTE updates.
*/
spin_lock(&mm->page_table_lock);
entry = *pte;
/*异常属于映射链断裂的情况*/
if (!pte_present(entry)) {
/*
* If it truly wasn't present, we know that kswapd
* and the PTE updates will not touch it later. So
* drop the lock.
*/
spin_unlock(&mm->page_table_lock);
if (pte_none(entry))
return do_no_page(mm, vma, //页面不存在, 就是没有,或者被 try_to_swap_out
address, // 断开, 这种页面一般属于一个nomal address map
write_access,
pte //参见filemap_nopage 了解page cache 的换入
);
return do_swap_page(mm, vma,//not present, 属于swapper_space
address, pte,
pte_to_swp_entry(entry),
write_access
);
}
/*异常属于内核授权,但是cpu不容许的情况,COW*/
//如果是read 时产生陷入或者是pte的映射问题
//或者是一个非法的操作,已经在do_page_fault 过滤掉了
if (write_access) {//由于页面写保护产生的陷入, 而OS 却容许用户写入
if (!pte_write(entry))
return do_wp_page( mm, //就是处理COW 的第二步
vma, //Copy on Write
address, //第一步是建立一个不容许写的页
pte, entry //却在vma 中赋予用户写的权限
);
entry = pte_mkdirty(entry);
}
/*好像一般都满足write_access为1,read not present从
if (!pte_present(entry))就返回了(fix me),read present更是连
handle mem fault都进不了*/
entry = pte_mkyoung(entry);
establish_pte(vma, address, pte, entry);
spin_unlock(&mm->page_table_lock);
return 1;
}
handle_mm_fault 已经修复了映射链上的系统页面,handle_pte_fault主要是修复
映射链上的用户页面:
1)do_no_page:还未建立映射,或者已经被断开还存在于lru cache(page cache)
或者已经回收到了node-zone-buddy.
2)do_swap_page:从lru恢复,如果已经被回收到node-zone-buddy就从磁盘调入.
3)do_wp_page: 处理COW的copy操作,用户现在要写此页面,copy一份给他.
1)do_no_page:
分配一个匿名页面,或者用vm指定的操作寻找对应页面.在设置pte
的时候考虑COW:对于写操作,表示内核容许写(这里不会遭遇COW,cow是另一个处理
函数,handle_pte_fault已经区分的很清楚了),直接将pte置为可写.如果是read操
作,考虑mmap创建的vma(not anonymous page),并且页面已经是共用页面,则我们
不能直接给这个进程写权限,而是要取消写权限这是COW处理中的一环:建立一个写
保护的页面.参照分析filemap.c时对函数filemap_nopage的分析,那里讲的很详细
-->filemap_nopage 对这个read操作的进程直接返回一个共享页面,如果这是第一
个要求访问此页面的进程do_no_page不会取消这个进程的写操作权限.
static int do_no_page(struct mm_struct * mm, struct vm_area_struct * vma,
unsigned long address, int write_access, pte_t *page_table)
{
struct page * new_page;
pte_t entry;
if (!vma->vm_ops || !vma->vm_ops->nopage)
return do_anonymous_page(mm, vma, page_table, write_access, address);
/*
* The third argument is "no_share", which tells the low-level code
* to copy, not share the page even if sharing is possible. It's
* essentially an early COW detection.
* 对于mmap建立的vm,此函数是 filemap_nopage
*/
new_page = vma->vm_ops->nopage(vma, address & PAGE_MASK, (vma->vm_flags & VM_SHARED)?0:write_access);
if (new_page == NULL) /* no page was available -- SIGBUS */
return 0;
if (new_page == NOPAGE_OOM)
return -1;
++mm->rss;
//其实在上面两个调入函数中(do_anonymous_page vma->vm_ops->nopage)
//也存在cow 的第二步操作,即写操作时do_anonymous_page 返回
//新分配的页面, vma->vm_ops->nopage 不共享页面,而进行copy 操作.
//cow 本质: 写时拷贝,读时共享
//写时拷贝就是刚分析的do_anonymous_page vma->vm_ops->nopage 加上
//do_wp_page
//读时共享就是do_anonymous_page 在read 条件下,返回zero 公共页
//vma->vm_ops->nopage 在read 条件下尽力共享页面.
/*
* This silly early PAGE_DIRTY setting removes a race
* due to the bad i386 page protection. But it's valid
* for other architectures too.
*
* Note that if write_access is true, we either now have
* an exclusive copy of the page, or this is a shared mapping,
* so we can make it writable and dirty to avoid having to
* handle that later.
*/
flush_page_to_ram(new_page);
flush_icache_page(vma, new_page);
entry = mk_pte(new_page, vma->vm_page_prot);
if (write_access) {
entry = pte_mkwrite(pte_mkdirty(entry));
} else if (page_count(new_page) > 1 &&
!(vma->vm_flags & VM_SHARED))
entry = pte_wrprotect(entry);
set_pte(page_table, entry);
/* no need to invalidate: a not-present page shouldn't be cached */
update_mmu_cache(vma, address, entry);
return 2; /* Major fault */
}
2)do_swap_page
对于开始还出的页面,可以从lru恢复,如果已经被回收到node-zone-buddy就从
磁盘调入.
static int do_swap_page(struct mm_struct * mm,
struct vm_area_struct * vma, unsigned long address,
pte_t * page_table, swp_entry_t entry, int write_access)
{
struct page *page = lookup_swap_cache(entry); //先查找swap cache
//如果查找的到,肯定也存在于lru,说不定就要开始换出了
pte_t pte;
if (!page) { /*如果找不到,就从磁盘swap in*/
lock_kernel();
swapin_readahead(entry); /*先预读一部分*/
page = read_swap_cache(entry); /*类似普通文件读写,先读入,并加入swap
cache,并返回指定页面*/
unlock_kernel();
if (!page)
return -1;
flush_page_to_ram(page);
flush_icache_page(vma, page);
}
mm->rss++;
/*得到指定的页面,就开始恢复页面映射*/
pte = mk_pte(page, vma->vm_page_prot);
/*
* Freeze the "shared"ness of the page, ie page_count + swap_count.
* Must lock page before transferring our swap count to already
* obtained page count.
*/
lock_page(page); /*is_page_shared 要求锁定页面,详见filemap.c的分析*/
swap_free(entry);
if (write_access && !is_page_shared(page))
pte = pte_mkwrite(pte_mkdirty(pte));
UnlockPage(page);
set_pte(page_table, pte);
/* No need to invalidate - it was non-present before */
update_mmu_cache(vma, address, pte);
return 1; /* Minor fault */
}
其中预读和读取到swap cache(read_swap_cache)留做以后详解,这里仅仅简单
分析一下预读.
/*
* Primitive swap readahead code. We simply read an aligned block of
* (1 = pager_daemon.swap_cluster
* (1 内存.
全文完.
发表评论
-
so库的性能注意事项
2012-01-20 08:22 666so库的性能注意事项 2010年09月07日 一个共享库 ... -
linux 进程编程
2012-01-20 08:21 648linux 进程编程 2010年06 ... -
Linux下文件I/O操作详解
2012-01-20 08:21 542Linux下文件I/O操作详解 2010年12月26日 ... -
linux HoN 运行不流畅解决
2012-01-19 13:34 944linux HoN 运行不流畅解决 2011年02月13日 ... -
我们专业要用到的软件-AutoCAD系统要求,给即将买电脑的亲爱滴童鞋们点参考建议
2012-01-19 13:34 581我们专业要用到的软件-AutoCAD系统要求,给即将买电脑的亲 ... -
新萝卜家园 Ghost XP SP3 电脑城装机专用版 10.5
2012-01-19 13:34 764新萝卜家园 Ghost XP SP3 ... -
Photoshop教程(二):首选项的基本设置
2012-01-19 13:34 580Photoshop教程(二):首选项的基本设置 2011年0 ... -
NIO
2012-01-17 03:24 970NIO 2011年07月27日 1 ... -
as3 xuexi
2012-01-17 03:24 606as3 xuexi 2011年08月20日 AS 调用外 ... -
教你怎么用IPHONE
2012-01-17 03:23 1312教你怎么用IPHONE 2011年04月09日 1. / ... -
回忆初中,回忆四班
2012-01-16 01:58 698回忆初中,回忆四班 2011 ... -
002销售奇书---建材家具就应该这样卖!!
2012-01-16 01:58 586002销售奇书---建材家具就应该这样卖!! 2010年06 ... -
关键讲述亚军背后的故事
2012-01-16 01:58 561关键讲述亚军背后的故 ... -
古代的吴越男人是找不到汉人老婆,甚至很难找到吴越老婆的
2012-01-16 01:57 1559古代的吴越男人是找不 ... -
log4j警告的解决办法-狂奔的蜗牛-iteye技术网站
2012-01-11 01:59 585log4j警告的解决办法-狂奔的蜗牛-iteye技术网站 2 ... -
sql基础――mssql2005-亲爱精诚、养天地正气,法古今完人-iteye技术网站
2012-01-11 01:58 638sql基础――mssql2005-亲爱精诚、养天地正气,法古今 ... -
asp另存为文件名-名称-类型
2012-01-11 01:58 621asp另存为文件名-名称-类型 2011年08月01日 ... -
HTML-4-css
2012-01-11 01:58 558HTML-4-css 2011年08月01日 0301 ... -
如何在GDM中添加Session
2012-01-11 01:58 668如何在GDM中添加Session 2011年08月01日 ...
相关推荐
swap电路:当输入c=0时,输出x等于输入a,输出y等于输入b。当输入c=1时,则交换两输出,即输出y等于输入a
fatalerror99 array, bind & mem_fn, dynamic_bitset, function, functional/hash, in_place_factory & typed_in_place_factory, lambda, ref, smart_ptr, static_assert, string_algo, type_traits, typeof ...
先使用1位的swap搭建4位的swap,再使用4位的swap模块和Logisim内置的comparator元件搭建排序电路(请不要使用Plexers类元件) 功能描述:该电路具有4个4位的二进制数字作为输入和4个4位的二进制数字作为输出。它的功能...
进行一个字节8bit为的反转,根据C语言位域知识,进行10次赋值可任意调整转换值
color swapping of background and foreground
swap logisim emmm 电路
AI faceswap软件自动化安装脚本。
使用python完成的faceswap程序,使用单张图片完成对人脸的三维建模
An OpenCV program written in Python that swap the faces from two given images. Landmarks points are calculated to swap the face parts.
This document presents the AT command of UIM HOT SWAP operation and application examples. This document can apply to SIM7100/SIM7500/SIM7600 series modules.
信息安全_数据安全_Blockchain_Anchored_Swap_Meet_—a_mock_trial 信息保护 可信计算 数据泄露 应急响应 数据泄密
python instagram swap user code
「安全研究」Blockchain_Anchored_Swap_Meet_—a_mock_trial - 技术分析 NGFW 数据安全 安全意识 web安全 安全认证
Comeau C++ under LINUX INTEL ELF Preprocess away long long routines for now, even in relaxed mode.
push_swap::card_file_box:push_swap @ 19-优化的堆栈排序
/* configure LED1 */ gd_eval_led_init(LED1); /* configure LED2 */ gd_eval_led_init(LED2); printf("\r\n press user key button :"); printf("\r\n swap BB :");
能够实现参数的交换,swap函数利用变量的引用实现
Swap DMA_TO_DEVICE and DMA_FROM_DEVICE.
pus shwao pus shwaopu waopu shwao pus s opus shw opus shw o
these includes are used for the fake swap support of dev drum.