一. 概述
在最近初学ebpf时,使用到了挂载点finish_task_switch
统计内核线程的运行时间,遂进入内核源码对其进行学习分析。
finish_task_switch
在context_switch
被调用,其功能是完成进程切换的收尾工作,比如地址空间的清理。而context_switch
是进程切换的核心部分,其由两部分组成:
- 切换页全局目录到一个新的地址空间(switch_mm)。
- 切换内核态堆栈及硬件上下文(switch_to)。
context_switch
的代码如下:
static __always_inline struct rq *context_switch(struct rq *rq, struct task_struct *prev,
struct task_struct *next, struct rq_flags *rf)
{
prepare_task_switch(rq, prev, next);//执行进程切换的准备工作。
arch_start_context_switch(prev);
if (!next->mm) { // to kernel
enter_lazy_tlb(prev->active_mm, next);//通知处理器架构不需要切换用户虚拟地址空间,这种加速进程切换计数称为惰性TLB
next->active_mm = prev->active_mm;//继承前一个进程的内存描述符
if (prev->mm) // from user
mmgrab(prev->active_mm);//增加前一个进程的活跃地址空间的引用计数,以确保地址空间在进程切换后仍然有效
else //from kernel
prev->active_mm = NULL;
} else { // to user
membarrier_switch_mm(rq, prev->active_mm, next->mm);
switch_mm_irqs_off(prev->active_mm, next->mm, next);//切换地址空间
if (!prev->mm) { // from kernel
rq->prev_mm = prev->active_mm;
prev->active_mm = NULL;
}
}
rq->clock_update_flags &= ~(RQCF_ACT_SKIP|RQCF_REQ_SKIP);
prepare_lock_switch(rq, next, rf);
switch_to(prev, next, prev);
barrier();
return finish_task_switch(prev);
}
在分析这段源码之前,我们首先需要知道的是,context_switch( )函数建立next的地址空间。在task struct结构体中有这样两个字段:mm和active_mm。进程描述符的active_mm字段指向进程所使用的内存描述符,而mm字段指向进程所拥有的内存描述符。对于一般的进程,这两个字段有相同的地址,但是,内核线程没有它自己的地址空间而且它的 mm字段总是被设置为 NULL。
context_switch( )函数实现:如果next是一个内核线程,那么它使用prev所使用的地址空间。
具体实