目录
进程控制块PCB
就绪队列结构体
调度队列成员
下一个进程的选择
进程切换
加入就绪队列
linux进程调度相关的知识再重新梳理一遍。抽取主要数据结构中的主要成员,以最简单的方式实现进程调度。
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();
}
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
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状态,添加到就绪队列
参考
https://course.0voice.com/v1/course/intro?courseId=2&agentId=0