目录
写在文章的前面---本人C/C++面试岗位,学艺不精,研究生做的与代码无关的工作,仅记录每次面试遇到的问题勉励自己!
参考:https://www.cnblogs.com/vinozly/p/5618451.html
写在文章的前面---本人C/C++面试岗位,学艺不精,研究生做的与代码无关的工作,仅记录每次面试遇到的问题勉励自己!
- LINUX下内核如何管理内存
一、x86下的物理地址空间布局
物理地址空间的顶部以下一段空间,被PCI设备的I/O内存映射占据,它们的大小和布局由PCI规范所决定。640K~1M这段地址空间被BIOS和VGA适配器所占据。Linux系统在初始化时,会根据实际的物理内存的大小,为每个物理页面创建一个page对象,所有的page对象构成一个mem_map数组。进一步,针对不同的用途,Linux内核将所有的物理页面划分到3类内存管理区中,如图,分别为ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM。
- ZONE_DMA(直接访问内存的区域)的范围是0~16M,该区域的物理页面专门供I/O设备的DMA使用。之所以需要单独管理DMA的物理页面,是因为DMA使用物理地址访问内存,不经过MMU,并且需要连续的缓冲区,所以为了能够提供物理上连续的缓冲区,必须从物理地址空间专门划分一段区域用于DMA。
- ZONE_NORMAL的范围是16M~896M,该区域的物理页面是内核能够直接使用的。
- ZONE_HIGHMEM的范围是896M~结束,该区域即为高端内存,内核不能直接使用。
二、linux虚拟地址内核空间分布
在kernel image下面有16M的内核空间用于DMA操作。位于内核空间高端的128M地址主要由3部分组成,分别为vmalloc area,持久化内核映射区,临时内核映射区。
由于ZONE_NORMAL和内核线性空间存在直接映射关系,所以内核会将频繁使用的数据如kernel代码、GDT、IDT、PGD、mem_map数组等放在ZONE_NORMAL里。而将用户数据、页表(PT)等不常用数据放在ZONE_ HIGHMEM里,只在要访问这些数据时才建立映射关系(kmap())。比如,当内核要访问I/O设备存储空间时,就使用ioremap()将位于物理地址高端的mmio区内存映射到内核空间的vmalloc area中,在使用完之后便断开映射关系。
三、linux虚拟地址用户空间分布
用户进程的代码区一般从虚拟地址空间的0x08048000开始,这是为了便于检查空指针。代码区之上便是数据区,未初始化数据区,堆区,栈区,以及参数、全局环境变量。
1.page(页)
内核把物理页作为内存管理的基本单位,尽管处理器的最小可寻址单位通常为字,但是,内存管理单元(MMU,管理内存并把虚拟地址转换为物理地址的硬件)通常以页为单位进行处理,故从虚拟内存的角度来看,页就是最小单位。
struct page{
unsigned long flags;//页的状态:脏,锁定等等32中状态
atomic_t _counts;//页的被引用计数,-1说明没有使用,可以在新的分配中使用
atomic_t _mapcount;
unsigned long private;
struct address_space *mapping;
pgoff_t index;
struct list_head lru;
void *virtual;//页的虚拟地址,除开高端内存(高端内存为null),基本上就是页在虚拟内存中的地址。
}
需要注意的是:该结构对页的描述是短暂的,内核仅仅用这个数据结构来描述当前时刻在相关的物理页中存放的东西。因为内核需要知道一个页是否空闲,如果被分配了是谁拥有的。拥有者可能是用户空间进程、动态分配的内核数据、静态内核代码、页高速缓存等。
2.zone(区)
由于硬件的限制,内核不能对所有的页一视同仁。有些页位于内存页特定的物理地址所以不能将其用于一些特定的任务。所以内核把页分为不同的区(zone)。因为LINUX必须处理如下的两个由于硬件缺陷而引起的寻址问题:
1. 一些硬件只能够用某些特定的内存地址来执行DMA(直接内存访问)
2.一些体系结构的内存的物理寻址范围比虚拟寻址范围大的多。这样,就有一些内存不能永久的映射到内核空间上。
ZONE_DMA:这个区包含的页能够用来执行DMA。
ZONE_NORMAL:这个区包含的都是能够正常映射的页。
ZONE_HIGHMEM: 这个区包含“高端内存”,其中的页不能永久的映射到内核地址空间(若有体系内存能够被全部映射,那么ZONE_HIGHMEM为空)
struct zone{
/*持有该区的最小值,最低和最高水位值。内核使用水位为每个内存区设置合适的内核消耗基准*/
unsigned long watermark[NR_WMARK];
/*lock是自旋锁,防止该结构被并发访问,只保护结构不保护结构中的页*/
spinlock_t lock;
/*表示这个区的名字:"DMA"、"Normal"、"HighMem"*/
const char* name;
/*
省略诸多变量及函数
*/
}
3.获得页
/*获得2的order的次方个连续的物理页,并返回一个指向第一个页的page的结构体*/
struct page *alloc_pages(gfp_t gfp_mask, unsigned int order)
void *page_adress(struct page *pages)//该函数可以把指定的page的结构体转化为它的逻辑地址
/*封装了alloc_pages函数,返回第一个物理页的逻辑地址*/
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)//如歌省略order函数则会分配一个页
/*释放页,必须只能释放自己的页,因为内核不存在纠错机制,错误的页地址可能会引起系统崩溃*/
void __free_pages(struct page *pages,unsigned int order)
/*kmalloc一次最多能申请的内存大小由include/linux/Kmalloc_size.h的内容来决定*/
void *kmalloc(size_t size, gfp_t flags)
/*对内存区的请求不是很频繁,较高的内存访问时间也可以接受,这就可以分配一段线性连续,物理不连续的地址
带来的好处是一次可以分配较大块的内存。测试可以分配1G的空间*/
void *vmalloc(unsigned long size)
kmalloc()函数和vmalloc()函数的区别:
- kmalloc()函数分配的内存是物理上连续的。
- 而Vmalloc()函数分配的内存仅仅是虚拟地址连续的。
正常内核编程通常使用kmalloc(),这主要是处于性能的考虑。
- vmalloc()将物理不连续的页转换为虚拟地址空间上连续的页,必须专门建立页表项,vmalloc()仅仅在当需要使用大块的内存的时候才会使用,典型的如模块被动态插入内核的时候。
- 很多硬件设备需要的是物理地址连续的页,因为很多硬件设备存在于内存管理单元(MMU)之外。
- vmalloc()函数可能睡眠,不能在中断上下文使用,而kmalloc加GFP_ATOMIC可以保证用在不能睡眠的地方。
4. slab机制
在最高层是 cache_chain,这是一个 slab 缓存的链接列表。这对于 best-fit算法非常有用,可以用来查找最适合所需要的分配大小的缓存(遍历列表)。
cache_chain 的每个元素都是一个 kmem_cache 结构的引用(称为一个 cache)。它定义了一个要管理的给定大小的对象池。
每个缓存都包含了一个 slabs 列表,这是一段连续的内存块(通常都是页面)。存在3 种 slab:
1.slabs_full:完全分配的slab。
2.slabs_partial:部分分配的slab。
3.slabs_empty:空slab,或者没有对象被分配。
slab 列表中的每个 slab都是一个连续的内存块(一个或多个连续页),它们被划分成一个个对象。这些对象是从特定缓存中进行分配和释放的基本元素。注意 slab 是 slab分配器进行操作的最小分配单位,因此如果需要对 slab 进行扩展,这也就是所扩展的最小值。通常来说,每个 slab 被分配为多个对象。由于对象是从 slab 中进行分配和释放的,因此单个 slab 可以在 slab列表之间进行移动。例如,当一个 slab中的所有对象都被使用完时,就从slabs_partial 列表中移动到 slabs_full 列表中。当一个 slab完全被分配并且有对象被释放后,就从 slabs_full 列表中移动到slabs_partial 列表中。当所有对象都被释放之后,就从 slabs_partial 列表移动到 slabs_empty 列表中。
struct kmem_cache {
struct array_cache *array[NR_CPUS];//per_cpu数据,记录了本地高速缓存的信息,也是用于跟踪最近释放的对象,每次分配和释放都要直接访问它。
unsigned int batchcount;//本地高速缓存转入和转出的大批数据数量
unsigned int limit;//本地高速缓存中空闲对象的最大数目
unsigned int shared;
unsigned int buffer_size;/*buffer的大小,就是对象的大小*/
u32 reciprocal_buffer_size;
unsigned int flags; /* constant flags */
unsigned int num; /* # of objs per slab *//*slab中有多少个对象*/
/* order of pgs per slab (2^n) */
unsigned int gfporder;/*指定了slab包含的页数目以2为底得对数*/
gfp_t gfpflags; /*与伙伴系统交互时所提供的分配标识*/
size_t colour; /* cache colouring range *//*slab中的着色*/
unsigned int colour_off; /* colour offset */着色的偏移量
struct kmem_cache *slabp_cache;
unsigned int slab_size; //slab管理区的大小
unsigned int dflags; /* dynamic flags */
/* constructor func */
void (*ctor)(void *obj); /*构造函数*/
const char *name; /*缓存slab上的名字*/
struct list_head next; //用于将kmem_cache的所有实例保存在全局链表cache_chain上
#ifdef CONFIG_DEBUG_SLAB
/*
一些用于调试用的变量
*/
#endif /* CONFIG_DEBUG_SLAB */
//用于组织该高速缓存中的slab
struct kmem_list3 *nodelists[MAX_NUMNODES];/*最大的内存节点*/
};
/* Size description struct for general caches. */
struct cache_sizes {
size_t cs_size;
struct kmem_cache *cs_cachep;
#ifdef CONFIG_ZONE_DMA
struct kmem_cache *cs_dmacachep;
#endif
};
struct kmem_list3 {
/*三个链表中存的是一个高速缓存slab*/
/*在这三个链表中存放的是cache*/
struct list_head slabs_partial; //包含空闲对象和已经分配对象的slab描述符
struct list_head slabs_full;//只包含非空闲的slab描述符
struct list_head slabs_free;//只包含空闲的slab描述符
unsigned long free_objects; /*高速缓存中空闲对象的个数*/
unsigned int free_limit; //空闲对象的上限
unsigned int colour_next; /* Per-node cache coloring *//*即将要着色的下一个*/
spinlock_t list_lock;
struct array_cache *shared; /* shared per node */
struct array_cache **alien; /* on other nodes */
unsigned long next_reap; /* updated without locking *//**/
int free_touched; /* updated without locking */
};
struct slab {
struct list_head list; //用于将slab连入keme_list3的链表
unsigned long colouroff; //该slab的着色偏移
void *s_mem; /* 指向slab中的第一个对象*/
unsigned int inuse; /* num of objs active in slab */已经分配出去的对象
kmem_bufctl_t free; //下一个空闲对象的下标
unsigned short nodeid; //节点标识符
};
建立新的kmem_cache:
/*name是一个字符串,存放kmem_cache缓存的名字;size是缓存所存放的对象的大小;align是slab内第一个对象的偏移;flag是可选的配置项,用来控制缓存的行为。最后一个参数ctor是对象的构造函数,一般是不需要的,以NULL来代替kmem_cache_create()成功执行之后会返回一个指向所创建的缓存的指针,否则返NULL。kmem_cache_create()可能会引起阻塞(睡眠),因此不能在中断上下文中使用。
*/
struct kmem_cache * kmem_cache_create( const char *name, size_t size, size_t align,
unsigned long flags, void (*ctor)(void*));
/*该函数成功则返回0,失败返回非零值。调用kmem_cache_destroy()之前应该满足下面几个条件:首先,cachep所指向的缓存中所有slab都为空闲,否则的话是不可以撤销的;其次在调用kmem_cache_destroy()过程中以及调用之后,调用者需要确保不会再访问这个缓存;最后,该函数也可能会引起阻塞,因此不能在中断上下文中使用。
*/
int kmem_cache_destroy( struct kmem_cache *cachep);
/*
可以通过下面函数来从kmem_cache中分配一个对象:
这个函数从cachep指定的缓存中返回一个指向对象的指针。如果缓存中所有slab都是满的,那么slab分配器会通过调用kmem_getpages()创建一个新的slab。
*/
void* kmem_cache_alloc(struct kmem_cache* cachep, gfp_t flags);
/*
这个函数是将被释放的对象返还给先前的slab,其实就是将cachep中的对象objp标记为空闲而已
*/
void kmem_cache_free(struct kmem_cache* cachep, void* objp);
- LINUX下进程和线程的同步方式
- 继承的public,protected,private以及new出来的子类与没有new的子类里面的包含的基类函数的区别