嵌入式Linux实时技术改进与实现

来源:本站
导读:目前正在解读《嵌入式Linux实时技术改进与实现》的相关信息,《嵌入式Linux实时技术改进与实现》是由用户自行发布的知识型内容!下面请观看由(电工技术网 - www.9ddd.net)用户发布《嵌入式Linux实时技术改进与实现》的详细说明。
简介:随着微处理器技术的发展,嵌入式系统已经成为计算机应用领域的一个重要的组成部分。

Linux虽然为分时操作系统,但由于其功能强大、源代码开放以及可移植性强等优势,已成为日益流行的嵌入式实时操作系统的解决方案,然而,在实时方面它还不能很好地满足实时系统方面的需要,其本身仅仅提供了一些实时处理的支持。为使Linux满足实时应用的要求,常用的方法是通过对Linux的内核进行裁减和修改,使其能够满足实时性的要求。目前,我们根据实际需要对于Linux采用以下方法进行改进。

2、实现低延迟

使用两种方法来实现低延迟:一种就是锁分解,即把大循环中保持的锁分解为每一轮循环中都获得锁和释放锁,典型的代码结构示例如下:

锁分解前:

spin_lock(&x_lock);

for (…) {some operations;

…}

spin_unlock(&x_lock);

锁分解后:

for (…) { spin_lock(&x_lock);

some operations;

spin_unlock(&x_lock);}

另一种是增加抢占点,即自愿被抢占。增加抢占点之后:

/* Do the write .. */

for (;;) {

size_t size = count;

if (size > chunk)

size = chunk;

ret = -EFAULT;

if (copy_from_user(tty->write_buf, buf, size))

break;

lock_kernel();

ret = write(tty, file, tty->write_buf, size);

unlock_kernel();

if (ret <= 0)

break;

written += ret;

buf += ret;

count -= ret;

if (!count)

break;

ret = -ERESTARTSYS;

if (signal_pending(current))

break;

cond_resched();

}

语句cond_resched()将判断是否有进程需要抢占当前进程,如果是将立即发生调度,这就是增加的抢占点,它支持四种抢占模式:

(1).No Forced Preemption (Server),这种模式等同于没有使能抢占选项的标准内核,主要适用于科学计算等服务器环境。

(2).Voluntary Kernel Preemption (Desktop),这种模式使能了自愿抢占,但仍然失效抢占内核选项,它通过增加抢占点缩减了抢占延迟,因此适用于一些需要较好的响应性的环境,如桌面环境,当然这种好的响应性是以牺牲一些吞吐率为代价的。

(3).Preemptible Kernel (Low-Latency Desktop),这种模式既包含了自愿抢占,又使能了可抢占内核选项,因此有很好的响应延迟,实际上在一定程度上已经达到了软实时性。它主要适用于桌面和一些嵌入式系统,但是吞吐率比模式2更低。

(4).Complete Preemption (Real-Time),这种模式使能了所有实时功能,因此完全能够满足软实时需求,它适用于延迟要求为100微秒或稍低的实时系统。

实现实时是以牺牲系统的吞吐率为代价的,因此实时性越好,系统吞吐率就越低。

3、中断线程化

中断线程化是实现Linux实时性的一个重要步骤,在Linux标准内核中,中断是最高优先级的执行单元,不管内核当时处理什么,只要有中断事件,系统将立即响应该事件并执行相应的中断处理代码,除非当时中断关闭。因此,如果系统有严重的网络或I/O负载,中断将非常频繁,后发生的实时任务将很难有机会运行,也就是说,毫无实时性可言。中断线程化之后,中断将作为内核线程运行而且赋予不同的实时优先级,实时任务可以有比中断线程更高的优先级,这样,实时任务就可以作为最高优先级的执行单元来运行,即使在严重负载下仍有实时性保证。

中断线程化的另一个重要原因是spinlock被mutex取代。中断处理代码中大量地使用了spinlock,当spinlock被mutex取代之后,中断处理代码就有可能因为得不到锁而需要被挂到等待队列上,但是只有可调度的进程才可以这么做,如果中断处理代码仍然使用原来的 spinlock,则spinlock取代mutex的努力将大打折扣,因此为了满足这一要求,中断必须被线程化,包括IRQ和softirq。

中断线程化的实现方法是:对于IRQ,在内核初始化阶段init(该函数在内核源码树的文件init/main.c中定义)调用 init_hardirqs(该函数在内核源码树的文件kernel/irq/manage.c中定义)来为每一个IRQ创建一个内核线程,IRQ号为0 的中断赋予实时优先级49,IRQ号为1的赋予实时优先级48,依次类推直到25,因此任何IRQ线程的最低实时优先级为25。原来的do_IRQ被分解成两部分,架构相关的放在类似于arch/*/kernel/irq.c的文件中,名称仍然为do_IRQ,而架构独立的部分被放在IRQ子系统的位置 kernel/irq/handle.c中,名称为_do_IRQ。当发生中断时,CPU将执行do_IRQ来处理相应的中断,do_IRQ将做了必要的架构相关的处理后调用_do_IRQ。函数_do_IRQ将判断该中断是否已经被线程化(如果中断描述符的状态字段不包含SA_NODELAY标志说明中断被线程化了),如果是将唤醒相应的处理线程,否则将直接调用handle_IRQ_event(在IRQ子系统位置的kernel/irq /handle.c文件中)来处理。对于已经线程化的情况,中断处理线程被唤醒并开始运行后,将调用do_hardirq(在源码树的IRQ子系统位置的文件kernel/irq/manage.c中定义)来处理相应的中断,该函数将判断是否有中断需要被处理(中断描述符的状态标志 IRQ_INPROGRESS),如果有就调用handle_IRQ_event来处理。handle_IRQ_event将直接调用相应的中断处理句柄来完成中断处理。

如果某个中断需要被实时处理,它可以用SA_NODELAY标志来声明自己非线程化,例如:系统的时钟中断就是,因为它被用来维护系统时间以及定时器等,所以不应当被线程化。

static struct irqaction irq0=

{ timer_interrupt, SA_INTERRUPT | SA_NODELAY, CPU_MASK_NONE, "timer", NULL, NULL};

这是在静态声明时指定不要线程化,也可以在调用request_irq时指定,如:

request_irq (HIGHWIRE_SMI_IRQ,highwire_smi_interrupt,SA_NODELAY, "System Management Switch", NULL))

对于softirq,标准Linux内核已经使用内核线程的方式来处理,为了使其易于被抢占,改进实时性,具体的修改包括:把ksoftirqd的优先级设置为nice值为-10,即它的优先级高于普通的用户态进程和内核态线程,但它不是实时线程,因此这样一来softirq对实时性的影响将显著减小。在处理软中断期间,抢占是使能的,这使得实时性更进一步地增强。在处理软中断的函数_do_softirq中,每次处理完一个待处理的软中断后,都将调用cond_resched_all(),这显著地增加了调度点数,提高了整个系统的实时性。

4、spinlock转换成mutex

spinlock是一个高效的共享资源同步机制,在SMP(对称多处理器Symmetric Multiple Proocessors)的情况下,它用于保护共享资源,如全局的数据结构或一个只能独占的硬件资源。但是spinlock保持期间将使抢占失效,用 spinlock保护的区域称为临界区(Critical Section),在内核中大量地使用了spinlock,有大量的临界区存在,因此它们将严重地影响着系统的实时性。为此使用mutex来替换 spinlock,它的意图是让spinlock可抢占,但是可抢占后将产生很多后续影响。

Spinlock失效抢占的目的是避免死锁。Spinlock如果可抢占了,一个spinlock的竞争者将可能抢占该spinlock的保持者来运行,但是由于得不到spinlock将自旋在那里,如果竞争者的优先级高于保持者的优先级,将形成一种死锁的局面,因为保持者无法得到运行而永远不能释放spinlock,而竞争者由于不能得到一个不可能释放的spinlock而永远自旋在那里。

由于中断处理函数也可以使用spinlock,如果它使用的spinlock已经被一个进程保持,中断处理函数将无法继续进行,从而形成死锁,这样的spinlock在使用时应当中断失效来避免这种死锁的情况发生。标准linux内核就是这么做的,中断线程化之后,中断失效就没有必要,因为遇到这种状况后,中断线程将挂在等待队列上并放弃CPU让别的线程或进程来运行。

等待队列就是解决这种死锁僵局的方法,让每个spinlock都有一个等待队列,该等待队列是按进程或线程的优先级排队的。如果一个进程或线程竞争的spinlock已经被另一个线程保持,它将把自己挂在该spinlock的优先级化的等待队列上,然后发生调度把CPU让给别的进程或线程。

5、优先级继承和死锁检测

spinlock被mutex化后会产生优先级逆转(Priority Inversion)现象。所谓优先级逆转,就是优先级高的进程由于优先级低的进程保持了竞争资源被迫等待,而让中间优先级的进程运行,优先级逆转将导致高优先级进程的抢占延迟增大,中间优先级的进程的执行时间的不确定性导致了高优先级进程抢占延迟的不确定性,因此为了保证实时性,必须消除优先级逆转现象。

优先级继承协议(Priority Inheritance Protocol)和优先级顶棚协议(Priority Ceiling Protocol)就是专门针对优先级逆转问题提出的解决办法。

所谓优先级继承,就是spinlock的保持者将继承高优先级的竞争者进程的优先级,从而能先于中间优先级进程运行,尽可能快地释放锁,这样高优先级进程就能很快得到竞争的spinlock,使得抢占延迟更确定,更短。

所谓优先级顶棚,就是根据静态分析确定一个spinlock的可能拥有者的最高优先级,然后把spinlock的优先级顶棚设置为该确定的值,每次当进程获得该spinlock后,就将该进程的优先级设置为spinlock的优先级顶棚值。

Spinlock被mutex化后引入的另一个问题就是死锁,典型的死锁有两种:

一种为自锁,即一个spinlock保持者试图获得它已经保持的锁,很显然,这会导致该进程无法运行而死锁。另一种为非顺序锁而导致的,即进程P1 已经保持了spinlock LOCKA但是要获得进程P2已经保持的spinlock LOCKB,而进程P2要获得进程P1已经保持的spinlock LOCKA,这样进程P1和P2都将因为需要得到对方拥有的但永远不可能释放的spinlock而死锁。对这两种情况都要进行检测,一旦发生这种死锁,内核将输出死锁执行路径并panic。

6、大内核锁可抢占

大内核锁(BKL---Big Kernel Lock)实质上也是spinlock,只是它一般用于保护整个内核,该锁的保持时间比较长,因此它对整个系统的实时性影响是非常大的,大内核锁使用了 semaphore来实现,如果内核配置为前面三种抢占模式,struct semaphore是架构相关的,如对于x86,结构定义如下:

struct semaphore {

atomic_t count;

int sleepers;

wait_queue_head_t wait;

};

但对于第四种抢占模式,其结构为:

struct semaphore {

atomic_t count;

struct rt_mutex lock;

};

注意新的spinlock定义也包含字段struct rt_mutex lock,因此可抢占大内核锁和新的spinlock共用了低层的处理代码。使用semaphore之后,大内核锁就可抢占了。

7、结论

Linux实时性能的逐步完善,必将大大促进嵌入式Linux在工业控制、后PC时代信息电器等领域的广泛应用,应用的需要也会进一步促进大量新型实时算法的出现。通过对Linux的改动,就可以开发出一种可靠的且廉价的硬实时操作系统,具有很好的发展和应用前景。本文作者的创新点:通过改进延迟,优先级继承并增加抢占机制,改善了系统的实时性,为嵌入式系统开发打下了基础。

提醒:《嵌入式Linux实时技术改进与实现》最后刷新时间 2024-03-14 01:13:26,本站为公益型个人网站,仅供个人学习和记录信息,不进行任何商业性质的盈利。如果内容、图片资源失效或内容涉及侵权,请反馈至,我们会及时处理。本站只保证内容的可读性,无法保证真实性,《嵌入式Linux实时技术改进与实现》该内容的真实性请自行鉴别。