⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 008_mm_memory_c.html

📁 重读linux 2.4.2o所写的笔记
💻 HTML
📖 第 1 页 / 共 3 页
字号:
  }  @page {    @top {      content: flow(header);    }    @bottom {      content: flow(footer);    }  }  /* end default print css */ /* custom css *//* end custom css */  /* ui edited css */    body {    font-family: Verdana;        font-size: 10.0pt;    line-height: normal;    background-color: #ffffff;  }    .documentBG {    background-color: #ffffff;  }  /* end ui edited css */</style>   </head>  <body  revision="dcbsxfpf_49fkz6hmgt:4">      <table align=center cellpadding=0 cellspacing=0 height=5716 width=768>
  <tbody>
  <tr>
    <td height=5716 valign=top width=100%>
      <pre>2007-3-6   <br>mm/memory.c   PageReserved<br><br>   <br>   这1000多行代码,有绝大多数函数定义为static,意味着这个模块只提供了少量<br>的对外接口. 这几个接口是我们理解此模块的一个重要线索.<br><br><br>第一部分 :全局量的仔细解释<br><br>I)首先是几个全局变量,下面给出其具体含义:<br><br>+======================================================================<br>/*mem_map 中最高地址页面的map number,可以使用的ram全在其中*/<br>unsigned long max_mapnr;<br><br>/*物理内存页面总数(当然包括highmem在内)*/<br>unsigned long num_physpages; <br><br>/*从虚拟地址 high_memory 开始不再是线性映射区<br> *如果全部可以线性映射其值为物理内存顶端之虚拟地址<br> */<br>void * high_memory;  <br>struct page *highmem_start_page; /*高端物理内存的起始 page(struct page)*/<br>+======================================================================<br><br><br>II) i386下的 mem_map<br>另外一个重要的全局数组:<br>==========================================<br>mem_map_t * mem_map; /*page 结构的总数组*/<br>==========================================<br>  这个数组存放有全部的struct page结构(i386 not numa),每一个物理内存页面<br>都对应一个page 机构,存储于此数组.下面来看看i386体系下如何初始化mem_map:<br><br><br>  setup.S-&gt;asmlinkage void __init start_kernel(void) (init/main.c)<br>  |<br>  +--&gt;setup_arch ---&gt;  处理e820内存报告<br>                --&gt;   关于内存的提示信息<br>                ---&gt;  初始化bootmem (init_bootmem)<br>                ---&gt;  paging_init<br>                           +<br>  |                        +--&gt; pagetable_init(含fix map,vmalloc init)<br> \ /                       +--&gt; load cr3<br>  .                        +--&gt; kmap_init                        <br>  .                        +--&gt; free_area_init(zone-buddy初始化)<br><br>   函数paging_init如此调用free_area_init(zones_size);参数zones_size给出<br>每一个zone(DMA,NORMAL,HIGH)的大小.再看:<br>void __init free_area_init(unsigned long *zones_size)<br>{<br>	free_area_init_core(0, &amp;contig_page_data, &amp;mem_map, zones_size,<br>	                                                          0, 0, 0);<br>}<br>    对于i386此种"连续"内存结构,使用了一个pg_data_t类型的变量:contig_page_data<br>注意第三个参数是&amp;mem_map.<br>void __init free_area_init_core(int nid, pg_data_t *pgdat, <br>    struct page **gmap,	unsigned long *zones_size , <br>    unsigned long zone_start_paddr, unsigned long *zholes_size, <br>    struct page *lmem_map)<br>{<br>   /* pgdat 是contig_page_data, gmap是&amp;mem_map, lmem_map是 0 */<br>   .....<br> 	if (lmem_map == (struct page *)0) { <br> 	  //非numa体系此值为0, 分配mem map数组<br>		lmem_map = (struct page *) alloc_bootmem_node(pgdat, map_size);<br>		lmem_map = (struct page *)(PAGE_OFFSET + <br>			MAP_ALIGN((unsigned long)lmem_map - PAGE_OFFSET));<br>	}<br>	<br>	/*mem_map指向刚分配的内存,即是contig_page_data.node_mem_map*/<br>	*gmap = pgdat-&gt;node_mem_map = lmem_map; <br>	pgdat-&gt;node_size = totalpages;<br>	pgdat-&gt;node_start_paddr = zone_start_paddr;<br>	<br>	pgdat-&gt;node_start_mapnr = (lmem_map - mem_map); <br>	        /*从0号物理内存页开始*/<br><br>}    <br>  <br><br><br>III)NUMA下的 mem_map<br><br>  而对于NUMA类型的体系,mem_map已经失去了其存在的意义,不过2.4处理的<br>不够好,代码凌乱,可读性不强.以arm体系为例:(arch/arm/mm/init.c)<br>void __init paging_init(struct meminfo *mi, struct machine_desc *mdesc)<br>{  <br>  .........<br>  	for (node = 0; node &lt; numnodes; node++) {<br>     ......<br>    <br>		 free_area_init_node(node, pgdat, 0, zone_size,<br>				  bdata-&gt;node_boot_start, zhole_size);<br>	 }<br>	 ......<br>}<br><br>  这里调用的free_area_init_node定义于mm/numa.c <br>void __init free_area_init_node(int nid, pg_data_t *pgdat, struct page *pmap,<br>	unsigned long *zones_size, unsigned long zone_start_paddr, <br>	unsigned long *zholes_size)<br>{<br>	int i, size = 0;<br>	struct page *discard;<br><br>	if (mem_map == (mem_map_t *)NULL)<br>		mem_map = (mem_map_t *)PAGE_OFFSET; /*仅为mem_map指定了一个值,其指向<br>		                                     *的地址并没有存放struct page<br>		                                     */<br><br>	free_area_init_core(nid, pgdat, &amp;discard, zones_size, zone_start_paddr,<br>					zholes_size, pmap);<br>	pgdat-&gt;node_id = nid;<br>  .......<br>}<br>void __init free_area_init_core(int nid, pg_data_t *pgdat, <br>    struct page **gmap,	unsigned long *zones_size , <br>    unsigned long zone_start_paddr, unsigned long *zholes_size, <br>    struct page *lmem_map)<br>{<br>   /* pgdat 是NODE_pgdat, gmap是&amp;discard, lmem_map还是 0 */<br>   .....<br> 	if (lmem_map == (struct page *)0) { <br> 	  //numa体系此值亦为0, 分配mem map数组<br>		lmem_map = (struct page *) alloc_bootmem_node(pgdat, map_size);<br>		lmem_map = (struct page *)(PAGE_OFFSET + <br>			MAP_ALIGN((unsigned long)lmem_map - PAGE_OFFSET));<br>	}<br>	<br>	/* *gmap再numa下不指向mem_map,被丢弃不用*/<br>	*gmap = pgdat-&gt;node_mem_map = lmem_map; <br>	pgdat-&gt;node_size = totalpages;<br>	pgdat-&gt;node_start_paddr = zone_start_paddr;<br>	<br>	pgdat-&gt;node_start_mapnr = (lmem_map - mem_map); <br>	        /*node_start_mapnr再numa下失去其意义*/<br>	        /*变成了lmem_map这个地址到PAGE_OFFSET有多少个页面*/<br>	<br>  offset = lmem_map - mem_map;	/*减去常量PAGE_OFFSET*/<br>	for (j = 0; j &lt; MAX_NR_ZONES; j++) {    <br>	   ....<br>  	zone-&gt;offset = offset;  /*失去意义*/<br>  	 ....<br> 		zone-&gt;zone_mem_map = mem_map + offset; /*又加上常量PAGE_OFFSET所以<br> 		                                        *zone_mem_map确实有意义<br> 		                                        */<br>		zone-&gt;zone_start_mapnr = offset; /*失去意义*/<br>    ...<br>		offset += size;<br> }    <br>  <br><br>}    <br><br>从上面的分析,很明显2.4还没有处理好NUMA和discon mem.在NUMA体系下,mem_map<br>已经失去存在的意义,只是一个跳板.arm的vir_to_page宏就没有使用这个mem_map.<br>而ia64-sn的使用方法就巨绕:<br>*******include/asm-ia64/page.h 定义 <br># define virt_to_page(kaddr)	(mem_map + platform_map_nr(kaddr))<br>mem_map是一个常量PAGE_OFFSET???<br>*******include/asm-ia64/machvec_sn1.h定义<br>#define platform_map_nr		sn1_map_nr<br>*******arch/ia64/sn/sn1/setup.c定义<br>sn1_map_nr (unsigned long addr)<br>{<br>#ifdef CONFIG_DISCONTIGMEM<br>	return MAP_NR_SN1(addr);<br>#else<br>	return MAP_NR_DENSE(addr);<br>#endif<br>}<br>*******include/asm-ia64/sn/mmzone.h定义<br>#define NODE_DATA(n)   (&amp;((plat_node_data + (n))-&gt;gendata))<br><br>#define NODE_MEM_MAP(nid)      (NODE_DATA((nid))-&gt;node_mem_map)<br><br>#define LOCAL_MAP_NR(kvaddr) \<br>        (((unsigned long)(kvaddr)-LOCAL_BASE_ADDR((kvaddr))) &gt;&gt; PAGE_SHIFT)<br>#define MAP_NR_SN1(kaddr)   (LOCAL_MAP_NR((kaddr)) + \<br>                (((unsigned long)ADDR_TO_MAPBASE((kaddr)) - PAGE_OFFSET) / \<br>                sizeof(mem_map_t)))<br><br><br>仔细分析MAP_NR_SN1就知道其种缘由了:<br>  mem_map + (((unsigned long)ADDR_TO_MAPBASE((kaddr)) - PAGE_OFFSET) / \<br>                sizeof(mem_map_t))<br>  就定位到了pgdat-&gt;node_mem_map,而node_mem_map了在numa中依然有效,指向真<br>实的"mem_map".<br>  <br>  pgdat-&gt;node_start_mapnr也失去了意义,不过在kernel2.4中大概只有mips64使<br>用这个值,并且类似ia64的vir_to_page,见include/asm-mips64/pgtable.h<br>#define mips64_pte_pagenr(x) \<br>	(PLAT_NODE_DATA_STARTNR(PHYSADDR_TO_NID(pte_val(x))) + \<br>	PLAT_NODE_DATA_LOCALNR(pte_val(x), PHYSADDR_TO_NID(pte_val(x))))<br>#define pte_page(x)		(mem_map+mips64_pte_pagenr(x))<br>使用这个变量的时候最终还是加上了mem_map!!!<br><br>  不过从另外一个角度理解mem_map也可以:这紧紧是一个虚拟的值,借助于这个变<br>量也简化了一些设计,比如使用zone-&gt;offset的地方,如:mm/page_alloc.c<br>static void __free_pages_ok (struct page *page, unsigned long order)<br>{<br>   .........<br>	base = mem_map + zone-&gt;offset;<br>	page_idx = page - base;<br>   ....<br>}<br>  本来zone-&gt;offset减去了mem_map,现在加上mem_map,还是指向这个zone所属的<br>pgdat-&gt;zone_mem_map. 虽然失去其本意,也达到了目的,必是2.4向NUMA转化的过<br>渡性代码.<br>  <br>   在kernel2.6,numa体系下,根本不再有mem_map这个全局变量,<br>pgdat-&gt;node_start_mapnr也时刻保持其原意.zone-&gt;offset更是消失不见了,直接<br>换成了指针.一切都变得clean&amp;clear.<br>     <br>注:看64bit代码时突然想到两个64bit值之差赋值给long型的变量是否有问题,后来<br>参考了:<br>&lt;&lt;Compiler Usage Guidelines for <br>64-Bit Operating Systems <br>on AMD64 Platforms Application Note &gt;&gt;<br>指出在linux的64bit环境中, long是64bit的!!!和windows不同啊.<br><br><br>第二部分  本模块对外接口详解<br><br>I)clear_page_tables : 释放一个进程指定范围内的页表,以及关联的pmd,对应<br>        的pgd entry被清空,pgd本身没有被释放.至于页表中是否还有未断开<br>        影射的物理页面,此函数未做检查.(所以调用之前要先调用<br>        zap_page_range). 对于这个函数和其涉及的其他函数请自己阅读.<br>II)copy_page_range <br>    从一个进程的mm拷贝指定的vm area到另一个进程,拷贝内容包括:为新进程指<br>定的这段空间分配pmd页面,分配page table页面.从src进程的mm拷贝pte到新分配<br>的页表.增加copy的pte所引用页面的引用计数,并且处理pte是swap entry的情况(<br>增加swap page的引用计数). 对于vm area属性是可写但无VM_SHARE属性的情况,将<br>新进程的pte置写保护,等到新进程写此页面的时候进行COW. 如果vm area具有共享<br>属性(那当然就是容许在进程间共享此页面)将新设置的pte的dirty位清除:新进程<br>还未曾访问此页面.<br>  通过上面的分析阅读copy_page_range应该不是问题了.注意在copy_page_range<br>的时候就为COW设置了伏笔:共享一个page,但是不容许写.<br>  <br>III)zap_page_range: 从一个进程指定的地址开始释放指定大小的虚拟地址空间内<br>       所有已映射页面. clear_page_tables仅释放"cpu meta data",而此函数<br>       仅释放"user used data pages".如果页面被交换到磁盘,则释放磁盘页面.<br>       此函数相关子函数:<br>       zap_pmd_range  zap_pte_range free_pte.<br>       都是对mm进行遍历,只有free_pte还有些"业务"逻辑.<br>static inline int free_pte(pte_t pte)<br>{<br>	if (pte_present(pte)) {<br>		struct page *page = pte_page(pte);<br>		if ((!VALID_PAGE(page)) || PageReserved(page))<br>			return 0;<br>		/* <br>		 * free_page() used to be able to clear swap cache<br>		 * entries.  We may now have to do it manually.  <br>		 */<br>		if (pte_dirty(pte) &amp;&amp; page-&gt;mapping)<br>			set_page_dirty(page);<br>		free_page_and_swap_cache(page);<br>		return 1;<br>	}<br>	swap_free(pte_to_swp_entry(pte));<br>	return 0;<br>}<br>     PageReserved检查page是否就有PG_reserved属性. 到底什么是PG_reserved<br>页面这里说明一下: 其实非常简单,内核不认为PG_reserved属性的页面是"ram"页<br>面.从而不会在"任何"地方使用这个物理页面. 这里的任何地方不是指真正的所有<br>的内核程序,而是指不知道PG_reserved的页面是什么页面的代码.<br>   从另外一种意义上说,就是内核的某个地方保留了这个page,不得再做他用.<br>首先, 参考arch/i386/mm/init.c的分析,函数mem_init,使用函数free_all_bootmem<br>清除所有未分配出去的页面的PG_reserved位.仔细阅读一下函数对bootmem的初始<br>化就可以发现:所有非ram页面,在bootmem中已经标记为已分配,PG_reserved置位.<br>所以free_all_bootmem也没有将非ram页面的PG_reserved标识清除掉.而对于high<br> mem, mem_init 将非ram页面的PG_reserved位设置上.这就是PG_reserved的第一<br>个含义:非ram页面.<br>    然后看setup_arch对reserve_bootmem的调用,可以知道,(0, PAGE_SIZE)被保<br>留,这样不会有"任何"代码可以分配这段内存. 这就是第二个含义:被内核保留用于<br>特殊用途.<br>    关于zap_page_range不再做其他说明.<br><br>   <br>IV)user kiobuf <br>   在2.6内核,对应的函数叫做get_user_pages,用于AIO.这里的user kiobuf提供<br>这样一种服务:<br>   从内核读取数据直接映射到用户空间,减少了copy,可以"大大的"提高效率.不过<br>看来2.4想用于raw io.到时候再仔细分析,这里先看看user kiobuf是如何建立和使<br>用的.<br>int map_user_kiobuf(int rw, struct kiobuf *iobuf, unsigned long va, size_t len)<br>{<br>  .....<br>	int			datain = (rw == READ);<br>	/* Make sure the iobuf is not already mapped somewhere. */<br>	if (iobuf-&gt;nr_pages)<br>		return -EINVAL;<br>	mm = current-&gt;mm;<br>	dprintk ("map_user_kiobuf: begin\n");<br>	//准备扫描指定的区间(遍历ptr到end之间涉及的vma)<br>	ptr = va &amp; PAGE_MASK;<br>	end = (va + len + PAGE_SIZE - 1) &amp; PAGE_MASK;<br>	err = expand_kiobuf(iobuf, (end - ptr) &gt;&gt; PAGE_SHIFT);<br>	if (err)<br>		return err;<br>	down(&amp;mm-&gt;mmap_sem);<br> 	iobuf-&gt;locked = 0;<br>	iobuf-&gt;offset = va &amp; ~PAGE_MASK;<br>	iobuf-&gt;length = len;<br>	i = 0;<br>	/* <br>	 * First of all, try to fault in all of the necessary pages<br>	 */<br>	while (ptr &lt; end) {<br>		if (!vma || ptr &gt;= vma-&gt;vm_end) {<br>		  /*指定的虚拟地址va必须有一个vam区间对应*/<br>			vma = find_vma(current-&gt;mm, ptr); <br>			if (!vma) <br>				goto out_unlock;<br>			if (vma-&gt;vm_start &gt; ptr) {<br>			  /*必须使用堆栈(fix me)做io buf*/<br>				if (!(vma-&gt;vm_flags &amp; VM_GROWSDOWN))<br>					goto out_unlock;<br>				/*先对vma进行调整*/<br>				if (expand_stack(vma, ptr))<br>					goto out_unlock;<br>			}<br>			/*检查一下访问权限*/<br>			if (((datain) &amp;&amp; (!(vma-&gt;vm_flags &amp; VM_WRITE))) ||<br>					(!(vma-&gt;vm_flags &amp; VM_READ))) {<br>				err = -EACCES;<br>				goto out_unlock;<br>			}<br>		}<br>		/*为涉及到的空间影射物理内存页面*/<br>		if (handle_mm_fault(current-&gt;mm, vma, ptr, datain) &lt;= 0) <br>			goto out_unlock;<br>		spin_lock(&amp;mm-&gt;page_table_lock);<br>		/*记录页面的page 结构到 iobuf中,以便后用*/<br>		map = follow_page(ptr);<br>		if (!map) {<br>			spin_unlock(&amp;mm-&gt;page_table_lock);<br>			dprintk (KERN_ERR "Missing page in map_user_kiobuf\n");<br>			goto out_unlock;<br>		}<br>		map = get_page_map(map);<br>		if (map) {<br>			flush_dcache_page(map);<br>			 /*增加引用计数,以免被swap out对应unmap_kiobuf*/<br>			atomic_inc(&amp;map-&gt;count);<br>		} else<br>			printk (KERN_INFO "Mapped page missing [%d]\n", i);<br>		spin_unlock(&amp;mm-&gt;page_table_lock);<br>		iobuf-&gt;maplist[i] = map;<br>		iobuf-&gt;nr_pages = ++i;<br>		<br>		ptr += PAGE_SIZE;<br>	}<br><br>	up(&amp;mm-&gt;mmap_sem);<br>	.......<br>}<br><br>   与此相关的其他几个函数unmap_kiobuf,lock_kiovec,unlock_kiovec个人认为<br>极为简单,就不再分析.<br><br><br>V)zeromap_page_range  remap_page_range<br>int zeromap_page_range(unsigned long address, unsigned long size, <br>pgprot_t prot)<br>    相关函数zeromap_pmd_range,zeromap_pte_range,forget_pte.是一个遍历<br>当前进程的页面映射表的函数.将指定地址空间的每一项pte都指向zero page页<br>(一个4k的全0页面),并将pte置写保护,以后可以进行COW.<br>    如果某个pte已经映射了一个页面,就将其释放. 不过这种情况应该极少出现<br>一般调用此函数做zero map时都已经撤销了映射.<br>    值得注意的是,这个不涉及vma.没有加锁.<br><br>int remap_page_range(unsigned long from, unsigned long phys_addr, <br>unsigned long size, pgprot_t prot)<br>    相关函数remap_pmd_range,remap_pte_range. 作用是将phy_addr开始的连续<br>物理页面映射到虚存from, 大小为size(page align).这些物理页面都不能是ram<br>页面.只负责mem map不能管理的页面,和PageReserved的页面.主要应用于驱动将<br>设备内存映射到内核的一段虚存. 看看下面的函数就知道了:<br>static remap_pte_range(pte,address, size, phys_addr, pgprot_t prot)<br>{<br>	......<br>	do {<br>		struct page *page;<br>		pte_t oldpage;<br>		oldpage = ptep_get_and_clear(pte);<br><br>		page = virt_to_page(__va(phys_addr));<br>		/*只负责映射保留页面和非ram页面*/<br>		if ((!VALID_PAGE(page)) || PageReserved(page))<br> 			set_pte(pte, mk_pte_phys(phys_addr, prot));<br> 		/*同时负责拆除老的映射页面*/<br>		forget_pte(oldpage);<br>		.....<br>		pte++;<br>	} while (address &amp;&amp; (address &lt; end));<br>}<br>   如果某个pte已经映射了一个页面,就将其释放.这个操作也没有涉及vma,没有<br>加锁.<br><br>  <br>VI)vmtruncate (sys_truncate)<br>   先来看看此系统调用执行流程:<br>sys_truncate-&gt;do_sys_truncate-&gt;do_truncate()<br>{ ......<br>   newattrs.ia_valid = ATTR_SIZE | ATTR_CTIME;<br>   error = notify_change(dentry, &amp;newattrs);<br>   ....<br>}<br>notify_change(){<br>......<br>	lock_kernel();<br>	if (inode-&gt;i_op &amp;&amp; inode-&gt;i_op-&gt;setattr) //ext2 未设置此op<br>		error = inode-&gt;i_op-&gt;setattr(dentry, attr);<br>	else {<br>	  //所以对ext2来讲执行此公共流程<br>		error = inode_change_ok(inode, attr);<br>		if (!error)<br>			inode_setattr(inode, attr);<br>	}<br>	unlock_kernel();<br>.......<br>}<br><br>inode_setattr(){<br>  ......<br>  if (ia_valid &amp; ATTR_SIZE)<br>  		vmtruncate(inode, attr-&gt;ia_size);<br>  ......<br>  mark_inode_dirty(inode);<br>  <br>}<br>   可见, vmtruncate是其核心. 对于一个文件在进行truncate时要考虑文件已经<br>使用mmap映射到内存的情况. 将所有的映射一并truncate.此外还要处理page cache<br>保证page cache不再含有相关数据.最后才是文件本身进行truncate. vmtruncate<br>即此服务.<br>void vmtruncate(struct inode * inode, loff_t offset)<br>{<br>	unsigned long partial, pgoff;<br>	struct address_space *mapping = inode-&gt;i_mapping;<br>	unsigned long limit;<br><br>	if (inode-&gt;i_size &lt; offset)<br>		goto do_expand; /*对于文件扩展不影响对此文件的maping*/<br>	inode-&gt;i_size = offset;<br>	/* 清除page cache中相关的缓冲数据 */<br>	truncate_inode_pages(mapping, offset);<br>	spin_lock(&amp;mapping-&gt;i_shared_lock);<br><br>	/*检查是否存在此文件的mapping*/<br>	if (!mapping-&gt;i_mmap &amp;&amp; !mapping-&gt;i_mmap_shared)<br>		goto out_unlock;<br><br>	pgoff = (offset + PAGE_CACHE_SIZE - 1) &gt;&gt; PAGE_CACHE_SHIFT;<br>	partial = (unsigned long)offset &amp; (PAGE_CACHE_SIZE - 1);<br>   /*truncate maping*/<br>	if (mapping-&gt;i_mmap != NULL)<br>    /*<br>     *始释放指定虚拟地址空间内所有已映射页面.(not clear cpu meta data)<br>     */<br>		vmtruncate_list(mapping-&gt;i_mmap, pgoff, partial);<br>	if (mapping-&gt;i_mmap_shared != NULL)<br>		vmtruncate_list(mapping-&gt;i_mmap_shared, pgoff, partial);<br><br>out_unlock:<br>	spin_unlock(&amp;mapping-&gt;i_shared_lock);<br>	/* this should go into -&gt;truncate */<br>	inode-&gt;i_size = offset;<br>	/*最后对文件进行truncate,ext2 参考ext2_truncate */<br>	if (inode-&gt;i_op &amp;&amp; inode-&gt;i_op-&gt;truncate)<br>		inode-&gt;i_op-&gt;truncate(inode);<br>	return;<br><br>do_expand:<br>	limit = current-&gt;rlim[RLIMIT_FSIZE].rlim_cur;<br>	if (limit != RLIM_INFINITY) {<br>		if (inode-&gt;i_size &gt;= limit) {<br>			send_sig(SIGXFSZ, current, 0);<br>			goto out;<br>		}<br>		if (offset &gt; limit) {<br>			send_sig(SIGXFSZ, current, 0);<br>			offset = limit;<br>		}<br>	}<br>	inode-&gt;i_size = offset;<br>	if (inode-&gt;i_op &amp;&amp; inode-&gt;i_op-&gt;truncate)<br>		inode-&gt;i_op-&gt;truncate(inode);<br>out:<br>	return;<br>}<br>  vmtruncate_list就是将truncate语义指定的范围内的已映射页面umap.这里不<br>再分析.(就是调用zap_page_range)<br>  清除page cache在分析filemap.c的时候已经分析过了,对于文件本身的操作等<br>到分析buffer.c再议.<br>         <br>       <br>    <br>VII)Understanding Swap in<br>  接口函数: swapin_readahead,handle_mm_fault,make_pages_present.<br>  我们从handle_mm_fault谈起,在分析fault.c的时候提到过一个mm fault产生的<br>几种情形,首先是进入到do_page_fault的时候<br>/*<br> *  有三种情况下执行流到达此函数:<br> *    1. pmd, pgt, pte 有一个为空, 即未建立映射或已经撤销.(nomal page <br>         cache)<br> *    2. 页面不在内存(not present)被内核交换到了磁盘(swap space).<br> *    3. 权限不正确. (内核已经授权或者根本不容许那样访问)<br> *   <br> *  说明: <br> *    情况1)涉及的页面或者没有建立映射或者属于nomal address map(like mmap)<br> *          一般要到page cache中试图寻找.<br> *            try_to_swap_out处理nomal address map的时候,把相应的pte置成0.<br> *   <br> *    情况2.涉及页面属于swapper_space管理. try_to_swap_out 不会把pte 置0,<br>            而是讲pte置为相应的swp_entry_t并将present位置0.<br> *   <br> *    再转交下一级函数处理时, 所有非法操作都在这个函数中处理掉了.所谓非法<br> * 是用户企图进行一次内核(vma)不容许的操作,如vma无写属性,而进程进行了写操<br> * 作.<br> */<br>asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long error_code)<br>{<br>.........<br>	/*<br>	 * 如果处时指令流抵达此地,说明异常点在一个完好的vma中,<br>	 * 并且符合OS 赋予用户的权限.<br>	 */<br>	switch (handle_mm_fault(mm, vma, address, write)) { <br>	case 1:                                            <br>		tsk-&gt;min_flt++;<br>		break;<br>	case 2:<br>		tsk-&gt;maj_flt++;<br>		break;<br>	case 0:<br>		goto do_sigbus;<br>	default:<br>		goto out_of_memory;<br>	}<br>........<br>}<br> 上面的分析给出了handle_mm_fault(mm, vma, address, write)执行时所面临的<br>条件"说明异常点在一个完好的vma中,并且符合OS 赋予用户的权限":vma是经过了<br>扩展(expand stack)或修改,用户的这次操作应该得到相应的服务.<br>int handle_mm_fault(struct mm_struct *mm, struct vm_area_struct * vma,<br>	unsigned long address, int write_access)<br>{<br>	int ret = -1;<br>	pgd_t *pgd;<br>	pmd_t *pmd;<br><br>	pgd = pgd_offset(mm, address);<br>	pmd = pmd_alloc(pgd, address); /*查找或者分配pmd page*/<br><br>	if (pmd) {<br>		pte_t * pte = pte_alloc(pmd, address); /*查找或者分配page table*/<br>		if (pte)<br>			ret = handle_pte_fault(mm, vma, address, write_access, pte);<br>	}<br>	return ret;<br>}<br>  可见在发生页面异常的时候,容许进程被调度,和中断有所不同.这是合理的:可<br>以看作进程请求内核处理mm fault,就像一个系统调用.<br>static inline int handle_pte_fault(struct mm_struct *mm,<br>	struct vm_area_struct * vma, unsigned long address,<br>	int write_access, pte_t * pte)<br>{<br>	pte_t entry;<br><br>	/*<br>	 * We need the page table lock to synchronize with kswapd<br>	 * and the SMP-safe atomic PTE updates.<br>	 */<br>	spin_lock(&amp;mm-&gt;page_table_lock);<br>	entry = *pte;<br>	<br>	/*异常属于映射链断裂的情况*/<br>	if (!pte_present(entry)) {<br>		/*<br>		 * If it truly wasn't present, we know that kswapd<br>		 * and the PTE updates will not touch it later. So<br>		 * drop the lock.<br>		 */<br>		spin_unlock(&amp;mm-&gt;page_table_lock);<br>		if (pte_none(entry))<br>			return do_no_page(mm, vma, //页面不存在, 就是没有,或者被 try_to_swap_out<br>			                   address, // 断开, 这种页面一般属于一个nomal address map<br>			                   write_access, <br>			                   pte  //参见filemap_nopage 了解page cache 的换入<br>			                   );<br>		return do_swap_page(mm, vma,//not present, 属于swapper_space<br>			                  address, pte, <br>			                  pte_to_swp_entry(entry), <br>			                  write_access<br>			                 );<br>	}<br><br>  /*异常属于内核授权,但是cpu不容许的情况,COW*/<br>  //如果是read 时产生陷入或者是pte的映射问题<br>  //或者是一个非法的操作,已经在do_page_fault 过滤掉了<br>	if (write_access) { //由于页面写保护产生的陷入, 而OS 却容许用户写入<br>		if (!pte_write(entry))<br>			return do_wp_page( mm, //就是处理COW 的第二步<br>			                    vma, //Copy on Write<br>			                    address, //第一步是建立一个不容许写的页<br>			                    pte, entry //却在vma 中赋予用户写的权限<br>			                   );<br><br>		entry = pte_mkdirty(entry);<br>	}<br>	/*好像一般都满足write_access为1,read not present从<br>	  if (!pte_present(entry))就返回了(fix me),read present更是连<br>	  handle mem fault都进不了*/<br>	entry = pte_mkyoung(entry);<br>	establish_pte(vma, address, pte, entry);<br>	spin_unlock(&amp;mm-&gt;page_table_lock);<br>	return 1;<br>}<br>  <br>handle_mm_fault 已经修复了映射链上的系统页面,handle_pte_fault主要是修复<br>映射链上的用户页面:<br>  1)do_no_page:还未建立映射,或者已经被断开还存在于lru cache(page cache)<br>或者已经回收到了node-zone-buddy.<br>  2)do_swap_page:从lru恢复,如果已经被回收到node-zone-buddy就从磁盘调入.<br>  3)do_wp_page: 处理COW的copy操作,用户现在要写此页面,copy一份给他.<br><br>1)do_no_page: 分配一个匿名页面,或者用vm指定的操作寻找对应页面.在设置pte<br>的时候考虑COW:对于写操作,表示内核容许写(这里不会遭遇COW,cow是另一个处理<br>函数,handle_pte_fault已经区分的很清楚了),直接将pte置为可写.如果是read操<br>作,考虑mmap创建的vma(not anonymous page),并且页面已经是共用页面,则我们<br>不能直接给这个进程写权限,而是要取消写权限这是COW处理中的一环:建立一个写<br>保护的页面.参照分析filemap.c时对函数filemap_nopage的分析,那里讲的很详细<br>--&gt;filemap_nopage 对这个read操作的进程直接返回一个共享页面,如果这是第一<br>个要求访问此页面的进程do_no_page不会取消这个进程的写操作权限.<br>static int do_no_page(struct mm_struct * mm, struct vm_area_struct * vma,<br>	unsigned long address, int write_access, pte_t *page_table)<br>{<br>	struct page * new_page;<br>	pte_t entry;<br><br>	if (!vma-&gt;vm_ops || !vma-&gt;vm_ops-&gt;nopage)<br>		return do_anonymous_page(mm, vma, page_table, write_access, address);<br><br>	/*<br>	 * The third argument is "no_share", which tells the low-level code<br>	 * to copy, not share the page even if sharing is possible.  It's<br>	 * essentially an early COW detection.<br>	 * 对于mmap建立的vm,此函数是 filemap_nopage<br>	 */<br>	new_page = vma-&gt;vm_ops-&gt;nopage(vma, address &amp; PAGE_MASK, (vma-&gt;vm_flags &amp; VM_SHARED)?0:write_access);<br>	if (new_page == NULL)	/* no page was available -- SIGBUS */<br>		return 0;<br>	if (new_page == NOPAGE_OOM)<br>		return -1;<br>	++mm-&gt;rss;<br>    //其实在上面两个调入函数中(do_anonymous_page vma-&gt;vm_ops-&gt;nopage)<br>    //也存在cow 的第二步操作,即写操作时do_anonymous_page 返回<br>    //新分配的页面, vma-&gt;vm_ops-&gt;nopage 不共享页面,而进行copy 操作.<br>    //cow 本质: 写时拷贝,读时共享<br>    //写时拷贝就是刚分析的do_anonymous_page vma-&gt;vm_ops-&gt;nopage 加上<br>    //do_wp_page<br>    //读时共享就是do_anonymous_page 在read 条件下,返回zero 公共页<br>    //vma-&gt;vm_ops-&gt;nopage 在read 条件下尽力共享页面.<br>    <br>	/*<br>	 * This silly early PAGE_DIRTY setting removes a race<br>	 * due to the bad i386 page protection. But it's valid<br>	 * for other architectures too.<br>	 *<br>	 * Note that if write_access is true, we either now have<br>	 * an exclusive copy of the page, or this is a shared mapping,<br>	 * so we can make it writable and dirty to avoid having to<br>	 * handle that later.<br>	 */<br>	flush_page_to_ram(new_page);<br>	flush_icache_page(vma, new_page);<br>	entry = mk_pte(new_page, vma-&gt;vm_page_prot);<br>	if (write_access) {<br>		entry = pte_mkwrite(pte_mkdirty(entry));<br>	} else if (page_count(new_page) &gt; 1 &amp;&amp;<br>		   !(vma-&gt;vm_flags &amp; VM_SHARED))<br>		entry = pte_wrprotect(entry);<br>	set_pte(page_table, entry);<br>	/* no need to invalidate: a not-present page shouldn't be cached */<br>	update_mmu_cache(vma, address, entry);<br>	return 2;	/* Major fault */<br>}<br><br>2)do_swap_page<br>  对于开始还出的页面,可以从lru恢复,如果已经被回收到node-zone-buddy就从<br>磁盘调入.<br>static int do_swap_page(struct mm_struct * mm,<br>	struct vm_area_struct * vma, unsigned long address,<br>	pte_t * page_table, swp_entry_t entry, int write_access)<br>{<br>	struct page *page = lookup_swap_cache(entry); //先查找swap cache<br>	//如果查找的到,肯定也存在于lru,说不定就要开始换出了<br>	pte_t pte;<br><br>	if (!page) { /*如果找不到,就从磁盘swap in*/<br>		lock_kernel();<br>		swapin_readahead(entry); /*先预读一部分*/<br>		page = read_swap_cache(entry); /*类似普通文件读写,先读入,并加入swap<br>		    cache,并返回指定页面*/<br>		unlock_kernel();<br>		if (!page)<br>			return -1;<br><br>		flush_page_to_ram(page);<br>		flush_icache_page(vma, page);<br>	}<br><br>	mm-&gt;rss++;<br>  /*得到指定的页面,就开始恢复页面映射*/<br>	pte = mk_pte(page, vma-&gt;vm_page_prot);<br><br>	/*<br>	 * Freeze the "shared"ness of the page, ie page_count + swap_count.<br>	 * Must lock page before transferring our swap count to already<br>	 * obtained page count.<br>	 */<br>	lock_page(page); /*is_page_shared 要求锁定页面,详见filemap.c的分析*/<br>	swap_free(entry);<br>	if (write_access &amp;&amp; !is_page_shared(page))<br>		pte = pte_mkwrite(pte_mkdirty(pte));<br>	UnlockPage(page);<br><br>	set_pte(page_table, pte);<br>	/* No need to invalidate - it was non-present before */<br>	update_mmu_cache(vma, address, pte);<br>	return 1;	/* Minor fault */<br>}<br> 其中预读和读取到swap cache(read_swap_cache)留做以后详解,这里仅仅简单<br>分析一下预读.<br>/* <br> * Primitive swap readahead code. We simply read an aligned block of<br> * (1 &lt;&lt; page_cluster) entries in the swap area. This method is chosen<br> * because it doesn't cost us any seek time.  We also make sure to queue<br> * the 'original' request together with the readahead ones...  <br> */<br>void swapin_readahead(swp_entry_t entry)<br>{<br>	int i, num;<br>	struct page *new_page;<br>	unsigned long offset;<br><br>	/*<br>	 * Get the number of handles we should do readahead io to. Also,<br>	 * grab temporary references on them, releasing them as io completes.<br>	 */<br>	/*从entry所在cluster的开始预读一个连续区间(may be whole cluster)*/<br>	num = valid_swaphandles(entry, &amp;offset); <br>	<br>	for (i = 0; i &lt; num; offset++, i++) {<br>		/* Don't block on I/O for read-ahead */<br>		/*如果在异步状态的页面总数大于容许预读的总数,此次预读夭折*/<br>		if (atomic_read(&amp;nr_async_pages) &gt;= pager_daemon.swap_cluster<br>				* (1 &lt;&lt; page_cluster)) {<br>			while (i++ &lt; num)<br>				swap_free(SWP_ENTRY(SWP_TYPE(entry), offset++));<br>			break;<br>		}<br>		/* Ok, do the async read-ahead now */<br>		/* read_swap_cache_async try lock page, may be sleep.*/<br>		new_page = read_swap_cache_async(SWP_ENTRY(SWP_TYPE(entry), offset), 0);<br>		if (new_page != NULL)<br>			page_cache_release(new_page);<br>		swap_free(SWP_ENTRY(SWP_TYPE(entry), offset));<br>	}<br>	return;<br>}<br><br>  全局变量page_cluster定义一个cluster包含多少个页面.当系统中处于异步io<br>状态的页面大于容许预读的页面总数的时候,不再进行预读.<br>  <br>3)do_wp_page<br>  就是Copy 一个页面.只不过尽力避免分copy,相关逻辑比较难懂的也就是为何调<br>用is_page_shared要lock页面,这个我们已经分析过了.<br><br><br><br>int make_pages_present(unsigned long addr, unsigned long end)模拟从addr<br>开始到end结束的这段空间发生page fault的情况,从而讲对应页面统统调入内存.<br><br><br>全文完.<br><br></pre>
    </td>
  </tr>
  </tbody>
</table></body></html>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -