Linux进程切换(2) 同步处理

一、前言

本文主要描述了主调度器(schedule函数)中的同步处理。

二、进程调度简介

进程切换有两种,一种是当进程由于需要等待某种资源而无法继续执行下去,这时候只能是主动将自己挂起(调用schedule函数),引发一次任务调度过程。另外一种是进程被抢占。所谓抢占(preempt)就是在当前进程欢快执行的时候,终止其对CPU资源的占用,切换到另外一个更高优先级的进程执行。进程被抢占往往是由于各种调度事件的发生:

1、 时间片用完

2、 在中断上下文中唤醒其他优先级更高的进程

3、 在其他进程上下文中唤醒其他优先级更高的进程。

4、 在其他进程上下文中修改了其他进程的调度参数

5、 ……

在当前进程被抢占的场景下,调度并不是立刻发生,而是延迟执行,具体的方法是设定当前进程的need_resched等于1,然后静静的等待最近一个调度点的来临,当调度点到来的时候,内核会调用schedule函数,抢占当前task的执行。

此外,我们还需要了解基本的抢占控制的知识。在一个进程的thread info中有一个preempt_count的成员用来控制抢占,当该成员等于0的时候表示允许抢占,在本文中,我们分别用preempt counter、hardirq counter和softirq counter分别表示其中的bit field。更详细的描述可以参考相关文档的描述

三、schedule函数使用了哪些同步机制

schedule函数的代码框架如下:

asmlinkage __visible void __sched schedule(void)
{
    do {
        preempt_disable();-----------------a

                raw_spin_lock_irq(&rq->lock);--------b

选择next task

切到next task执行

raw_spin_unlock_irq(&rq->lock); -------c
        sched_preempt_enable_no_resched(); --------d
    } while (need_resched()); ---------------e
}

我们以X进程切换到Y进程为例,描述schedule函数中同步机制的使用情况。在X进程上下文中,a点首先关闭了抢占,X task的preempt counter会加1。然后在b点会持有该CPU runqueue的spinlock,当然在这个过程中会disable CPU中断处理,同时将X task的preempt counter再次加1,这时候X task的preempt counter应该等于2。

打开X task的抢占的时候是在重新调度X在某个CPU上执行的时候,这时候,在上面代码中的c和d点来递减preempt counter,当进入e点的时候,preempt counter已经等于0。

由于在切换过长设计runqueue队列的操作,因此需要spin lock来保护。不过在进程切换过程中,runqueue spin lock是不同进程来协同处理的。我们仍然以X进程切换到Y进程为例。在X进程中,在b点持锁并disable了本地中断,而spin lock的释放是在Y进程中完成的(c点),在释放spin lock的同时,也会打开cpu中断。

四、可不可以禁止抢占的时候调用schedule函数

在进程上下文中,下面的调用序列是否可以呢?

preempt_disable

……schedule……

preempt_enable

无论什么场景,disable preempt然后调用schedule都是很奇怪的一件事情:本来你已经禁止抢占了,但是又显示的调用schedule函数,你这不是精神分裂吗?schedule函数怎么处理这个精神分裂的task呢?在调用schedule函数之前,它毫无疑问是期待preempt count等于0的,只有当前task的preempt count等于0才说明抢占的合理性。不过在整个进程切换的过程中,首先会在a点禁止抢占,这样可以确保CPU和当前task之间的关系不变(cpu不变、current task不变,runqueue不变)。这样,在a和b之间的对caller的调用检查就比较好开展了,具体如下:

static inline void schedule_debug(struct task_struct *prev)
{

if (unlikely(in_atomic_preempt_off())) {
        __schedule_bug(prev);
        preempt_count_set(PREEMPT_DISABLED);
    }
}

in_atomic_preempt_off这个宏就是对当前preempt count进行测试,这时候正确的preempt counter应该是等于1,其他的bit field,例如softirq counter、hardirq count等都是0。具体关于preempt count的位域描述可以参考本站软中断的文档。如果没有设定正确的preempt_count就调用schedule函数,那么说明在atomic上下文中错误的进行了调度,__schedule_bug会打印出相关信息,方便调试。

虽然在错误的场景中调用了schedule函数,但是内核还是要艰难前行啊,因此这里会修改preempt count的值为PREEMPT_DISABLED,而这才是进入schedule函数正确的姿势。

五、可不可以关闭中断调用schedule函数?

在进程上下文中,下面的调用序列是否可以呢?

local_irq_disable

……schedule……

local_irq_enable

当然这里也许不是直接调用schedule函数,很多内核接口API会隐含调用schedule函数,因此也许你会有意无意的写出上面形态的代码。

首先需要明确一点:从X进程切换到Y进程的时候,如果在X进程中关闭中断,然后切换到Y进程,如果中断不恢复的话,那么Y进程会一直执行,直到Y自己良心发现,让出CPU。这当然是不被允许的。因此,在调用schedule进行进程切换的时候,无论调用者是否关闭中断,在b点都会关闭中断(注意,这时候并没有记录之前的中断状态)。而在切入到Y进程之后,在c点都会显式的打开CPU中断。因此,上面的代码虽然不推荐,但是也不会对调度产生太大的影响。

六、禁止中断是否可以禁止抢占?

禁止了中断的确等于了禁止抢占,但是并不意味着它们两个完全等同,因为在preempt disable---preempt enable这个的调用过程中,在打开抢占的时候有一个抢占点,内核控制路径会在这里检查抢占,如果满足抢占条件,那么会立刻调度schedule函数进行进程切换,但是local irq disable---local irq enable的调用中,并没有显示的抢占检查点,当然,中断有点特殊,因为一旦打开中断,那么pending的中断会进来,并且在返回中断点的时候会检查抢占,但是也许下面的这个场景就无能为力了。进程上下文中调用如下序列:

(1)local irq disable

(2)wake up high level priority task

(3)local irq enable

当唤醒的高优先级进程被调度到本CPU执行的时候,按理说这个高优先级进程应该立刻抢占当前进程,但是这个场景无法做到。在调用try_to_wake_up的时候会设定need resched flag并检查抢占,但是由于中断disable,因此不会立刻调用schedule,但是在step (3)的时候,由于没有检查抢占,这时候本应立刻抢占的高优先级进程会发生严重的调度延迟.....直到下一个抢占点到来。

原文地址:https://www.cnblogs.com/alantu2018/p/8447592.html

时间: 2024-08-01 08:05:18

Linux进程切换(2) 同步处理的相关文章

Linux进程切换(1) 基本框架

一.前言 本文主要是以context_switch为起点,分析了整个进程切换过程中的基本操作和基本的代码框架,很多细节,例如tlb的操作,cache的操作,锁的操作等等会在其他专门的文档中描述.进程切换包括体系结构相关的代码和系统结构无关的代码.第二.三.四分别描述了context_switch的代码脉络,后面的章节是以ARM64为例子,讲述了具体进程地址空间的切换过程和硬件上下文的切换过程. 二.context_switch代码分析 在kernel/sched/core.c中有一个contex

Linux进程切换(2) TLB处理

一.前言 进程切换是一个复杂的过程,本文不准备详细描述整个进程切换的方方面面,而是关注进程切换中一个小小的知识点:TLB的处理.为了能够讲清楚这个问题,我们在第二章描述在单CPU场景下一些和TLB相关的细节,第三章推进到多核场景,至此,理论部分结束.在第二章和第三章,我们从基本的逻辑角度出发,并不拘泥于特定的CPU和特定的OS,这里需要大家对基本的TLB的组织原理有所了解,具体可以参考本站的<TLB操作>一文.再好的逻辑也需要体现在HW block和SW block的设计中,在第四章,我们给出

linux进程切换问题

#define switch_to(prev,next,last) do { unsigned long esi,edi; asm volatile("pushfl\n\t" "pushl %%ebp\n\t" "movl %%esp,%0\n\t" /* save ESP */ "movl %5,%%esp\n\t" /* restore ESP */ "movl $1f,%1\n\t" /* save

[linux]进程(三)——idle进程

9,linux进程切换 进程切换:基本概念:进程上下文:当一个进程在执行时,CPU的所有寄存器中的值.进程的状态以及堆栈中的内容被称为该进程的上下文.当内核需要切换到另一个进程时,它需要保存当前进程的所有状态,即保存当前进程的上下文,运行于进程上下文的进程是可以被抢占的.硬件上下文:进程恢复执行前必须载入寄存器的一组数据称为硬件上下文linux内核在进程切换的时候是并不区分进程和线程的~因为切换是针对task,进程切换的时机:一般是在系统调用或者中断的时候,发生在内核态.进程切换过程:进程切换统

Linux内核设计第八周学习总结 理解进程调度时机跟踪分析进程调度与进程切换的过程

陈巧然 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.视频内容 Linux系统的一般执行过程 最一般的情况:正在运行的用户态进程X切换到运行用户态进程Y的过程 1. 正在运行的用户态进程X 2. 发生中断——save cs:eip/esp/eflags(current) to kernel stack, then load cs:eip(entry of a specific IS

Linux内核源码学习之进程切换细节整理

linux中的进程是个最基本的概念,进程从运行队列到开始运行有两个开始的地方, 一个就是switch_to宏中的标号1:"1:/t",//只要不是新创建的进程,几乎都是从上面的那个标号1开始的,而switch_to宏则是除了内核本身,所有的进程要 想运行都要经过的地方 另 一个就是ret_form_fork 这样看来,虽然linux的进程体系以及进程调度非常复杂,但是总体看来就是一个沙漏状, 对于系统中的每个新进程它首次被执行的过程必然是: sys_fork---->do_for

Linux中四种进程或线程同步互斥控制方法

原文地址:http://blog.itpub.net/10697500/viewspace-612045/ 一.Linux中 四种进程或线程同步互斥的控制方法: 1.临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问. 2.互斥量:为协调共同对一个共享资源的单独访问而设计的. 3.信号量:为控制一个具有有限数量用户资源而设计. 4.事 件:用来通知线程有一些事件已发生,从而启动后继任务的开始. 二.临界区(Critical Section) 保证在某一时刻只有一个线程

Linux进程调度(3):进程切换分析

3.调度函数schedule()分析 当kernel/sched.c:sched_tick()执行完,并且时钟中断返回时,就会调用kernel/sched.c:schedule()完成进程切换.我们也可以显示调用schedule(),例如在前面"Linux进程管理"的介绍中,进程销毁的do_exit()最后就直接调用schedule(),以切换到下一个进程. schedule()是内核和其他部分用于调用进程调度器的入口,选择哪个进程可以运行,何时将其投入运行.schedule通常都和一

Linux内核进程调度的时机和进程切换

陈铁+ 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 对于现代操作系统,多任务是必备的,在linux系统下,进程会不断的被内核调度,从X进程切换为Y进程,以实现用户所见到的多任务状态,下面我们就看一看这样的过程,分析一下内核如何对进程调度,以及进程间如何切换. 内核使用schedule()函数实现进程的调度,而通常的用户进程要无法主动调度这个函数,只能通过中断处理过程(包括时钟中断