原文地址:http://jinsuo2007.blog.163.com/blog/static/1922799520114279642747/

关于RT-Preempt Patch  

本文档描述了安装并使用针对Linux内核的Realtime Preemption patch的过程,而且还讨论了如何开始编写硬实时的程序。本文档针对于目前最成熟的x86体系结构。

关于RT-Preempt Patch?

标准的Linux内核只达到了软实时的要求:对用户空间的时间处理提供了基本的POSIX操作,但是对硬时间限制没有保证。通过Ingo Molnar的Realtime Preemption patch(简称为RT-Preempt)和Thomas Gleixner的有着高精度支持的通用时钟事件层,内核就获得了硬实时的能力。

RT-Preempt patch在业界已经获得了广泛的关注,它简洁的设计和向主线整合的目标使得它成为硬实时应用程序的有趣选择,不论是专业调音台还是工业控制。

当这个patch变得越来越有用,重要的组成部分进入了Linux内核,我们觉得很有必要为它写更多文档。本文浓缩概述了RT-Preempt内核及其使用。

RT-Preempt把Linux变成一个完全可抢占的内核,改变有以下几点:

1.通过rtmutexes的重新实现使内核里的锁源语(使用自旋锁)可被抢占

2.以前被如spinlock_t和rwlock_t保护的临界区现在变得可以被抢占了。使用raw_spinloc_t创建不可抢占区域(在内核中)依旧是可能的(类似spinlock_t的相同API)。

3.为内核里的自旋锁和信号量实现优先级继承。更多优先级反转和优先级继承的信息请参考: http://www.embedded.com/story/OEG20020321S0023

4.把中断处理器变为可被抢占的内核线程:RT-Preempt patch在内核线程上下文中处理软中断处理器。

5.把老的Linux计时器API变成分别的几个基本结构,有针对高精度内核计时器的还有一个是针对超时的,这使得用户空间的POSIX计时器具有高精度。

安装?

获得资源

你要先获得一个vanilla内核资源的拷贝。使用kernel.org的镜像服务器下载内核存档 http://kernel.org/

你可以从 http://www.kernel.org/pub/linux/kernel/projects/rt/获得realtime preemption patch。确保RT-Preempt版本与你要使用的内核版本相匹配。如果你要寻找更老内核版本的补丁,试试 http://www.kernel.org/pub/linux/kernel/projects/rt/older/,里面包含已经过时的补丁存档。

图表1:获得源

# wget ftp://ftp.kernel.org/pub/linux/kernel/v2.6/linux-2.6.23.1.tar.bz2   # wget http://www.kernel.org/pub/linux/kernel/projects/rt/patch-2.6.23.1-rt11.bz2  

给内核打补丁

下载完成后,解压内核包,进入到内核源码目录,给内核打补丁

# tar xfj linux-2.6.23.1.tar.bz2   # cd linux-2.6.23.1  # bzcat ../patch-2.6.23.1-rt11.bz2 | patch -p1  

realtime preemption patch目前被过度地开发,所以新版本出现得非常快。如果你想跟上开发的进度,我们向你推荐像quilt http://savannah.nongnu.org/projects/quilt一样的补丁管理系统。

另一种下载并给内核打补丁的方法

使用Ketchup https://rt.wiki.kernel.org/index.php/Ketchup会更简单。

配置和编译

如果你对从源码生成一个定制的linux内核的过程不熟悉的话,z在读下面的文档之前你可以参考Kernel Rebuild Guide http://www.digitalhermit.com/linux/Kernel-Build-HOWTO.html。在编译你刚打好补丁的内核之前你得先配置它。如果你已经有了一个.config文件适合你的硬件需求,把它拷贝到内核源代码目录下,将它重命名为.config。以下的截屏展现了一个x86内核的主配置目录。

图表3.配置内核(1)

http://www.pengutronix.de/images/rt-wiki/menuconfig1.PNG

大多数实时选项都可以在“Processor type and features”目录中找到,如下截屏。

图表4.配置内核(2)

http://www.pengutronix.de/images/rt-wiki/menuconfig2.PNG

默认的配置可以不用管,但你要保证:

*启用CONFIG_PREEMPT_RT

*激活High-Resolution-Timer选项(注意,支持高精度计时器的平台数量还是非常有限的)。目前这个选项只在x86系统中支持,PowerPC和ARM还不支持)

*禁用所有电源管理选项如ACPI或APM(不是所有的ACPI功能是“坏的”,但是你得非常仔细地检查出来哪个功能会影响你的实时系统。所以如果你不需要就最好禁用它们所有。)注意:自rt patch 2.6.18-rt6之后,你可能需要激活ACPI选项从而激活高精度计时器。

更多有意思的选项可以在"Kernel Hacking"菜单项下面找到。这个菜单列出了有关系统调试和性能衡量的选项。记住调试选项既可能增加内核的大小也可能造成更高的延迟。如果你不想调试内核或得到一些自动生成的直方图,那么你别激活任何不不要的选项。如果你激活了任何延迟关键选项,内核会在启动时发出警告。

图表5.配置内核(3)

http://www.pengutronix.de/images/rt-wiki/menuconfig3.PNG

配置后,内核可以像往常一样被编译和安装。补丁自动地在内核的本地版本号后面加了"-rt**"后缀,所以推荐你用同样的方式命名你的内核镜像。不要忘记在你的启动管理器的配置文件中为你的新内核创建启动项,然后重新跑一下。并且建议你在RT-Preempt的启动参数中添加"laptic"。

检测内核

在内核被启动后第一件事情就是去检测,用如下的方法:

图表6.内核版本字符串

# uname -a   Linux krachkiste 2.6.18-rt5 #3 PREEMPT Thu Oct 06 14:28:47 CEST 2006 i686 GNU/Linux   

补丁把自己的修订号-rt*加到了内核版本字符串后面。所以如果你在做配置的时候没有手动地改变内核版本字符串,你就会发现在启动正确的内核之后-rt**被追加在了内核版本号后面。

为了确保你的内核正确并能够运行,你还可以检测对于如下的字符串的dmesg输出: Real-Time Preemption Support (C) 2004-2006 Ingo Molnar

现在看一下进程列表。IRQ handler在内核线程上下文中被看作是打了补丁的内核。单独的IRQ handler如同用户空间任务一样被表示成任务结构(task struct)。这样它们就可以被用户空间的工具罗列并控制。下面的图表部分地显示了在一个被打补丁的内核下的系统中的一个正在运行的进程列表。

注意:与非RT内核不同,中断处理器在这里是内核线程,所以它们被列出来(在方括号内)。

图表7.检测进程列表中的内核线程

# ps ax   PID TTY      STAT   TIME COMMAND   1 ?        S      0:00 init [2]   2 ?        S      0:00 [softirq-high/0]   3 ?        S      0:00 [softirq-timer/0]   4 ?        S      0:00 [softirq-net-tx/]   5 ?        S      0:00 [softirq-net-rx/]   6 ?        S      0:00 [softirq-block/0]   7 ?        S      0:00 [softirq-tasklet]   8 ?        S      0:00 [softirq-hrtreal]   9 ?        S      0:00 [softirq-hrtmono]   10 ?        S<     0:00 [desched/0]   11 ?        S<     0:00 [events/0]   12 ?        S<     0:00 [khelper]   13 ?        S<     0:00 [kthread]   15 ?        S<     0:00 [kblockd/0]   58 ?        S      0:00 [pdflush]   59 ?        S      0:00 [pdflush]   61 ?        S<     0:00 [aio/0]   60 ?        S      0:00 [kswapd0]   647 ?        S<     0:00 [IRQ 7]   648 ?        S<     0:00 [kseriod]   651 ?        S<     0:00 [IRQ 12]   654 ?        S<     0:00 [IRQ 6]   675 ?        S<     0:09 [IRQ 14]   /  687 ?        S<     0:00 [kpsmoused]   689 ?        S      0:00 [kjournald]   691 ?        S<     0:00 [IRQ 1]   769 ?        S<s    0:00 udevd --daemon   871 ?        S<     0:00 [khubd]   882 ?        S<     0:00 [IRQ 10]   2433 ?        S<     0:00 [IRQ 11]   [...]   

现在我们看一下 /proc/interrupts。一个被打补丁的内核下的中断进程项的格式与vanilla的内核有一点不一样,如下所示:

图表8.检测 /proc/interrupts(比2.6.19-rt4早的版本)

# cat /proc/interrupts   CPU0   0:     497464  XT-PIC         [........N/  0]  pit   2:          0  XT-PIC         [........N/  0]  cascade   7:          0  XT-PIC         [........N/  0]  lpptest   10:          0  XT-PIC         [........./  0]  uhci_hcd:usb1   11:      12069  XT-PIC         [........./  0]  eth0   14:       4754  XT-PIC         [........./  0]  ide0   NMI:          0   LOC:       1701   ERR:          0   MIS:          0   

图表9.检测 /proc/interrupts(比2.6.19-rt4晚的版本)

{{{CPU0 0: 15499 IO-APIC-edge timer 1: 8 IO-APIC-edge i8042 4: 161 IO-APIC-edge serial 9: 0 IO-APIC-fasteoi acpi 12: 3 IO-APIC-edge i8042 14: 4082 IO-APIC-edge ide0 16: 93 IO-APIC-fasteoi eth0 }}}

你可以在运行时使用chrt在用户空间改变一个内核线程(如一个中断处理器)的优先级,chrt在 http://rlove.org/schedutils/可以下载。使用这个工具你可以改变内部调度策略和进程的优先级。

图表10.使用chrt的例子

# chrt -f -p $PRIO $PID_OF_THE_KTHREAD   # chrt -p $PID_OF_THE_KTHREAD   

用上面的第一个命令你可以使用“FIFO”策略,改变pid为$PID_OF_THE_KTHREAD的线程的优先级到$PRIO,用第二个命令你可以看到你做的改变带来的结果。

一个实时的“Hello World”例子?

在前面我们描述了实时抢占的一些内部机制。这个补丁的主要目标是,无需改变一个通用linux环境提供的编程API就可以实现一个实时环境。但是在编写实时应用程序时仍就有重要的几点。为了能提供确定的实时行为有三件事情需要一个任务设置好:

*设置一个实时时间调度策略和优先级。

*锁住内存,这样虚存带来的页错误就不会损害确定性的行为。

*Pre-faulting栈,这样未来的栈错误就不会损害确定性的行为。

以下的例子包含了一个打上realtime preemption patch的实时应用程序的一些非常基本的代码例子,按照下面的方法编译: gcc -o test_rt test_rt.c -lrt

#include <stdlib.h>  #include <stdio.h>  #include <time.h>  #include <sched.h>  #include <sys/mman.h>  #include <string.h>    #define MY_PRIORITY (49) /* we use 49 as the PRREMPT_RT use 50                              as the priority of kernel tasklets                              and interrupt handler by default */    #define MAX_SAFE_STACK (8*1024) /* The maximum stack size which is                                     guranteed safe to access without                                     faulting */    #define NSEC_PER_SEC    (1000000000) /* The number of nsecs per sec. */    void stack_prefault(void) {            unsigned char dummy[MAX_SAFE_STACK];            memset(&dummy, 0, MAX_SAFE_STACK);          return;  }    int main(int argc, char* argv[])  {          struct timespec t;          struct sched_param param;          int interval = 50000; /* 50us*/            /* Declare ourself as a real time task */            param.sched_priority = MY_PRIORITY;          if(sched_setscheduler(0, SCHED_FIFO, &param) == -1) {                  perror("sched_setscheduler failed");                  exit(-1);          }            /* Lock memory */            if(mlockall(MCL_CURRENT|MCL_FUTURE) == -1) {                  perror("mlockall failed");                  exit(-2);          }            /* Pre-fault our stack */            stack_prefault();            clock_gettime(CLOCK_MONOTONIC ,&t);          /* start after one second */          t.tv_sec++;            while(1) {                  /* wait until next shot */                  clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &t, NULL);                    /* do the stuff */                    /* calculate next shot */                  t.tv_nsec += interval;                    while (t.tv_nsec >= NSEC_PER_SEC) {                         t.tv_nsec -= NSEC_PER_SEC;                          t.tv_sec++;                  }     }  }  
Logo

更多推荐