linux内核调度浅析

目录

进程控制块PCB

就绪队列结构体

调度队列成员

下一个进程的选择

进程切换

加入就绪队列


        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内核调度浅析_第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状态,添加到就绪队列

 参考

https://course.0voice.com/v1/course/intro?courseId=2&agentId=0

你可能感兴趣的:(linux内核分析,linux内核,调度)