第一部分 课本学习

进程的切换和系统的一般执行过程

  • 进程调度的时机

    Linux内核系统通过schedule函数实现进程调度,进程调度的时机就是内核调用schedule函数的时机。当内核即将返回用户空间时,内核会检查need_resched标志是否设置。如果设置,则调用schedule函数,此时是从中断(异常/系统调用)处理程序返回用户空间的时间点作为一个固定的调度时机点。

    简单总结进程调度时机如下:

    用户进程通过特定的系统调用主动让出CPU

    中断处理程序在内核返回用户态时进行调度。

    内核线程主动调用schedule函数让出CPU。

    中断处理程序主动调用schedule函数让出CPU,涵盖以上第一种和第二种情况。

  • 调度策略与算法

    调度策略:Linux系统中常用的几种调度策略为SCHED_NORMAL、SCHED_FIFO、SCHED_RR。其中SCHED_NORMAL是用于普通进程的调度类,而SCHED_FIFO和SCHED_RR是用于实时进程的调度类,优先级高于SCHED_NORMAL。内核中根据进程的优先级来区分普通进程和实时进程,Linux内核进程优先级为0139,数值越高,优先级越低,0为最高有限级。实时进程的优先级取值为099;而普通进程只具有nice值,nice值映射到优先级为100~139。子进程会继承父进程的优先级。

    CFS调度算法:CFS即为完全公平调度算法,其基本原理是基于权重的动态优先级调度算法。每个进程使用CPU的顺序由进程已使用的CPU虚拟时间(vruntime)决定,已使用的虚拟时间越少,进程排序就越靠前,进程再次被调度执行的概率就越高。每个进程每次占用CPU后能够执行的时间(ideal_runtime)由进程的权重决定,并且保证在某个时间周期(__sched_period)内运行队列里的所有进程都能够至少被调度执行一次。

  • 进程上下文切换

    在实际代码中,每个进程切换基本由两个步骤组成。

    切换页全局目录(CR3)以安装一个新的地址空间,这样不同进程的虚拟地址如0x8048400就会经过不同的页表转换为不同的物理地址。

    切换内核态堆栈和硬件上下文,因为硬件上下文提供了内核执行新进程所需要的所有信息,包含CPU寄存器状态

  • Linux系统的运行过程

    最基本和一般场景是:正在运行的用户态进程X切换到用户态进程Y的过程。具体过程如下。

    1、 正在运行的用户态进程X。

    2、发生中断(包括异常、系统调用等)。

    3、SAVE_ALL,保存现场,此时完成了中断上下文切换,即从进程X的用户态到进程X的内核态。

    4、中断处理过程中或中断返回前调用了schedule函数,其中的switch_to做了关键的进程上下文切换。

    5、标号1,之后开始运行用户态进程Y(这里Y曾经通过以上步骤被切换出去过因此可以从标号1继续执行)。

    6、restore_all,恢复现场,与3中保存现场相对应。

    7、iret - pop cs:eip/ss:esp/eflags,从Y进程的内核堆栈中弹出2中硬件完成的压栈内容。此时完成了中断上下文的切换,即从进程Y的内核态返回到进程Y的用户态。

    8、继续运行用户态进程Y。

  • Linux系统架构与执行过程概览

    Linux系统的整体架构如图所示:



    Ls命令执行过程示意图如下:

第二部分 代码分析

  • context_switch代码
static inline void context_switch(struct rq *rq, struct task_struct *prev, struct task_struct *next)
{
struct mm_struct *mm, *oldmm; prepare_task_switch(rq, prev, next); mm = next->mm;
oldmm = prev->active_mm;
/*
* For paravirt, this is coupled with an exit in switch_to to
* combine the page table reload and the switch backend into
* one hypercall.
*/
arch_start_context_switch(prev); if (!mm) { //如果被切换进来的进程的mm为空切换,内核线程mm为空
next->active_mm = oldmm; //将共享切换出去的进程的active_mm
atomic_inc(&oldmm->mm_count); //有一个进程共享,所有引用计数加一
enter_lazy_tlb(oldmm, next); //普通mm不为空,则调用switch_mm切换地址空间
} else
switch_mm(oldmm, mm, next); if (!prev->mm) {
prev->active_mm = NULL;
rq->prev_mm = oldmm;
}
/*
* Since the runqueue lock will be released by the next
* task (which is an invalid locking op but in the case
* of the scheduler it's an obvious special-case), so we
* do an early lockdep release here:
*/
spin_release(&rq->lock.dep_map, 1, _THIS_IP_); context_tracking_task_switch(prev, next);
// 这里切换寄存器状态和栈
switch_to(prev, next, prev); barrier();
/*
* this_rq must be evaluated again because prev may have moved
* CPUs since it called schedule(), thus the 'rq' on its stack
* frame will be invalid.
*/
finish_task_switch(this_rq(), prev);
}
  • switch_to代码
#define switch_to(prev, next, last) //prev指向当前进程,next指向被调度的进程
do { unsigned long ebx, ecx, edx, esi, edi; asm volatile("pushfl\n\t" //把prev进程的flag保存到prev进程的内核堆栈中
"pushl %%ebp\n\t" //把prev进程的基址ebp保存到prev进程的内核堆栈中 "movl %%esp,%[prev_sp]\n\t"//保存ESP
"movl %[next_sp],%%esp\n\t"//更新ESP,将下一栈顶保存到ESP中 "movl $1f,%[prev_ip]\n\t"//保存当前进程EIP*
"pushl %[next_ip]\n\t"//把next进程起点压入next进程的内核堆栈栈顶
__switch_canary
"jmp __switch_to\n"//prev进程中设置next进程堆栈
//jmp不同于call,是通过寄存器传递参数,而不是通过堆栈传递参数,所以ret时弹出的是之前压入栈顶的next进程起点
//wancheng EIP的切换
"1:\t"
"popl %%ebp\n\t"
"popfl\n" /* output parameters */
: [prev_sp] "=m"(prev->thread.sp), //保存prev进程的esp
[prev_ip] "=m"(prev->thread.ip), //保存prev进程的eip
"=a" (last), /* clobbered output registers: */
"=b" (ebx), "=c"(ecx), "=d" (edx),
"=S" (esi), "=D"(edi) __switch_canary_oparam /* input parameters: */
: [next_sp] "m" (next->thread.sp), //next进程内核堆栈栈顶地址,即esp
[next_ip] "m" (next->thread.ip), //next进程的原eip /* regparm parameters for __switch_to():*/
//jmp通过eax寄存器和edx寄存器传递参数
[prev] "a" (prev),
[next] "d" (next) __switch_canary_iparam : /* 重新加载段寄存器
"memory");
} while (0)

第三部分 实验楼实验

在实验楼中配置运行MenuOS系统:





配置gdb断点:



开始执行,分别停在各断点处:





Schedule函数的作用非常重要,是进程调度的主体函数。其中pick_next_task函数是schedule函数中重要的函数,负责根据调度策略和调度算法选择下一个进程,context_switch函数是schedule函数中实现进程切换的函数,switch_to是context_switch函数中进行进程关键上下文切换的函数。由于switch_to内部是内嵌汇编代码,无法跟踪调试。

2018-2019-1 20189203《Linux内核原理与分析》第九周作业的更多相关文章

  1. 2019-2020-1 20199303<Linux内核原理与分析>第二周作业

    2019-2020-1 20199303第二周作业 1.汇编与寄存器的学习 寄存器是中央处理器内的组成部份.寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令.数据和位址.在中央处理器的控制部件中 ...

  2. 20169219 linux内核原理与分析第二周作业

    "linux内核分析"的第一讲主要讲了计算机的体系结构,和各寄存器之间对数据的处理过程. 通用寄存器 AX:累加器 BX:基地址寄存器 CX:计数寄存器 DX:数据寄存器 BP:堆 ...

  3. 2019-2020-1 20199314 <Linux内核原理与分析>第二周作业

    1.基础学习内容 1.1 冯诺依曼体系结构 计算机由控制器.运算器.存储器.输入设备.输出设备五部分组成. 1.1.1 冯诺依曼计算机特点 (1)采用存储程序方式,指令和数据不加区别混合存储在同一个存 ...

  4. Linux内核原理与分析-第一周作业

    本科期间,学校开设过linux相关的课程,当时的学习方式主要以课堂听授为主.虽然老师也提供了相关的学习教材跟参考材料,但是整体学下来感觉收获并不是太大,现在回想起来,主要还是由于自己课下没有及时动手实 ...

  5. 2019-2020-1 20199314 <Linux内核原理与分析>第一周作业

    前言 本周对实验楼的Linux基础入门进行了学习,目前学习到实验九完成到挑战二. 学习和实验内容 快速学习了Linux系统的发展历程及其简介,学习了下的变量.用户权限管理.文件打包及压缩.常用命令的和 ...

  6. Linux内核原理与分析-第二周作业

    写之前回看了一遍秒速五厘米:如果

  7. 2018-2019-1 20189221《Linux内核原理与分析》第一周作业

    Linux内核原理与分析 - 第一周作业 实验1 Linux系统简介 Linux历史 1991 年 10 月,Linus Torvalds想在自己的电脑上运行UNIX,可是 UNIX 的商业版本非常昂 ...

  8. 2020-2021-1 20209307 《Linux内核原理与分析》第九周作业

    这个作业属于哪个课程 <2020-2021-1Linux内核原理与分析)> 这个作业要求在哪里 <2020-2021-1Linux内核原理与分析第九周作业> 这个作业的目标 & ...

  9. 2019-2020-1 20199329《Linux内核原理与分析》第十三周作业

    <Linux内核原理与分析>第十三周作业 一.本周内容概述 通过重现缓冲区溢出攻击来理解漏洞 二.本周学习内容 1.实验简介 注意:实验中命令在 xfce 终端中输入,前面有 $ 的内容为 ...

  10. 2019-2020-1 20199329《Linux内核原理与分析》第十二周作业

    <Linux内核原理与分析>第十二周作业 一.本周内容概述: 通过编程理解 Set-UID 的运行机制与安全问题 完成实验楼上的<SET-UID程序漏洞实验> 二.本周学习内容 ...

随机推荐

  1. 在vue2中隐藏elementUI的tab栏

    <template> <el-tabs v-model="activeName" @tab-click="handleClick" ref=& ...

  2. Service的绑定过程

    --摘自<Android进阶解密> 第一步:ContextImpl到AMS的调用过程 第二步:Service的绑定过程 1)几个与Service相关的对象类型 * ServiceRecor ...

  3. aspnet mvc 中 跨域请求的处理方法

    ASP.NET 处理跨域的两种方式    方式1,后端程序处理.原理:给响应头加上允许的域即可,*表示允许所有的域                 定义一个cors的过滤器 加在在action或者co ...

  4. 第二篇flask响应方式

    响应三件套 1.Flask中的HTTPResponse @app.route('/home') # app中的route装饰器 def home(): # 视图 return '登陆成功' #HTTP ...

  5. C#超简单自定义事件

    我知道你为啥点进来,所以不要犹豫了,立刻马上果断创建控制台项目,直接复制下面精干短小而又强大的代码运行: using System; using System.Collections.Generic; ...

  6. JS编码方式

    1. escape(): 采用unicode字符集对指定的字符串除0-255以外进行编码.所有的空格符.标点符号.特殊字符以及更多有联系非ASCII字符都将被转化成%xx格式的字符编码(xx等于该字符 ...

  7. (98)Wangdao.com_第三十天_拖拉事件

    拖拉事件 拖拉 drag ,是指用户在某个对象上按下鼠标键不放,拖动它到另一个位置,然后释放鼠标键,将该对象放在那里. 一旦某个元素节点的 draggable 属性设为true,就无法再用鼠标选中该节 ...

  8. 微信公众号_订阅号_微信JS-SDK网页开发

    微信JS-SDK( 分享接口很常用 ) 是微信公众平台 面向网页开发者提供的基于微信内的网页开发工具包 借助微信高效使用相机.语音.选题.位置等手机系统的能力,同时可以直接使用微信分享.扫一扫.支付等 ...

  9. Restful概念的理解和践行

    在实习是leader有让我们实习生看restful相关的知识,奈何当初根基很浅,看了一篇博文,但是还是很难用自己的话来描述.现在又接触了Restful的内容,就补上一篇文章. 在Spring Fram ...

  10. centos7安装python3 以及tab补全功能

    1.安装python3 1.1下载python源码包 网址:https://www.python.org/downloads/release/python-362/ 下载地址:https://www. ...