zoukankan      html  css  js  c++  java
  • (三)Linux进程调度器-进程切换_学习笔记

    一、学习笔记

    说明:
    Kernel版本:4.14

    1. 概述
    进程切换:内核将CPU上正在运行的进程挂起,选择下一个进程来运行。ARM架构中,CPU上一次只能运行一个任务,内核需要为任务分配运行时间来进行调度,以便同时能处理多个任务请求。如下图所示:

    当进行任务切换的时候,思考下两个问题:

    (1) 怎样通过抢占来实现进程的切换?
    (2) 当进程切换的时候,到底切换的什么,是怎么实现的?

    这两个问题,也是本文探讨的主题了。

    2. 抢占

    2.1 用户抢占

    2.1.1 抢占触发点

    可以触发抢占的情况很多,比如进程的时间片耗尽、进程等待在某些资源上被唤醒时、进程优先级改变等。Linux内核是通过设置 TIF_NEED_RESCHED 标志来对进程进行标记的,设置该位则表明需要进行调度切换,而实际的切换将在抢占执行点来完成。补充:就算是没有设置TIF_NEED_RESCHED,调用schedule()时也是会发生抢占。
    先看一下两个关键结构体:struct task_struct 和 struct thread_info。我们在前边的文章中也讲过struct task_struct 用于描述任务,该结构体的首个字段放置的正是struct thread_info,struct thread_info结构体中flag字段就可用于设置TIF_NEED_RESCHED,此外该结构体中的 preempt_count 也与抢占相关。

    struct task_struct {
        ...
        struct thread_info    thread_info;
        ...
    };
    
    struct thread_info {
        unsigned long        flags;        /* low level flags TIF_NEED_RESCHED就会设置到这里面*/
        mm_segment_t        addr_limit;    /* address limit */
    #ifdef CONFIG_ARM64_SW_TTBR0_PAN
        u64            ttbr0;        /* saved TTBR0_EL1 */
    #endif
        int            preempt_count;    /* 0 => preemptable, <0 => bug 抢占计数,表示抢占是否使能*/
    };
    
    #include <asm/current.h>
    #define current_thread_info() ((struct thread_info *)current)   //通过该宏可以直接获取thread_info的信息
    #endif
    #define thread_saved_pc(tsk)    ((unsigned long)(tsk->thread.cpu_context.pc)) //获取pc指针

    看看具体哪些函数过程中,设置了TIF_NEED_RESCHED标志吧:

    (1) 内核提供了 set_tsk_need_resched 函数来将 thread_info 中flag字段设置成 TIF_NEED_RESCHED;
    (2) 设置了 TIF_NEED_RESCHED 标志,表明需要发生抢占调度;

    #define TIF_NEED_RESCHED    1 /*bit1位掩码 archarm64includeasm	hread_info.h*/

    2.1.2 抢占执行点

    用户抢占:抢占执行发生在进程处于用户态。抢占的执行,最明显的标志就是调用了 schedule() 函数,来完成任务的切换。具体来说,在用户态执行抢占在以下几种情况:

    (1) 异常处理后返回到用户态;
    (2) 中断处理后返回到用户态;
    (3) 系统调用后返回到用户态;

    如下图:

    (1) ARMv8有4个Exception Level,其中用户程序运行在EL0,OS运行在EL1,Hypervisor运行在EL2,Secure monitor运行在EL3;
    (2) 用户程序在执行过程中,遇到异常或中断后,将会跳到ENTRY(vectors)向量表处开始执行
    (3) 返回用户空间时进行标志位判断,设置了TIF_NEED_RESCHED则需要进行调度切换,没有设置该标志,则检查是否有收到信号,有信号未处理的话,还需要进行信号的处理操作;

    2.2 内核抢占

    Linux内核有三种内核抢占模型,先上图:

    (1) CONFIG_PREEMPT_NONE:不支持抢占,中断退出后,需要等到低优先级任务主动让出CPU才发生抢占切换;
    (2) CONFIG_PREEMPT_VOLUNTARY:自愿抢占,代码中增加抢占点,在中断退出后遇到抢占点时进行抢占切换;
    (3) CONFIG_PREEMPT:抢占,当中断退出后,如果遇到了更高优先级的任务,立即进行任务抢占;

    2.2.1 抢占触发点
    (1) 在内核中抢占触发点,也是设置 struct thread_info 的flag字段,设置 TIF_NEED_RESCHED 表明需要请求重新调度。
    (2) 抢占触发点的几种情况,在用户抢占中已经分析过,不管是用户抢占还是内核抢占,触发点都是一致的;

    2.2.2 抢占执行点
    内核抢占:抢占执行发生在进程处于内核态。

    总体而言,内核抢占执行点可以归属于两大类
    (1) 中断执行完毕后进行抢占调度;
    (2) 主动调用 preempt_enable 或 schedule 等接口的地方进行抢占调度;

    2.3 preempt_count

    (1) Linux内核中使用struct thread_info 中的 preempt_count 字段来控制抢占
    (2) preempt_count 的低8位用于控制抢占,当大于0时表示不可抢占,等于0表示可抢占。
    (3) preempt_enable()会将 preempt_count 值减1,并判断是否需要进行调度,在条件满足时进行切换;
    (4) preempt_disable()会将 preempt_count 值加1;

    此外,preemt_count字段还用于判断进程处于各类上下文以及开关控制等,如图:

    preemt_count 位图的定义见 includelinuxpreempt.h

    3. 上下文切换

    (1) 进程上下文:包含CPU的所有寄存器值、进程的运行状态、堆栈中的内容等,相当于进程某一时刻的快照,包含了所有的软硬件信息;
    (2) 进程切换时,完成的就是上下文的切换,进程上下文的信息会保存在每个struct task_struct结构体中,以便在切换时能完成恢复工作;

    进程上下文切换的入口就是__schedule(),分析也围绕这函数展开。

    3.1 __schedule()

    __schedule()函数调用分析如下:

    主要的逻辑:
    (1) 根据CPU获取运行队列,进而得到运行队列当前的task,也就是切换前的prev;
    (2) 根据prev的状态进行处理,比如pending信号的处理等,如果该任务是一个worker线程还需要将其睡眠,并唤醒同CPU上的另一个worker线程;
    (3) 根据调度类来选择需要切换过去的下一个task,也就是next;
    (4) context_switch完成进程的切换;

    3.2 context_switch()
    context_switch()的调用分析如下:

    核心的逻辑有两部分:
    (1) 进程的地址空间切换:切换的时候要判断切入的进程是否为内核线程,
    1)所有的用户进程都共用一个内核地址空间,而拥有不同的用户地址空间;
    2)内核线程本身没有用户地址空间。在进程在切换的过程中就需要对这些因素来考虑,涉及到页表的切换,以及cache/tlb的刷新等操作。
    (2) 寄存器的切换:包括CPU的通用寄存器切换、浮点寄存器切换,以及ARM处理器相关的其他一些寄存器的切换;

    进程的切换,带来的开销不仅是页表切换和硬件上下文的切换,还包含了Cache/TLB刷新后带来的miss的开销。在实际的开发中,也需要去评估新增进程带来的调度开销。

    参考:

    三)Linux进程调度器-进程切换

  • 相关阅读:
    12/18随笔周记
    12/11博客
    12/5周期
    页面管理———页边距
    页面管理
    开发周记
    Echarts图表在VUE项目中使用动态数据源
    VUE.js项目中控制台报错: Uncaught (in promise) NavigationDuplicated解决方法
    Maven工程中web项目提示The superclass "javax.servlet.http.HttpServlet" was not found on the Java Build Path错误
    Java运算符(i++与++i)
  • 原文地址:https://www.cnblogs.com/hellokitty2/p/14322952.html
Copyright © 2011-2022 走看看