linux内核调度浅析
Posted 为了维护世界和平_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux内核调度浅析相关的知识,希望对你有一定的参考价值。
目录
linux进程调度相关的知识再重新梳理一遍。抽取主要数据结构中的主要成员,以最简单的方式实现进程调度。
进程控制块PCB
task_struct
/* 进程PCB */
struct task_struct
enum task_state state;
enum task_flags flags;
int pid;
struct cpu_context cpu_context;
struct list_head run_list;
int counter;
int priority;
int need_resched;
int preempt_count;
struct task_struct *next_task;
struct task_struct *prev_task;
;
state进程的状态
flags进程的标志位
pid 进程的ID
cpu_context 进程的上下文信息,切换信息时需要保存与恢复
counter 进程调度时间片
prioity 进程优先级
need_resched是否需要调度,在时间片用完的时候会置位need_resched
next_task,prev_task 在队列中的进程,next_task是下一个进程,prev_task是上一个进程。
就绪队列结构体
进程添加到调度器中,需要借助与就绪队列,队列采用链式队列。
struct list_head
struct list_head *next, *prev;
;
struct run_queue
struct list_head rq_head;
unsigned int nr_running;
u64 nr_switches;
struct task_struct *curr;
;
rq_head队列的头
nr_running 队列中进程的数量
nr_switches 统计计数,统计进程切换次数
curr当前进程
调度队列成员
struct sched_class
const struct sched_class *next;
void (*task_fork)(struct task_struct *p);
void (*enqueue_task)(struct run_queue *rq, struct task_struct *p);
void (*dequeue_task)(struct run_queue *rq, struct task_struct *p);
void (*task_tick)(struct run_queue *rq, struct task_struct *p);
struct task_struct * (*pick_next_task)(struct run_queue *rq,
struct task_struct *prev);
;
next指向下一个调度类
task_fork,进程创建时,对进程做调度相关的初始化
enqueue_task 加入就绪队列
dequeue_task 移除就绪队列
task_tick 与调度相关的时钟中断,在定时器中周期调度,用于维护时间变量counter。
pick_next_task选择下一个进程
enqueue_task的实现
static void enqueue_task_simple(struct run_queue *rq,
struct task_struct *p)
list_add(&p->run_list, &rq->rq_head);
rq->nr_running++;
调用list_add方法 ,将task_struct 加入到run_queue里;将nr_running加1
以上结构体之间的关系
下一个进程的选择
linux 0.11中的调度算法
static struct task_struct *pick_next_task_simple(struct run_queue *rq,
struct task_struct *prev)
struct task_struct *p, *next;
struct list_head *tmp;
int weight;
int c;
repeat:
c = -1000;
//循环遍历就绪队列,找出时间片最大的进程作为next进程
list_for_each(tmp, &rq->rq_head)
p = list_entry(tmp, struct task_struct, run_list);
weight = goodness(p);//获取每个进程的权重
if (weight > c)
c = weight;//权重跟新
next = p;//选择下一个要运行的进程
if (!c)
reset_score();
goto repeat;
return next;
调度场景
1、自愿调度:进程调用seched()主动放弃CPU控制权
static void __schedule(void)
struct task_struct *prev, *next, *last;
struct run_queue *rq = &g_rq;
prev = current;
/* 检查是否在中断上下文中发生了调度 */
schedule_debug(prev);
/* 关闭中断包含调度器*/
raw_local_irq_disable();
if (prev->state)
dequeue_task(rq, prev);
next = pick_next_task(rq, prev);
clear_task_resched(prev);
if (next != prev)
last = switch_to(prev, next);
rq->nr_switches++;
rq->curr = current;
schedule_tail(last);
//自愿调度的入口,先关闭抢占,避免发生嵌套抢占
void schedule(void)
preempt_disable();
__schedule();
preempt_enable();
- 如果prev->state为TASK_RUNNING (=0)说明当前进程在运行,发生抢占调用。
- 如果当前进程处于其他状态(TASK_UNINTERRUPTIBLE,TASK_INTERRUPTIBLE),说明主动请求调度。
- 如果主动调度了,则dequeue_task函数把当前进程移除就绪队列。什么时候加入就绪队列,下文分析。
- 选择下一个进程pick_next_task,就是上文介绍的pick_next_task_simple
- 清除当前进程的一些状态
- 如果下一个进程不是当前进程,则需要切换switch_to
- 调度收尾schedule_tail 打开本地中断
2、抢占调度:中断处理后会检查是否可以抢占当前进程的运行。在汇编程序中
.align 2
el1_irq:
kernel_entry 1
bl irq_handle
get_thread_info tsk
ldr w24, [tsk, #TI_PREEMPT]
cbnz w24, 1f
ldr w0, [tsk, #NEED_RESCHED]
cbz w0, 1f
bl el1_preempt
1:
kernel_exit 1
el1_preempt:
mov x24, lr
bl preempt_schedule_irq
ret x24
在中断处理完成后,调用get_thread_info宏来获取当前进程的task_struct数据结构。
读取进程抢占计数preempt_count值,如果大于0,说明是禁止抢占的,退出中断现场;否则允许抢占,读取need_resched值,来判断当前进程是否要抢占;如果need_resched为1,则进行抢占调度。
进程切换
switch_to(prev, next)
.align
.global cpu_switch_to
cpu_switch_to:
add x8, x0, #THREAD_CPU_CONTEXT
mov x9, sp
stp x19, x20, [x8], #16
stp x21, x22, [x8], #16
stp x23, x24, [x8], #16
stp x25, x26, [x8], #16
stp x27, x28, [x8], #16
stp x29, x9, [x8], #16
str lr, [x8]
add x8, x1, #THREAD_CPU_CONTEXT
ldp x19, x20, [x8], #16
ldp x21, x22, [x8], #16
ldp x23, x24, [x8], #16
ldp x25, x26, [x8], #16
ldp x27, x28, [x8], #16
ldp x29, x9, [x8], #16
ldr lr, [x8]
mov sp, x9
ret
- 保存进程上下文:需要保存x19~x29,sp,lr 寄存器值到 task_struct->cpu_context
- 恢复next进程 .
x0寄存器是prev参数;x1寄存器是next参数
加入就绪队列
在创建进程的时候 fork->wake_up_process
void wake_up_process(struct task_struct *p)
struct run_queue *rq = &g_rq;
p->state = TASK_RUNNING;
enqueue_task(rq, p);
设置TASK_RUNNING状态,添加到就绪队列
以上是关于linux内核调度浅析的主要内容,如果未能解决你的问题,请参考以下文章