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


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

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

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);} elseraw_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); //进程上下文切换

转载于:https://www.cnblogs.com/keady/p/10092871.html

2018-2019-1 20189201 《LInux内核原理与分析》第九周作业相关推荐

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

    写之前回看了一遍秒速五厘米:如果?下落的速度正好 那么13年的长度刚好是地球的最远距离!直径两端 在进行实验楼操作之前,先听授了网易云课堂中孟老师关于"计算机是如何工作的?"的介绍 ...

  2. 2022-2023-1 20222809《Linux内核原理与分析》第一周作业

    Linux内核原理与分析第一周作业 配置环境 1.参考Linux(Ubuntu)系统安装图文教程中第二种借助virtualbox成功配置Ubuntu环境 2.升级更新软件包 可以通过调节分辨率和虚拟机 ...

  3. 2017-2018-1 20179215《Linux内核原理与分析》第二周作业

    20179215<Linux内核原理与分析>第二周作业 这一周主要了解了计算机是如何工作的,包括现在存储程序计算机的工作模型.X86汇编指令包括几种内存地址的寻址方式和push.pop.c ...

  4. 2018-2019-1 20189213《Linux内核原理与分析》第四周作业

    <Linux内核原理与分析>第四周学习总结: 1.课本知识总结: 本章内容并不多,首先是介绍了一些Linux内核源代码的目录结构,并基于Linux内核源代码构造一个简单的操作系统MenuO ...

  5. 实验楼 linux内核原理与分析,《Linux内核原理与分析》第一周作业 20189210

    实验一 Linux系统简介 这一节主要学习了Linux的历史,Linux有关的重要人物以及学习Linux的方法,Linux和Windows的区别.其中学到了LInux中的应用程序大都为开源自由的软件, ...

  6. 20169210《Linux内核原理与分析》课程总结

    每周作业链接汇总 第一周作业:对实验楼<Linux 基础入门(新版)>课程的学习,其中有用户及文件权限管理.Linux 目录结构及文件基本操作.环境变量与文件查找.文件打包与解压缩等共17 ...

  7. 《Linux内核原理与分析》第二周作业

    反汇编一个简单的C程序 1.实验要求 使用: gcc –S –o test.s test.c -m32 命令编译成汇编代码,对汇编代码进行分析总结.其中test.c的具体内容如下: int g(int ...

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

    OS是如何工作的 学习任务: 阅读学习教材「庖丁解牛Linux 」第2章 学习蓝墨云班课中第三周视频「操作系统是如何工作的?」,并完成实验楼上配套实验二. 云班课学习笔记: 计算机三大法宝 程序存储计 ...

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

    linux内核分析学习笔记 --第五章 系统调用的三层机制 学习重点--深入理解系统调用的过程 给MenuOS添加命令 添加命令的方式较为简单,在LinuxKernel/menu/test.c目录下, ...

  10. 20169210《Linux内核原理与分析》第十一周作业

    第17章 设备与模块 关于设备驱动和设备管理,讨论四种内核成分. 设备类型:在所有的linux系统中为了统一普遍设备的操作所分的类. 模块:Linux内核中用于按需加载和卸载目标码的机制. 内核对象: ...

最新文章

  1. 11-Python基础之模块
  2. 11G RAC ORA-32701
  3. Leaflet中使用leaflet.polylineDecorator插件绘制箭头线及虚线矩形
  4. 页面加载完后立刻执行JS的两种方法
  5. 如何理解lvs中DR模型的arp请求-arp_announce和arp_ignore
  6. python photoshop自动化_你会用Python 搞定你的电子签名吗?
  7. Stata数据处理:清洗CFPS数据库
  8. win10共享打印机搜索不到计算机,Win10系统搜不到共享打印机的解决方法
  9. Kotlin-简约之美-进阶篇(四):访问权限控制
  10. python基础教程: 利用turtle库绘制笑脸和哭脸的例子
  11. 从投资人发现“新大陆”,看“产融星城”为何成?
  12. chage(charger)
  13. 简简单单做股票读书笔记(4/8)
  14. CRS-4544: Unable to connect to OHAS has启动失败
  15. Linux (Ubuntu)c编程 (入门必看)
  16. 产品经理三大领域的技术
  17. 无线LED智能照明控制系统
  18. SQL研习录(24)——CHECK约束
  19. 全国电子设计大赛 物品清单分析2013年
  20. ssm基于JavaEE的电脑销售管理系统设计与实现毕业设计源码021143

热门文章

  1. 分布式锁的三种实现的对比
  2. zuul默认的路由规则及禁用路由规则
  3. Linux wc指令统计文件信息
  4. MySQL索引覆盖扫描(覆盖索引)
  5. java监听窗口饮品,Java 完成部分水吧点饮品系统的:点饮品,饮品管理片段的实现...
  6. cordova打包安卓app
  7. Mac电脑设置adb环境变量
  8. 三星S5 电信版(G9009D)Android 5.0系统,root教程【亲测可行】
  9. 清除浏览器某一特定网站的缓存(Microsoft Edge、Chrome等)
  10. 浅谈java内存分析和垃圾收集器