【INT的内核笔记】scheduler_tick()源码分析

限时加码!20+主流AI编程工具免费用 购周边加赠Coding Plan Lite,Claude Code、Cursor等即刻畅享,学习进阶更高效! 阅读详情

1. 调用时机

一般会在时钟中断时被调用,作用有:

  • 剥夺时间片耗完的进程的CPU使用权,当然具体细节上没这么简单;
  • 进行定时的CPU间负载均衡处理;

2. 大致流程

  • idle进程(swapper进程)的处理。

    idle进程没有时间片可言,主要思路是如果当前逻辑CPU的可执行队里已有进程,应该尽快调度。

    这里所指的调度还是延时调度,也就是设置TIF_NEED_RESCHED,

    还会有些强制调度之类的处理,细节暂未研究;

  • 实时进程的处理。

    在这版本的源码中,没有调度类的概念,实时进程和普通进程是放在相同的可执行队列的。

    实时进程的调度状况:

    • 如果可执行的话,会一直停留在active队列,

      优先级比所有普通进程高,

      因此如果实时进程没有阻塞,同一逻辑CPU中的普通进程没机会运行;

    • SCHED_RR类型的实时进程在时间片消耗完后,会被放到active队列的末尾,

      并且设置TIF_NEED_RESCHED位,

      因此拥有不小于当前优先级的其他实时进程会被调度到;

    • SCHED_FIFO类型的实时进程,就真的除非被更高优先级的实时进程抢占,

      时钟中断这边并不能让它们让出CPU;

  • 交互式进程的处理

    • 当时间片用完时,

      设置TIF_NEED_RESCHED位;

      但当其比expired队列所有进程优先级都高时,

      并不放入expired队列中,而是重新放入active队列中;

      这是对交互式进程的一种优待处理;

    • 当时间片未用完时,

      交互式进程的时间片会被细分为粒度,当运行完一个粒度后,

      就需要重新调度,也就是设置TIF_NEED_RESCHED位;

      这是对交互式进程的一种削弱,毕竟地位没有实时进程高;

  • 普通非交互式进程的处理

    • 当时间片用完时,

      设置TIF_NEED_RESCHED位,并放入到expired队列中;

    • 当时间片未用完时,不作任何操作;

  • 定时负载均衡

    ​ 在整个函数结束之前执行的一步,对各个CPU进行负载均衡处理。

    ​ 具体有点复杂,会在load_balance()的文章进行大致介绍;

3. 源码注释

/*
 * This function gets called by the timer code, with HZ frequency.
 * We call it with interrupts disabled.
 *
 * It also gets called by the fork code, when changing the parent's
 * timeslices.
 */
/**
 * 维持当前最新的time_slice计数器
 * 每次时钟节拍到来时,scheduler_tick函数将被调用,以执行与调度相关的操作。
 */
void scheduler_tick(void)
{
	int cpu = smp_processor_id();
	runqueue_t *rq = this_rq();
	task_t *p = current;

	/**
	 * 把转换为纳秒的TSC的当前值存入本地运行队列的timestamp_last_tick中。这个时间戳由sched_clock获得。
	 */
	rq->timestamp_last_tick = sched_clock();

	// idle进程的处理 //////////////////////////////////////////////////////////////////////////////////////////
	if (p == rq->idle) {
		/**
		 * wake_priority_sleeper()的逻辑如下:
		 * 	1.检查运行队列中除了IDLE进程外,是否还有其他可运行进程;
		 * 	2.如果有,就设置当前进程的TIF_NEED_SCHEDULED字段;
		 * 	3.好像多核处理器会直接强迫调度?
		 *	scheduler_tick()是被时钟中断调用的,
		 *	因此,如果是单核情况下,肯定会等到中断返回后再正式调度;

		 * rebalance_tick(cpu, rq, NOT_IDLE);
		 */
		if (wake_priority_sleeper(rq))
			goto out;
			
		/** 
		 * 平衡每个CPU上的可执行队列,
		 * 目标当然就是全部长度都一样。
		 */
		rebalance_tick(cpu, rq, SCHED_IDLE);
		
		/**
		 * 没有必要更新IDLE进程的时间片计数器,所以此处直接返回。
		 */
		return;
	}

	/* Task might have expired already, but not scheduled off yet */
	/**
	 * 检查current->array是否指向本地运行队列的活动链表。
	 * 如果不是,说明进程已经过期但还没有被替换,设置TIF_NEED_SCHEDULED标志,以强制进行重新调度。
	 */ 
	if (p->array != rq->active) {
		set_tsk_need_resched(p);
		goto out;
	}
	/**
	 * 获得运行队列的自旋锁。
	 */
	spin_lock(&rq->lock);
	/*
	 * The task was running during this tick - update the
	 * time slice counter. Note: we do not update a thread's
	 * priority until it either goes to sleep or uses up its
	 * timeslice. This makes it possible for interactive tasks
	 * to use up their timeslices at their highest priority levels.
	 */

	// 实时进程的处理 //////////////////////////////////////////////////////////////////////////////////////////
	if (rt_task(p)) {
		/*
		 * RR tasks need a special form of timeslice management.
		 * FIFO tasks have no timeslices.
		 */
		/**
		 * 对SCHED_RR类型的实时进程,需要递减它的时间片。
		 * 对SCHED_FIFO类型的实时进程,什么都不做,退出。
		 * 
		 * 当SCHED_RR类型的实时进程,其时间片耗尽后,
		 * 1.更新时间片;
		 * 2.设置TIF_NEED_SCHEDULED字段,以便返回用户时进行调度;
		 * 3.将其放到active的末尾,这样的效果是能让优先级相同的其他实时进程被调度到;
		 * 
		 * 这版的内核在实时进程和普通进程方面比较混乱,全部都放在一个可执行队列中,
		 * 依靠不把实时进程放到expired队列,和实时进程永远比普通进程优先级高,
		 * 这两点来实现实时进程长久占用CPU的效果。
		 * 
		 */
		if ((p->policy == SCHED_RR) && !--p->time_slice) {

			/* 重新计算它的时间片,它根据进程的静态优先级来计算它的时间片。 */
			p->time_slice = task_timeslice(p);
			/**
			 * 直到这里,说明进程一定不是第一次运行了,它已经用完了一次它的时间片,将first_time_slice置为0.
			 * 这样,它即使退出,也不会将剩余的时间片还给父进程了。
			 */
			p->first_time_slice = 0;
			/**
			 * 设置调度标志,以达到尽快抢占的目的。
			 */
			set_tsk_need_resched(p);

			/* put it at the end of the queue: */
			/**
			 * 将实时进程放到队列末尾。这样,如此链表中还有其他同优先级的RR进程,其他进程就能够得到运行了。
			 */
			requeue_task(p, rq->active);
		}
		goto out_unlock;
	}

	// 普通进程的处理 //////////////////////////////////////////////////////////////////////////////////////////
	/* 首先递减普通进程的时间片计数器。如果用完,继续执行以下操作 */
	if (!--p->time_slice) {
		/**
		 * 既然用完了,就将当前进程从活动集合中摘除。
		 */
		dequeue_task(p, rq->active);
		/**
		 * 当然,当前进程既然已经过期,就必须设置重新调度标志,以便在中断返回前调用schedule选择另外一个进程来运行。
		 */
		set_tsk_need_resched(p);
		/**
		 * 更新当前进程的动态优先级。
		 * effective_prio根据当前进程的static_prio和sleep_avg字段,计算进程的动态优先级。
		 */
		p->prio = effective_prio(p);
		/**
		 * 重填进程的时间片
		 */
		p->time_slice = task_timeslice(p);
		/**
		 * 既然当前进程的一个时间片已经用完,当然就需要清除first_time_slice标志了。
		 */
		p->first_time_slice = 0;

		/**
		 * 如果本地运行队列的expired_timestamp为0,表示过期进程集合为空。
		 * 并且当前进程马上就会变成过期进程,那么将当前jiffies赋给expired_timestamp
		 * expired_timestamp表示当前队列中,过期队列中最老进程被插入过期队列的时间。
		 */
		if (!rq->expired_timestamp)
			rq->expired_timestamp = jiffies;
		/**
		 * 把当前进程插入过期集合或者活动集合。
		 * TASK_INTERACTIVE判断当前进程是否是一个交互式进程。
		 * TASK_INTERACTIVE宏检查运行队列中的第一个过期进程的等待时间
		 * 是否已经超过1000个时钟节拍乘以运行队列中的可运行进程数+1,
		 * 如果是返回1.
		 * 如果当前进程的静态优先级大于过期进程的静态优先级,也返回1.
		 */
		if (!TASK_INTERACTIVE(p) || EXPIRED_STARVING(rq)) {
			/**
			 * 当前进程不是交互式进程,或者过期队列中有优先级更高的进程,那么将当前进程插入到过期队列。
			 */
			enqueue_task(p, rq->expired);
			/**
			 * 如果当前进程是过期队列中优先级最高的低,就更新过期队列的最高优先级。
			 */
			if (p->static_prio < rq->best_expired_prio)
				rq->best_expired_prio = p->static_prio;
		} 
		/* 进程是交互式进程,并且比过期队列中所有进程的静态优先级高,那么就将它加到活动队列中。这实际上是对交互式进程的优待。 */
		else
			enqueue_task(p, rq->active);
	} 
	/* 普通进程的时间片还没有用完,需要进一步检查是否时间片太长 */
	else {
		/*
		 * Prevent a too long timeslice allowing a task to monopolize
		 * the CPU. We do this by splitting up the timeslice into
		 * smaller pieces.
		 *
		 * Note: this does not mean the task's timeslices expire or
		 * get lost in any way, they just might be preempted by
		 * another task of equal priority. (one with higher
		 * priority would have preempted this task already.) We
		 * requeue this task to the end of the list on this priority
		 * level, which is in essence a round-robin of tasks with
		 * equal priority.
		 *
		 * This only applies to tasks in the interactive
		 * delta range with at least TIMESLICE_GRANULARITY to requeue.
		 */
		/**
		 * 总的来说,就是对交互式进程进行限制,防止它独占CPU,
		 * 因为交互式进程有一定优待,有可能时间片用完后,仍然停留在active队列中,
		 * 一定情况下,可能像实时进程那样,一直占着CPU。
		 * 
		 * 但是交互式进程,地位比实时进程低,不允许这样的行为,
		 * 所以这里就进行了一点削弱。
		 * 把交互式进程的时间片分成几份也就是TIMESLICE_GRANULARITY,
		 * 用完一份之后就重新调度一下。
		 */
		if (TASK_INTERACTIVE(p) && !((task_timeslice(p) -
			p->time_slice) % TIMESLICE_GRANULARITY(p)) &&
			(p->time_slice >= TIMESLICE_GRANULARITY(p)) &&
			(p->array == rq->active)) {

			requeue_task(p, rq->active);
			set_tsk_need_resched(p);
		}
	}
out_unlock:
	/**
	 * 释放自旋锁。
	 */
	spin_unlock(&rq->lock);
out:
	/**
	 * 调用rebalance_tick函数,该函数应该保证不同CPU的运行队列包含数量基本相同的可运行进程。
	 */
	rebalance_tick(cpu, rq, NOT_IDLE);
}

参考

《深入理解linux内核》

baoyou.xie Linux 2.6.12代码注释

Linux进程管理17 - CFS调度器4 - 周期调度 task_tick_fair 详细参考:Linux进程管理11 - 调度时机3 - 周期调度 & 抢占触发点。 阅读详情

相关推荐

ArduSub程序学习(1)——ardupilot框架整理

AP_HAL层(硬件抽象层)是我们制作 ArduPilot 的方式可移植到许多不同的平台。有一个顶级AP_HAL libraries/AP_HAL,用于定义代码其余部分的接口 有针对具体的板子特性,然后就有了AP_HAL_XXX 子目录,例如,AP_HAL_AVR 用于基于 AVR 的 开发板,AP_HAL_PX4用于 Pixhawk 开发板和 AP_HAL_Linux 基于 Linux 开发板。工具目录tools directories工具目录是杂项支持目录。

weixin_49839886的博客 2788

linux调度器源码分析 - 运行(四)

linux调度器源码分析 - 运行(四) 本文为原创,转载请注明:http://www.cnblogs.com/tolimit/   引言   之前的文章已经将调度器的数据结构、初始化、加入进程都进行了分析,这篇文章将主要说明调度器是如何在程序稳定运行的情况下进行进程调度的。   系统定时器   因为我们主要讲解的是调度器,而会涉及到一些系统定时器的知识,这里我们简单讲解

zdy0_2004的专栏 734

汽车LIN总线(3) -- 协议层:帧的类型

主要介绍LIN协议层中帧的类型,介绍中加入一点自己的理解(带下划线的是,其余是官方资料)。

晴朗luo的博客 864

linux内核调度问题分析

假如内核只有3个线程,线程0创建线程1和线程2.当系统时钟到来时,时钟中断处理函数会检查是否有进程需要调度。当有进程需要调度时,调度器会选择线程1或者线程2。start_kernel运行在线程0里,线程0创建线程1和线程2。函数调用关系start_kernel()->kernel_debug()->do_fork 创建新线程,并把新线程添加到调度器的就绪队列中。线程0创建线程1和线程2后,进入while线程,线程0不会退出,等待被调度出去。1、产生时钟中断。处理器采用定时器来周期性地执行。

2873

scheduler_tick函数详解

scheduler_tick是调度器中的一个核心重要的函数,它叫做周期调度器,驱动调度器运行的机制之一。 event_handler()--&gt;tick_handle_periodic()-&gt;tik_periodic()-&gt;update_process_times()--&gt;scheduler_tick() 以上是该函数的调用回溯,由时钟中断驱动着这个函数的运行,每个CPU ...

程序猿Ricky的日常干货 6409

scheduler_tick函数

本博,我们详细分解每次定时器中断调用的最重要的更新时间片的函数 —— scheduler_tick函数当每次时钟节拍到来时,即我们提到过的timer_interrupt会调用do_timer_interrupt_hook,从而调用do_timer和update_process_times函数,update_process_times则就是用来更新进程使用到的一些跟时间相关的字段,其罪重要的

云(存储),松(耦合) 7075

RT-Thread线程让出实验:从yield函数理解实时系统调度原理

在实时操作系统(RTOS)中,线程调度是核心机制,它决定了多任务环境下CPU资源的分配策略。其基本原理是基于优先级和状态的调度算法,通过就绪队列管理线程执行顺序,确保高优先级任务及时响应。线程让出(yield)作为一种主动协作式调度技术,允许运行中的线程自愿放弃CPU,将其重新插入同优先级就绪队列末尾,从而触发调度器重新选择执行线程。这一机制对于实现同优先级任务间的公平轮转、防止低优先级线程饿死具有重要价值,尤其在资源受限的嵌入式系统中能优化CPU利用率。在RT-Thread等实时操作系统应用场景中,理解y

weixin_33755649的博客 362

Sched_ext 回调深度解析(三):enable —— 任务被调度器接管的关键时刻(6.18.26)

本文分析了Linux内核sched_ext调度器的enable回调机制。当任务被sched_ext调度器正式接管时触发enable回调,完成从READY到ENABLED状态的转换。与init_task不同,enable回调仅在任务真正由sched_ext管理时触发。文章详细介绍了底层机制,包括调度类选择(scx_setscheduler_class)、任务接管流程(scx_enable_task)以及三种触发场景:注册调度器时批量切换、fork新任务时自动继承和系统调用主动切换。通过流程图展示了状态转换过

geshifei的博客 558

Sched_ext 回调深度解析(八):running —— 任务开始执行(6.18.26)

important;important;fill:none;running要点触发时机每次调度切换时触发与stopping配对 非一次性执行顺序先stopping旧任务后running新任务跨CPU陷阱可能不在目标CPU上执行必须用scx_bpf_task_cpu获取CPU上下文约束持有rq lock 不能阻塞只能调用SCX_KF_REST典型用途推进全局vtime时钟与stopping配合实现vtime调度。

geshifei的博客 360

FreeRTOS——任务

使用FreeRTOS时,我们可以创建多个任务(task),有些文档把任务也称为线程(thread)。在FreeRTOS中,任务就是一个函数for(;;注意:(1)此函数通常以无限循环的形式存在,不能有返回值或者退出,但是任务可以进行自我删除(2)多个任务可以调用同一个函数。

qq_68719489的博客 1692

FreeRTOS嵌入式开发实战:从多任务调度到STM32项目应用

实时操作系统(RTOS)是嵌入式开发中实现多任务管理的核心技术,它通过任务调度器在单核MCU上模拟并行执行,解决了传统前后台模式中任务阻塞和实时性差的问题。其原理基于优先级抢占和时间片轮转调度算法,确保高优先级任务能及时响应。这一机制的技术价值在于将复杂应用拆分为逻辑清晰、功能独立的模块,提升代码可维护性和系统可靠性。在物联网设备、工业控制等应用场景中,FreeRTOS作为轻量级开源RTOS内核,凭借其可裁剪性和丰富的通信机制(如队列、信号量),成为嵌入式开发的常见选择。开发者常关注FreeRTOS堆栈溢出

weixin_30600197的博客 573

Linux CFS调度器之周期性调度器scheduler_tick函数

Linux CFS调度器之周期性调度器scheduler_tick函数简介

小立仔 2199

《linux kernel develpoment》第四章

《linux内核设计与实现》 进程调度 1、多任务

一位不知名的linux爱好者入门 412

Linux核心调度器之周期性调度器scheduler_tick--Linux进程的管理与调度(十八)

日期 内核版本 架构 作者 GitHub CSDN 2016-6-29 Linux-4.6 X86 & arm gatieme LinuxDeviceDrivers Linux进程管理与调度 我们前面提到linux有两种方法激活调度器:核心调度器和 一种是直接的, 比如进程打算睡眠或出于其他原因放弃CPU 另一种是通过周期性的机制, 以固定的频率运行, 不时

OSKernelLAB(gatieme) 1万+

Linux调度器的核心scheduler_tick介绍

在Linux内核中扮演着关键角色。它不仅负责处理定时器中断和更新系统时间,还记录进程的运行时间,并决定是否需要进行任务切换。通过这些功能,有效保障了系统的时间管理和任务调度,使操作系统能够高效、准确地管理多个进程。

望获实时Linux系统 1150

Linux-进程的管理与调度18(基于6.1内核)

Linux-进程的管理与调度18(基于6.1内核)---Linux核心调度器scheduler_tick

ListQ的博客 1087

Tick数据是什么,要如何获取?

我们日常看到的K线行情数据是基于时间单位的,而tick数据则记录了更细致的维度,即每次价格变化都被记录。

qq_34102871的博客 4522

进程管理基础学习笔记 - 4. scheduler_tick

目录1. 前言2. scheduler_tick参考文档 1. 前言 本专题我们开始学习进程管理部分。本文主要参考了《奔跑吧, Linux内核》、ULA、ULK的相关内容。本文只是作为学习笔记以用于构建知识框架,可能存在一些理解不恰当或不到位的地方,后续会随着学习的深入,逐步进行迭代。 本文主要记录system tick中断时进程调度部分的处理流程,注意此函数只是在判断符合条件时设置了TIF_NEED_RESCHED 标志,并没有真正执行进程切换。 kernel版本:5.10 平台:arm64 2. sch

HZero 431

内核调度、负载均衡】【scheduler_tick

scheduler_tick 系统的软中断触发会周期性的调度scheduler_tick函数 /* * This function gets called by the timer code, with HZ frequency. * We call it with interrupts disabled. */ void scheduler_tick(void) { int cpu = smp_processor_id(); struct rq *rq = cpu_rq(cpu); st

勿于浮沙筑高台 1092
上一篇: 【内核资料】小知识点,likely与unlikely
下一篇: 【INT的内核笔记】调度时机与抢占
Rachelint
博客等级 码龄9年 7粉丝 60原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值