【转】【进程管理】Linux进程调度:调度时机
转自:https://zhuanlan.zhihu.com/p/163728119
概述:
进程切换分为自愿(voluntary)和强制(involuntary)两种。通常自愿切换是指任务由于等待某种资源,将state改为非RUNNING状态后,调用schedule()主动让出CPU;而强制切换(即抢占)则是任务状态仍为RUNNING却失去CPU使用权,情况有任务时间片用完、有更高优先级的任务、任务中调用cond_resched()或yield让出CPU。
本文主要介绍抢占式进程切换(involuntary context switch),抢占又根据抢占发生的时机划分为用户抢占和内核抢占。
注意:1.文章基于arm64架构的linux5.0内核代码;2. 默认开启内核抢占CONFIG_PREEMPT
关于调度器:
内核中存在两个调度器,即主调度器及周期性调度器,通称为通用调度器。主调度器函数为__schedule();周期性调度器函数为scheduler_tick()。其中主调度器在内核代码中需要被主动调用,周期性调度器则伴随着系统的tick中断每秒HZ次的发生。
抢占前的检查:
- 是否安全?
抢占发生的前提是要确保此次抢占是安全的。什么算安全?即current任务没有持有自旋锁,否则可能会发生死锁。于是在引入内核抢占机制(CONFIG_PREEMPT)的时同时引入了preempt_count,用来保证抢占的安全性,获取锁前会去inc抢占计数,而抢占发生前会去检查preempt_count是否为0。
- 是否需要抢占?
关于是否需要去抢占,会去判断thread_info的成员flags是否设置了TIF_NEED_RESCHED标志位。(tif_need_resched()即用来判断此flag是否置位)
*后面会有具体的抢占例子分析。
进程切换的统计:
每个进程的强制切换和自愿切换的次数都会被记录在/proc/pid/status中:
/*通过procfs查看*/
grep ctxt /proc/26995/status
voluntary_ctxt_switches: 79
nonvoluntary_ctxt_switches: 4
/*使用pidstat命令查看*/
pidstat -w
自愿切换和强制切换的统计值在实践中有什么意义呢?
答:大致而言,如果一个进程的自愿切换占多数,意味着它对CPU资源的需求不高;如果一个进程的强制切换占多数,表明它对CPU的依赖较强,意味着对它来说CPU资源可能是个瓶颈(这里需要排除进程频繁调用sched_yield()导致强制切换的情况)
内核抢占的几个时机:
- 中断返回内核空间:
发生在内核态的中断el1_irq退出前,即irq handler之后,kernel_exit恢复现场之间。会去检查current进程的preempt_count和need_resched以判断是否需要进行一次抢占。
/*文:arch/arm64/kernel/entry.S
函数调用:
el1_irq
irq_handler
el1_preempt
preempt_schedule_irq
__schedule(true)
*/
el1_irq:
kernel_entry 1
enable_da_f
irq_handler
#ifdef CONFIG_PREEMPT
ldr x24, [tsk, #TSK_TI_PREEMPT] /* 抢占前的检查:preempt_count和need_resched.
注意:设置need_resched的地方,同时也会设置TIF_NEED_RESCHED,所以这里检查need_resched即可。
*/
cbnz x24, 1f /* 判断是否要跳转*/
bl el1_preempt /* 跳转el1_preempt中尝试抢占*/
1:
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_on
#endif
kernel_exit 1
ENDPROC(el1_irq) #ifdef CONFIG_PREEMPT
el1_preempt:
mov x24, lr
1: bl preempt_schedule_irq /* 中断抢占的核心函数。注意:执行到此中断仍是关闭的,所以跳转回来时中断也要是关闭的*/
ldr x0, [tsk, #TSK_TI_FLAGS]
tbnz x0, #TIF_NEED_RESCHED, 1b
ret x24
#endif
/*文件:kernel/sched/core.c */ asmlinkage __visible void __sched preempt_schedule_irq(void) // 封装了__schedule函数,且它会保证返回时local中断仍关闭。
{
enum ctx_state prev_sta; /* Catch callers which need to be fixed */
BUG_ON(preempt_count() || !irqs_disabled()); //检测异常:若抢占计数不为零,或者中断没有关闭 则dump_stack并产生oops。
prev_state = exception_enter(); //保存当前cpu的异常状态下的上下文到prev_state do {
preempt_disable(); //关抢占,__schedule的过程不允许被抢占
local_irq_enable(); //使能中断
__schedule(true); //调度器核心函数。true表示此次切换为抢占。
local_irq_disable(); //关闭中断
sched_preempt_enable_no_resched(); //开抢占
} while (need_resched()); //如果调度出去后的进程操作了被中断进程的thread_info.flags,使它仍为TIF_NEED_SCHED,那就继续进行调度。 exception_exit(prev_state); //恢复抢占前的保存在prev_state中的异常上下文。
}
- 内核中调用cond_resched()
内核代码中显式调用cond_resched()触发抢占,它的核心函数是preempt_schedule_common
/*cond_resched()在kernel代码中有着较广泛的使用*/
int __sched _cond_resched(void)
{
if (should_resched(0)) { //判断preempt_count是否为0。为0则代表开启抢占,need_resched也已被设置。
preempt_schedule_common(); //__schedule函数的封装函数。
return 1;
}
rcu_all_qs();
return 0;
}
static void __sched notrace preempt_schedule_common(void)
{
do {
preempt_disable_notrace();
preempt_latency_start(1);
__schedule(true); //调用__sechdule,且传参true代表这里是抢占
preempt_latency_stop(1);
preempt_enable_no_resched_notrace();
} while (need_resched()); //如果调度出去后的进程操作了被当前进程的thread_info.flags,使它仍为TIF_NEED_SCHED,那就继续进行调度
}
- 内核中使能preempt时
内核代码中调用preempt_enable()开启preempt时也会检查执行抢占。
/*函数调用:
preempt_enable()
__preempt_schedule()
preempt_schedule()
preempt_schedule_common()
*/
当前任务开启抢占时,也会检查并执行一次抢占。类似cond_resched()最终调用preempt_schedule_common()函数。
注意:非抢占的内核在spin_unlock()中不检查调度,而cond_resched在两种内核中都检查调度 (具体可查看代码实现)
用户抢占:
- 中断返回用户空间:
当中断发生在进程的用户态,中断返回用户空间时也会检查是否需要执行抢占。
/*文件:arch/arm64/kernel/entry.S
函数调用:
ret_to_user
work_pending
do_notify_resume
schedule()
*/
work_pending:
mov x0, sp // 'regs'
bl do_notify_resume //跳转到do_notify_resume函数。
#ifdef CONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_on // enabled while in userspace
#endif
ldr x1, [tsk, #TSK_TI_FLAGS] // re-check for single-step
b finish_ret_to_user
ret_to_user:
disable_daif //D A I F分别为PSTAT中的四个异常屏蔽标志位,此处屏蔽这4中异常。(irq这里理论上没有开,为什么还关?)
ldr x1, [tsk, #TSK_TI_FLAGS] //获取thread_info中的flags变量的值
and x2, x1, #_TIF_WORK_MASK //_TIF_WORK_MASK是一些列flags的集合,其中包括NEED_RESCHED|SIGPENDING|NOTIFY_RESUME等值
cbnz x2, work_pending //判断并跳转。此处判断若有_TIF_WORK_MASK的中任何flag都去执行work_pending
finish_ret_to_user:
enable_step_tsk x1, x2
#ifdef CONFIG_GCC_PLUGIN_STACKLEAK
bl stackleak_erase
#endif
kernel_exit 0
ENDPROC(ret_to_user)
asmlinkage void do_notify_resume(struct pt_regs *regs, /* 处理thread_info中pending的flag事件。
unsigned long thread_flags) 进入此函数,中断必须是关闭的。这里大概就是ret_to_user中再次调disable_daif的原因。
*/
{
do {
/* Check valid user FS if needed */
addr_limit_user_check();
if (thread_flags & _TIF_NEED_RESCHED) { //上来就判断_TIF_NEED_RESCHED,若置位则直接调schedule
/* Unmask Debug and SError for the next task */ //取消被屏蔽的其他异常,只关闭IRQ异常!
local_daif_restore(DAIF_PROCCTX_NOIRQ);
schedule(); //调度的核心函数,进行抢占。
} else {
......
} while (thread_flags & _TIF_WORK_MASK);
}
- 系统调用返回用户空间
系统调用返回用户空间和中断返回用户空间都是通过ret_to_user函数,所以判断执行抢占的部分是相同的。
/*文件:arch/arm64/kernel/entry.S
函数调用:
ret_to_user
work_pending
do_notify_resume
schedule()
*/
arm64中syscall通过同步异常el0_sync->el0_svc->el0_svc_handler处理完成后,ret_to_user返回用户空间。
参考中断返回用户空间。
主动调度:
当进程阻塞时,例如mutex,sem,waitqueue获取不到资源,在或者进程进行磁盘读写。这种情况下进程会将自己的状态从TASK_RUNNING修改为TASK_INTERRUPTIBLE或是TASK_UNINTERRUPTIBLE,然后调用schedule()主动让出CPU并等待唤醒。
/*以磁盘IO中的一处使用举例:
文件:fs/direct-io.c
*/
static struct bio *dio_await_one(struct dio *dio)
{
....
while (dio->refcount > 1 && dio->bio_list == NULL) {
__set_current_state(TASK_UNINTERRUPTIBLE); //先将当前进程的state设置为TASK_UNINTERRUPTIBLE不可唤醒状态。
dio->waiter = current;
spin_unlock_irqrestore(&dio->bio_lock, flags);
if (!(dio->iocb->ki_flags & IOCB_HIPRI) ||
!blk_poll(dio->bio_disk->queue, dio->bio_cookie, true))
io_schedule(); //调用io_schedule让出cpu,并等待io完成后被唤醒。
/* wake up sets us TASK_RUNNING */
.....
}
关于周期性调度器的简要说明:
前文我们说到,执行抢占时会检查TIF_NEED_RESCHED标志,以判断是否需要执行抢占。那TIF_NEED_RESCHED在什么地方被设置呢?其一是在进程被wakeup的时候,再一地方就是在周期性调度器(scheduler_tick)中。
时钟tick以每秒HZ次的频率发生,在时钟tick的中断处理函数里,会去调用周期性调度器scheduler_tick()检查并设置TIF_NEED_RESCHED和need_resched。而这本身就在一个中断里,当中断返回时又回去检查执行抢占。
/*函数调用:
.....
tick_sched_timer()
tick_sched_handle()
update_process_times()
scheduler_tick()
task_tick_fair()
entity_tick()
resched_curr()
set_tsk_need_resched(curr); //设置TIF_NEED_RESCHED
set_preempt_need_resched(); //设置need_resched,即PREEMPT_NEED_RESCHED
*/
scheduler_tick(){
sched_clock_tick(); //更新系统的时钟ticket
update_rq_lock(rq); //更新当前cpu就需队列中的时钟计数,struct rq中包含clock和clock_task两个runqueue的时间
curr->sched_class->task_tick(rq,curr,0); //调用对应调度类中的task_tick方法(每个调度类都有实现自己的task_tick)
//重点关注cfs中实现的task_tick_fair->entity_tick;分析如下面代码
//entity_tick中会检查是否需要抢占调度
cpu_load_update_active(rq);
trigger_load_balance(rq); //若是smp架构的话会去调负载均衡
}
void resched_curr(struct rq *rq)
{
.....
if (cpu == smp_processor_id()) {
set_tsk_need_resched(curr); //设置TIF_NEED_RESCHED
set_preempt_need_resched(); //设置need_resched,即PREEMPT_NEED_RESCHED
return;
}
......
}
转自:https://zhuanlan.zhihu.com/p/163728119
【转】【进程管理】Linux进程调度:调度时机的更多相关文章
- Linux进程管理 (2)CFS调度器
关键词: 目录: Linux进程管理 (1)进程的诞生 Linux进程管理 (2)CFS调度器 Linux进程管理 (3)SMP负载均衡 Linux进程管理 (4)HMP调度器 Linux进程管理 ( ...
- Linux进程管理 (7)实时调度
关键词:RT.preempt_count.RT patch. 除了CFS调度器之外,还包括重要的实时调度器,有两种RR和FIFO调度策略.本章只是一个简单的介绍. 更详细的介绍参考<Linux进 ...
- Linux进程管理 (9)实时调度类分析,以及FIFO和RR对比实验
关键词:rt_sched_class.SCHED_FIFO.SCHED_RR.sched_setscheduler().sched_setaffinity().RR_TIMESLICE. 本文主要关注 ...
- Linux内核——进程管理之CFS调度器(基于版本4.x)
<奔跑吧linux内核>3.2笔记,不足之处还望大家批评指正 建议阅读博文https://www.cnblogs.com/openix/p/3262217.html理解linux cfs调 ...
- [进程管理]Linux进程状态解析之T、Z、X
Linux进程状态:T (TASK_STOPPED or TASK_TRACED),暂停状态或跟踪状态. 向进程发送一个SIGSTOP信号,它就会因响应该信号而进入 ...
- OS之进程管理---实时CPU调度
引言 一般来说,我们将实时操作系统区分为软实时系统(soft real-time system)和硬实时系统(hard real-time system).软实时系统不保证会调度关键实时进程,而只保证 ...
- 【读书笔记】《Linux内核设计与实现》进程管理与进程调度
大学跟老师做嵌入式项目,写过I2C的设备驱动,但对Linux内核的了解也仅限于此.Android系统许多导致root的漏洞都是内核中的,研究起来很有趣,但看相关的分析文章总感觉隔着一层窗户纸,不能完全 ...
- [进程管理]Linux进程状态解析之R、S、D
Linux是一个分时操作系统,能够在一个cpu上运行多个程序,每个被运行的程序实例对应一个或多个进程,这里介绍一下Linux进程状态. Linux是一个多用户,多任务的系统,可以同时运行多个用户的多个 ...
- [进程管理]linux 下 进程和线程的区别(baidu 面试)
进程是程序执行时的一个实例,即它是程序已经执行到课中程度的数据结构的汇集.从内核的观点看,进程的目的就是担当分配系统资源(CPU时间.内存等)的基本单位. 线程是进程的一个执行流,是CPU调度和分派的 ...
- [进程管理] Linux中Load average的理解
Load average的定义 系统平均负载被定义为在特定时间间隔内运行队列中的平均进程树.如果一个进程满足以下条件则其就会位于运行队列中: - 它没有在等待I/O操作的结果 - 它没有主动进入等待状 ...
随机推荐
- 物料Classification 分类系统
作用:可以追加物料的属性,因为在物料主界面字段是有限的,并且并不是符合所有企业的业务,可以使用追加属性的方式给物料添加各式各样的属性 1.创建特性,Tcode:CT04 2.创建分类 Tcode:CL ...
- wrf-python离线安装
由于客户环境不能联网,python的插件库只能离线安装,wrf库的安装中踩了不少坑,特此记录. 1.官方插件库pypi.org只有压缩包,没有提供wheel,在线安装没有问题. 2.下载压缩包解压后, ...
- object-fit: cover;
加上之后,改变宽高,图片还是原来的比例 没加上的话,改变宽高,图片会跟着伸缩变形.
- 【C++复习】第八章 多态性(1)(多态类型、运算符重载)
1.多态性 1.1 什么是多态? 多态是指相同消息被不同对象接收后导致不同的行为,所谓消息是指对类成员函数的调用,不同的行为是指不同的实现,也就是调用了不同的函数. 消息在C++编程中指的是对类的成员 ...
- git常用命令与AndroidStudio常用快捷键
git相关内容: 产生密钥:cd ~/.ssh (C:\Users\账户名称\.ssh)生成密钥:ssh-keygen -t rsa -C "your_email@youremail.com ...
- Es6中模块引入的相关内容
注意:AMD规范和commonJS规范 1.相同点:都是为了模块化. 2.不同点:AMD规范则是非同步加载模块,允许指定回调函数.CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行 ...
- 2. Marker 标记(就是在地图上放上标记)
1 <!DOCTYPE html> 2 <html lang="zh"> 3 <head> 4 <meta charset="U ...
- C++实现单链表相关操作
#include<iostream>#include<cstdlib>//C++动态分配存储空间using namespace std;#define OK 1#define ...
- Selector 如何关联 channel,以及需要注意的点
一.创建 selector Selector selector = Selector.open(); 1.一个 selector 可以管理多个 channel . 二.channel 如何注册到 se ...
- WindowsServer2012搭建FTP服务器站点
公司需要搭建一个FTP服务器给银行推送账单,这个文章整理的比较详细,可以参考 数据来源: https://blog.csdn.net/u010483330/article/details/125931 ...