• Linux内核空间布局


    x86物理地址空间

    Linux 内核空间分为三个区域ZONE: ZONE_DMA, ZONE_NORMAL, ZONE_HIGHMEM

     物理地址空间的顶部以下一段空间,被PCI设备的I/O内存映射占据,它们的大小和布局由PCI规范所决定。640K~1M这段地址空间被BIOS和VGA适配器所占据
    由于这两段地址空间的存在,导致相应的RAM空间不能被CPU所寻址(当CPU访问该段地址时,北桥会自动将目的物理地址“路由”到相应的I/O设备上,不会发送给RAM),从而形成RAM空洞。

    • ZONE_DMA的范围是0~16M,该区域的物理页面专门供I/O设备的DMA使用。之所以需要单独管理DMA的物理页面,是因为DMA使用物理地址访问内存,不经过MMU,并且需要连续的缓冲区,所以为了能够提供物理上连续的缓冲区,必须从物理地址空间专门划分一段区域用于DMA。
    • ZONE_NORMAL的范围是16M~896M,该区域的物理页面是内核能够直接使用的。
    • ZONE_HIGHMEM的范围是896M~结束,该区域即为高端内存,内核不能直接使用。

    ps: kmalloc为DMA分配内存 vmalloc 分配较大内核内存 malloc分配用户空间内存

    DMA区

    DMA ZONE产生的本质原因是:不一定所有的DMA都可以访问到所有的内存,这本质上是硬件的设计限制。k
    主板上的ISA总线中的DMA控制器只能寻址24位物理地址,因此最多能够寻址到16MB的地方.
    若ZONE_NORMAL区中的内存量不足的话,ZONE_DMA就会被当作ZONE_NORMAL.
    一些较新的体系结构不支持古老的外围设备,而忽略了区域 ZONE_DMA。
    IA64体系结构决定了ZONE_DMA的另一种实现方式,将其定义为覆盖4GB以下的所有内存。

    直接映射区

    针对早期计算机实际配置的物理内存很小, 一般地只有几MB, 所以为了提高内核通过虚拟地址访问物理地址内存的速度, 内核空间的虚拟地址与物理内存地址采用了
    一种从低地址到高地址一一对应的固定映射方式.

    也称为线性映射区.

    但是由于硬件工艺的提升, 对大于1G的实际物理内存无法简单的对其进行直接映射,Linux规定内核映射区的上边界的值最大不能大于常数high_memory(896).
    采取部分映射,剩余部分交给高端映射区.

    物理内存管理

    Linux内核是以物理页面(也称为page frame)为单位管理物理内存的,为了方便的记录每个物理页面的信息,Linux定义了page结构体:(位于include/linux/mm_types.h)

    struct page {
    	unsigned long flags;		/* Atomic flags, some possibly
    					 * updated asynchronously */
    	/*
    	 * Five words (20/40 bytes) are available in this union.
    	 * WARNING: bit 0 of the first word is used for PageTail(). That
    	 * means the other users of this union MUST NOT use the bit to
    	 * avoid collision and false-positive PageTail().
    	 */
    	union {
    		struct {	/* Page cache and anonymous pages */
    			/**
    			 * @lru: Pageout list, eg. active_list protected by
    			 * zone_lru_lock.  Sometimes used as a generic list
    			 * by the page owner.
    			 */
    			struct list_head lru;
    			/* See page-flags.h for PAGE_MAPPING_FLAGS */
    			struct address_space *mapping;
    			pgoff_t index;		/* Our offset within mapping. */
    			/**
    			 * @private: Mapping-private opaque data.
    			 * Usually used for buffer_heads if PagePrivate.
    			 * Used for swp_entry_t if PageSwapCache.
    			 * Indicates order in the buddy system if PageBuddy.
    			 */
    			unsigned long private;
    		};
    		struct {	/* slab, slob and slub */
    			union {
    				struct list_head slab_list;	/* uses lru */
    				struct {	/* Partial pages */
    					struct page *next;
    #ifdef CONFIG_64BIT
    					int pages;	/* Nr of pages left */
    					int pobjects;	/* Approximate count */
    #else
    					short int pages;
    					short int pobjects;
    #endif
    				};
    			};
    			struct kmem_cache *slab_cache; /* not slob */
    			/* Double-word boundary */
    			void *freelist;		/* first free object */
    			union {
    				void *s_mem;	/* slab: first object */
    				unsigned long counters;		/* SLUB */
    				struct {			/* SLUB */
    					unsigned inuse:16;
    					unsigned objects:15;
    					unsigned frozen:1;
    				};
    			};
    		};
    		struct {	/* Tail pages of compound page */
    			unsigned long compound_head;	/* Bit zero is set */
    
    			/* First tail page only */
    			unsigned char compound_dtor;
    			unsigned char compound_order;
    			atomic_t compound_mapcount;
    		};
    		struct {	/* Second tail page of compound page */
    			unsigned long _compound_pad_1;	/* compound_head */
    			unsigned long _compound_pad_2;
    			struct list_head deferred_list;
    		};
    		struct {	/* Page table pages */
    			unsigned long _pt_pad_1;	/* compound_head */
    			pgtable_t pmd_huge_pte; /* protected by page->ptl */
    			unsigned long _pt_pad_2;	/* mapping */
    			union {
    				struct mm_struct *pt_mm; /* x86 pgds only */
    				atomic_t pt_frag_refcount; /* powerpc */
    			};
    #if ALLOC_SPLIT_PTLOCKS
    			spinlock_t *ptl;
    #else
    			spinlock_t ptl;
    #endif
    		};
    		struct {	/* ZONE_DEVICE pages */
    			/** @pgmap: Points to the hosting device page map. */
    			struct dev_pagemap *pgmap;
    			unsigned long hmm_data;
    			unsigned long _zd_pad_1;	/* uses mapping */
    		};
    
    		/** @rcu_head: You can use this to free a page by RCU. */
    		struct rcu_head rcu_head;
    	};
    
    	union {		/* This union is 4 bytes in size. */
    		/*
    		 * If the page can be mapped to userspace, encodes the number
    		 * of times this page is referenced by a page table.
    		 */
    		atomic_t _mapcount;
    
    		/*
    		 * If the page is neither PageSlab nor mappable to userspace,
    		 * the value stored here may help determine what this page
    		 * is used for.  See page-flags.h for a list of page types
    		 * which are currently stored here.
    		 */
    		unsigned int page_type;
    
    		unsigned int active;		/* SLAB */
    		int units;			/* SLOB */
    	};
    
    	/* Usage count. *DO NOT USE DIRECTLY*. See page_ref.h */
    	atomic_t _refcount;
    
    #ifdef CONFIG_MEMCG
    	struct mem_cgroup *mem_cgroup;
    #endif
    
    	/*
    	 * On machines where all RAM is mapped into kernel address space,
    	 * we can simply calculate the virtual address. On machines with
    	 * highmem some memory is mapped into kernel virtual memory
    	 * dynamically, so we need a place to store that address.
    	 * Note that this field could be 16 bits on x86 ... ;)
    	 *
    	 * Architectures with slow multiplication can define
    	 * WANT_PAGE_VIRTUAL in asm/page.h
    	 */
    #if defined(WANT_PAGE_VIRTUAL)
    	void *virtual;			/* Kernel virtual address (NULL if
    					   not kmapped, ie. highmem) */
    #endif /* WANT_PAGE_VIRTUAL */
    
    #ifdef LAST_CPUPID_NOT_IN_PAGE_FLAGS
    	int _last_cpupid;
    #endif
    } _struct_page_alignment;
    
    

    Linux系统在初始化时,会根据实际的物理内存的大小,为每个物理页面创建一个page对象,所有的page对象构成一个mem_map数组。

    内存管理区

    内核源码中,内存管理区的结构体定义为:

    struct zone {
    ...
           struct free_area  free_area[MAX_ORDER];
    ...
           spinlock_t            lru_lock;      
           struct zone_lru {
                  struct list_head list;
           } lru[NR_LRU_LISTS];
           struct zone_reclaim_stat reclaim_stat;
           unsigned long             pages_scanned;     /* since last reclaim */
           unsigned long             flags;               /* zone flags, see below */
           atomic_long_t            vm_stat[NR_VM_ZONE_STAT_ITEMS];
           unsigned int inactive_ratio;
    ...
           wait_queue_head_t   * wait_table;
           unsigned long             wait_table_hash_nr_entries;
           unsigned long             wait_table_bits;
    ...
           struct pglist_data       *zone_pgdat;
           unsigned long             zone_start_pfn;
    ...
    };
    
    • 其中zone_start_pfn表示该内存管理区在mem_map数组中的索引。
    • 内核在分配物理页面时,通常是一次性分配物理上连续的多个页面,为了便于快速的管理,内核将连续的空闲页面组成空闲区段,大小是2、4、8、16…等,
      然后将空闲区段按大小放在不同队列里,这样就构成了MAX_ORDER个队列,
      也就是zone里的free_area数组。这样在分配物理页面时,可以快速的定位刚好满足需求的空闲区段。这一机制称为buddy system。
    • 当释放不用的物理页面时,内核并不会立即将其放入空闲队列(free_area),
      而是将其插入非活动队列lru,便于再次时能够快速的得到。每个内存管理区都有1个inacitive_clean_list。
      另外,内核中还有3个全局的LRU队列,分别为active_list,inactive_dirty_list和swapper_space。其中:
      • active_list用于记录所有被映射了的物理页面
      • inactive_dirty_list用于记录所有断开了映射且未被同步到磁盘交换文件中的物理页面
      • swapper_space则用于记录换入/换出到磁盘交换文件中的物理页面。

    物理页分配

    分配物理内存的函数主要有

    struct page * __alloc_pages(zonelist_t *zonelist, unsigned long order);
    参数zonelist即从哪个内存管理区中分配物理页面,参数order即分配的内存大小。

    __get_free_pages(unsigned int flags,unsigned int order);
    参数flags可选GFP_KERNEL__GFP_DMA等,参数order同上。
    该函数能够分配物理上连续的内存区域,得到的虚拟地址与物理地址是一一对应的。

    void * kmalloc(size_t size,int flags);
    该函数能够分配物理上连续的内存区域,得到的虚拟地址与物理地址是一一对应的。

    物理页的回收

    当空闲物理页面不足时,就需要从inactive_clean_list队列中选择某些物理页面插入空闲队列中,如果仍然不足,就需要把某些物理页面里的内容写回到磁盘交换文件里,腾出物理页面,为此内核源码中为磁盘交换文件定义了:

    页表管理

    为了保持兼容性,Linux最多支持4级页表,而在x86上,实际只用了其中的2级页表,即PGD(页全局目录表)和PT(页表),中间的PUD和PMD所占的位长都是0,因此对于x86的MMU是不可见的。

    在内核源码中,分别为PGD,PUD,PMD,PT定义了相应的页表项,即

    (定义在include/asm-generic/page.h中)

    typedef struct {unsigned long pgd;} pgd_t;
    typedef struct {unsigned long pud;} pud_t;
    typedef struct {unsigned long pmd;} pmd_t;
    typedef struct {unsigned long pte;} pte_t;
    

    为了方便的操作页表项,还定义了以下宏:
    (定义在arch/x86/include/asm/pgtable.h中)

    mk_pte
    pgd_page/pud_page/pmd_page/pte_page
    pgd_alloc/pud_alloc/pmd_alloc/pte_alloc
    pgd_free/pud_free/pmd_free/pte_free
    set_pgd/ set_pud/ set_pmd/ set_pte
    

    x86-64

    在64位Linux操作系统上,分区如下:
    最开始的16M内存是DMA ZONE 内存,用slab分配器的kmalloc分配获取。
    DMA32 ZONE为16M~4G,高于4G的内存为Normal ZONE。

    REFERENCES

  • 相关阅读:
    局部变量和全局变量
    Javascript高级编程学习笔记(26)—— 函数表达式(4)私有变量
    Javascript高级编程学习笔记(25)—— 函数表达式(3)模仿块级作用域
    Javascript高级编程学习笔记(24)—— 函数表达式(2)闭包
    Javascript高级编程学习笔记(23)—— 函数表达式(1)递归
    Javascript高级编程学习笔记(22)—— 对象继承
    Javascript高级编程学习笔记(21)—— 对象原型
    Javascript高级编程学习笔记(20)—— 创建对象
    Javascript高级编程学习笔记(19)—— 对象属性
    Javascript高级编程学习笔记(18)—— 引用类型(7)单体内置对象
  • 原文地址:https://www.cnblogs.com/sonnet/p/15187549.html
Copyright © 2020-2023  润新知