20135302魏静静——linux课程第八周实验及总结

linux课程第八周实验及总结

实验及学习总结

1. 进程切换在内核中的实现

linux中进程切换是很常见的一个操作,而这个操作是在内核中实现的。

实现的时机有以下三个时机:

    • 中断处理过程(包括时钟中断、I/O中断、系统调用和异常)中,直接调用schedule(),或者返回用户态时根据need_resched标记调用schedule();
    • 内核线程可以直接调用schedule()进行进程切换,也可以在中断处理过程中进行调度,也就是说内核线程作为一类的特殊的进程可以主动调度,也可以被动调度;
    • 用户态进程无法实现主动调度,仅能通过陷入内核态后的某个时机点进行调度,即在中断处理过程中进行调度。

linux内核中的线程切换主要通过schedule()这个函数实现。

schedule()函数

    • 切换时候,调用call schedule();来执行schedule()函数
    • 使用struct task_struct *tsk = current; 来获取当前进程;sched_ submit _work(tsk); 避免死锁;最后调用 _schedule()来处理切换过程
    • next = pick_ next _task(rq, prev);,_ schedule()中用来确定使用哪一种进程调度的策略。但总是选择了下一个进程来进行切换,即根据调度策略选择一个优先级最高的任务将其定为下一个进程,最后都是调用context _switch来进行进程上下文的切换过程。

context_switch

    • 进程上下文切换
    • 其中prepare_ task _ switch()函数是完成切换前的准备工作;接着后面判断当前进程是不是内核线程,如果是内核线程,则不需要切换上下文。
    • 接着调用switch_mm(),把虚拟内存从一个进程映射切换到新进程中
    • 调用switch_to(),从上一个进程的处理器状态切换到新进程的处理器状态。这包括保存、恢复栈信息和寄存器信息。

switch_to

    • switch_to是一个宏,不是函数,它的参数prev, next, last不是值拷贝,而是它的调用者context_switch()的局部变量。
    • 局部变量是通过%ebp寄存器来索引的,也就是通过n(%ebp),n是编译时决定的,在不同的进程的同一段代码中,同一局部变量的n是相同的。在switch_to中,发生了堆栈的切换,即ebp发生了改变,所以要格外留意在任一时刻的局部变量属于哪一个进程。关于__switch_to()这个函数的调用,并不是通过普通的call来实现,而是直接jmp,函数参数也并不是通过堆栈来传递,而是通过寄存器来传递。

进程切换:

    • 如图所示,A为此时正运行的进程(prev),B为待切换的进程(next)。切换过程一共分为四步:

      第一步:即movl %%esp,%0也就是将寄存器esp中的值保存在进程A的thread.esp中。

      第二步:即movl %3,%%esp也就是将进程B的thread.esp的值赋给寄存器esp。(实际上这个值就是上一次从B中切换走的时候执行的第一步的结果。为了要返回,必须为以后考虑周全。)

      第三步:即movl $1f,%1其中1f就是说程序后面标号为1的地方,将标号为1的地方的代码的地址保存到A的thread.eip中。

      第四步:即pushl %4,将进程B的thread.eip的值压栈,此时的esp指向已是进程B的堆栈。(实际上此时的thread.eip就是上一次从B中切换走的时候第三步执行的结果,即标号一得位置。所以任何进程恢复运行,首先肯定是执行的标号1的代码。)

      pushl %4后面的一句代码是调转jmp __switch_to 而__switch_to是个函数,他执行完成以后会有一个ret的操作,即将栈中的第一个地址作为函数返回的地址,所以就会跳到标号1的地方去执行代码了。

      由于__switch_to的代码在schedule()中,而shedule()函数又在其他系统调用函数中,比如sys_exit()中,所以先返回到调用B进程上次切换走时的schedule()中,然后返回到调用schedule()的系统调用函数中,最后系统调用又是在用户空间调用的,所有返回到系统调用的那个地方,接着执行用户空间的代码。这样就彻底的回到了B进程。注意由于此时的返回路径是根据B堆栈中保存的返回地址来返回的,所以肯定会返回到B进程中。

进程的切换时需要保存要切换进程的相关信息,但是这与中断是不同的,因为中断是在一个进程当中,而切换进程需要在不同的进程间切换。

进程切换时需要保存的数据如下:

    • 用户地址空间: 包括程序代码,数据,用户堆栈等
    • 控制信息 :进程描述符,内核堆栈等
    • 硬件上下文(注意中断也要保存硬件上下文只是保存的方法不同)

2.Linux系统的一般执行过程

最一般的情况:正在运行的进程X切换到运行进程Y的过程

    • 正在运行的用户态进程X
    • 发生中断——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).
    • SAVE_ALL //保存现场
    • 中断处理过程中或中断返回前调用了schedule(),其中的switch_to做了关键的进程上下文切换
      • asm volatile("pushfl\n\t"      /* save    flags */
      • "pushl %%ebp\n\t"        /* save    EBP   */ \
      • "movl %%esp,%[prev_sp]\n\t"  /* save    ESP   */ \
      • "movl %[next_sp],%%esp\n\t"  /* restore ESP   */ \
      • "movl $1f,%[prev_ip]\n\t"    /* save    EIP   */
      • "pushl %[next_ip]\n\t"   /* restore EIP   */
      • __switch_canary                   \
      • "jmp __switch_to\n"  /* regparm call  */ \
      • "1:\t"                        \
      • "popl %%ebp\n\t"     /* restore EBP   */    \
      • "popfl\n"         /* restore flags */
      • /* output parameters */                \
      • : [prev_sp] "=m" (prev->thread.sp),
      • [prev_ip] "=m" (prev->thread.ip),
      • "=a" (last),                              /* input parameters: */
      • : [next_sp]  "m" (next->thread.sp),
      • [next_ip]  "m" (next->thread.ip),
    • 标号1之后开始运行用户态进程Y(这里Y曾经通过以上步骤被切换出去过因此可以从标号1继续执行)
    • restore_all //恢复现场
    • iret - pop cs:eip/ss:esp/eflags from kernel stack
    • 继续运行用户态进程Y
    • CPU执行状态:

 

几种特殊情况

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

3. 总结

  linux内核中实现进程的切换主要通过保存进程相关的信息实现,这里需要注意进程切换中内核级进程的切换和用户态进程切换的不同。内核态可以直接调用schedule函数并不需要陷入中断这个过程。而用户态则需要陷入内核态才能实现进程的切换。从switch_to这个函数当中也可以验证我们进程切换时会保存相关信息的推断。

Linux是一个多进程的操作系统,所以,其他的进程必须等到正在运行的进程空闲CPU后才能运行。当正在运行的进程等待其他的系统资源时,Linux内核将取得CPU的控制权,并将CPU分配给其他正在等待的进程,这就是进程切换。内核中的调度算法决定将CPU分配给哪一个进程。

Linux系统有一个进程控制表(process table),一个进程就是其中的一项。进程控制表中的每一项都是一个task_struct结构,在task_struct结构中存储各种低级和高级的信息,包括从一些硬件设备的寄存器拷贝到进程的工作目录的链接点。进程控制表既是一个数组,又是一个双向链表,同时又是一个树,其物理实现是一个包括多个指针的静态数组。系统启动后,内核通常作为某一个进程的代表。一个指向task_struct的全局指针变量current用来记录正在运行的进程。变量current只能由kernel /sched.c中的进程调度改变。

参考:

Linux下函数调用堆栈帧的详细解释 http://blog.chinaunix.net/uid-21237130-id-159883.html 
Linux中断一:初看Linux中断 http://blog.csdn.net/xuexingyang/article/details/7350420 
Linux用户态和内核态 http://blog.chinaunix.net/uid-1829236-id-3182279.html 
深入理解Linux下用户态与内核态切换 http://www.threeway.cc/sitecn/InformationInfo.aspx?pid=2578&tid=1382 
Linux操作系统是如何工作的?破解操作系统的奥秘
linux 内核原理——关于进程的简介 http://essayteam.blogbus.com/logs/3870611.html 
时间: 2024-10-16 08:29:20

20135302魏静静——linux课程第八周实验及总结的相关文章

20135302魏静静——linux课程第七周实验及总结

linux课程第七周实验及总结 实验及学习总结 1. 编译链接的过程和ELF可执行文件格式(以hello为例) GNU编译系统编译源码: 首先,运行C预处理器(cpp),将.c文件翻译成.i文件——gcc -E -o hello.cpp hello.c -m32 接着,运行C编译器(cc1),将.i文件翻译成ASCII汇编语言文件.s文件——gcc  -S -o hello.s hello.cpp -m32 然后,运行汇编器(as),将.s文件翻译成可重定位目标文件.o文件——gcc -c he

20135302魏静静——linux课程第三周实验及总结

实验:跟踪分析Linux内核的启动过程 使用gdb跟踪调试内核从start_kernel到init进程启动 使用实验楼的虚拟机打开shell cd LinuxKernel/ qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img 内核启动完成后进入menu程序(<软件工程C编码实践篇>的课程项目),支持三个命令help.version和quit,您也可以添加更多的命令,对选修过<软件工程C编码实践篇>的童

20135302魏静静——Linux课程期中总结

Linux期中总结 Linux课程第一周实验及总结:[http://www.cnblogs.com/20135302wei/p/5218607.html] 冯诺依曼体系结构的核心思想是存储程序计算机.在计算机中有两种指令,一是用户指令,一是系统调用. 当用户使用计算机时,计算机根据其汇编的指令一步步运行,当使用系统调用完后,再返回用户模式,保证系统的稳定. 程序中执行call的堆栈变化 汇编基础 通用寄存器 16位  32位 AX    eax 累加器BX    ebx 基址寄存器CX    e

linux学习第八周总结

linux学习第八周总结 本周学习了两个服务,DNS和ansible 由于这些服务很复杂,我也只能是到达刚了解或者是刚刚入门的程度,所以只说一些简单基本的东西,简单总结. 一.DNS服务 1.简介 域名系统(英文:DomainNameSystem,缩写:DNS)是互联网的一项服务.它作为将域名和IP地址相互映射的一个分布式数据库,能够使人更方便地访问互联网.DNS使用TCP和UDP端口53.当前,对于每一级域名长度的限制是63个字符,域名总长度则不能超过253个字符. 记录类型 主条目:域名服务

《软件测试技术》课程第八周随笔

软件测试第八周随笔,本周软件测试的课程已全部结束,随笔重点回顾下因果图.  (一)概念: 因果图法是一种适合于描述对于多种输入条件组合的测试方法,根据输入条件的组合.约束关系和输出条件的因果关系,分析输入条件的各种组合情况,从而设计测试用例的方法,它适合于检查程序输入条件涉及的各种组合情况.因果图法一般和判定表结合使用,通过映射同时发生相互影响的多个输入来确定判定条件.因果图法最终生成的就是判定表,它适合于检查程序输入条件的各种组合情况.采用因果图法能帮助我们按照一定的步骤选择一组高效的测试用例

linux安全第八周总结

20135336 王维臻 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ” 一.知识概要 (一)进程切换的关键代码switch_to分析 1.进程进度与进程调度的时机分析 2.进程上下文切换相关代码分析 (二)Linux系统的一般执行过程 1.Linux系统的一般执行过程分析 2.Linux系统执行过程中的几个特殊情况 3.内核与舞女 (三)Linux系统架构和执行过程概览 1.Linux操作系统架构概览 2

《Linux内核分析》课程第八周学习总结

姓名:何伟钦 学号:20135223 ( *原创作品转载请注明出处*) ( 学习课程:<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-100002900 一.进程调度与进程切换 (一)不同的进程有不同的调度需求   第一种分类: I/O密集型(I/O-bound) 频繁的进行I/O 通常会花费很多时间等待I/O操作的完成 CPU密集型(CPU-bound) 计算密集型 需要大量的CPU时间进行运算 第二种分类: 批处理进程 不必与用

Linux学习第八周作业

1.请描述网桥.集线器.二层交换机.三层交换机.路由器的功能.使用场景与区别. 网桥 网桥有两个端口,一进一出,可以将两个相似的网络连接起来,从而放大信号的传输距离. 集线器 hub,局域网内对接收到的信号进行放大,但它可以有多个接口.不隔离广播域和冲突域. 二层交换机 数据链路层设备,可以识别数据包中的MAC地址,并根据MAC地址进行数据转发.隔离冲突域,但不隔离广播域. 三层交换机 数据链路层与网络层设备,三层交换机的最重要目的是加快大型局域网内部的数据交换,所具有的路由功能也是为这一目的服

20135302魏静静——课本第4章学习笔记

第4章   进程调度 调度:调度是一个平衡的过程.一方面,它要保证各个运行的进程能够最大限度的使用CP:另一方面,保证各个进程能公平的使用CPU. 调度功能:决定哪个进程运行以及进程运行多长时间. 调度实现原理:与进程的优先级有关 Linux上调度实现的方法:O(1)的调度算法 调度相关的系统调用 4.1多任务   多任务系统可以划分为两类:非抢占式多任务( cooperative multitasking )和抢占式多任务(preemptive multitasking).像所 有Unix 的