Linux内核设计基础(五)之内存管理

我感觉学习操作系统首先要从内存分配和管理入手。首先我们应该知道现代操作系统是以页为单位进行内存管理的,32位体系结构支持4KB的页,而64位体系结构支持8KB的页。页是用来分配的,如何才能进行高效和充分的利用,这是内存管理单元(MMU)应当仔细考虑的。

页分配

内核用结构体struct page表示每个物理页。内核用这一结构来管理系统中所有的页,因为内核需要知道一个页是否空闲(也就是页有没有被分配),如果页已经被分配,内核需要知道谁拥有这个页,拥有者可能是用户空间进程、动态分配的内核数据、静态内核代码或页高速缓存。我们用页表来统一管理所有的struct page。另外内核用区对具有相似特性的页进行分组。主要是存在下面两种制约:

  • 一些硬件只能用某些特定的内存地址执行DMA(直接内存访问)
  • 一些体系结构的内存的物理寻址范围比虚拟寻址范围大得多。这样,就有一些内存不能永久地映射到内核空间(高端内存)

于是Linux划分了一下四个区:

  • ZONE_DMA——这个区包含的页能用来执行DMA操作。
  • ZONE_DMA32——同上,不过只能被32位设备访问。
  • ZONE_NORMAL——这个区包含的都是能正常映射的页。
  • ZONE_HIGHEM——高端内存,其中的页并不能永久映射到内核地址空间。

slab层

用于频繁使用的数据结构的缓存,且避免因频繁分配和使用导致的内存碎片。slab层是由高速缓存组成的,而每个高速缓存可以由多个slab组成,slab由一个或多个物理上连续的页组成。每个slab都包含一些缓存的数据结构。这样说还是很抽象,举个inode的例子。

inode是磁盘文件在内存中的体现,会频繁地进行创建和释放,所以有必要进行缓存管理。在这里高速缓存是inode_cachep,它由多个slab组成,而每个slab包含尽可能多的struct inode对象。所以当我们需要一个新的inode结构时,不必现创建,只需从部分满或空的slab返回一个指向已分配但未使用的inode结构的指针即可。当内核使用完这个inode对象时,slab分配器就把该对象标记为空闲,留给后来者。

再举个进程控制块的例子。

我们知道进程在不停地创建和消除,而用struct task_struct去管理一个进程,不停的创建和释放task_struct会很费时。所以内核初始化期间,在fork_init()中着手创建高速缓存:

struct kmem_cache *task_struct_cachep;(内核用这个全局变量存放指向task_struct高速缓存的指针)
task_struct_cachep = kmem_cache_create("task_struct",
                                        sizeof(struct task_struct),
                                        ARCH_MIN_TASKALIGN,
                                        SLAB_PANIC | SLAB_NOTRACK,
                                        NULL);

这样当我们创建进程(执行fork)时,只需从这个高速缓存中索取即可:

struct task_struct *tsk;
tsk = kmem_cache_alloc(task_struct_cachep, GFP_KERNEL);
if(!tsk) return NULL;

内核栈

我们在进程时要注意节省栈资源,要控制函数内的局部变量,尽量不要出现大型数组或大型结构体。尤其对于内核栈,一旦造成溢出,就会影响到内核数据(如thread_info)。所以应当优先考虑动态分配。另外一个进程的内核栈和中断栈是分开的,这样可以减轻内核栈的负担(一个内核栈只占1页或2页)。

Linux内核设计基础(五)之内存管理

时间: 2024-08-28 14:07:08

Linux内核设计基础(五)之内存管理的相关文章

Linux内核源代码情景分析-内存管理之slab-回收

在上一篇文章Linux内核源代码情景分析-内存管理之slab-分配与释放,最后形成了如下图的结构: 图 1 我们看到空闲slab块占用的若干页面,不会自己释放:我们是通过kmem_cache_reap和kmem_cache_shrink来回收的.他们的区别是: 1.我们先看kmem_cache_shrink,代码如下: int kmem_cache_shrink(kmem_cache_t *cachep) { if (!cachep || in_interrupt() || !is_chaine

Linux内核源代码情景分析-内存管理

用户空间的页面有下面几种: 1.普通的用户空间页面,包括进程的代码段.数据段.堆栈段.以及动态分配的"存储堆". 2.通过系统调用mmap()映射到用户空间的已打开文件的内容. 3.进程间的共享内存区. 这些页面的的周转有两方面的意思. 1.页面的分配,使用,回收.如进程压栈时新申请的页面,这类页面不进行盘区交换,不使用时释放得以回收. 这部分通过一个场景来解释: Linux内核源代码情景分析-内存管理之用户堆栈的扩展. 2.盘区交换.如要执行硬盘上的对应代码段.把硬盘上的代码段换入内

Linux内核源代码情景分析-内存管理之用户页面的定期换出

我们已经看到在分配页面时,如果页面数不够,那么会调用page_launder,reclaim_page,__free_page将页面换出,并重新投入分配. 为了避免总是在CPU忙碌的时候,也就是在缺页异常发生的时候,临时再来搜寻可供换出的内存页面并加以换出,Linux内核定期地检查并且预先将若干页面换出,腾出空间,以减轻系统在缺页异常发生时的负担. 为此,在Linux内核中设置了一个专司定期将页面换出的"守护神"kswapd和kreclaimd. static int __init k

Linux内核源代码情景分析-内存管理之用户页面的分配

首先介绍几个重要的数据结构. 1.page typedef struct page { struct list_head list; struct address_space *mapping; unsigned long index; struct page *next_hash; atomic_t count; unsigned long flags; /* atomic flags, some possibly updated asynchronously */ struct list_h

Linux内核源代码情景分析-内存管理之用户页面的换入

在下面几种情况下会发生,页面出错异常(也叫缺页中断): 1.相应的页面目录项或者页面表项为空,也就是该线性地址与物理地址的映射关系尚未建立,或者已经撤销. 2.相应的物理页面不在内存中. 本文讨论的就是这种情况. 3.指令中规定的访问方式与页面的权限不符,例如企图写一个"只读"的页面. 假设已经建立好了映射,但是页表项最后一位P为0,表示页面不在内存中:整个页表项如下图,offset表示页面在一个磁盘设备的位置,也就是磁盘设备的逻辑页面号:而type则是指该页面在哪一个磁盘设备中. 图

Linux内核源代码情景分析-内存管理之用户堆栈的扩展

在下面几种情况下会发生,页面出错异常(也叫缺页中断): 1.相应的页面目录项或者页面表项为空,也就是该线性地址与物理地址的映射关系尚未建立,或者已经撤销.本文讨论的就是这种情况. 2.相应的物理页面不在内存中. 3.指令中规定的访问方式与页面的权限不符,例如企图写一个"只读"的页面. 首先看下进程地址空间示意图: 假设现在需要调用某个子程序,因此CPU需将返回地址压入堆栈,也就是要将返回地址写入虚拟空间地址为(%esp-4)的地方.可是,在我们这个情景中地址(%esp-4)落入了空洞中

Linux内核源代码情景分析-内存管理之slab-分配与释放

首先说缓存区的数据结构: struct kmem_cache_s { /* 1) each alloc & free */ /* full, partial first, then free */ struct list_head slabs;//指向所有的slab块链表,前面是完全块,然后是非完全块,最后是空闲块 struct list_head *firstnotfull;//指向第一个非完全块,如果没有非完全块,就指向上面的slabs unsigned int objsize;//对象的大

Linux内核源代码情景分析-内存管理之恢复映射

refill_inactive_scan和swap_out,把活跃的页面变成不活跃脏的页面.挑选的原则是最近没有被访问,且age小于0. page_launder,把不活跃脏的页面变成不活跃干净的页面. 不活跃脏的页面,有如下特点: 使用计数为1: page->list链入mapping->dirty_pages/clean_pages: page->next_hash和page->pprev_hash链入全局的Hash表: page->lru链入了全局的inactive_d

Linux内核设计基础(九)之进程管理和调度

在Linux中进程用结构体task_struct来管理一个进程所需的所有信息(所以一般较大,在32位机上,大约有1.7KB).为了提高效率,Linux使用了一些卓越的技术. 通过slab分配task_struct结构 Linux创建进程迅速,正是因为slab分配器预先分配和重复使用task_struct,这样就避免了动态分配和释放所带来的资源消耗(毕竟一个task_struct较大,而且内核中进程的创建和消除很频繁). 将task_struct放置在内核栈的尾端 这样做是为了让那些像x86那样寄