• (三)Linux进程调度器进程切换【转】 sky


    转自:https://www.cnblogs.com/LoyenWang/p/12386281.html

    背景

    • Read the fucking source code! --By 鲁迅
    • A picture is worth a thousand words. --By 高尔基

    说明:

    1. Kernel版本:4.14
    2. ARM64处理器,Contex-A53,双核
    3. 使用工具:Source Insight 3.5, Visio

    1. 概述

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

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

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

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

    2. 抢占

    2.1 用户抢占

    2.1.1 抢占触发点

    • 可以触发抢占的情况很多,比如进程的时间片耗尽、进程等待在某些资源上被唤醒时、进程优先级改变等。Linux内核是通过设置TIF_NEED_RESCHED标志来对进程进行标记的,设置该位则表明需要进行调度切换,而实际的切换将在抢占执行点来完成。

    不看代码来讲结论,那都是耍流氓。先看一下两个关键结构体:struct task_structstruct thread_info。我们在前边的文章中也讲过struct task_struct用于描述任务,该结构体的首个字段放置的正是struct thread_infostruct thread_info结构体中flag字段就可用于设置TIF_NEED_RESCHED,此外该结构体中的preempt_count也与抢占相关。

    struct task_struct {
    #ifdef CONFIG_THREAD_INFO_IN_TASK
    	/*
    	 * For reasons of header soup (see current_thread_info()), this
    	 * must be the first element of task_struct.
    	 */
    	struct thread_info		thread_info;
    #endif
            ...
    }
    
    /*
     * low level task data that entry.S needs immediate access to.
     */
    struct thread_info {
    	unsigned long		flags;		/* low level flags */
    	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
    

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

    • 内核提供了set_tsk_need_resched函数来将thread_infoflag字段设置成TIF_NEED_RESCHED
    • 设置了TIF_NEED_RESCHED标志,表明需要发生抢占调度;

    2.1.2 抢占执行点

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

    • 异常处理后返回到用户态;
    • 中断处理后返回到用户态;
    • 系统调用后返回到用户态;

    如下图:

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

    2.2 内核抢占

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

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

    2.2.1 抢占触发点

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

    2.2.2 抢占执行点

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

    总体而言,内核抢占执行点可以归属于两大类:

    • 中断执行完毕后进行抢占调度;
    • 主动调用preemp_enableschedule等接口的地方进行抢占调度;

    2.3 preempt_count

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

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

    3. 上下文切换

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

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

    3.1 __schedule()

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

    主要的逻辑:

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

    3.2 context_switch()

    context_switch()的调用分析如下:

    核心的逻辑有两部分:

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

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

    作者:LoyenWang
    出处:https://www.cnblogs.com/LoyenWang/
    公众号:LoyenWang
    版权:本文版权归作者和博客园共有
    转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任
    【作者】张昺华
    【大饼教你学系列】https://edu.csdn.net/course/detail/10393
    【新浪微博】 张昺华--sky
    【twitter】 @sky2030_
    【微信公众号】 张昺华
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    ubuntu安装jdk的两种方法
    LeetCode 606. Construct String from Binary Tree (建立一个二叉树的string)
    LeetCode 617. Merge Two Binary Tree (合并两个二叉树)
    LeetCode 476. Number Complement (数的补数)
    LeetCode 575. Distribute Candies (发糖果)
    LeetCode 461. Hamming Distance (汉明距离)
    LeetCode 405. Convert a Number to Hexadecimal (把一个数转化为16进制)
    LeetCode 594. Longest Harmonious Subsequence (最长的协调子序列)
    LeetCode 371. Sum of Two Integers (两数之和)
    LeetCode 342. Power of Four (4的次方)
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/15750968.html
Copyright © 2020-2023  润新知