首页 > 编程知识 正文

印象笔记linux版本(linux段页式内存管理)

时间:2023-05-05 17:16:26 阅读:73976 作者:2847

在linux操作系统中,如果内存足够,内核会尽量使用内存作为“文件缓存”(page cache )以提高系统性能。 例如,page cache缓存硬盘内容,dcache和icache缓存文件系统数据。 这些内容是为了提高性能而设计的,也可以再次从硬盘中读取来构建对象,该部分可以在存储器紧张时直接释放。

因此,内存回收在Linux内存管理中占有非常重要的地位。 系统内存只是有限的,运行的过程成百上千,系统内存越来越小,必须提供内存回收机制以满足其他任务的需要。 回收内存时,出现以下问题

要知道哪些内存可以在什么时候回收,就需要知道回收可以解决哪些问题。 回收内存的策略是如何回收内存,是将对系统性能的影响降到最低的方法。 1内存回收的目标是内核回收后系统无法正常运行,如内核的代码段,因此常见的内核代码段、数据段、内核申请的内存、内核线程占用的内存等

内核空间对于所有进程都是通用的,内核使用的页面通常伴随着整个系统的运行周期,频繁的页面交换和交换对性能非常有影响,因此内核中的页面基本上无法回收。 不是技术上不能实现,而是这样才能弥补损失。

另一方面,另一个是APP应用程序主动申请锁定的页面,实时性要求比较高,频繁的交换和缺页异常处理不能满足时间要求,因此这部分程序使用mlock api自主锁定页面,实现了

很明显,并不是内存中的所有页面都可以交换。 实际上,它只交换与用户空间建立映射关系的物理页面,内核空间中内核占用的页面驻留在内存中。 下面将进一步分类介绍用户空间页面和内核空间页面。 用户空间中的页面根据其内容和性质可以分为以下几类:

进程映像所占的页面。 进程的代码段、数据段、堆段和动态分配的“存储堆”

的代码段和数据段使用的内存页将被替换

系统调用mmap ) )将文件内容映射到用户空间

这些页面上使用的交换空间是映射的文件本身

进程间共享内存区域

那个页面的切换比较复杂

此外,内核在运行时使用的页面是动态分配的,但它会永久驻留在内存中,根据内容和性质将这些页面分为两类。

内核调用kmalloc (或vmalloc )以立即释放分配给内核中临时使用的数据结构的页面。 但是,一个页面包含多个相同类型的数据结构,因此在整个页面空闲时释放页面。 通过在中调用alloc_pages (),为临时使用和管理目的分配了内核的页面,如每个进程的内核堆栈占用的两个页面,以及从内核空间复制参数时使用的页面。 这些页面也使用一次就没有保存价值,所以马上释放。 内核中还有一个页面,已经使用过,但内容具有保存价值,因此不会立即释放。 这样的页面被“释放”后,进入LRU队列,经过一定时间的缓冲后“老化”。 在此期间又要使用那个内容的话,我会再投入那个。 否则,它将继续恶化,直到条件不允许才回收。 此类用途的内核页面包括:

用于读取/写入空间文件系统的缓冲器,用于缓冲空间文件系统中的几个索引节点inode,该空间文件系统用于缓冲文件系统中的几个文件目录结构dentry

文件映射的页包括page cache、slab中的dcache、icache、用户进程可执行程序的代码段和文件映射页。 其中,page cache包括文件系统的page和块设备的buffer cache,所有的都是文件。 block也是文件,还有相关的file、inode等。 另外,回收时的处理因页面是否脏而异。 脏页必须写入磁盘后才能回收。 漂亮的页面可以直接释放。

匿名页,进程使用各种API (包括malloc、mmap、brk/sbrk )申请的物理内存)这些API通常只申请虚拟地址,实际页面分配为page FFE 这个部分(包括进程间通信中的共享内存)不能直接写回,而是为他们创建swap区域,这些页面也可以转换为文件映射页面,然后写回磁盘。 2内存回收机制内核进行内存回收的主要原因有两个。

内核必须为随时突发的内存申请提供足够的内存,以避免由于使用cache和其他相关内存而导致系统中的剩馀内存长期处于空闲状态。

内核使用内存中的page cache缓存文件的一部分,以提高文件的读写效率。 因此,内核需要设计一种周期性回收内存的机制,以避免cache的使用和其他相关内存的使用导致系统的剩馀内存长期很少。

当收到大于的可用内存的申请时,将触发强制内存回收。

因此,内核针对这两种回收需求分别实现了两种不同的机制。

关于,Linux系统设计了kswapd守护进程,内核分配物理页时,由于系统内存不足,

没法在低水位情况下分配内存,因此会唤醒kswapd内核线程来异步回收内存针对第②种,Linux系统会触发直接内存回收(direct reclaim),在内核调用页分配函数分配物理页面时,由于系统内存短缺,不能满足分配请求,内核就会直接触发页面回收机制,尝试回收内存来解决问题

这两种回收的触发方式不同,其区别如下图所示

回收方式特点kswapd回收是内核线程,它和调用者是异步关系直接内存回收该机制是内存短缺直接回收,所以是同步回收,会阻塞调用者进程的执行3 kswapd内核线程

为了避免总在CPU忙碌时也就是缺页异常发生时,临时再来搜寻空页面换出的页面进行换出,内核将定期检查并预先将若干页面换出以腾出空间,维持系统空闲内存的的保有量,以减轻系统在缺页异常发生时的负担。为此内核设置了一个专司页面换出的守护神kswapd进程。

kswapd内核线程初始化时会为系统每个NUMA内存节点创建一个名为“kswapd%d”的内核线程,kswapd进程创建的代码如下:

static int __init kswapd_init(void){int nid;swap_setup();for_each_node_state(nid, N_MEMORY) kswapd_run(nid);hotcpu_notifier(cpu_callback, 0);return 0;} swap_setup函数根据物理内存大小设定全局变量page_cluster,当megs小于16时候,page_cluster为2,否则为3

page_cluster为每次swap in或者swap out操作多少内存页 为2的指数,当为0的时候,为1页,为1的时候,2页,2的时候4页,通过/proc/sys/vm/page-cluster 查看

然后通过for_each_node_state遍历所有 节点,kswapd_run中kthread_run为每个节点创建一 个kswapd%d线程

Kswapd的主循环是一个死循环,只有当kthread _should_stop的时候才会break跳出循环体,会kswapd_try_to_sleep中睡眠,并让出CPU控制权。当系统内存紧张时,这时内存分配函数会调用wakeup_kswapd()来唤醒kswapd内核线程,此时kswapd内核线程在kswapd_try_to_sleep函数中被唤醒,然后调用balance_pgdat()函数来回收页面。下面重点是看看kswapd_try_to_sleep

其主要的流程为:

首先,定义一个wait在kswapd_wait上等待,设置进程状态为TASK_INTERRUPTIBLE,通过prepare_kswapd_sleep判断kswapd是否准好睡眠可以尝试睡眠HZ/10,若返回不为0,则说明没有HZ/10内没有被唤醒了,HZ一般定义为1000,则是100ms如果中途没有被唤醒,说明kswap可以睡眠,让出CPU,schedule出去如果中途被唤醒则返回上层函数,执行内存回收 3.1 kswap的触发条件

kswap进程虽然是系统启动时就会创建,但是大多数时候它处于睡眠状态,只有在进程由于内存不足导致分配内存失败时会被唤醒,从而回收内存,供进程使用。

在NUMA系统中,使用pg_data_t来描述物理内存布局,和kswapd相关参数有:

typedef struct pglist_data {... wait_queue_head_t kswapd_wait; ----------------------------//等待队列 wait_queue_head_t pfmemalloc_wait; struct task_struct *kswapd; /* Protected by mem_hotplug_begin/end() */ int kswapd_max_order;------------------------------------- enum zone_type classzone_idx;-----------------------------//最合适分配内存的zone序号...} pg_data_t; kswapd_wait是一个等待队列,每个pg_data_t都有一个等待队列,在free_area_init_core函数中初始化。kswapd_max_order和classzone_idx:在分配内存路径上的唤醒函数wakeup_kswapd作为参数传递给kswapd内核线程

在分配内存路径上,如果在低水位(ALLOC_WMARK_LOW)的情况下无法成功分配内存,那么就会通过wakeup_kswapd函数唤醒kswapd内核线程来回收页面以便释放一些内存。

kswap唤醒路径1

在这种情况下,使用wake_all_kswapds函数唤醒kswapd内核线程来回收内存,以便释放一些内存。

kswap唤醒路径2——直接内存回收(阻塞)

当路径1唤醒kswap返回后,会尝试分配内存,如果还是失败,会再一次切换zone,如果切换后还是无法分配出内存,就只能进行直接内存回收了。直接回收内存阻塞在于throttle_direct_reclaim,它会一直唤醒kswap回收内存,直到空闲内存满足要求才返回。

3.2 balance_pgdat函数

kswapd内核线程被唤醒后,调用balance_pgdat来回收页面,balance_pgdat()是回收页面的主函数。这是一个大循环,首先从高端zone往低端zone方向查找第一个处于不平衡状态end_zone;而后从最低端zone开始回收页面,直到end_zone;在大循环里检查从最低端zone到classzone_idx的zone是否处于平衡状态,而后不断加大扫描力度。

在kswapd回收内存过程中有一个扫描控制结构体,用于控制这个回收过程。既然是回收内存,就需要明确要回收多少内存,在哪里回收,以及回收时的操作权限等,我们看下这个控制结构struct scan_control主要的一些变量,

struct scan_control {unsigned long nr_to_reclaim; //shrink_list()需要回收的页面数量gfp_t gfp_mask;//分配掩码int order; //进程内存分配页面数量,从分配器传递过来的参数nodemask_t*nodemask; //指定可以在那个node回收内存struct mem_cgroup *target_mem_cgroup; //是否针对某个cgroup扫描回收内存int priority; //控制每次扫描数量,默认是总页数的1/4096enum zone_type reclaim_idx; //进行页面回收的最大zone idunsigned int may_writepage:1; //是否可以回写unsigned int may_unmap:1; //是否可以执行unmapunsigned int may_swap:1; //是否可以将页面交换 ...unsigned int compaction_ready:1; //是否可以进行内存压缩,即碎片整理unsigned long nr_scanned; //已扫描的非活动页面数量unsigned long nr_reclaimed; //shrink_zones()中已回收页面数量...};

该函数中有两个重要的函数,zone_balanced用于判断zone在分配order个页面以后的空闲页面是否处于WMARK_HIGH水位之上。返回true,表示zone处于WMARK_HIGH之上。

static bool zone_balanced(struct zone *zone, int order, int classzone_idx){unsigned long 悦耳的水杯 = high_w悦耳的水杯_pages(zone);if (!zone_water悦耳的水杯_ok_safe(zone, order, 悦耳的水杯, classzone_idx))return false;/* * If any eligible zone is balanced then the node is not considered * to be congested or dirty */clear_bit(PGDAT_CONGESTED, &zone->zone_pgdat->flags);clear_bit(PGDAT_DIRTY, &zone->zone_pgdat->flags);return true;}

对于这块后面再单独分析,我们主要关注重点的函数kswapd_shrink_node,其处理流程如下

对于此内容,后面单独章节进行介绍。

4 总结

本章主要是学习了Linux内核触发页面回收的机制有3个:

直接页面回收机制: 在内核态调用页面分配接口函数分配物理内存时,由于系统内存短缺,不能满足分配请求,因此会直接到页面回收机制,尝试回收内存来解决当前的问题周期性内存回收机制:也就是kswapd内核线程的工作职责,当内核路径调用alloc_pages分配物理内存页面时,由于系统内存短缺,没法再低水位情况下分配内存,因此会唤醒kswapd的内核线程来异步回收内存slab(slab shrinker机制):对于slab,是由缓存的,所以当内存短缺,直接页面回收和周期性回收内存会调用slab回收机制回收对象。

slab机制分配的内存主要是用于slab对象和kmalloc接口,页可以用于内核空间的内存分配,比较文件的Node缓存等。

kswapd本身是一个内核线程,它和调用的关系是异步的,如我们用户空间的进程尝试调用alloc_pages来分配内存,当发现在低水位的情况下无法分配出内存时,它将唤醒kswapd内核线程。这时,Kswapd内核线程就开始执行页面回收工作了,同时test进程会尝试其他办法来分配内存,如调用直接回收内存机制。所以对于页面回收机制的主要调用关系如下图所示

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。