内核代码阅读(8) - 内核线程 kswapd 和 kreclaimd

简介: 内核线程 kswapd 和 kreclaimd

页面的换出 kswapd 和 kreclaimd

内核线程 kswapd 和 kreclaimd 的启动

static int __init kswapd_init(void)
{
    printk("Starting kswapd v1.8\n");
    swap_setup();
    kernel_thread(kswapd, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL);
    kernel_thread(kreclaimd, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL);
    return 0;
}
module_init(kswapd_init)

0) 这是一个内建模块。在系统初始化调用。

1) 启动了两个内核线程 kswapd 和 kreclaimd 。

2) swap_setup 根据物理内存大小设置page_cluster。这个是从磁盘读block时候的预读参数。

void __init swap_setup(void)
{
    if (num_physpages < ((16 * 1024 * 1024) >> PAGE_SHIFT))
        page_cluster = 2;
    else if (num_physpages < ((32 * 1024 * 1024) >> PAGE_SHIFT))
        page_cluster = 3;
    else
        page_cluster = 4;
}

kswap 内核线程

kswap的主流程

int kswapd(void *unused)
{
    struct task_struct *tsk = current;
    tsk->session = 1;
    tsk->pgrp = 1;
    strcpy(tsk->comm, "kswapd");
    sigfillset(&tsk->blocked);
    kswapd_task = tsk;
    tsk->flags |= PF_MEMALLOC;
    for (;;) {
        static int recalc = 0;
        if (inactive_shortage() || free_shortage()) {
            int wait = 0;
            if (waitqueue_active(&kswapd_done))
                wait = 1;
            do_try_to_free_pages(GFP_KSWAPD, wait);
        }
        refill_inactive_scan(6, 0);
        if (time_after(jiffies, recalc + HZ)) {
            recalc = jiffies;
            recalculate_vm_stats();
        }
        wake_up_all(&kswapd_done);
        run_task_queue(&tq_disk);
        if (!free_shortage() || !inactive_shortage()) {
            interruptible_sleep_on_timeout(&kswapd_wait, HZ);
        } else if (out_of_memory()) {
            oom_kill();
        }
    }
}

1) 设置进程的flags为 PF_MEMALLOC。这个标记了kswapd是一个内存管理者的角色,有权使用最后一点‘血本’内存。同时也为了避免递归。

2) 每次循环的末尾都会进入 interruptible_sleep_on_timeout,睡眠HZ。HZ是系统1秒内产生多少次中断,已经编译就不能更改。

也就是1秒醒来一次。但是也会被中断提前醒来。

3) 每次 kswapd 主要干两件事情。

kswap主流程 之 页面缺口的判断 inactive_shortage

if (inactive_shortage() || free_shortage()) {
    int wait = 0;
    if (waitqueue_active(&kswapd_done))
        wait = 1;
    do_try_to_free_pages(GFP_KSWAPD, wait);
}

0) 计算是否缺少不活跃的页面,或者缺少空闲页。

1) 不活跃页面是alloc_pages的潜在页面,如果不够需要释放出来。


int inactive_shortage(void)
{
    int shortage = 0;
    shortage += freepages.high;
    shortage += inactive_target;
    shortage -= nr_free_pages();
    shortage -= nr_inactive_clean_pages();
    shortage -= nr_inactive_dirty_pages;
    if (shortage > 0)
        return shortage;
    return 0;
}

1) 缺少的空闲页面数 = freepages.high + inactive_target - 系统目前的free_pages - 系统目前有的空闲页 - 系统目前的不活跃脏页

kswap主流程 之 释放空闲页 do_try_to_free_pages

static int do_try_to_free_pages(unsigned int gfp_mask, int user)
{
    int ret = 0;
    if (free_shortage() || nr_inactive_dirty_pages > nr_free_pages() +
        nr_inactive_clean_pages())
        ret += page_launder(gfp_mask, user);
    if (free_shortage() || inactive_shortage()) {
        shrink_dcache_memory(6, gfp_mask);
        shrink_icache_memory(6, gfp_mask);
        ret += refill_inactive(gfp_mask, user);
    } else {
        kmem_cache_reap(gfp_mask);
        ret = 1;
    }
    return ret;
}

1) 先试着清洗不活跃脏页面 page_launder。

2) 如果还缺少不活跃页面,则开始从buffer和cache中释放。

do_try_to_free_pages 之 清洗页面 page_launder
int page_launder(int gfp_mask, int sync)
{
dirty_page_rescan:
    spin_lock(&pagemap_lru_lock);
    maxscan = nr_inactive_dirty_pages;
    while ((page_lru = inactive_dirty_list.prev) != &inactive_dirty_list &&
           maxscan-- > 0) {
        page = list_entry(page_lru, struct page, lru);
        if (PageDirty(page)) {
            int (*writepage)(struct page *) = page->mapping->a_ops->writepage;
            int result;
            if (!writepage)
                goto page_active;
            if (!launder_loop) {
                list_del(page_lru);
                list_add(page_lru, &inactive_dirty_list);
                UnlockPage(page);
                continue;
            }
            ClearPageDirty(page);
            page_cache_get(page);
            spin_unlock(&pagemap_lru_lock);
            result = writepage(page);
            page_cache_release(page);
            spin_lock(&pagemap_lru_lock);
            if (result != 1)
                continue;
            set_page_dirty(page);
            goto page_active;
        }
    }
}

1) 页面清洗从 inactive_dirty_list中依次扫面,找到可以清洗的页面。

2) 清洗动作由 writepage完成。

3) 做两次扫描,为什么呢?

do_try_to_free_pages 之 swap文件系统和swap进程
page_launder之后,页面还是短缺

这个时候还是缺页面,则进行下面4个暴力的回收

0) shrink_dcache_memory(6, gfp_mask);

1) shrink_icache_memory(6, gfp_mask);

2) ret += refill_inactive(gfp_mask, user);

3) kmem_cache_reap(gfp_mask);

0) 和 1) 和文件系统相关,先来看 2)refill_inactive

refill_inactive

主要做两件事情:

0) 扫描 active_list,期望从中发现不用了的,没有及时放入 inactive_dirty_list中的页面。

1) swap_out, 找一个进程。

refill_inactive 之 swap_out
static int swap_out(unsigned int priority, int gfp_mask)
{
    int counter;
    int __ret = 0;
    counter = (nr_threads << SWAP_SHIFT) >> priority;
    if (counter < 1)
        counter = 1;
    for (; counter >= 0; counter--) {
        struct list_head *p;
        unsigned long max_cnt = 0;
        struct mm_struct *best = NULL;
        int assign = 0;
        int found_task = 0;
    select:
        spin_lock(&mmlist_lock);
        p = init_mm.mmlist.next;
        for (; p != &init_mm.mmlist; p = p->next) {
            struct mm_struct *mm = list_entry(p, struct mm_struct, mmlist);
            if (mm->rss <= 0)
                continue;
            found_task++;
            if (assign == 1) {
                mm->swap_cnt = (mm->rss >> SWAP_SHIFT);
                if (mm->swap_cnt < SWAP_MIN)
                    mm->swap_cnt = SWAP_MIN;
            }
            if (mm->swap_cnt > max_cnt) {
                max_cnt = mm->swap_cnt;
                best = mm;
            }
        }
        if (best)
            atomic_inc(&best->mm_users);
        spin_unlock(&mmlist_lock);
        if (!best) {
            if (!assign && found_task > 0) {
                assign = 1;
                goto select;
            }
            break;
        } else {
            __ret = swap_out_mm(best, gfp_mask);
            mmput(best);
            break;
        }
    }
    return __ret;
}

1) swap_out的目标是选择一个合适的进程然后进行swap。

2) 所有的进程组织成了一个双向链表,而1号进程是init_mm,所有从init_mm下一个进程开始找合适的进程。

3) 什么是合适的进程,有两个判断的阶段。

rss 和 swap_cnt。

开始swap_cnt就是rss的值,然后每次swap掉一个页面都swap_cnt--,然后当swap_cnt都是0的时候,又开始一轮赋值。

这样保证了大的rss优先被选出来,同时又保证所有的进程依次每选中。

refill_inactive 之 swap_out_mm
static int swap_out_mm(struct mm_struct * mm, int gfp_mask)
{
    int result = 0;
    unsigned long address;
    struct vm_area_struct* vma;
    spin_lock(&mm->page_table_lock);
    address = mm->swap_address;
    vma = find_vma(mm, address);
    if (vma) {
        if (address < vma->vm_start)
            address = vma->vm_start;
        for (;;) {
            result = swap_out_vma(mm, vma, address, gfp_mask);
            if (result)
                goto out_unlock;
            vma = vma->vm_next;
            if (!vma)
                break;
            address = vma->vm_start;
        }
    }
    mm->swap_address = 0;
    mm->swap_cnt = 0;
out_unlock:
    spin_unlock(&mm->page_table_lock);
    return result;
}

1) address = mm->swap_address;

对找到的合适被swap的进程,从上一次被swap的逻辑地址接着swap。

2) vma = find_vma(mm, address);

从address找到对应的vma。

3) result = swap_out_vma(mm, vma, address, gfp_mask);

循环遍历vma,直到swap成功一次为止。

refill_inactive 之 swap_out_vma
static int swap_out_vma(struct mm_struct * mm, struct vm_area_struct * vma, unsigned long address, int gfp_mask)
{
    pgd_t *pgdir;
    unsigned long end;
    if (vma->vm_flags & (VM_LOCKED|VM_RESERVED))
        return 0;
    pgdir = pgd_offset(mm, address);
    end = vma->vm_end;
    if (address >= end)
        BUG();
    do {
        int result = swap_out_pgd(mm, vma, pgdir, address, end, gfp_mask);
        if (result)
            return result;
        address = (address + PGDIR_SIZE) & PGDIR_MASK;
        pgdir++;
    } while (address && (address < end));
    return 0;
}

1) vma->vm_flags & (VM_LOCKED|VM_RESERVED)

如果vma是被lock了或者vm_reserved了,不进行swap。

2) pgdir = pgd_offset(mm, address);

计算出address对应的pgdir

3) 一个vma可能包含多个pgd项,循环这个vma对应的pgdir,直到成功的swap一个页面为止。

refill_inactive 之 swap_out_pgd
static inline int swap_out_pgd(struct mm_struct * mm, struct vm_area_struct * vma, pgd_t *dir, unsigned long address, unsigned long end, int gfp_mask)
{
    pmd_t * pmd;
    unsigned long pgd_end;
    if (pgd_none(*dir))
        return 0;
    if (pgd_bad(*dir)) {
        pgd_ERROR(*dir);
        pgd_clear(dir);
        return 0;
    }
    pmd = pmd_offset(dir, address);
    pgd_end = (address + PGDIR_SIZE) & PGDIR_MASK;        
    if (pgd_end && (end > pgd_end))
        end = pgd_end;
    do {
        int result = swap_out_pmd(mm, vma, pmd, address, end, gfp_mask);
        if (result)
            return result;
        address = (address + PMD_SIZE) & PMD_MASK;
        pmd++;
    } while (address && (address < end));
    return 0;
}

1) pmd = pmd_offset(dir, address);

由pgdir和address计算出pmd的开始和结束。

2) 循环尝试swap所有的pmd,直到成功。

refill_inactive 之 swap_out_pmd
static inline int swap_out_pmd(struct mm_struct * mm, struct vm_area_struct * vma, pmd_t *dir, unsigned long address, unsigned long end, int gfp_mask)
{
    pte_t * pte;
    unsigned long pmd_end;
    if (pmd_none(*dir))
        return 0;
    if (pmd_bad(*dir)) {
        pmd_ERROR(*dir);
        pmd_clear(dir);
        return 0;
    }
    pte = pte_offset(dir, address);
    pmd_end = (address + PMD_SIZE) & PMD_MASK;
    if (end > pmd_end)
        end = pmd_end;
    do {
        int result;
        mm->swap_address = address + PAGE_SIZE;
        result = try_to_swap_out(mm, vma, address, pte, gfp_mask);
        if (result)
            return result;
        address += PAGE_SIZE;
        pte++;
    } while (address && (address < end));
    return 0;
}

1) pte = pte_offset(dir, address);

由pmd和address计算出pte的起始。

2) 循环尝试swap所有的pte。

refill_inactive 之 try_to_swap_out
static int try_to_swap_out(struct mm_struct * mm, struct vm_area_struct* vma, unsigned long address, pte_t * page_table, int gfp_mask)
{
    if (!pte_present(pte))
        goto out_failed;
    page = pte_page(pte);
    if ((!VALID_PAGE(page)) || PageReserved(page))
        goto out_failed;
    if (ptep_test_and_clear_young(page_table)) {
        age_page_up(page);
        goto out_failed;
    }
    flush_cache_page(vma, address);
    entry = get_swap_page();
    add_to_swap_cache(page, entry);
    set_page_dirty(page);
}

1) 开始尝试swap一个真正的页面了。

2) pte_present(pte)

首先判断这个页面是否已经配swap出去了。

3) page = pte_page(pte);

由pte找到page结构的指针

4) ptep_test_and_clear_young(page_table)

是否是一个刚刚被访问过了的年轻页面。

5) flush_cache_page(vma, address);

6) entry = get_swap_page();

从 swap_info 中申请一个swap项。

7) add_to_swap_cache(page, entry);

kswap最终的动作只是把一个进程的页面从页式映射中‘断开’,然后把这个页面加入到swap_cache。并设置为脏页。

加入到 active_list 中。

kreclaimd 内核线程

kreclaimd 的主流程

这个内核线程是在需要的时候被动的被唤醒,把一个zone里的inactive_clean_list的页面转移到free里面。

int kreclaimd(void *unused)
{
    struct task_struct *tsk = current;
    pg_data_t *pgdat;
    tsk->session = 1;
    tsk->pgrp = 1;
    strcpy(tsk->comm, "kreclaimd");
    sigfillset(&tsk->blocked);
    current->flags |= PF_MEMALLOC;
    while (1) {
        interruptible_sleep_on(&kreclaimd_wait);
        pgdat = pgdat_list;
        do {
            int i;
            for(i = 0; i < MAX_NR_ZONES; i++) {
                zone_t *zone = pgdat->node_zones + i;
                if (!zone->size)
                    continue;
                while (zone->free_pages < zone->pages_low) {
                    struct page * page;
                    page = reclaim_page(zone);
                    if (!page)
                        break;
                    __free_page(page);
                }
            }
            pgdat = pgdat->node_next;
        } while (pgdat);
    }
}

1) 设置当前task的属性: session, pgrp, task->comm, tsk->blocked。

2) 设置 PF_MEMALLOC。这个标记了kreclaimd是一个内存管理者的角色。

3) interruptible_sleep_on(&kreclaimd_wait);

主循环不主动的干活,而是等待page_alloc.c::__alloc_pages()分配页面不够用的时候,唤醒这个线程。

4) 遍历NUMA的pgdat_list链表。

5) zone_t *zone = pgdat->node_zones + i;

在每个node的结构pg_data中遍历所有的zone。

6) while(zone->free_pages < zone->pages_low)

在每个zone里,如果当前zone的free_pages数比low还低的话,就开始relcaim.

kreclaimd 之 relaim_page(zone)
while ((page_lru = zone->inactive_clean_list.prev) !=
                  &zone->inactive_clean_list && maxscan--) {
               page = list_entry(page_lru, struct page, lru);
               if (!PageInactiveClean(page)) {
                   printk("VM: reclaim_page, wrong page on list.\n");
                   list_del(page_lru);
                   page->zone->inactive_clean_pages--;
                   continue;
               }
               if (PageTestandClearReferenced(page) || page->age > 0 ||
                   (!page->buffers && page_count(page) > 1)) {
                   del_page_from_inactive_clean_list(page);
                   add_page_to_active_list(page);
                   continue;
               }
               if (page->buffers || PageDirty(page) || TryLockPage(page)) {
                   del_page_from_inactive_clean_list(page);
                   add_page_to_inactive_dirty_list(page);
                   continue;
               }
               if (PageSwapCache(page)) {
                   __delete_from_swap_cache(page);
                   goto found_page;
               }
               if (page->mapping) {
                   __remove_inode_page(page);
                   goto found_page;
               }
           }

1) 遍历zone的 inactive_clean_list,找到一个页面就返回。

2) if (page->buffers || PageDirty(page) || TryLockPage(page))

如果是脏页,把这个页面添加到 inactive_dirty_list中。

3) __delete_from_swap_cache(page);

把page从 cache 中摘除掉。

4) __remove_inode_page(page);

把page从 inode 中摘除。

kreclaimd 之 __free_page buddy system ---- 待补充!!!!
相关文章
|
2月前
|
消息中间件 存储 算法
【软件设计师备考 专题 】操作系统的内核(中断控制)、进程、线程概念
【软件设计师备考 专题 】操作系统的内核(中断控制)、进程、线程概念
84 0
|
4月前
|
Go 调度
go-issues#14592 runtime: let idle OS threads exit 内核线程暴增与线程回收问题
go-issues#14592 runtime: let idle OS threads exit 内核线程暴增与线程回收问题
26 0
|
4月前
|
Linux
Linux进程与线程的内核实现
task_struct称为进程描述符结构,该结构定义在文件中。进程描述符中包含一个具体进程的所有信息 进程描述符中包含的数据能完整地描述一个正在执行的程序:它打开的文件,进程的地址空间,挂起的信号,进程的状态等
38 0
Linux进程与线程的内核实现
|
4月前
|
存储 算法 Linux
一起聊聊内核中的线程:操作函数、进程状态、task_struct、举个例子、
一起聊聊内核中的线程:操作函数、进程状态、task_struct、举个例子、
68 0
|
5月前
|
存储 安全 Linux
Linux中断(tasklet,工作队列,内核线程的使用)
Linux中断(tasklet,工作队列,内核线程的使用)
43 0
|
5月前
|
监控 安全 API
6.9 Windows驱动开发:内核枚举进线程ObCall回调
在笔者上一篇文章`《内核枚举Registry注册表回调》`中我们通过特征码定位实现了对注册表回调的枚举,本篇文章`LyShark`将教大家如何枚举系统中的`ProcessObCall`进程回调以及`ThreadObCall`线程回调,之所以放在一起来讲解是因为这两中回调在枚举是都需要使用通用结构体`_OB_CALLBACK`以及`_OBJECT_TYPE`所以放在一起来讲解最好不过。
50 1
6.9 Windows驱动开发:内核枚举进线程ObCall回调
|
5月前
|
网络协议 安全 API
9.9 Windows驱动开发:内核远程线程实现DLL注入
在笔者上一篇文章`《内核RIP劫持实现DLL注入》`介绍了通过劫持RIP指针控制程序执行流实现插入DLL的目的,本章将继续探索全新的注入方式,通过`NtCreateThreadEx`这个内核函数实现注入DLL的目的,需要注意的是该函数在微软系统中未被导出使用时需要首先得到该函数的入口地址,`NtCreateThreadEx`函数最终会调用`ZwCreateThread`,本章在寻找函数的方式上有所不同,前一章通过内存定位的方法得到所需地址,本章则是通过解析导出表实现。
77 0
9.9 Windows驱动开发:内核远程线程实现DLL注入
|
5月前
|
监控 安全 API
7.1 Windows驱动开发:内核监控进程与线程回调
在前面的文章中`LyShark`一直在重复的实现对系统底层模块的枚举,今天我们将展开一个新的话题,内核监控,我们以`监控进程线程`创建为例,在`Win10`系统中监控进程与线程可以使用微软提供给我们的两个新函数来实现,此类函数的原理是创建一个回调事件,当有进程或线程被创建或者注销时,系统会通过回调机制将该进程相关信息优先返回给我们自己的函数待处理结束后再转向系统层。
62 0
7.1 Windows驱动开发:内核监控进程与线程回调
|
5月前
|
监控 Windows
4.4 Windows驱动开发:内核监控进程与线程创建
当你需要在Windows操作系统中监控进程的启动和退出时,可以使用`PsSetCreateProcessNotifyRoutineEx`函数来创建一个`MyCreateProcessNotifyEx`回调函数,该回调函数将在每个进程的创建和退出时被调用。PsSetCreateProcessNotifyRoutineEx 用于在系统启动后向内核注册一个回调函数,以监视新进程的创建和退出,
42 0
4.4 Windows驱动开发:内核监控进程与线程创建
|
6月前
|
存储 安全 调度
4.2 Windows驱动开发:内核中进程线程与模块
内核进程线程和模块是操作系统内核中非常重要的概念。它们是操作系统的核心部分,用于管理系统资源和处理系统请求。在驱动安全开发中,理解内核进程线程和模块的概念对于编写安全的内核驱动程序至关重要。内核进程是在操作系统内核中运行的程序。每个进程都有一个唯一的进程标识符(PID),它用于在系统中唯一地标识该进程。在内核中,进程被表示为一个进程控制块(PCB),它包含有关进程的信息,如进程状态、优先级、内存使用情况等。枚举进程可以让我们获取当前系统中所有正在运行的进程的PID和其他有用的信息,以便我们可以监视和管理系统中的进程。
71 0
4.2 Windows驱动开发:内核中进程线程与模块