Read the fucking source code!
--By 鲁迅A picture is worth a thousand words.
--By 高尔基说明:架构
进程切换:内核将CPU上正在运行的进程挂起,选择下一个进程来运行。
ARM架构中,CPU上一次只能运行一个任务,内核须要为任务分配运行时间来进行调度,以便同时能处理多个任务请求。
以下图所示:函数
当进行任务切换的时候,思考下两个问题:工具
这两个问题,也是本文探讨的主题了。this
TIF_NEED_RESCHED
标志来对进程进行标记的,设置该位则代表须要进行调度切换,而实际的切换将在抢占执行点来完成。不看代码来说结论,那都是耍流氓。先看一下两个关键结构体:struct task_struct
和struct thread_info
。咱们在前边的文章中也讲过struct task_struct
用于描述任务,该结构体的首个字段放置的正是struct thread_info
,struct thread_info
结构体中flag
字段就可用于设置TIF_NEED_RESCHED
,此外该结构体中的preempt_count
也与抢占相关。线程
struct task_struct { #ifdef CONFIG_THREAD_INFO_IN_TASK /* * For reasons of header soup (see current_thread_info()), this * must be the first element of task_struct. */ struct thread_info thread_info; #endif ... } /* * low level task data that entry.S needs immediate access to. */ struct thread_info { unsigned long flags; /* low level flags */ mm_segment_t addr_limit; /* address limit */ #ifdef CONFIG_ARM64_SW_TTBR0_PAN u64 ttbr0; /* saved TTBR0_EL1 */ #endif int preempt_count; /* 0 => preemptable, <0 => bug */ }; #include <asm/current.h> #define current_thread_info() ((struct thread_info *)current) //经过该宏能够直接获取thread_info的信息 #endif
看看具体哪些函数过程当中,设置了TIF_NEED_RESCHED
标志吧:3d
set_tsk_need_resched
函数来将thread_info
中flag
字段设置成TIF_NEED_RESCHED
;TIF_NEED_RESCHED
标志,代表须要发生抢占调度;用户抢占:抢占执行发生在进程处于用户态。
抢占的执行,最明显的标志就是调用了schedule()
函数,来完成任务的切换。
具体来讲,在用户态执行抢占在如下几种状况:code
以下图:blog
ENTRY(vectors)
向量表处开始执行;TIF_NEED_RESCHED
则须要进行调度切换,没有设置该标志,则检查是否有收到信号,有信号未处理的话,还须要进行信号的处理操做;Linux内核有三种内核抢占模型,先上图:接口
struct thread_info
的flag
字段,设置TIF_NEED_RESCHED
代表须要请求从新调度。内核抢占:抢占执行发生在进程处于内核态。队列
整体而言,内核抢占执行点能够归属于两大类:
preemp_enable
或schedule
等接口的地方进行抢占调度;struct thread_info
中的preempt_count
字段来控制抢占。preempt_count
的低8位用于控制抢占,当大于0时表示不可抢占,等于0表示可抢占。preempt_enable()
会将preempt_count
值减1,并判断是否须要进行调度,在条件知足时进行切换;preempt_disable()
会将preempt_count
值加1;此外,preemt_count
字段还用于判断进程处于各种上下文以及开关控制等,如图:
struct task_struct
结构体中,以便在切换时能完成恢复工做;进程上下文切换的入口就是__schedule()
,分析也围绕这函数展开。
__schedule()
__schedule()
函数调用分析以下:
主要的逻辑:
task
,也就是切换前的prev
;prev
的状态进行处理,好比pending
信号的处理等,若是该任务是一个worker线程
还须要将其睡眠,并唤醒同CPU上的另外一个worker线程
;task
,也就是next
;context_switch
完成进程的切换;context_switch()
context_switch()
的调用分析以下:
核心的逻辑有两部分:
进程的地址空间切换
:切换的时候要判断切入的进程是否为内核线程,1)全部的用户进程都共用一个内核地址空间,而拥有不一样的用户地址空间;2)内核线程自己没有用户地址空间。在进程在切换的过程当中就须要对这些因素来考虑,涉及到页表的切换,以及cache/tlb
的刷新等操做。寄存器的切换
:包括CPU的通用寄存器切换、浮点寄存器切换,以及ARM处理器相关的其余一些寄存器的切换;进程的切换,带来的开销不只是页表切换和硬件上下文的切换,还包含了Cache/TLB
刷新后带来的miss
的开销。在实际的开发中,也须要去评估新增进程带来的调度开销。