课本:第八章 进程的切换和系统的一般执行过程

  • 进行进程调度的时机

    • Linux内核通过schedule函数实现进程调度,schedule函数在运行队列中找到一个进程,把CPU分配给它
    • 调用schedule函数一次就是调度一次,调用schedule函数的时候就是进程调度的时机
    • 当内核即将返回用户空间时,内核会检查need_resched标志是否设置,若设置,则调用schedule函数,此时是从中断处理程序返回用户空间的时间点作为一个固定的调度时机点
    • 内核线程是一个特殊进程,只有内核态没有用户态,内核线程和中断处理程序中任何需要暂时中止当前执行路径的位置都可以直接调用schedule()
    • 内核线程作为一类的特殊的进程可以主动调用schedule函数让出CPU,也可以被动调度
  • 上下文
    • 进程切换,又称上下文切换,是指为了控制进程执行,内核必须有能力挂起正在CPU上执行的进程,恢复以前挂起的某个进程的执行
    • 内核线程以进程上下文的形式运行在内核空间中
    • 每个进程可以拥有属于自己的地址空间,但所有进程必须共享CPU及寄存器,所以在恢复一个进程执行之前,内核必须确保每个寄存器装入了挂起进程时的值
    • 进程恢复执行前必须装入寄存器的一组数据,称为硬件上下文
    • 进程上下文包含了进程执行需要的所有信息
      • 用户地址空间:包括程序代码、数据、用户堆栈等
      • 控制信息:进程描述符、内核堆栈等
      • 硬件上下文,相关寄存器的值
    • 进程切换就是更变进程上下文,最核心的是几个关键寄存器的保存与变换
      • CR3寄存器代表进程页目录表,即地址空间、数据
      • ESP寄存器(内核态时)代表进程内核堆栈,struct thread、PCB、内核堆栈存储于连续8KB区域中,通过ESP获取地址
      • EIP寄存器及其他寄存器代表进程硬件上下文,即要执行的下条指令及环境
      • 这些寄存器从一个进程的状态切换到另一个进程的状态,进程切换就算完成了
    • 实际代码中,每个进程切换基本由两个步骤组成
      • 切换页全局目录CR3以安装一个新的地址空间,这样不同进程的虚拟地址如0x8048400就会经过不同的页表转换为不同的物理地址
      • 切换内核态堆栈和硬件上下文,因为硬件上下文提供了内核执行新进程所需要的所有信息,包含CPU寄存器状态
  • 进程切换的过程(正在运行的用户态进程X切换到用户态进程Y的过程)
    • 正在运行的用户态进程X
    • 发生中断(包括异常、系统调用等),硬件完成以下动作
      • save cs:eip/ss:esp/eflags:当前CPU上下文压入用户态进程X的内核堆栈
      • load cs:eip(entry of a specific ISR) and ss:esp(point to kernel stack):加载当前进程内核堆栈相关信息,跳转到中断处理程序,即中断执行路径的起点
    • SAVE_ALL,保存现场,此时完成了中断上下文切换,即从进程X的用户态到进程X的内核态
    • 中断处理过程中或中断返回前调用了schedule函数,其中switch_to做了关键的进程上下文切换。将当前用户进程X的内核堆栈切换到选出的next进程(此处为进程Y)的内核堆栈,并完成了进程上下文所需的EIP等寄存器状态切换
    • 标号1(即代码1:\t处)之后开始运行用户态进程Y
    • restore_all,恢复现场,与保存现场对应
    • iret - pop cs:eip/ss:esp/eflags,从Y进程的内核堆栈中弹出硬件完成的压栈内容。至此,完成了中断上下文切换,即从进程Y的内核态返回到进程Y的用户态
    • 继续运行用户态进程Y
  • 操作系统内核
    • Linux操作系统内核通过中断上下文切换和进程上下文切换这些基本的运行机制来保障Linux操作系统内核为用户提供最基本和重要的服务。如下:

      • 通过系统调用的形式为进程提供各种服务
      • 通过异常处理程序与中断服务程序为硬件的正常工作提供各种服务
      • 通过内核线程为系统提供动态的维护服务和中断服务中可延时处理的任务
    • 对于x86-32位的系统所有进程地址空间3GB以上的部分(内核态)都是共享的,也就是说所有进程看到的3GB以上部分的地址和内容都是完全一样的,尽管逻辑上每个进程的地址空间是独立的
  • Linux操作系统的整体构架如下:

  • ls命令执行过程示意

实验:理解进程调度时机跟踪分析进程调度与进程切换的过程

使用实验楼,配置menuOS:



打开gdb,设置断点,其中switch_to为宏定义,不能设置断点,需到context_switch函数中单步执行查看调用:



执行程序,停在了schedule函数处,查看代码发现调用了schedule函数,发生了进程调度:



继续执行程序,代码来到了pick_next_task断点处:



继续执行,来到了context_switch处:



在context_switch中单步执行,发现其中调用到了switch_to:





继续单步执行,结束切换:

代码分析

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);
// Here we just switch the register state and the stack.切换寄存器状态和栈
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);
}

schedule函数选择一个新的进程来运行,并调用context_switch函数进行上下文的切换

switch_to函数部分

#define switch_to(prev, next, last)
do {
/*
* Context-switching clobbers all registers, so we clobber
* them explicitly, via unused output variables.
* (EAX and EBP is not listed because EBP is saved/restored
* explicitly for wchan access and EAX is the return value of
* __switch_to())
*/
unsigned long ebx, ecx, edx, esi, edi; asm volatile(
"pushfl\n\t" // 保存当前进程flags
"pushl %%ebp\n\t" // 当前进程堆栈基址压栈
"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进程的栈顶为起点,next_ip一般为$1f,对于新创建的子进程是ret_from_fork
__switch_canary
"jmp __switch_to\n" // prve进程中,设置next进程堆栈,jmp与call不同,是通过寄存器传递参数(call通过堆栈),所以ret时弹出的是之前压入栈顶的next进程起点
// 完成EIP的切换
"1:\t" // next进程开始执行
"popl %%ebp\n\t" // restore EBP
"popfl\n" // restore flags // 输出量
: [prev_sp] "=m" (prev->thread.sp), // 保存当前进程的esp
[prev_ip] "=m" (prev->thread.ip), // 保存当前进仓的eip
"=a" (last), // 要破坏的寄存器
"=b" (ebx), "=c" (ecx), "=d" (edx),
"=S" (esi), "=D" (edi) __switch_canary_oparam // 输入量
: [next_sp] "m" (next->thread.sp), // next进程的内核堆栈栈顶地址,即esp
[next_ip] "m" (next->thread.ip), // next进程的eip // regparm parameters for __switch_to():
[prev] "a" (prev),
[next] "d" (next) __switch_canary_iparam : // 重新加载段寄存器
"memory");
} while (0)

context_switch调用宏switch_to进行硬件上下文切换,主要是内联汇编代码

问题与总结

总统来说实验过程没有遇到什么问题,但是对于原理还需要进一步理解,switch_to处的执行过程还需要断点到context_switch函数后单步执行进入函数内部,才能看到调用switch_to的执行过程。

linux内核调用schedule()函数进行进程调度,并调用context_switch进行上下文的切换,调用switch_to来进行进程关键上下文切换。

在linux中,进程主动调度的时机可以在中断处理过程中、内核线程中,但用户态进程无法实现主动调度,仅能通过陷入内核态的新时机点进行调度,即在中断处理过程中进行调度。

《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. 2020-2021-1 20209307 《Linux内核原理与分析》第九周作业

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

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

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

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

    2018-2019-1 20189221 <Linux内核原理与分析>第九周作业 实验八 理理解进程调度时机跟踪分析进程调度与进程切换的过程 进程调度 进度调度时机: 1.中断处理过程(包 ...

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

    <Linux内核原理与分析>第九周作业 一.本周内容概述: 阐释linux操作系统的整体构架 理解linux系统的一般执行过程和进程调度的时机 理解linux系统的中断和进程上下文切换 二 ...

随机推荐

  1. Codeforces Round #467 (Div. 2) B. Vile Grasshoppers

    2018-03-03 http://codeforces.com/problemset/problem/937/B B. Vile Grasshoppers time limit per test 1 ...

  2. bind封装

    原理:通过apply或者call方法来实现. (1)初始版本 Function.prototype.bind=function(obj,arg){ var arg=Array.prototype.sl ...

  3. (转载)Unity UGUI点击不同Button执行不同的方法(无参方法)

      将脚本随意挂在任何位置 但是这个btnParent一定是 按钮的父节点   脚本很简单自己敲一遍就全都明白了 上脚本 OnClickTest using UnityEngine; using Un ...

  4. 【HNOI 2018】排列

    Problem Description 给定 \(n\) 个整数 \(a_1, a_2, \ldots , a_n(0 \le a_i \le n)\),以及 \(n\) 个整数 \(w_1, w_2 ...

  5. CSS基础学习(一) 之 line-height 与 height 属性区别

    官方定义: height:定义了了元素的高度.默认情况下,该属性订了 content area(内容区域) 的高度.如果box-sizing属性设置为 border-box,那么height就表示bo ...

  6. variable 'o' used without having been completely initialized Compiling Vertex program

    variable 'o' used without having been completely initialized Compiling Vertex program   v2f vert (ap ...

  7. spring aop 中的JoinPoint

    AspectJ使用org.aspectj.lang.JoinPoint接口表示目标类连接点对象,如果是环绕增强时,使用org.aspectj.lang.ProceedingJoinPoint表示连接点 ...

  8. linux iso 下载地址

    Centos 5.3  下载地址: http://www.karan.org/mock/5.3/CentOS-5.3-i386-bin-1to6.torrent http://www.karan.or ...

  9. itchat和wordcloud对微信好友的签名进行画像

    获取好友列表的时候,返回的json信息中还看到了有个性签名的信息,脑洞一开,把大家的个性签名都抓下来,看看高频词语,还做了个词云. # coding:utf-8 import itchat # 先登录 ...

  10. 【XAF问题】不能将值NULL插入列"Oid"

    一.问题 1. 不能将值NULL插入列"Oid" 二.解决方法 解决方法:删表,oid不能为空,继承的对象变了