《Linux内核分析》

第八章 可执行程序工作原理进程的切换和系统的一般执行过程

8.1 知识点

进程调度的时机

  • ntel定义的中断类型主要有以下几种

    • 硬中断(Interrupt)
    • 软中断/异常(Exception)
      • 故障(Fault)
      • 退出(Abort)
      • 陷阱(Trap)
  • schedule()函数选择一个新的进程来运行,并调用context_switch进行上下文的切换,这个宏调用switch_to来进行关键上下文切换
    • next = pick_next_task(rq, prev);//进程调度算法都封装这个函数内部
    • context_switch(rq, prev, next);//进程上下文切换
    • switch_to利用了prev和next两个参数:prev指向当前进程,next指向被调度的进程
  • Linux系统的一般执行过程
    • 最一般的情况:正在运行的用户态进程X切换到运行用户态进程Y的过程

      • 1.正在运行的用户态进程X
      • 2.发生中断——save cs:eip/esp/eflags(current) to kernel stack,then load cs:eip(entry of a specific ISR) and ss:esp(point to kernel stack).
      • 3.SAVE_ALL //保存现场
      • 4.中断处理过程中或中断返回前调用了schedule(),其中的switch_to做了关键的进程上下文切换
      • 5.标号1之后开始运行用户态进程Y(这里Y曾经通过以上步骤被切换出去过因此可以从标号1继续执行)
      • 6.restore_all //恢复现场
      • 7.iret - pop cs:eip/ss:esp/eflags from kernel stack
      • 8.继续运行用户态进程Y
  • 几种特殊情况
    • 通过中断处理过程中的调度时机,用户态进程与内核线程之间互相切换和内核线程之间互相切换,与最一般的情况非常类似,只是内核线程运行过程中发生中断没有进程用户态和内核态的转换;
    • 内核线程主动调用schedule(),只有进程上下文的切换,没有发生中断上下文的切换,与最一般的情况略简略;
    • 创建子进程的系统调用在子进程中的执行起点及返回用户态,如fork;
    • 加载一个新的可执行程序后返回到用户态的情况,如execve;
    • ch_to利用了prev和next两个参数:prev指向当前进程,next指向被调度的进程



8.2 核心代码分析

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)

8.3 实验

  • 克隆menu,编译内核,启动gdb







  • 在schedule(),context_switch(),pick_next_task()打入断点

  • 按c执行,停在schedule函数处

  • 按c继续执行到pick_next_task断点处

  • 按c继续执行到context_switch断点处,用来实现进程的切换。

总结

一次一般的进程切换过程,其中必须完成的关键操作是:切换地址空间、切换内核堆栈、切换内核控制流程,加上一些必要的寄存器保存和恢复。这里,除去地址空间的切换,其他操作要强调“内核”一词。这是因为,这些操作并非针对用户代码,切换完成后,也没有立即跑到next的用户空间中执行。用户上下文的保存和恢复是通过中断和异常机制,在内核态和用户态相互切换时才发生的。schedule()是内核和其他部分用于调用进程调度器的入口,选择哪个进程可以运行,何时将其投入运行。就如switch_to中的方法,通过压栈出栈交换prev_ip和next_ip。然后返回,从而完成进程调度。而用哪个作为下来的进程,则通过优先级的算法和进程调度算法来决定。

2019-2020-1 20199308《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. D - Expanding Rods POJ - 1905(二分)

    D - Expanding Rods POJ - 1905 When a thin rod of length L is heated n degrees, it expands to a new l ...

  2. Java 程序该怎么优化?(工具篇)

    程序员:为什么程序总是那么慢?时间都花到哪里去了? 面试官:若你写的 Java 程序,出现了性能问题,该怎么去排查呢? 工欲善其事必先利其器,为你呈上一箩筐性能优化工具,必有一款满足你,废话不多说,直 ...

  3. int不可为null引发的 MyBatis做持久层框架,返回值类型要为Integer问题

    MyBatis做持久层框架,返回值类型要为Integer MyBatis 做持久层时,之前没注意,有时候为了偷懒使用了int类型做为返回的类型,这样是不可取的,MyBatis做持久层框架,返回值类型要 ...

  4. markdown 插入图片太大?怎么设定图片大小?

    你一定在插入图片的时候,遇到图片太大,影响观感的问题. Markdown中,图片大小的设定方式有两种 第一种: ![](https://img2018.cnblogs.com/blog/1735896 ...

  5. 1006 Sign In and Sign Out (25 分)

    At the beginning of every day, the first person who signs in the computer room will unlock the door, ...

  6. Shell:Day08.笔记

    函数:写一个代码块,用来重复调用的: 1.函数的写法格式 2.参数,在函数名后面直接加,即可:如果在外面  abc(){   函数体 $@  }  abc 1 2 3 4 5   :wq    a.s ...

  7. 【Linux】Apache服务配置

    一. URL 统一资源定位符 http://www.sina.com.cn:80/admin/index.html 二. 环境安装 LAMP 源码包编译安装 版本可以自定义 生产环境 安全 稳定 开发 ...

  8. Windows 手动安装 Apache24 web服务器

    文章更新于:2020-02-18 按照惯例,需要的文件附上链接放在文首 文件名:httpd-2.4.41-o111c-x64-vc15-r2.7z 文件大小:6.1MB 下载链接:https://ww ...

  9. go 递归函数

    一.什么是递归函数? 当一个函数在其函数体内调用自身,则称之为递归. 二.引用经典例子:斐波那切数列 package main import "fmt" func main(){ ...

  10. MySQL入门,第四部分,学会创建、删除表

    一.列完整性约束 列完整性约束:是指对某一列设置的约束条件.该列上的数据必须满足.最常见的有: NOT NULL 该列值不能为空 NULL  该列值可以为空 UNIQUE 该列值不能存在相同 DEFA ...