Schedule()调度函数源码解析与总结 您所在的位置:网站首页 linux线程调度源码 Schedule()调度函数源码解析与总结

Schedule()调度函数源码解析与总结

2024-01-14 19:02| 来源: 网络整理| 查看: 265

目录 提示一、schedule()内核源码及注释分析二、Schedule()调度函数整理分析结论

提示

本篇博客是参考调度程序schedule()注释,结合我个人查询到的其他知识以及个人理解完成的。由于本人知识储备有限,其中的注释及理解是否正确我自己也不知道,只能作为参考,有问题的地方欢迎大家指出。

一、schedule()内核源码及注释分析 // 通过schedule函数,完成对进程的调度 asmlinkage void schedule(void) { //声明调度数据结构体变量:sched_data struct schedule_data * sched_data; // 声明进程控制块PCB结构体变量:上一个PCB prev,下一个PCB next,指针p struct task_struct *prev, *next, *p; //声明表头结构体:tmp struct list_head *tmp; // 声明两个整型:当前CPU this_cpu,时间片 c int this_cpu, c; // =============== 处理使用完cpu的当前进程current =============== // #### 锁运行队列,关中断 #### // ① 运行队列:即就绪队列,Linux中称为run queue; // ② 锁运行队列:将运行的队列上锁,此期间只能由某一个程序执行操作, // 避免多个程序同时对队列进行操作,造成数据污染产生差错; // ③ 关中断:关闭中断信号产生的程序,主要用于在此期间执行 // 一些不能被中断信号打断的程序(开中断,即打开产生中断信号的程序)。 spin_lock_prefetch(&runqueue_lock); // #### 将当前运行的进程调度出去 #### // 正在运行的而进程采用current宏表示 // ① 如果active_mm没有借用当前进程的mm,则执行报错函数bug() // (内核线程没有mm空间,但其active_mm会借用当前进程的mm,保持与用户线程处理的统一性) if (!current->active_mm) BUG(); // 将当前正在运行的进程调度出去,current变为prev,变为上一个刚刚用完CPU的PCB(即成为过去式) prev = current; //再获取上次运行进程所用的cpu this_cpu = prev->processor; // #### 如果内核处于执行中断处理程序中(可能性小),则报错 #### // ① 中断中,即内核执行中断处理程序的期间,若允许进程调度,那么当中断处理程序运行时,进行了内核抢占, // 那么处理器就会去执行抢占的进程,这个进程的具体信息处理器能够通过进程控制块去找到; // ② unlikely()这种设计的原因:注意:unlikely用于gcc>=2.96之后的编译优化,表示if内代码运行的可能性比较低, // 这样编译器就可以将else里面的代码提前,cpu在进行指令预取方面有性能提高,反之,likely则是if内代码运行可能性高。 if (unlikely(in_interrupt())) { // 提示 printk("Scheduling in interrupt\n"); //报错,提示出问题 BUG(); } // #### 释放上次运行的进程prev所占用的资源 #### // ① 如果之前运行的进程prev占用了全局内核锁,释放; // ② 如果当前cpu占用了全局中断锁,释放;开当前cpu中断线 release_kernel_lock(prev, this_cpu); // #### 对sched_data进行事实保护,每个CPU只能运行一个进程 #### sched_data = &aligned_data[this_cpu].schedule_data; // #### 锁运行队列,关中断 #### spin_lock_irq(&runqueue_lock); // #### 将耗尽时间片的上次运行进程prev移动到运行队列最后 #### //如果是实时进程(小概率事件) if (unlikely(prev->policy == SCHED_RR)) //如果时间片已经用完 if (!prev->counter) { //将nice转换为时间片 //【nice为UNIX时期沿用的负向优,向优先级,取值-20~19,值越大越谦让,值越小,优先级越高】 prev->counter = NICE_TO_TICKS(prev->nice); //将其移动到运行队列尾部 move_last_runqueue(prev); } // #### 根据上次进程prev的状态,给予对应操作 #### //prev->state:获取进程状态 switch (prev->state) { // 若进程状态处于TASK_INTERRUPTIBLE浅度睡眠,则执行下方代码(浅度睡眠,即可中断的睡眠) case TASK_INTERRUPTIBLE: //如果该进程接收到唤醒信号 if (signal_pending(prev)) { //让该进程状态置为TASK_RUNNING(就绪态) prev->state = TASK_RUNNING; break; } // 如果该进程处于其他状态,如:TASK_STOPED(暂停),TASK_ZOMBE(死亡但用户未注销), // TASK_UNINNTERRUPTIBLE(深度睡眠,不可中断的睡眠)状态,就执行如下代码。 default: del_from_runqueue(prev);//比如调用exit(),wait4()等 //如果进程处于就绪态,则不做任何操作,让其等待执行即可 case TASK_RUNNING:; } // #### 清空need_resched #### // ① need_resched条件:如果为真, 内核会重新进程一次调度 prev->need_resched = 0; // =============== 调度一个进程来使用CPU =============== repeat_schedule: // #### 选择即将使用CPU的进程next #### //获得空闲进程 next = idle_task(this_cpu); //找最大值的常用初始化 c = -1000; //遍历运行队列 list_for_each(tmp, &runqueue_head) { p = list_entry(tmp, struct task_struct, run_list); //如果程序可以在cpu上跑,并且允许在这颗cpu上跑 if (can_schedule(p, this_cpu)) { //获取调度权重 int weight = goodness(p, this_cpu, prev->active_mm); //更新最大权重与选中进程 if (weight > c) c = weight, next = p; } } // #### 如果c==0,说明所有进程时间片用完了,可能性很小 #### // ① 个人理解:c==0时,!c为1即为真;unlikely是一种对发生可能性的描述,unlikely(!c), // 表示!c为真即c==0的概率是unlikely的,就是很小概率,几乎不可能的,但是如果真出现这种可能了,则执行if里的语句。 if (unlikely(!c)) { struct task_struct *p; //开运行队列锁,开中断 spin_unlock_irq(&runqueue_lock); //锁住进程双向链表 read_lock(&tasklist_lock); //更新每个进程的时间片 for_each_task(p) p->counter = (p->counter >> 1) + NICE_TO_TICKS(p->nice); //开进程双向链表 read_unlock(&tasklist_lock); //锁运行队列,关中断 spin_lock_irq(&runqueue_lock); //再次寻找最值得调度的进程 goto repeat_schedule; } // #### 将选择好的进程切换来使用CPU #### //将当前运行的进程指向为我们选择好的next进程 sched_data->curr = next; //将正在使用CPU的进程以及CPU内核更新为所选择的进程next和this_cpu task_set_cpu(next, this_cpu); //解锁运行队列,开中断 spin_unlock_irq(&runqueue_lock); // #### 如果选择到的进程仍为之前的进程(认为可能性很小) #### if (unlikely(prev == next)) { prev->policy &= ~SCHED_YIELD; // 执行“处理相同进程”的方法 goto same_process; } // =============== 维护每个流程的“last schedule”值 =============== //(即使我们重新调度到同一进程,也必须重新计算)目前,这仅用于SMP,而且是近似值,因此我们不必在保持运行队列自旋锁的同时维护它。 #ifdef CONFIG_SMP //更新调度进程时的时钟,用于smp中另外一个cpu调度参考 sched_data->last_schedule = get_cycles(); /* * 我们提前删除了调度程序锁(它是一个全局自旋锁),因此我们必须在switch_to()期间锁定前一个进程,以避免重新调度。 */ #endif /* CONFIG_SMP */ //记录调度次数 // 有3个进程受上下文切换的影响:prev == .... ==> (last => next) // 下一个堆栈上的是“更多以前的”“prev”,但通过switch_to()将prev设置为(刚才运行的)“last”进程。这听起来可能有点令人困惑,但很有道理。 kstat.context_swtch++; prepare_to_switch(); { //新进程的运行空间 struct mm_struct *mm = next->mm; //原进程的运行空间 struct mm_struct *oldmm = prev->active_mm; //如果新进程没有运行空间,则是内核进程 if (!mm) { //内核进程在调度出去的时候会释放其借用的运行空间,如果此处仍然存在,则有问题 if (next->active_mm) BUG(); //借用原进程的运行空间 next->active_mm = oldmm; //原进程运行空间计数加1,用于内存交换信息 atomic_inc(&oldmm->mm_count); //tlb采用lazy刷新方式 enter_lazy_tlb(oldmm, next, this_cpu); } //反之,如果是用户进程 else { //用户进程的两个运行空间应该相同,如果不同则报错 if (next->active_mm != mm) BUG(); //切换用户空间 switch_mm(oldmm, mm, next, this_cpu); } //如果原进程是内核进程 if (!prev->mm) { //释放其引用的运行空间 prev->active_mm = NULL; //运行空间计数-1 mmdrop(oldmm); } } //切换寄存器状态与堆栈 switch_to(prev, next, prev); //原进程放入运行队列尾部 __schedule_tail(prev); // =============== 处理调度后仍为相同进程 =============== same_process: //针对smp,要将当前进程的内核深度清0 reacquire_kernel_lock(current); //再次调度 if (current->need_resched) goto need_resched_back; return; } 二、Schedule()调度函数整理分析结论

参考连接: Linux进程调度时机Schedule函数解析 linux调度子系统8 - schedule函数



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有