关于Linux内核抢占调度

实时操作系统与非实时操作系统

在工控领域,我们经常听到实时系统和非实时系统,Linux、Windows是典型的非实时系统,典型的实时操作系统有VxWorks,RT-Thread,uCOS,QNX,WinCE等。

“实时、非实时系统”和“抢占、非抢占调度”的关系

抢占式调度:优先级高的进程可以抢占CPU执行,在这种调度算法中,系统总是选择优先级别的最高的算法进行调度,并且 一旦高优先级别的任务准备就绪之后,它就会马上被调度而不等待低优先级的任务主动放弃CPU。

非抢占式调度:一般指轮询式的调度,当一个进程已经处于内核态,即已经产生了内部的系统调用,除非自愿放弃CPU,否则该进程将会一直运行下去,直至完成或退出内核。

在 RTOS中,主要的调度算法是基于任务优先级的抢占式调度,如果不使用抢占式调度,就无法保证在规定时间内完成工作,因为无法确认当前占有CPU的进程什么时候退出CPU。
所以实时系统和抢占式调度是相互依存的关系,没有抢占式调度,就没有实时系统。(暂下这么一个结论,其他实现实时系统的方式暂未考证)

Linux的preempt抢占机制

当前的Linux内核加入了内核抢占(preempt)机制。内核抢占指用户程序在执行系统调用期间可以被抢占,该进程暂时挂起,使新唤醒的高优先 级进程能够运行。这种抢占并非可以在内核中任意位置都能安全进行,比如在临界区中的代码就不能发生抢占。临界区是指同一时间内不可以有超过一个进程在其中 执行的指令序列。在Linux内核中这些部分需要用自旋锁保护。

如何避免进程被抢占(自旋锁)

自旋锁怎么用

前边了解到Linux中有些地方是不可以进行抢占的,我们要限制一些在临界区的抢占,我们应该利用自旋锁进行保护。
自旋锁的用法教程非常多,这里不再赘述。
参考文献:https://blog.csdn.net/silent123go/article/details/52760424

当执行了我们执行了锁资源之后,程序将执行preempt_disable(),代码如下。

/**
 * 当内核不可抢占时,spin_lock的实现过程。
 */
#define _spin_lock(lock)	\
do { \
	/**
	 * 调用preempt_disable禁用抢占。
	 */
	preempt_disable(); \
	/**
	 * _raw_spin_lock对自旋锁的slock字段执行原子性的测试和设置操作。
	 */
	_raw_spin_lock(lock); \
	__acquire(lock); \
} while(0)

判断是否可抢占的条件(未完成、待补充)

void preempt_count_add(int val) 用于增加当前进程的引用计数,这样可以避免当前进程被抢占
与之对应的是void preempt_count_sub(int val)
用来当前进程的引用计数,这样当引用计数为0时,当前进程就可以被抢占.
这两个函数是一对的,一般一起使用
其使用的例程如下:

#define __irq_enter()					\
	do {						\
		account_irq_enter_time(current);	\
		preempt_count_add(HARDIRQ_OFFSET);	\
		trace_hardirq_enter();			\
	} while (0)

/*
 * Exit irq context without processing softirqs:
 */
#define __irq_exit()					\
	do {						\
		trace_hardirq_exit();			\
		account_irq_exit_time(current);		\
		preempt_count_sub(HARDIRQ_OFFSET);	\
	} while (0)

可以看到在进入irq是调用preempt_count_add 来增加引用计数避免被抢占,离开irq是调用preempt_count_sub 来减少引用计数使能抢占
其源码分析如下:

void preempt_count_add(int val)
{

#ifdef CONFIG_DEBUG_PREEMPT
	/*
	 * Underflow?
	 */
	if (DEBUG_LOCKS_WARN_ON((preempt_count() < 0)))
		return;
#endif
	__preempt_count_add(val);
#ifdef CONFIG_DEBUG_PREEMPT
	/*
	 * Spinlock count overflowing soon?
	 */
	DEBUG_LOCKS_WARN_ON((preempt_count() & PREEMPT_MASK) >=
				PREEMPT_MASK - 10);
#endif
	preempt_latency_start(val);
}

假定不打开CONFIG_DEBUG_PREEMPT的话,则preempt_count_add 中首先调用__preempt_count_add 来增加引用计数,然后调用preempt_latency_start 来开始
Start timing the latency.这个has如果没有定义CONFIG_DEBUG_PREEMPT 和 CONFIG_PREEMPT_TRACER的话,也等同于空函数.

void preempt_count_sub(int val)
{
#ifdef CONFIG_DEBUG_PREEMPT
	/*
	 * Underflow?
	 */
	if (DEBUG_LOCKS_WARN_ON(val > preempt_count()))
		return;
	/*
	 * Is the spinlock portion underflowing?
	 */
	if (DEBUG_LOCKS_WARN_ON((val < PREEMPT_MASK) &&
			!(preempt_count() & PREEMPT_MASK)))
		return;
#endif

	preempt_latency_stop(val);
	__preempt_count_sub(val);
}

假定不打开CONFIG_DEBUG_PREEMPT的话,则ppreempt_count_sub 中首先调用preempt_latency_stop 来Stop timing the latency来增加引用计数,然后调用preempt_latency_start 来开始
然后调用__preempt_count_sub 来减少当前进程的引用计数,如果引用计数是0的话,则使能进程抢占.

在这里插入图片描述

参考文献:http://blog.sina.com.cn/s/blog_502c8cc401012pxj.html

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

塔通天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值