高端内存_linux内核高端内存 - CSDN
精华内容
参与话题
  • 高端内存和低端内存

    2019-07-01 14:28:39
    我是看ldd3的第十五章(409页)时,对用户虚拟地址、物理地址、总线地址、内核逻辑地址、内核虚拟地址区分不了,才...Linux物理内存空间分为DMA内存区(DMAZone)、低端内存区(NormalZone)与高端内存区(HighmemZone)三...

    我是看ldd3的第十五章(409页)时,对用户虚拟地址、物理地址、总线地址、内核逻辑地址、内核虚拟地址区分不了,才搜索资料的。找了几篇blog都没有把问题说清楚,下面这blog解释的还是不错的。

     

     

    2011-11-8-13-51

     

    一、高端内存和低端内存的划分

         Linux物理内存空间分为DMA内存区(DMA Zone)、低端内存区(Normal Zone)与高端内存区(Highmem Zone)三部分。DMA Zone通常很小,只有几十M,低端内存区与高端内存区的划分来源于Linux内核空间大小的限制。 

    二、来源:

    过去,CPU的地址总线只有32位, 32的地址总线无论是从逻辑上还是从物理上都只能描述4G的地址空间(232=4Gbit),在物理上理论上最多拥有4G内存(除了IO地址空间,实际内存容量小于4G),逻辑空间也只能描述4G的线性地址空间。

    为了合理的利用逻辑4G空间,Linux采用了3:1的策略,即内核占用1G的线性地址空间,用户占用3G的线性地址空间。所以用户进程的地址范围从0~3G,内核地址范围从3G~4G,也就是说,内核空间只有1G的逻辑线性地址空间。

    如果Linux物理内存小于1G的空间,通常内核把物理内存与其地址空间做了线性映射,也就是一一映射,这样可以提高访问速度。但是,当Linux物理内存超过1G时,线性访问机制就不够用了,因为只能有1G的内存可以被映射,剩余的物理内存无法被内核管理,所以,为了解决这一问题,Linux把内核地址分为线性区和非线性区两部分,线性区规定最大为896M,剩下的128M为非线性区。从而,线性区映射的物理内存成为低端内存,剩下的物理内存被成为高端内存。与线性区不同,非线性区不会提前进行内存映射,而是在使用时动态映射。

    三、例子

    假设物理内存为2G,则低段的896M为低端内存,通过线性映射给内核使用,其他的1128M物理内存为高端内存,可以被内核的非线性区使用。由于要使用128M非线性区来管理超过1G的高端内存,所以通常都不会映射,只有使用时才使kmap映射,使用完后要尽快用kunmap释放。

    对于物理内存为1G的内核,系统不会真的分配896M给线性空间,896M最大限制。下面是一个1.5G物理内存linux系统的真实分配情况,只有721M分配给了低端内存区,如果是1G的linxu系统,分配的就更少了。

    MemTotal   1547MB

    HighTotal     825MB

    LowTotal     721MB

    申请高端内存时,如果高端内存不够了,linux也会去低端内存区申请,反之则不行。


    转载自:https://blog.csdn.net/YuZhiHui_No1/article/details/46711601

    展开全文
  • Linux内存管理之高端内存

    千次阅读 2018-08-10 22:47:10
    Linux对内存的管理划分成三个层次,分别是Node、Zone、Page。对这三个层次简介如下: 层次 说明 Node(存储节点) CPU被划分成多个节点,每个节点都有自己的一块内存,可以参考NUMA架构有关节点的介绍 ...

    Linux对内存的管理划分成三个层次,分别是Node、Zone、Page。对这三个层次简介如下:

    层次 说明
    Node(存储节点) CPU被划分成多个节点,每个节点都有自己的一块内存,可以参考NUMA架构有关节点的介绍
    Zone(管理区) 每一个Node(节点)中的内存被划分成多个管理区域(Zone),用于表示不同范围的内存
    Page(页面) 每一个管理区又进一步被划分为多个页面,页面是内存管理中最基础的分配单位

    它们之间的关系,用一张经典的图表示如下:图片来源

    接下来我们重点讲述的是ZONE_HIGNMEM,也就是高端内存。

    1 高端内存的由来

    在32bit的x86机器中,4G的虚拟地址空间被划分成3G的用户空间(0—3G),1G的内核空间(3-4G)。在64bit的机器中于此不同。也就是说虚拟地址空间从0x0000 0000~0xBFFF FFFF为用户空间,0xC000 0000~0xFFFF FFFF为内核空间。Linux把每个内存节点的物理内存划分成3个管理区,也就是划分成ZONE_DMA、ZONE_NORMAL、ZONE_HIGHMEM。它们的介绍如下:

    内存区域 说明
    ZONE_DMA 范围是0~16M,该区域的物理页面专门供I/O设备的DMA使用
    ZONE_NORMAL 范围是16~896M,该区域的物理页面是内核能够直接使用的
    ZONE_HIGHMEM 范围是896~结束,高端内存,内核不能直接使用

    上面说到虚拟地址空间中的3~4G为Linux内核虚拟地址空间,其中内核空间3G~3G+896M对应物理内存空间的0~896M,并且在内核初始化时就已经将3G~3G+896M(0xC0000 0000~0xF7FF FFFF)虚拟地址空间与物理内存空间的0~896M进行了对应,内核在访问0~896M的内存的时候,只需要将其对应的虚拟地址空间增加一个偏移量就可以了。对于超过896M的物理地址空间,它们属于高端内存,高端内存区包含的内存页不能由内核直接访问,尽管它们也线性地映射到了线性地址空间的第4个G。也就是说对于高端内存区,只有128M的线性地址(0xF800 0000~0xFFFF FFFF)留给它进行映射。

    有这样一个问题:内核虚拟地址空间只有1G,也就是说内核只能访问1G物理内存空间,但是如果物理内存是2G,那内核如何访问剩余的1G物理内存空间呢?按照我们刚才说的,这2G的物理内存地址被划分成了3个Zone,物理内存0~896M是内核直接可以访问的,896M~2G这一部分内核要如何访问呢?实际上,当内核想要访问高于896M的物理内存空间时,会从0xF800 0000~0xFFFF FFFF这一块线性地址空间中找一段,然后映射到想要访问的那一块超过896M部分的物理内存,用完之后就归还。由于0xF800 0000~0xFFFF FFFF这一块没有和固定的物理内存空间进行映射,也就是说,这128M的线性地址空间可以和高于896M的物理内存空间短暂的、任意的建立映射,循环使用者128M线性地址空间,这样内核就可以访问剩下的高于896M的物理内存空间了。

    2 高端内存简介

    刚才说虚拟地址空间的3G~4G部分属于Linux内核空间,其中1G的虚拟内核空间如下图:

    上图中PAGE_OFFSET通常为0xC000 0000,而high_memory指的是0xF7FF FFFF,在物理内存映射区和和第一个vmalloc区之间插入的8MB的内存区是一个安全区,其目的是为了“捕获”对内存的越界访问。处于同样的理由,插入其他4KB大小的安全区来隔离非连续的内存区。Linux内核可以采用三种不同的机制将页框映射到高端内存区,分别叫做永久内核映射、临时内核映射以及非连续内存分配。相关区域介绍如下:

    虚拟内存区域 说明
    物理内存映射 映射了物理内存空间0~896M部分,这一部分是内核可以直接访问的
    永久内核映射 允许内核建立高端页框到内核虚拟地址空间的长期映射。永久内核映射不能用于中断处理程序和可延迟函数,因为建立永久内核映射可能阻塞当前进程
    固定映射的线性地址空间 其中的一部分用于建立临时内核映射
    vmalloc区 该区用于建立非连续内存分配,本文重点讨论的内容

    实际上,把内存区映射到一组连续的页框是最好的选择,这样可以充分利用高速缓存就并获得较低的平均访问时间。不过,如果对内存区的请求不是很频繁,那通过连续的线性地址来访问非连续的页框这样一种分配方式将会很有意义,因为这样可以避免外部碎片,而缺点是必须打乱内核页表。在内核虚拟地址空间中,为非连续内存区保留的线性地址空间的起始地址由VMALLOC_START宏定义,而末尾地址有VMALLOC_END宏定义。下面重点讲述非连续内存区,也就是vmalloc区。

    3 vmalloc区相关数据结构

    在Linux的实现中,每一个非连续内存区都对应着一个类型为struct vm_struct的描述符,源代码如下:源代码网址

    struct vm_struct {
    	struct vm_struct	*next;/*指向下一个vm_struct结构体的指针*/
    	void			*addr;/*内存区内第一个内存单元的线性地址*/
    	unsigned long		size;/*内存区的大小(以字节为单位)加4096字节(4096为内存区之间的安全区间的大小)*/
    	unsigned long		flags;/*非连续内存区映射的内存类型*/
    	struct page		**pages;/*指向nr_pages数组的指针,该数组有指向页描述符的指针组成*/
    	unsigned int		nr_pages;/*内存区填充的页的个数*/
    	phys_addr_t		phys_addr;
    	const void		*caller;
    };

    下面我们重点分析vmalloc()和vfree()函数的实现。下面是vmalloc()函数的源代码:源代码网址

    static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
    				 pgprot_t prot, int node)
    {
    	const int order = 0;
    	struct page **pages;
    	unsigned int nr_pages, array_size, i;
    	gfp_t nested_gfp = ((GFP_KERNEL | __GFP_HIGHMEM) & GFP_RECLAIM_MASK) | __GFP_ZERO;
    	/*
    	计算连续的线性地址空间对应多少个页框
    	*/
    	nr_pages = get_vm_area_size(area) >> PAGE_SHIFT;
    	/*
    	接下来需要创建一个数组,数组元素为指向page的指针,数组元素个数nr_pages
    	这一步是用来计算创建数组需要多大的内存空间
    	*/
    	array_size = (nr_pages * sizeof(struct page *));
    	/*设置area结构体中nr_pages域的值*/
    	area->nr_pages = nr_pages;
    	/* Please note that the recursion is strictly bounded. */
    	/*下面的if...else...就是创建一个数组,返回的是一个二级指针*/
    	if (array_size > PAGE_SIZE) {
    		pages = __vmalloc_node(array_size, 1, nested_gfp|__GFP_HIGHMEM,
    				PAGE_KERNEL, NUMA_NO_NODE, area->caller);
    		area->flags |= VM_VPAGES;
    	} else {
    		pages = kmalloc_node(array_size, nested_gfp, NUMA_NO_NODE);
    	}
    	/*设置设置area结构体中pages域*/
    	area->pages = pages;
    	if (!area->pages) {//错误处理
    		remove_vm_area(area->addr);
    		kfree(area);
    		return NULL;
    	}
    
    	/*
    	接下来就是重头戏,要分配页框了...
    	需要说明的是,这里是一个一个页框的分配,这些页框在物理内存上不一定连续
    	刚才创建的数组,在下面也用到了...
    	如果页面分配正常,没有出错就继续下面的处理,分配失败则需要进行出错处理
    	*/
    	for (i = 0; i < area->nr_pages; i++) {
    		struct page *page;
    		gfp_t tmp_mask = (GFP_KERNEL | __GFP_HIGHMEM) | __GFP_NOWARN;
    
    		if (NUMA_NO_NODE == NUMA_NO_NODE)
    			page = alloc_page(tmp_mask);
    		else
    			page = alloc_pages_node(NUMA_NO_NODE, tmp_mask, order);
    
    		if (unlikely(!page)) {
    			/* Successfully allocated i pages, free them in __vunmap() */
    			area->nr_pages = i;
    			goto fail;
    		}
    		area->pages[i] = page;
    	}
    	/*
    	执行到这里,说明前面的线性地址空间获取、页面分配都没有错误
    	连续线性地址空间有了,物理页面也获取到了,接下来需要做的就是将线性地址空间与这些
    	页框建立联系,而map_vm_area函数所做的就是修改内核使用的页表项,以此表明分配给
    	非连续内存区的每个页框现在对应着一个线性地址
    	看一下map_vm_area函数的调用链,其中缩进表示函数调用,处于同一缩进的表示函数的调用先后
    	--map_vm_area
    		--vmap_page_range
    			-- vmap_page_range_noflush
    				--pgd_offset_k
    				--pud_alloc
    				--vmap_pud_range
    					--pud_alloc
    					--vmap_pmd_range
    						--pmd_alloc
    						--vmap_pte_range
    							--pte_alloc_kernel
    							--set_pte_at
    	*/
    	if (map_vm_area(area, PAGE_KERNEL, &pages))
    		goto fail;
    	return area->addr;
    
    fail:
    	warn_alloc_failed(GFP_KERNEL | __GFP_HIGHMEM, order,
    			  "vmalloc: allocation failure, allocated %ld of %ld bytes\n",
    			  (area->nr_pages*PAGE_SIZE), area->size);
    	vfree(area->addr);
    	return NULL;
    }
    
    /**
     *	vmalloc  -  allocate virtually contiguous memory
     *	@size:		allocation size
     *	Allocate enough pages to cover @size from the page level
     *	allocator and map them into contiguous kernel virtual space.
     *
     *	For tight control over page level allocator and protection flags
     *	use __vmalloc() instead.
     */
    void *vmalloc(unsigned long size)
    {
    	struct vm_struct *area;
    	void *addr;
    	unsigned long real_size = size;
    
    	size = PAGE_ALIGN(size);
    	/*
    	如果申请的字节数为0或者申请的页面数(size >> PAGE_SHIFT就是申请的页面数)大于系统总页面数时,申请失败
    	*/
    	if (!size || (size >> PAGE_SHIFT) > totalram_pages)
    		goto fail;
    
    	/*
    	从__get_vm_area_node()函数调用的参数中我们看到了之前提到的VMALLOC_START和VMALLOC_END,这其实就是
    	vmalloc区的开始和结束,在这里,__get_vm_area_node这个函数简单来说就是在VMALLOC_START与VMALLOC_END之间
    	找个空闲的线性地址空间,然后返回内存区结构体vm_struct,而vm_struct中的addr代表了返回的内存区的线性地址的其实
    	
    	*/
    	area = __get_vm_area_node(size, 1, VM_ALLOC | VM_UNINITIALIZED,
    				  VMALLOC_START, VMALLOC_END, NUMA_NO_NODE, GFP_KERNEL | __GFP_HIGHMEM, __builtin_return_address(0));
    	if (!area)
    		goto fail;
    	/*
    	连续的线性地址空间得到了,接下来就需要得到这些线性地址对应的页框了,这将由__vmalloc_area_node函数来实现,关于本函数的分析,请看上面
    	*/
    	addr = __vmalloc_area_node(area, GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL, NUMA_NO_NODE);
    	if (!addr)
    		return NULL;
    
    	/*
    	 * In this function, newly allocated vm_struct has VM_UNINITIALIZED
    	 * flag. It means that vm_struct is not fully initialized.
    	 * Now, it is fully initialized, so remove this flag here.
    	 */
    	clear_vm_uninitialized_flag(area);
    
    	/*
    	 * A ref_count = 2 is needed because vm_struct allocated in
    	 * __get_vm_area_node() contains a reference to the virtual address of
    	 * the vmalloc'ed block.
    	 */
    	kmemleak_alloc(addr, real_size, 2, GFP_KERNEL | __GFP_HIGHMEM);
    
    	return addr;
    
    fail:
    	warn_alloc_failed(GFP_KERNEL | __GFP_HIGHMEM, 0,
    			  "vmalloc: allocation failure: %lu bytes\n",
    			  real_size);
    	return NULL;
    }

    上面的代码其实并不是真正的vmalloc函数的实现,真正的实现是函数嵌套函数的,我把这些函数调用进行展开得到的就是上面的代码。有关vmalloc函数实现的具体分析,请看上面的代码以及其中的中文注释。

    这里说一个宏:PAGE_ALIGN,这是一个嵌套的宏,最后翻译过来就是:#define PAGE_ALIGN(addr)    (addr + PAGE_SIZE-1)&PAGE_MASK,其中,PAGE_SIZE=1<<12,PAGE_MASK= ~(PAGE_SIZE-1),说人话,这个宏定义到底是做什么的呢?其实就是将size的值设置为4096的整数倍,举个例子,我们要申请1byte,也就是原来的size=1,执行size=PAGE_ALIGN(size)后,相当于size=(1+4096-1)&PAGE_MASK=4096&PAGE_MASK=4096,正好是4096的一倍,再举个例子,要申请4097字节,即size=4098,执行size=PAGE_ALIGN(size)后,size=(4098+4096-1)&PAGE_MASK=(8192+1)&PAGE_MASK=8192,也就是说,当我们申请的字节数不足4096字节(一个普通页面大小为4K,也就是4096字节)时,按照一个页面进行申请,超过一个页面但不足两个页面时按照两个页面进行申请。

    与vmalloc函数相对的是vfree,其过程类似,这里不再展开。

    常见问题:问题来自参考博客

    1、用户空间(进程)是否有高端内存概念?

    用户进程没有高端内存概念。只有在内核空间才存在高端内存。用户进程最多只可以访问3G物理内存,而内核进程可以访问所有物理内存。

    2、64位内核中有高端内存吗?

    目前现实中,64位Linux内核不存在高端内存,因为64位内核可以支持超过512GB内存。若机器安装的物理内存超过内核地址空间范围,就会存在高端内存。

    3、用户进程能访问多少物理内存?内核代码能访问多少物理内存?

    32位系统用户进程最大可以访问3GB,内核代码可以访问所有物理内存。

    64位系统用户进程最大可以访问超过512GB,内核代码可以访问所有物理内存。

    4、为什么不把所有的地址空间都分配给内核?

    若把所有地址空间都给内存,那么用户进程怎么使用内存?怎么保证内核使用内存和用户进程不起冲突?

    本人水平有限,如您发现文中有错误,请评论或者发邮箱:ieyaolulu@163.com,谢谢!

    展开全文
  • 高端内存

    2010-09-29 14:53:00
    什么是高端内存linux 中 内核使用3G-4G的线性地址空间,也就是说总共只有1G的地址空间可以用来映射物理地址空间。但是,如果内存大于1G的情况下呢?是不是超过1G的内存 就无法使用了呢?为此内核引入了一个高端...

    一. 什么是高端内存


    linux 中 内核使用3G-4G的线性地址空间,也就是说总共只有1G的地址空间可以用来映射物理地址空间。但是,如果内存大于1G的情况下呢?是不是超过1G的内存 就无法使用了呢?为此内核引入了一个高端内存的概念,把1G的线性地址空间划分为两部分:小于896M物理地址空间的称之为低端内存,这部分内存的物理地 址和3G开始的线性地址是一一对应映射的,也就是说内核使用的线性地址空间3G--(3G+896M)和物理地址空间0-896M一一对应;剩下的 128M的线性空间用来映射剩下的大于896M的物理地址空间,这也就是我们通常说的高端内存区。
    所谓的建立高端内存的映射就是能用一个线性地址来访问 高端内存的页。如何理解这句话呢?在开启分页后,我们要访问一个物理内存地址,需要经过MMU的转换,也就是一个32位地址vaddr的高10位用来查找该vaddr所在页目录 项,用12-21位来查找页表项,再用0-11位偏移和页的起始物理地址相加得到paddr,再把该paddr放到前端总线上,那么我们就可以访问该vaddr对应的物理内存了。在低端内存中,每一个物理内存页在系统 初始化的时候都已经存在这样一个映射了。而高端内存还不存在这样一个映射(页目录项,页表都是空的 ), 所以我们必须要在系统初始化完后,提供一系列的函数来实现这个功能,这就是所谓的高端内存的映射。那么我们为什么不再系统初始化的时候把所有的内存映射都 建立好呢?主要原因是,内核线性地址空间不足以容纳所有的物理地址空间(1G的内核线性地址空间和最多可达4G的物理地址空间),所以才需要预留一部分 (128M)的线性地址空间来动态的映射所有的物理地址空间,于是就产生了所谓的高端内存映射。


    二.内核如何管理高端内存





    上面的图展示了内核如何使用3G-4G的线性地址空间,首先解释下什么是high_memory
    在arch/x86/mm/init_32.c里面由如下代码:

    # ifdef CONFIG_HIGHMEM
           highstart_pfn = highend_pfn = max_pfn;
          if (max_pfn > max_low_pfn)
                highstart_pfn = max_low_pfn;
           e820_register_active_regions( 0, 0, highend_pfn) ;
           sparse_memory_present_with_active_regions( 0) ;
           printk( KERN_NOTICE "%ldMB HIGHMEM available./n" ,
                pages_to_mb( highend_pfn - highstart_pfn) ) ;
           num_physpages = highend_pfn;
          high_memory = (void *) __va(highstart_pfn * PAGE_SIZE-1 )+1 ;
    # else
           e820_register_active_regions( 0, 0, max_low_pfn) ;
           sparse_memory_present_with_active_regions( 0) ;
           num_physpages = max_low_pfn;
          high_memory = (void *) __va(max_low_pfn * PAGE_SIZE - 1)+ 1 ;
    # endif

    high_memory是“具体物理内存的上限对应的虚拟 地址”,可以这么理解:当内存内存小于896M时,那么high_memory = (void *) __va(max_low_pfn * PAGE_SIZE),max_low_pfn就是在内存中最后的一个页帧号,所以high_memory=0xc0000000+物理内存大小;当内存大于896M时,那么 highstart_pfn = max_low_pfn, 此时max_low_pfn就不是物理内存的最后一个页帧号了,而是内存为896M时的最后一个页帧号,那么high_memory= 0xc0000000+896M.总之high_memory是不能超过0xc0000000+896M.
    由于我们讨论的是物理内存大于896M的情况,所以high_memory实际上就是0xc0000000+896M,从high_memory开始的128M(4G-high_memory)就是用作用来映射剩下的大于896M的内存的,当然这128M还可以用来映射设备 的内存(MMIO)。
    从上图我们看到有VMALLOC_START,VMALLOC_END,PKMAP_BASE,FIX_ADDRESS_START等宏术语,其实这些术 语划分了这128M的线性空间,一共分为三个区域:VMALLOC区域(本文不涉及这部分内容,关注本博客的其他文章),永久映射区(permanetkernel mappings), 临时映射区(temporary kernel mappings).这三个区域都可以用来映射高端内存,本文重点阐述下后两个区域是如何映射高端内存的。


    三. 永久映射区(permanet kernel mappings)


    1. 介绍几个定义:


    PKMAP_BASE:永久映射区的起始线性地址。
    pkmap_page_table :永久映射区对应的页表
    LAST_PKMAP :pkmap_page_table里面包含的entry的数量=1024
    pkmap_count[LAST_PKMAP]数组:每一个元素的值对应一个entry的引用计数。关于引用计数的值,有以下几种情况:

    0:说明这个entry可用。
    1:entry不可用,虽然这个entry没有被用来映射任何内存,但是他仍然存在TLB entry没有被flush,

        所以还是不可用。


    N:有N-1个对象正在使用这个页面


    首先,要知道这个区域的大小是4M,也就是说128M的线性地址空间里面,只有4M的线性地址空间是用来作永久映射区的。至于到底是哪4M,是由PKMAP_BASE决定的,这个变量表示用来作永久内存映射的4M区间的起始线性地址。
    在NON-PAE的i386上,页目录里面的每一项都指向一个4M的空间,所以永久映射区只需要一个页目录项就可以了。而一个页目录项指向一张页表,那么 永久映射区正好就可以用一张页表来表示了,于是我们就用 pkmap_page_table 来指向这张页表。
                                                               

                pgd = swapper_pg_dir + pgd_index( vaddr) ;
           pud = pud_offset( pgd, vaddr) ;//pud==pgd
           pmd = pmd_offset( pud, vaddr) ;//pmd==pud==pgd
           pte = pte_offset_kernel( pmd, vaddr) ;
           pkmap_page_table = pte;


    2. 具体代码分析(2.6.31)

                                                                                                                                  
    void * kmap( struct page * page)
    {
           might_sleep( ) ;
          if ( ! PageHighMem( page) )
                return page_address( page) ;
          return kmap_high( page) ;
    }
    kmap()函数就是用来建立永久映射的函数:由于调用kmap函数有可能会导致进程 阻 塞,所以它不能在中断处理函数等不可被阻塞的上下文下被调用,might_sleep()的作用就是当该函数在不可阻塞的上下文下被调用是,打印栈信息。 接下来判断该需要建立永久映射的页是否确实属于高端内存,因为我们知道低端内存的每个页都已经存在和线性地址的映射了,所以,就不需要再建立了, page_address()函数返回该page对应的线性地址。(关于page_address()函数,参考本博客的专门文章有解释)。最后调用 kmap_high(page),可见kmap_high()才真正执行建立永久映射的操作。

    /**
    * kmap_high - map a highmem page into memory
    * @page: &struct page to map
    *
    * Returns the page's virtual memory address.
    *
    * We cannot call this from interrupts, as it may block.
    */

    void * kmap_high( struct page * page)
    {
          unsigned long vaddr;
          /*
          * For highmem pages, we can't trust "virtual" until
          * after we have the lock.
          */

           lock_kmap( ) ;
           vaddr = ( unsigned long ) page_address( page) ;
          if ( ! vaddr)
                vaddr = map_new_virtual( page) ;
           pkmap_count[ PKMAP_NR( vaddr) ] + + ;
           BUG_ON( pkmap_count[ PKMAP_NR( vaddr) ] < 2) ;
           unlock_kmap( ) ;
          return ( void * ) vaddr;
    }
    kmap_high函数分析:首先获得对pkmap_page_table操作的锁,然后再调用page_address()来返回该page是否已经被 映射,我们看到前面在kmap()里面已经判断过了,为什么这里还要再次判断呢?因为再获的锁的时候,有可能锁被其他CPU拿走了,而恰巧其他CPU拿了 这个锁之后,也是执行这段code,而且映射的也是同一个page,那么当它把锁释放掉的时候,其实就表示该page的映射已经被建立了,我们这里就没有 必要再去执行这段code了,所以就有必要在获得锁后再判断下。
    如果发现vaddr不为空,那么就是刚才说的,已经被其他cpu上执行的任务给建立了,这里只需要把表示该页引用计数的pkmap_count[]再加一 就可以了。同时调用BUG_ON来确保该引用计数确实是不小于2的,否则就是有问题的了。然后返回vaddr,整个建立就完成了。
    如果发现vaddr为空呢?调用map_new_virtual()函数,到此我们看到,其实真正进行建立映射的代码在这个函数里面

    static inline unsigned long map_new_virtual( struct page * page)
    {   
          unsigned long vaddr;
          int count ;
                        
    start:               
          count = LAST_PKMAP;//LAST_PKMAP=1024
          /* Find an empty entry */
          for ( ; ; ) {   
                last_pkmap_nr = ( last_pkmap_nr + 1) & LAST_PKMAP_MASK;
                if ( ! last_pkmap_nr) {
                         flush_all_zero_pkmaps( ) ;
                         count = LAST_PKMAP;
                }
                if ( ! pkmap_count[ last_pkmap_nr] )
                         break ;   /* Found a usable entry */
                if ( - - count )
                         continue ;
          
                /*
                    * Sleep for somebody else to unmap their entries
                    */
       
                {   
                         DECLARE_WAITQUEUE( wait, current) ;
               
                         __set_current_state( TASK_UNINTERRUPTIBLE) ;
                         add_wait_queue( & pkmap_map_wait, & wait) ;
                         unlock_kmap( ) ;
                         schedule( ) ;
                         remove_wait_queue( & pkmap_map_wait, & wait) ;
                         lock_kmap( ) ;

                         /* Somebody else might have mapped it while we slept */
                         if ( page_address( page) )
                                  return ( unsigned long ) page_address( page) ;

                         /* Re-start */
                         goto start;
                }
          }
           vaddr = PKMAP_ADDR( last_pkmap_nr) ;
           set_pte_at( & init_mm, vaddr,
                   & ( pkmap_page_table[ last_pkmap_nr] ) , mk_pte( page, kmap_prot) ) ;

           pkmap_count[ last_pkmap_nr] = 1;
           set_page_address( page, ( void * ) vaddr) ;

          return vaddr;
    }
    last_pkmap_nr:记录上次被分配的页表项在pkmap_page_table里的位置,初始值为0,所以第一次分配的时候last_pkmap_nr等于1。

    接下来判断什么时候last_pkmap_nr等于0,等于0就表示1023(LAST_PKMAP(1024)-1)个页表项已经被分配了,这时候就需 要调用flush_all_zero_pkmaps()函数,把所有pkmap_count[] 计数为1的页表项在TLB里面的entry给flush掉,并重置为0,这就表示该页表项又可以用了,可能会有疑惑为什么不在把pkmap_count置 为1的时候也就是解除映射的同时把TLB也flush呢?个人感觉有可能是为了效率的问题吧,毕竟等到不够的时候再刷新,效率要好点吧。

    再判断pkmap_count[last_pkmap_nr]是否为0,0的话就表示这个页表项是可用的,那么就跳出循环了到下面了。

    PKMAP_ADDR(last_pkmap_nr)返回这个页表项对应的线性地址vaddr.

    #define PKMAP_ADDR(nr) (PKMAP_BASE + ((nr) << PAGE_SHIFT))

    set_pte_at(mm, addr, ptep, pte)函数在NON-PAE i386上的实现其实很简单,其实就等同于下面的代码:

    static inline void native_set_pte(pte_t *ptep , pte_t pte)
    {
           *ptep = pte;
    }
    我们已经知道页表的线性起始地址存放在pkmap_page_table里面,那么相应的可用的页表项的地址就是& pkmap_page_table[last_pkmap_nr],得到了页表项的地址,只要把相应的pte填写进去,那么整个映射不就完成了吗?
    pte由两部分组成:高20位表示物理地址,低12位表示页的描述信息。
    怎么通过page查找对应的物理地址呢(参考page_address()一文)?其实很简单,用(page - mem_map) 再移PAGE_SHIFT位就可以了。
    低12位的页描述信息是固定的:kmap_prot=(_PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY | _PAGE_ACCESSED | _PAGE_GLOBAL).
    下面的代码就是做了这些事情:

    mk_pte(page, kmap_prot));
    #define mk_pte(page, pgprot) pfn_pte(page_to_pfn(page), (pgprot))
    #define page_to_pfn __page_to_pfn
    #define __page_to_pfn(page) ((unsigned long)((page) - mem_map) + /
                                  ARCH_PFN_OFFSET)
    static inline pte_t pfn_pte(unsigned long page_nr, pgprot_t pgprot)
    {
           return __pte(((phys_addr_t)page_nr << PAGE_SHIFT) |
                      massage_pgprot(pgprot));
    }

    接下来把pkmap_count[last_pkmap_nr]置为1,1不是表示不可用吗,既然映射已经建立好了,应该赋值为2呀,其实这个操作是在他 的上层函数kmap_high里面完成的(pkmap_count[PKMAP_NR(vaddr)]++).
    到此为止,整个映射就完成了,再把page和对应的线性地址加入到page_address_htable哈希链表里面就可以了(参考page_address一文)。


    我们继续看所有的页表项都已经用了的情况下,也就是1024个页表项全已经映射了内存了,如何处理。此时count==0,于是就进入了下面的代码:

    /*
                    * Sleep for somebody else to unmap their entries
                    */
                {
                         DECLARE_WAITQUEUE(wait, current);
               
                         __set_current_state(TASK_UNINTERRUPTIBLE);
                         add_wait_queue(&pkmap_map_wait, &wait);
                         unlock_kmap();
                         schedule();
                         remove_wait_queue(&pkmap_map_wait, &wait);
                         lock_kmap();

                         /* Somebody else might have mapped it while we slept */
                         if (page_address(page))
                                   return (unsigned long)page_address(page);

                         /* Re-start */
                         goto start;
                }
    这段代码其实很简单,就是把当前任务加入到等待队列pkmap_map_wait,当有其他任务唤醒这个队列时,再继续goto start,重新整个过程。这里就是上面说的调用kmap函数有可能阻塞的原因。
    那么什么时候会唤醒pkmap_map_wait队列呢?当调用kunmap_high函数,来释放掉一个映射的时候。
    kunmap_high函数其实页很简单,就是把要释放的页表项的计数减1,如果等于1的时候,表示有可用的页表项了,再唤醒pkmap_map_wait队列

    /**
    * kunmap_high - map a highmem page into memory
    * @page: &struct page to unmap
    *
    * If ARCH_NEEDS_KMAP_HIGH_GET is not defined then this may be called
    * only from user context.
    */
    void kunmap_high(struct page *page)
    {
           unsigned long vaddr;
           unsigned long nr;
           unsigned long flags;
           int need_wakeup;

           lock_kmap_any(flags);
           vaddr = (unsigned long)page_address(page);
           BUG_ON(!vaddr);
           nr = PKMAP_NR(vaddr);

           /*
          * A count must never go down to zero
          * without a TLB flush!
          */
           need_wakeup = 0;
           switch (--pkmap_count[nr]) {//减一
           case 0:
                BUG();
           case 1:
                /*
                    * Avoid an unnecessary wake_up() function call.
                    * The common case is pkmap_count[] == 1, but
                    * no waiters.
                    * The tasks queued in the wait-queue are guarded
                    * by both the lock in the wait-queue-head and by
                    * the kmap_lock. As the kmap_lock is held here,
                    * no need for the wait-queue-head's lock. Simply
                    * test if the queue is empty.
                    */
                need_wakeup = waitqueue_active(&pkmap_map_wait);
           }
           unlock_kmap_any(flags);

           /* do wake-up, if needed, race-free outside of the spin lock */
           if (need_wakeup)
                wake_up(&pkmap_map_wait);
    }

     

    展开全文
  • 关于高端内存的权威解释

    千次阅读 2013-04-23 15:39:18
    注:本文是我见到的所有关于高端内存解释的最详细、最清晰的解释,其他帖子寥寥数语写的都是垃圾,保存下来只为方便后来人和我自己,感谢原文作者! 原文地址:http://bbs.chinaunix.net/thread-1938084-1-1.html ...

    注:本文是我见到的所有关于高端内存解释的最详细、最清晰的解释,其他帖子寥寥数语写的都是垃圾,保存下来只为方便后来人和我自己,感谢原文作者!

    原文地址:http://bbs.chinaunix.net/thread-1938084-1-1.html



    注:本文提及的物理地址空间可以理解为就是物理内存,但是在某些情况下,把他们理解为物理内存是不对的。

    本文讨论的环境是NON-PAE的i386平台,内核版本2.6.31-14

    一.什么是高端内存

    linux中内核使用3G-4G的线性地址空间,也就是说总共只有1G的地址空间可以用来映射物理地址空间。但是,如果内存大于1G的情况下呢?是不是超过1G的内存就无法使用了呢?为此内核引入了一个高端内存的概念,把1G的线性地址空间划分为两部分:小于896M物理地址空间的称之为低端内存,这部分内存的物理地址和3G开始的线性地址是一一对应映射的,也就是说内核使用的线性地址空间3G--(3G+896M)和物理地址空间0-896M一一对应;剩下的128M的线性空间用来映射剩下的大于896M的物理地址空间,这也就是我们通常说的高端内存区。

    所谓的建立高端内存的映射就是能用一个线性地址来访问高端内存的页。如何理解这句话呢?在开启分页后,我们要访问一个物理内存地址,需要经过MMU的转换,也就是一个32位地址vaddr的高10位用来查找该vaddr所在页目录项,用12-21位来查找页表项,再用0-11位偏移和页的起始物理地址相加得到paddr,再把该paddr放到前端总线上,那么我们就可以访问该vaddr对应的物理内存了。在低端内存中,每一个物理内存页在系统初始化的时候都已经存在这样一个映射了。而高端内存还不存在这样一个映射(页目录项,页表都是空的),所以我们必须要在系统初始化完后,提供一系列的函数来实现这个功能,这就是所谓的高端内存的映射。那么我们为什么不再系统初始化的时候把所有的内存映射都建立好呢?主要原因是,内核线性地址空间不足以容纳所有的物理地址空间(1G的内核线性地址空间和最多可达4G的物理地址空间),所以才需要预留一部分(128M)的线性地址空间来动态的映射所有的物理地址空间,于是就产生了所谓的高端内存映射。

    二.内核如何管理高端内存


     

     

     

    上面的图展示了内核如何使用3G-4G的线性地址空间,首先解释下什么是high_memory

    在arch/x86/mm/init_32.c里面由如下代码:

     

     

    #ifdef CONFIG_HIGHMEM

            highstart_pfn = highend_pfn = max_pfn;

            if (max_pfn > max_low_pfn)

                    highstart_pfn = max_low_pfn;

            e820_register_active_regions(0, 0, highend_pfn);

            sparse_memory_present_with_active_regions(0);

            printk(KERN_NOTICE "%ldMB HIGHMEM available.\n",

                    pages_to_mb(highend_pfn - highstart_pfn));

            num_physpages = highend_pfn;

              high_memory = (void *) __va(highstart_pfn * PAGE_SIZE-1)+1; 

    #else

            e820_register_active_regions(0, 0, max_low_pfn);

            sparse_memory_present_with_active_regions(0);

            num_physpages = max_low_pfn;

             high_memory = (void *) __va(max_low_pfn * PAGE_SIZE - 1)+1; 

    #endif

     

     

    high_memory是“具体物理内存的上限对应的虚拟地址”,可以这么理解:当内存内存小于896M时,那么high_memory = (void *)__va(max_low_pfn * PAGE_SIZE),max_low_pfn就是在内存中最后的一个页帧号,所以high_memory=0xc0000000+物理内存大小;当内存大于896M时,那么highstart_pfn= max_low_pfn,此时max_low_pfn就不是物理内存的最后一个页帧号了,而是内存为896M时的最后一个页帧号,那么high_memory=0xc0000000+896M.总之high_memory是不能超过0xc0000000+896M.

    由于我们讨论的是物理内存大于896M的情况,所以high_memory实际上就是0xc0000000+896M,从high_memory开始的128M(4G-high_memory)就是用作用来映射剩下的大于896M的内存的,当然这128M还可以用来映射设备的内存(MMIO)。

    从上图我们看到有VMALLOC_START,VMALLOC_END,PKMAP_BASE,FIX_ADDRESS_START等宏术语,其实这些术语划分了这128M的线性空间,一共分为三个区域:VMALLOC区域(本文不涉及这部分内容,关注本博客的其他文章),永久映射区(permanetkernelmappings), 临时映射区(temporary kernelmappings).这三个区域都可以用来映射高端内存,本文重点阐述下后两个区域是如何映射高端内存的。

     

     

    三.永久映射区(permanet kernel mappings)

    1.介绍几个定义:

    PKMAP_BASE:永久映射区的起始线性地址。

    pkmap_page_table:永久映射区对应的页表

    LAST_PKMAP:pkmap_page_table里面包含的entry的数量=1024

    pkmap_count[LAST_PKMAP]数组:每一个元素的值对应一个entry的引用计数。关于引用计数的值,有以下几种情况:

     

    0:说明这个entry可用。

    1:entry不可用,虽然这个entry没有被用来映射任何内存,但是他仍然存在TLBentry没有被flush, 

     

         所以还是不可用。

     

     

    N:有N-1个对象正在使用这个页面

     

     

    首先,要知道这个区域的大小是4M,也就是说128M的线性地址空间里面,只有4M的线性地址空间是用来作永久映射区的。至于到底是哪4M,是由PKMAP_BASE决定的,这个变量表示用来作永久内存映射的4M区间的起始线性地址。

    在NON-PAE的i386上,页目录里面的每一项都指向一个4M的空间,所以永久映射区只需要一个页目录项就可以了。而一个页目录项指向一张页表,那么永久映射区正好就可以用一张页表来表示了,于是我们就用pkmap_page_table来指向这张页表。

                                                               

                   pgd = swapper_pg_dir + pgd_index(vaddr);

            pud = pud_offset(pgd, vaddr);//pud==pgd

            pmd = pmd_offset(pud, vaddr);//pmd==pud==pgd

            pte = pte_offset_kernel(pmd, vaddr); 

            pkmap_page_table = pte;

     

     

    2.具体代码分析(2.6.31)

     

                                                                                                                            

    void *kmap(struct page *page)

    {

            might_sleep();

            if (!PageHighMem(page))

                    return page_address(page);

            return kmap_high(page);

    }

    kmap()函数就是用来建立永久映射的函数:由于调用kmap函数有可能会导致进程阻塞,所以它不能在中断处理函数等不可被阻塞的上下文下被调用,might_sleep()的作用就是当该函数在不可阻塞的上下文下被调用是,打印栈信息。接下来判断该需要建立永久映射的页是否确实属于高端内存,因为我们知道低端内存的每个页都已经存在和线性地址的映射了,所以,就不需要再建立了,page_address()函数返回该page对应的线性地址。(关于page_address()函数,参考本博客的专门文章有解释)。最后调用kmap_high(page),可见kmap_high()才真正执行建立永久映射的操作。

     

     

    /**

    * kmap_high - map a highmem page into memory

    * @page: &struct page to map

    *

    * Returns the page's virtual memory address.

    *

    * We cannot call this from interrupts, as it may block.

    */

    void *kmap_high(struct page *page)

    {

            unsigned long vaddr;

            /*

             * For highmem pages, we can't trust "virtual" until

             * after we have the lock.

             */

            lock_kmap();

            vaddr = (unsigned long)page_address(page);

            if (!vaddr)

                    vaddr = map_new_virtual(page);

            pkmap_count[PKMAP_NR(vaddr)]++;

            BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 2);

            unlock_kmap();

            return (void*) vaddr;

    }

     

    kmap_high函数分析:首先获得对pkmap_page_table操作的锁,然后再调用page_address()来返回该page是否已经被映射,我们看到前面在kmap()里面已经判断过了,为什么这里还要再次判断呢?因为再获的锁的时候,有可能锁被其他CPU拿走了,而恰巧其他CPU拿了这个锁之后,也是执行这段code,而且映射的也是同一个page,那么当它把锁释放掉的时候,其实就表示该page的映射已经被建立了,我们这里就没有必要再去执行这段code了,所以就有必要在获得锁后再判断下。

    如果发现vaddr不为空,那么就是刚才说的,已经被其他cpu上执行的任务给建立了,这里只需要把表示该页引用计数的pkmap_count[]再加一就可以了。同时调用BUG_ON来确保该引用计数确实是不小于2的,否则就是有问题的了。然后返回vaddr,整个建立就完成了。

    如果发现vaddr为空呢?调用map_new_virtual()函数,到此我们看到,其实真正进行建立映射的代码在这个函数里面

     

     

    static inline unsigned long map_new_virtual(struct page *page)

    {       

            unsigned long vaddr;

            int count; 

                            

    start:                  

            count = LAST_PKMAP;//LAST_PKMAP=1024

            /* Find an empty entry */

            for (;;) {      

                    last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK;

                    if (!last_pkmap_nr) {

                            flush_all_zero_pkmaps();

                            count = LAST_PKMAP;

                    }

                    if (!pkmap_count[last_pkmap_nr])

                            break;  /* Found a usable entry */

                    if (--count)

                            continue;

            

                    /*

                     * Sleep for somebody else to unmap their entries

                     */     

                    {       

                            DECLARE_WAITQUEUE(wait, current);

                    

                            __set_current_state(TASK_UNINTERRUPTIBLE);

                            add_wait_queue(&pkmap_map_wait, &wait);

                            unlock_kmap();

                            schedule();

                            remove_wait_queue(&pkmap_map_wait, &wait);

                            lock_kmap();

     

                            /* Somebody else might have mapped it while we slept */

                            if (page_address(page))

                                    return (unsigned long)page_address(page);

     

                            /* Re-start */

                            goto start;

                    }

            }

            vaddr = PKMAP_ADDR(last_pkmap_nr);

            set_pte_at(&init_mm, vaddr,

                       &(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));

     

            pkmap_count[last_pkmap_nr] = 1;

            set_page_address(page, (void *)vaddr);

     

            return vaddr;

    }

     

    last_pkmap_nr:记录上次被分配的页表项在pkmap_page_table里的位置,初始值为0,所以第一次分配的时候last_pkmap_nr等于1。

     

    接下来判断什么时候last_pkmap_nr等于0,等于0就表示1023(LAST_PKMAP(1024)-1)个页表项已经被分配了,这时候就需要调用flush_all_zero_pkmaps()函数,把所有pkmap_count[]计数为1的页表项在TLB里面的entry给flush掉,并重置为0,这就表示该页表项又可以用了,可能会有疑惑为什么不在把pkmap_count置为1的时候也就是解除映射的同时把TLB也flush呢?个人感觉有可能是为了效率的问题吧,毕竟等到不够的时候再刷新,效率要好点吧。

     

    再判断pkmap_count[last_pkmap_nr]是否为0,0的话就表示这个页表项是可用的,那么就跳出循环了到下面了。

     

    PKMAP_ADDR(last_pkmap_nr)返回这个页表项对应的线性地址vaddr.

     

    #definePKMAP_ADDR(nr) (PKMAP_BASE + ((nr) << PAGE_SHIFT))

     

    set_pte_at(mm,addr, ptep, pte)函数在NON-PAE i386上的实现其实很简单,其实就等同于下面的代码:

     

    staticinline void native_set_pte(pte_t *ptep , pte_t pte)

    {

           *ptep = pte;

    }

    我们已经知道页表的线性起始地址存放在pkmap_page_table里面,那么相应的可用的页表项的地址就是&pkmap_page_table[last_pkmap_nr],得到了页表项的地址,只要把相应的pte填写进去,那么整个映射不就完成了吗?

    pte由两部分组成:高20位表示物理地址,低12位表示页的描述信息。

    怎么通过page查找对应的物理地址呢(参考page_address()一文)?其实很简单,用(page- mem_map) 再移PAGE_SHIFT位就可以了。

    低12位的页描述信息是固定的:kmap_prot=(_PAGE_PRESENT| _PAGE_RW | _PAGE_DIRTY | _PAGE_ACCESSED | _PAGE_GLOBAL).

    下面的代码就是做了这些事情:

     

    mk_pte(page,kmap_prot));

    #definemk_pte(page, pgprot) pfn_pte(page_to_pfn(page), (pgprot))

    #definepage_to_pfn __page_to_pfn

    #define__page_to_pfn(page) ((unsigned long)((page) - mem_map) + \

                                   ARCH_PFN_OFFSET)

    staticinline pte_t pfn_pte(unsigned long page_nr, pgprot_t pgprot)

    {

           return __pte(((phys_addr_t)page_nr <<PAGE_SHIFT) |

                       massage_pgprot(pgprot));

    }

     

    接下来把pkmap_count[last_pkmap_nr]置为1,1不是表示不可用吗,既然映射已经建立好了,应该赋值为2呀,其实这个操作是在他的上层函数kmap_high里面完成的(pkmap_count[PKMAP_NR(vaddr)]++).

    到此为止,整个映射就完成了,再把page和对应的线性地址加入到page_address_htable哈希链表里面就可以了(参考page_address一文)。

     

     

    我们继续看所有的页表项都已经用了的情况下,也就是1024个页表项全已经映射了内存了,如何处理。此时count==0,于是就进入了下面的代码:

     

    /*

                    * Sleepfor somebody else to unmap their entries

                    */

                   {

                          DECLARE_WAITQUEUE(wait, current);

                   

                          __set_current_state(TASK_UNINTERRUPTIBLE);

                          add_wait_queue(&pkmap_map_wait, &wait);

                          unlock_kmap();

                          schedule();

                          remove_wait_queue(&pkmap_map_wait, &wait);

                          lock_kmap();

     

                          /* Somebody else might have mapped it while we slept */

                          if (page_address(page))

                                  return (unsignedlong)page_address(page);

     

                          /* Re-start */

                          goto start;

                   }

    这段代码其实很简单,就是把当前任务加入到等待队列pkmap_map_wait,当有其他任务唤醒这个队列时,再继续gotostart,重新整个过程。这里就是上面说的调用kmap函数有可能阻塞的原因。

    那么什么时候会唤醒pkmap_map_wait队列呢?当调用kunmap_high函数,来释放掉一个映射的时候。

    kunmap_high函数其实页很简单,就是把要释放的页表项的计数减1,如果等于1的时候,表示有可用的页表项了,再唤醒pkmap_map_wait队列

     

    /**

    *kunmap_high - map a highmem page into memory

    * @page:&struct page to unmap

    *

    * IfARCH_NEEDS_KMAP_HIGH_GET is not defined then this may be called

    * onlyfrom user context.

    */

    voidkunmap_high(struct page *page)

    {

           unsigned long vaddr;

           unsigned long nr;

           unsigned long flags;

           int need_wakeup;

     

           lock_kmap_any(flags);

           vaddr = (unsigned long)page_address(page);

           BUG_ON(!vaddr);

           nr = PKMAP_NR(vaddr);

     

           /*

            * A count must never go down to zero

            * without a TLB flush!

            */

           need_wakeup = 0;

           switch (--pkmap_count[nr]) {//减一

           case 0:

                   BUG();

           case 1:

                   /*

                    * Avoidan unnecessary wake_up() function call.

                    * Thecommon case is pkmap_count[] == 1, but

                    * nowaiters.

                    * Thetasks queued in the wait-queue are guarded

                    * by boththe lock in the wait-queue-head and by

                    * thekmap_lock. As the kmap_lock is held here,

                    * no needfor the wait-queue-head's lock. Simply

                    * test ifthe queue is empty.

                    */

                   need_wakeup =waitqueue_active(&pkmap_map_wait);

           }

           unlock_kmap_any(flags);

     

           /* do wake-up, if needed, race-free outside ofthe spin lock */

           if (need_wakeup)

                  wake_up(&pkmap_map_wait);

    }

    展开全文
  • 内核也是程序,也应该具有自己的虚存空间,但是作为一种为用户程序服务的程序,内核空间有它自己的特点。   内核空间与用户空间的关系 在一个32位系统中,一个程序的虚拟空间最大可以是4GB,那么最直接的做法...
  • Linux HighMemory(高端内存

    千次阅读 2018-07-03 17:29:57
    在这种架构中,传统上Linux内核将4GB的虚拟内存地址空间分为3GB用户内存和1GB内核空间。在传统大小的32位系统上,这意味着内核可以将所有物理内存映射到其地址空间,这使得以下内容成为可能:直接从内核中访问任何...
  • 日期 内核版本 架构 作者 GitHub CSDN 2016-08-31 Linux-4.7 X86 & arm ... Linux内存管理 http://blog.csdn.net/vanbreaker/article/details/75799411 前景回顾前面我们讲到服务器体系(SMP, NUMA, M
  • Linux高端内存的由来

    万次阅读 2014-08-20 14:23:12
    抱着拿来主义,自己挑选了部分,以下... Linux内核高端内存的由来 当内核模块代码或线程访问内存时,代码中的内存地址都为逻辑地址,而对应到真正的物理内存地址,需要地址一对一的映射,如逻辑地址0xc0000003对应的物
  • Linux的用户空间与内核空间

    万次阅读 2018-09-03 10:51:23
    两者不能简单地使用指针传递数据,因为Linux使用的虚拟内存机制,用户空间的数据可能被换出,当内核空间使用用户空间指针时,对应的数据可能不在内存中。用户空间的内存映射采用段页式,而内核空间有自己的规则;...
  • Linux内核高端内存管理

    万次阅读 2008-08-22 12:58:00
    HIGHMEM 刺猬@http://blog.csdn.net/littlehedgehog 原先一直都对Linux高端内存的管理认识模模糊糊的,可能主要是初次接触Linux kernel 是0.11版吧,当初的内存...16M内存哪里还顾得上高端内存,脑子就一直没有这种
  • bounce buffer

    千次阅读 2012-07-07 01:05:18
    在 IA-32 系统中,物理内存最开始的1GB 被称为“低端内存”,1GB 以上的部分称为“高端内存”。先前的Linux 核心版本要求通往存储设备的数据缓存必须放在物理RAM 的低端内存区域,即使是应用程序可以同时使用高端...
  • 高端内存的理解

    千次阅读 2011-02-26 16:09:00
    内核地址空中highmem映射
  • linux 用户空间与内核空间——高端内存详解

    万次阅读 多人点赞 2013-12-04 18:05:19
    摘要:Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递数据,因为Linux使用的虚拟内存机制,用户空间的数据可能被换出,当内核空间使用用户空间指针时,对应的数据...
  • 高端内存和低端内存

    千次阅读 2015-07-01 20:45:39
     Linux物理内存空间分为DMA内存区(DMA Zone)、低端内存区(Normal Zone)与高端内存区(Highmem Zone)三部分。DMA Zone通常很小,只有几十M,低端内存区与高端内存区的划分来源于Linux内核空间大小的限制。  二...
  • 内存映射

    万次阅读 2013-02-26 11:47:55
    linux中的物理地址和虚拟地址  在支持MMU的32位处理器平台上,...Linux运行在虚拟存储空间,并负责把系统中实际存在的远小于4GB的物理内存根据不同需求映射到整个4GB的虚拟存储空间中。 n物理存储空间布局 Li
  • Linux系统虚拟内存空间一般布局示意图 说明:   1)线性地址空间:是指Linux系统中从0x00000000到0xFFFFFFFF整个4GB虚拟存储空间。   2)内核空间:内核空间表示运行在处理器最高级别的超级用户模式...
  • Linux 虚拟内存机制

    万次阅读 2016-08-09 21:31:01
    华为面试官问了我一个问题就是关于Linux虚拟内存机制,虽然我多少是回答上来,感觉好久没看操作系统的我是时候将其拿起来重温一遍 。 每个进程都有自己独立的4G内存空间,各个进程的内存空间具有类似的结构。 ...
  • Linux用户空间与内核空间

    万次阅读 热门讨论 2012-08-30 15:39:57
    Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递数据,因为Linux使用的虚拟内存机制,用户空间的数据可能被换出,当内核空间使用用户空间指针时,对应的数据可能不在...
  • Linux 虚拟内存和物理内存的理解

    万次阅读 多人点赞 2013-06-12 09:48:19
    首先,让我们看下虚拟内存:第一层理解1. 每个进程都有自己独立的4G内存空间,各个进程的内存空间具有类似的结构2. 一个新进程建立的时候,将会建立起自己的内存空间,此进程的数据,代码等从磁盘拷贝到自己的进程...
  • Linux 内存管理之highmem简介

    千次阅读 2012-09-10 23:25:56
    一般来说Linux 内核按照 3:1 的比率来划分虚拟内存(X86等):3 GB 的虚拟内存用于用户空间,1GB 的内存用于内核空间。当然有些体系结构如MIPS使用2:2 的比率来划分虚拟内存:2 GB 的虚拟内存用于用户空间,2 GB 的...
1 2 3 4 5 ... 20
收藏数 46,117
精华内容 18,446
关键字:

高端内存