那一天我二十一岁,在我一生的黄金时代。我有好多奢望。我想爱,想吃,还想在一瞬间变成天上半明半暗的云。那一年我二十一岁,在我一生的黄金时代。我有好多想法。我思索,想象,我不知该如何行动,我想知道一个城市,一个国家,这个社会的运转方式和发展动力。我思考人性,想象物质和精神,探求存在的意义。我渴望爱情 热爱生活 追求美好。我希望自己可以长存 。


*********************************************

一、书本第八章知识总结【进程的切换和系统的一般执行过程】

1. 进程调度及时机

  • 不同类型的进程有不同的调度需求,第一种分类:I/O-bound 会频繁的进程I/O,通常会花费很多时间等待I/O操作的完成;CPU-bound 是计算密集型,需要大量的CPU时间进行运算,使得其他交互式进程反应迟钝,因此需要不同的算法来使系统的运行更高效,以及CPU的资源最大限度的得到使用。第二种分类包括批处理进程(batch process);实时进程(real-time process)以及交互式进程(interactive process)。不同的分类需要不同的调度策略,即决定什么时候以怎样的方式选择一个新进程运行。Linux的调度基于分时和优先级。根据优先级排队,且优先级是动态的。
  • 进程调度的时机:进程调度的时机就是内核调用schedule函数的时机。当内核即将返回用户空间时,内核会检查need_resched标志是否设置。如果设置,则调用schedule函数,将从中断处理程序返回用户空间的时间点作为一个固定的调度时机点。schedule()函数实现调度:
    1)中断处理过程(包括时钟中断、I/O中断、系统调用和异常)中,直接调用schedule(),或者返回用户态时根据need_resched标记调用schedule()。
    2)内核线程可以直接调用schedule()进行进程切换,也可以在中断处理过程中进行调度,也就是说内核线程作为一类的特殊的进程可以主动调度,也可以被动调度。
    3)用户态进程无法实现主动调度,仅能通过陷入内核态后的某个时机点进行调度,即在中断处理过程中进行调度。

2. 进程的上下文切换

  • 为了控制进程的执行,内核必须有能力挂起正在 CPU 上执行的进程,并恢复以前挂起的某个进程的执行的过程,叫做进程切换、任务切换、上下文切换。挂起正在 CPU 上执行的进程,与中断时保存现场是有区别的,中断前后是在同一个进程上下文中,只是由用户态转向内核态执行。也即是说中断是在同一个进程中执行的,进程上下文是在不同的进程中执行的。
  • 进程上下文信息:
    1)用户地址空间:包括程序代码,数据,用户堆栈等。
    2)控制信息:进程描述符,内核堆栈等。
    3)硬件上下文(注意中断也要保存硬件上下文只是保存的方法不同)。
  • schedule()函数:此函数选择一个新的进程来运行,并调用context_ switch进行上下文的切换,这个宏调用switch_ to来进行关键上下文切换
    1)next = pick_ next_ task(rq, prev);//进程调度算法都封装这个函数内部。
    2)context_ switch(rq, prev, next);//进程上下文切换。
    3)switch_ to利用了prev和next两个参数:prev指向当前进程,next指向被调度的进程。
    其代码过程:
    1)创建一些局部变量。
    2)关闭内核抢占,初始化一部分变量。
    3)选择next进程。
    4)完成进程的调度。
    选择一个新的进程来运行,并调用 context_switch 宏进行上下文的切换,这个宏又调用 switch_to 宏来进行关键上下文切换;switch_to 宏中定义了 prev 和 next 两个参数:prev 指向当前进程,next 指向被调度的进程。
  • 实际代码中进程切换由两个步骤组成:
    1)切换页全局目录(RC3)以安装一个新的地址空间,这样不同的虚拟地址可以经过不同的页表转为不同的物理地址。
    2)切换内核态堆栈和硬件上下文
  • 进程切换上下文的代码过程分析
    • schedule()函数选择一个新的进程来运行。
    • 通过context_switch完成进程上下文切换。
    • switch_ to()函数代码分析:
      • 保存当前进程的flags
      • 把当前进程的堆栈基址压栈
      • switch_ to完成寄存器的切换:先保存当前进程的寄存器,再进行堆栈切换
      • 再切换eip,这样当前进程可以从新进程中恢复
      • next_ ip一般是$1f(对于新创建的进程来说就是ret_ from_ fork)
      • 表明下一进程栈顶是起点。
      • next_ ip一般是$1f,对于新创建的子进程是ret_ from_forkjmp _ switch_ to是函数调用,通过寄存器传递参数;函数执行结束return的时候从下一条指令开始(即是新进程的开始)
      • next进程曾经是prev进程,next执行完后执行的“下一个”其实是刚刚被切换的进程
  • 进程切换关键环节分析示意图

  • 代码分析

schedule()函数代码:

static void __sched __schedule(void)
{
  struct task_struct *prev, *next;
  unsigned long *switch_count;
  struct rq *rq;
  int cpu;

need_resched:
  preempt_disable();
  cpu = smp_processor_id();
  rq = cpu_rq(cpu);
  rcu_note_context_switch(cpu);
  prev = rq->curr;

  schedule_debug(prev);

  if (sched_feat(HRTICK))
    hrtick_clear(rq);

  /*
   * Make sure that signal_pending_state()->signal_pending() below
   * can't be reordered with __set_current_state(TASK_INTERRUPTIBLE)
   * done by the caller to avoid the race with signal_wake_up().
   */
  smp_mb__before_spinlock();
  raw_spin_lock_irq(&rq->lock);

  switch_count = &prev->nivcsw;
  if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {
    if (unlikely(signal_pending_state(prev->state, prev))) {
      prev->state = TASK_RUNNING;
    } else {
      deactivate_task(rq, prev, DEQUEUE_SLEEP);
      prev->on_rq = 0;

      /*
       * If a worker went to sleep, notify and ask workqueue
       * whether it wants to wake up a task to maintain
       * concurrency.
       */
      if (prev->flags & PF_WQ_WORKER) {
        struct task_struct *to_wakeup;

        to_wakeup = wq_worker_sleeping(prev, cpu);
        if (to_wakeup)
          try_to_wake_up_local(to_wakeup);
      }
    }
    switch_count = &prev->nvcsw;
  }

  if (task_on_rq_queued(prev) || rq->skip_clock_update < 0)
    update_rq_clock(rq);

  next = pick_next_task(rq, prev);
  clear_tsk_need_resched(prev);
  clear_preempt_need_resched();
  rq->skip_clock_update = 0;

  if (likely(prev != next)) {
    rq->nr_switches++;
    rq->curr = next;
    ++*switch_count;

    context_switch(rq, prev, next); /* unlocks the rq */
    /*
     * The context switch have flipped the stack from under us
     * and restored the local variables which were saved when
     * this task called schedule() in the past. prev == current
     * is still correct, but it can be moved to another cpu/rq.
     */
    cpu = smp_processor_id();
    rq = cpu_rq(cpu);
  } else
    raw_spin_unlock_irq(&rq->lock);

  post_schedule(rq);

  sched_preempt_enable_no_resched();
  if (need_resched())
    goto need_resched;
}

switch_ to()函数代码:

#define switch_to(prev, next, last) //prev指向当前进程,next指向被调度的进程
do {  

    unsigned long ebx, ecx, edx, esi, edi;

      asm volatile("pushfl\n\t"   //保存当前近程的flags
     "pushl %%ebp\n\t"            //把当前进程的基址压栈

     "movl %%esp,%[prev_sp]\n\t"  //把当前进程的栈顶esp保存到thread.sp中
     "movl %[next_sp],%%esp\n\t"  //把[next_sp]放到esp,从而这两步完成了内核堆栈的切换

    "movl $1f,%[prev_ip]\n\t"     //把1f放到[prev_ip]里,保存当前进程的EIP,当恢复prev进程时可从这里恢复
    "pushl %[next_ip]\n\t"        //把next进程的起点,即ip的位置压到堆栈中,next_ip一般是$1f
    __switch_canary
    "jmp __switch_to\n"           //执行__switch_to()函数,通过寄存器[prev][next],eax和edx传递参数
    "1:\t"
    "popl %%ebp\n\t"
    "popfl\n" 

     /* output parameters */
    : [prev_sp] "=m"(prev->thread.sp), //为了可读性更好,用字符串[prev_sp]标记参数
      [prev_ip] "=m"(prev->thread.ip),
      "=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_ip]  "m" (next->thread.ip),  

   /* regparm parameters for __switch_to():*/
  [prev] "a" (prev),
  [next] "d" (next)

  __switch_canary_iparam 

    : /* reloaded segment registers */
   "memory");
} while (0)

3. 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

  • Linux系统的一般执行过程中的几个特殊情况
    1)通过中断处理过程中的调度时机,用户态进程与内核线程之间互相切换和内核线程之间互相切换,与最一般的情况非常类似,只是内核线程运行过程中发生中断没有进程用户态和内核态的转换
    2)内核线程主动调用schedule(),只有进程上下文的切换,没有发生中断上下文的切换,与最一般的情况略简略
    3)创建子进程的系统调用在子进程中的执行起点及返回用户态,如fork
    4)加载一个新的可执行程序后返回到用户态的情况,如execve

4. Linux操作系统架构概览

  • 任何计算机系统都包含一个基本的程序集合,称为操作系统。

    • 内核(进程管理,进程调度,进程间通讯机制,内存管理,中断异常处理,文件系统,I/O系统,网络部分)
    • 其他程序(例如函数库、shell程序、系统程序等等)
  • 操作系统的目的
    • 与硬件交互,管理所有的硬件资源
    • 为用户程序(应用程序)提供一个良好的执行环境
  • 整体框架示意图,如下
  • ls命令执行过程示意图,如下

二、实验部分【理解进程调度时机跟踪分析进程调度与进程切换的过程】

实验步骤
1)打开实验楼虚拟机

2)在shell中依次运行以下命令,获取本次实验的代码,并编译运行
cd LinuxKernel
rm menu -rf
git clone https://github.com/mengning/menu.git
cd menu
mv test_exec.c test.c
make rootfs
截图如下:

3)关闭QEMU窗口,在shell窗口中,cd LinuxKernel回退到LinuxKernel目录,使用下面的命令启动内核并在CPU运行代码前停下以便调试:
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S

4)接下来,我们就可以水平分割一个新的shell窗口出来,依次使用下面的命令启动gdb调试
gdb
(gdb) file linux-3.18.6/vmlinux
(gdb) target remote:1234

5)关闭QEMU窗口,在shell窗口中,cd LinuxKernel回退到LinuxKernel目录,使用下面的命令启动内核并在CPU运行代码前停下以便调试:
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S

6)接下来,我们就可以水平分割一个新的shell窗口出来,依次使用下面的命令启动gdb调试
gdb
(gdb) file linux-3.18.6/vmlinux
(gdb) target remote:1234

7)并在内核函数schedule的入口处设置断点,接下来输入c继续执行,则系统即可停在该函数处,接下来我们就可以使用命令n或者s逐步跟踪,可以详细浏览pick_next_task,switch_to等函数的执行过程,有图为证:

三、实验收获

1. 关于进程调度与切换

本周视频主要讲解了进程切换的关键代码switch_ to分析、Linux系统的一般执行过程、Linux系统架构和执行过程。
schedule()函数实现进程调度,
context_ switch完成进程上下文切换,
switch_ to完成寄存器的切换。
在调度时机方面,内核线程作为一类的特殊的进程可以主动调度,也可以被动调度。
而用户态进程无法实现主动调度,仅能通过陷入内核态后的某个时机点进行调度,即在中断处理过程中进行调度。

2. 硬中断与软中断

Intel定义的中断有:
1)硬中断
CPU的两根引脚(可屏蔽中断和不可屏蔽中断)。CPU在执行每条指令后检测引脚的电平
一般外设都是以这种方式与CPU进行信号传递
2)软中断
包括零错误、系统调用、调试断点等,在CPU执行指令过程中发生的各种特殊情况,称为异常。
异常可以分为以下3种:
故障 出现问题,但是可以恢复到当前指令
退出 是不可恢复的严重故障,导致程序无法继续运行
陷阱 程序主动产生的异常,在执行当前指令后发生。比如系统调用(int 0x80)等。

3. schedule()函数主要做了这么三件事:

1)针对抢占的处理;检查prev的状态,并且重设state的状态;
2)next = pick_next_task(rq, prev); //进程调度;更新就绪队列的时钟;
3)context_switch(rq, prev, next); //进程上下文切换

2018-2019-1 20189201 《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. Ubuntu16.04安装NVIDA驱动和CUDA

    该GPU是计算卡,不会用做显示,所以如果你希望自己的显示使用GPU,本方法可能失效. 服务器配置: CPU: E5-母鸡 GPU: NVIDIA  Tesla K40c 操作系统:Ubuntu 16. ...

  2. macOS Mojave配置OpenGL开发环境

    ---恢复内容开始--- 前言: 本文写作目的: 是由于本人参考 csdn原文 的方法配置环境时踩了很多坑,所以在此写一篇文防止以后用到. 工具: Xcode CMake 步骤: 准备工作: Xcod ...

  3. 关于macOS 管理员(Admin)权限问题。

    最近突然想改下用户名,于是在用户与组里解锁,然后两个手指点击用户那一行,更改fullname,不过出于好奇把uid和uuid也改了. 之后发现current user等级由Admin变成Standar ...

  4. ThinkPHP—URL的访问以及各种方法的操作

    1.URL访问 ThinkPHP采用单一入口模式访问应用,对应用的所有请求都定向到应用的入口文件,系统会从URL参数中解析当前请求的模块.控制器和操作,下面是一个标准的URL访问格式: 第一种访问方式 ...

  5. 提取 linux 文件目录结构

    提取 linux  文件的目录结构 find /home/user1/ -type d |while read line ;do mkdir -p /home/user2/$line;done

  6. 关于selenium的8种元素定位

    selenium中有八种元素定位,分别是:id,name,class_name,tag_name,link_text.partial_link_text.xpath.css 简单的定位可以用 id.n ...

  7. CSS字体渐变 & 隐藏浏览器滚动条 & grid布局(转载)

    字体渐变  https://www.zhangxinxu.com/study/201104/css3-text-gradient-2.html 隐藏浏览器滚动条  https://blog.csdn. ...

  8. 第十七节,OpenCV(学习六)图像轮廓检测

    1.检测轮廓 轮廓检测是图像处理中经常用到的,OpenCV-Python接口中使用cv2.findContours()函数查找检测物体的轮廓. cv2.findContours(image, mode ...

  9. 通用RSA加密 - PHP+Java+Javascript加密解密

    php端生成 公钥私钥 1.openssl genrsa -out rsa_private_key.pem 1024    私钥 2.openssl rsa -in rsa_private_key.p ...

  10. Allowed memory size of 134217728 bytes exhausted

    错误信息: Allowed memory size of 134217728 bytes exhausted (tried to allocate 65015808 bytes) 由于报错信息和数据库 ...