转载:https://blog.csdn.net/FIELDOFFIER/article/details/44280717

《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”
实验环境:c+Linux64位 (32位系统可能结果会不同)
依照学术诚信条款,我保证此回答为本人原创,所有回答中引用的外部材料已经做了出处标记。


源代码以及运行环境搭建请参考mykernel,其中提供了一个简单的Linux内核源代码,本文主要分析其中的三个文件:

  • mypcb.h
  • mymain.c
  • myinterrupt.c

通过对这三个文件的分析来理解进程的切换原理。

注意,虽然在源代码中建立了4个进程并且进行循环的切换,但是为了简便分析时假定只有两个进程,编号0和1。另,在实际的Linux系统中每个进程都会有两个堆栈:用户态一个、内核态一个,但是在这个模拟的内核中每个进程只分配了一个堆栈。

首先,从mymain.c开始分析

在进程切换中最为重要的是运行栈的切换和eip(即程序计数器)的正确跳转,mymain.c中的函数my_start_kernel是最开始执行的代码,因此从这个函数开始进行分析。my_start_kernel函数首先建立起了4个进程并且进行了初始化,如分配栈等,注意在刚建立的时候只有0号进程的状态是runuable,其余的都是unrunnable。还有就是PCB结构中的threap.sp,每个进程对应一个栈,所以在这里thread.sp指向对应PCB内的char stack[KERNEL_STACK_SIZE - 1],即用这个字符数组作为运行栈,因为栈是由高地址向低地址增长,所以指向stack[KERNEL_STACK_SIZE - 1]。
接下来重点分析这段代码:

/* start process 0 by task[0] */pid = 0;my_current_task = &task[pid];asm volatile("movl %1,%%esp\n\t"     /* set task[pid].thread.sp to esp */"pushl %1\n\t"          /* push ebp */"pushl %0\n\t"          /* push task[pid].thread.ip */"ret\n\t"               /* pop task[pid].thread.ip to eip */"popl %%ebp\n\t": : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)   /* input c or d mean %ecx/%edx*/);
  • 这段内嵌汇编代码的功能是完成对0号进程的启动,其运行时的栈的情况如下:
  • 开始之前的栈的情况:

"movl %1,%%esp\n\t"     /* set task[pid].thread.sp to esp */
  • 本条指令把0号进程当前的栈顶地址放入esp,因为初始化的时候task[0].thread.sp指向的是stack[KERNEL_STACK_SIZE-1],所以执行完这条指令之后esp指向task[0]的堆栈的栈顶处,如下图:

"pushl %1\n\t"          /* push ebp */
  • 把task[0]的sp压入栈,栈的示意图如下:

"pushl %0\n\t"          /* push task[pid].thread.ip */
  • 把0号进程的ip,即my_process()函数的入口地址入栈,此时栈内情况如下图:

"ret\n\t"               /* pop task[pid].thread.ip to eip */
  • 从栈中弹出刚刚放入的my_process()函数入口地址赋给eip,开始运行0号进程

my_process()在执行时将会判断是否需要进行进程切换,由于我们假设内核中只有0和1两个进程,我们假定切换条件已经满足,在此直接分析从0号进程切换到1号进程的情况。
由于当前1号进程的pid[1].state是 “-1”(unrunnable),所以将会执行my_schedule()函数的else分支的内容。
my_interrupt.c文件的my_schedule()函数的else分支的内容如下:

else{next->state = 0;my_current_task = next;printk(KERN_NOTICE "switch from %d process to %d process\n \>>>process %d running!!!<<<\n\n\n",prev->pid,next->pid,next->pid);/* switch to new process */asm volatile(  "pushl %%ebp\n\t" /* save ebp */"movl %%esp,%0\n\t" /* save esp */"movl %2,%%esp\n\t" /* restore esp */"movl %2,%%ebp\n\t" /* restore ebp */"movl $1f,%1\n\t" /* save eip */   "pushl %3\n\t""ret\n\t" /* restore eip */: "=m" (prev->thread.sp),"=m" (prev->thread.ip): "m" (next->thread.sp),"m" (next->thread.ip));}
  • 在此重点分析其中内嵌的汇编代码。由于在从函数my_start_kernel()切换到函数my_process()的时候会有保存现场的动作,函数my_process()调用函数my_schedule()也会保存现场,所以在上述代码执行前的栈的情况应该是:

"pushl %%ebp\n\t" /* save ebp */
"movl %%esp,%0\n\t" /* save esp */
  • 保存0号进程的运行现场,即首先把0号进程的当前的栈底保存在栈中,然后把当前esp的值保存在0号进程的thread.sp中,这些现场值在切回到0号进程的时候是可以复原的,此时的栈的情况:

"movl %2,%%esp\n\t" /* restore esp */
"movl %2,%%ebp\n\t" /* restore ebp */
  • 开始进行栈切换,即从task[0].stack,切换到task[1].stack,此时的栈的情况:

"movl $1f,%1\n\t" /* save eip */
  • 这里的$1f是一个有特殊意义的数字,上述这条代码的功能是当下一次进程切换回0号进程时,将会从这里继续执行。
"pushl %3\n\t"
  • 把1号进程的进程入口地址(这里因为是1号进程第一次运行,所以就是my_process()函数的入口地址,若果不是第一次运行,由于执行过上一条代码”movl $1f,%1\n\t”那么进程1将会按照被切换掉的时候的代码继续执行下去)入栈。此时栈中情况如图:

"ret\n\t" /* restore eip */
  • 从当前栈task[1].stack中弹出刚刚送入的1号进程的入口地址,即执行my_process()函数,启动1号进程,与0号进程的启动类似,接下来就是判断是否需要进程切换。在此同样假设进程切换的条件都已经满足,接下来分析从1号进程切换到0号进程的过程。

首先,经过了一系列的函数调用,1号进程的栈已经发生了变化,示意图如下:

接下来开始分析从1号进程切换回0号进程的的流程

由于0号进程之前运行过,所以其状态是runnable,则调度过程中将进入my_schedule()方法的if分支执行,即执行如下代码:

if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */{//save current scene/* switch to next process */asm volatile(  "pushl %%ebp\n\t" /* save ebp */"movl %%esp,%0\n\t" /* save esp */"movl %2,%%esp\n\t" /* restore esp */"movl $1f,%1\n\t" /* save eip */   "pushl %3\n\t""ret\n\t" /* restore eip */"1:\t" /* next process start here */"popl %%ebp\n\t": "=m" (prev->thread.sp),"=m" (prev->thread.ip): "m" (next->thread.sp),"m" (next->thread.ip));my_current_task = next;//switch to the next taskprintk(KERN_NOTICE "switch from %d process to %d process\n>>>process %d running!!!<<<\n\n",prev->pid,next->pid,next->pid);}
  • 主要分析嵌入的汇编代码
"pushl %%ebp\n\t" /* save ebp */
"movl %%esp,%0\n\t" /* save esp */
  • 实现1号进程的运行栈环境保存,具体做法是把当前ebp入栈,然后把栈顶指针esp存入1号进程的task[1].thread.sp,执行后的栈情况如图:

"movl %2,%%esp\n\t" /* restore esp */
  • 此条汇编语句执行后,esp所指的栈已经改变,即由1号进程切换到0号进程,把进程0的thread.sp的值赋给esp,由于在从进程0切换到进程1的时候保存过thread.sp的值,所以此时的栈如图:

    "movl $1f,%1\n\t" /* save eip */"pushl %3\n\t"
  • 这两条代码与之前切换时实现相同的功能,都是为下一步的切换做的准备,之后的栈如图:

"ret\n\t" /* restore eip */
  • 这是由1号进程,即当前进程调用的my_schedule()将执行最后一条指令,其后的语句将在下次进行进程切换,切换回1号进程时继续执行。 此时栈情况如图:

此时eip和esp都已经切换回了由进程0切换到进程1最后时刻的值,当eip继续去指令时,得到的将是0号进程上一次被切换掉时的指令的下一条指令,如下:

/* start process 0 by task[0] */pid = 0;my_current_task = &task[pid];asm volatile("movl %1,%%esp\n\t"     /* set task[pid].thread.sp to esp */"pushl %1\n\t"          /* push ebp */"pushl %0\n\t"          /* push task[pid].thread.ip */"ret\n\t"               /* 进程0在这里被切换走 */"popl %%ebp\n\t" /* 接着应该执行这条指令 */: : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)   /* input c or d mean %ecx/%edx*/);
  • 也就是说接下来要执行的指令是:
"popl %%ebp\n\t"
  • 之后的栈的情况如图:

在这之后,0号进程将顺着执行流,完成对my_schedule()的调用,并且返回0号进程的my_process(),然后就是在进程0与进程1之间不断的切换。


参考文献:
https://github.com/mengning/mykernel/blob/master/mymain.c

https://github.com/ExiaHan/linuxKernelStudy/blob/master/secondWeekend/processSwitch.md

《Linux内核分析》(二)——从一个简单Linux内核分析进程切换原理相关推荐

  1. linux内核实验一:一个简单的时间片轮转多道程序内核代码

    学号517原创作品,转载请注明出处. 本实验资源来源: https://github.com/mengning/linuxkernel/ 一.mykernel 本次实验是在孟宁老师所建立的一个供用户开 ...

  2. Linux上利用nginx搭建一个简单的rtmp视频流服务器(不涉及直播)

    文章目录 Linux上利用nginx搭建一个简单的rtmp视频流服务器(不涉及直播) 一.基础环境搭建 二.构建Nginx 下载nginx-rtmp-module 安装Nginx 编译nginx,代理 ...

  3. linux下Qt编写串口调试助手,如何在linux下用QT写一个简单的串口调试助手

    如何在linux下用QT写一个简单的串口调试助手 QT5串口类 在QT5以前,编写串口一般使用的是qextserialport类,但在QT5之后有了QT自带的串口类SerialPort(串口基础类)和 ...

  4. [基因课学习笔记]一个简单的基因家族分析

    工作背景 探究在芝麻.大豆以及拟南芥中FAD4-like基因家族进化关系,并使其可视化(进化树) 操作环境及软件的准备 虚拟机应用:VMware Workstation pro 17 虚拟机操作系统: ...

  5. tensorflow学习笔记二——建立一个简单的神经网络拟合二次函数

    tensorflow学习笔记二--建立一个简单的神经网络 2016-09-23 16:04 2973人阅读 评论(2) 收藏 举报  分类: tensorflow(4)  目录(?)[+] 本笔记目的 ...

  6. P4学习笔记(二)一个简单P4交换机实现

    P4学习笔记(一)初始P4 P4学习笔记(二)一个简单P4交换机实现 文章目录 1. 架构模型 2.预定义模块详细描述 2.1 Arbiter 模块 2.2 Parser runtime 模块 2.3 ...

  7. Linux内核分析:完成一个简单的时间片轮转多道程序内核代码

    PS.贺邦   原创作品转载请注明出处  <Linux内核分析>MOOC课程    http://mooc.study.163.com/course/USTC-1000029000 1.m ...

  8. Linux内核分析2:一个简单的时间片轮转多道程序内核代码分析

    Lab2:一个简单的时间片轮转多道程序内核代码 席金玉   <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-100002900 ...

  9. 嵌入式Linux红外遥控,一个简单的IAL分析(红外遥控)(转)

    简单的IAL分析 一.程序说明 1.下面程序是基于一个红外的设备文件,从该设备中能接收到红外遥控的硬件编码. 2.两个文件需要覆盖掉libmingiui*/src/ial/中的两个文件编译时加上 -- ...

最新文章

  1. 一文读懂产业互联网的前世今生!
  2. unity渲染层级关系小结
  3. Springboot与抓拍系统对接实现查询违章数据与预览抓拍照片
  4. office软件的发展前景_2018年办公软件产业发展趋势
  5. html把div分成两栏,div+css制作上中下,中间两列的全屏自适应布局
  6. 无需Windbg | 使用VS 2019调试.NET程序的Crash异常
  7. HDU-2159 FATE 二维背包
  8. leetcode 621. 任务调度器(贪心算法)
  9. 我们如何在Linkerd 2.2里设计重试
  10. Maven : JsonMappingException: Incompatible Jackson version: 2.9.5
  11. HR招聘_(二)_招聘方法论(招聘原因及原则)
  12. python-按照相同的顺序打乱
  13. html期末作品,走完HTML和CSS,进军期末
  14. 打造黑苹果(一)组装硬件的选择与组装
  15. 【Python】使用Zoho/Hotmail给单人/多人发送Email邮件,以及发发送附件
  16. 【操作系统】DOS界面与常用操作命令
  17. 新建word文档,最上方页眉处总是自己出现一条横线,去除方法总结
  18. 2022年建筑设计中效果图渲染常见的7个错误
  19. python代码画土拨鼠_万圣节快到了,让我们用Python画一只蝙蝠图表吧(附代码)...
  20. 全球与中国4-叔戊基苯酚市场深度研究分析报告

热门文章

  1. 《数据库系统概念》14-静态散列
  2. Memcached原理与应用
  3. Memcache 查看列出所有key的方法
  4. C++ 获取类成员虚函数地址
  5. CVTE 2016 春季实习校招一面(C++后台)
  6. (Life)质量和服务_由购买联想笔记本想到的
  7. 支持向量机的基本原理
  8. sklearn.preprocessing.Imputer
  9. 你可能不知道的10条SQL技巧,涨知识了!
  10. linux设置最大打开文件数