主要是RTLinux环境下编程总结,在嵌入版发过,没几个人响应。
做过一个有关RTLinux的项目,时间一长,差不多忘光了,现在尽量把原来做过的东西总结一下,以备后用,同时正在做类似项目的一个借鉴
平台
主机:redhat 8.0
目标机:PC104模块、ISA总线脉冲输出、实时串口通信
linux-2.4.18.tar.bz2 +rtlinux-3.2-pre1.tar.bz2
简述
Linux是典型的分时应用系统,对于实时性要求很高的应用,必须对内核本身动手术。而RTLinux则采取了一种比较聪明也比较折中的办法:他们实现一个最底层的精简的调度器,用于调度实时线程,原来的内核本身则成为实时调度器的一个优先级最低的任务。这样,当有实时任务时,普通内核已经建立于其上的普通进程被强制中断,实时线程被强制执行;只有当若有实时线程都让出cpu之后,普通内核才被运行,再由普通内核去调度执行普通的应用程序……
实例
[code]
/*
* Copyright(C) Wind-Son.
* 2006-2007
*
* 实时应用:RT-Linux用于高精度脉冲控制
* 利用实时线程模拟定时器时钟产生连续的脉冲控制信号,
* 用于对实时性有较高要求的运动控制系统等。
*
*/
/*
* 安装和添加实时补丁
* 1. RedHat Linux Installation
* If you have not done it before, make a separate disk partition and install
* RedHat linux on the partition. RedHat 8.0 (Psyche) is recommended since
* it is most compatible with the RT-Linux we will use in this course. Also,
* make sure to install kernel development tools, gcc compiler, and utilities
* such as patch, depmod, make, and bzip2.
* 2. Get linux kernel
* Download \linux-2.4.18.tar.bz2" from the course website.
* 3. Get rtlinux kernel and patches
* Download \rtlinux-3.2-pre1.tar.bz2" from the course website.
* 4. Put a fresh copy of the rtlinux kernel in /usr/src/rtlinux-3.2-pre1
* Use the following commands.
* cd /usr/src
* tar xjf rtlinux-3.2-pre1.tar.bz2
* This will create \rtlinux-3.2-pre1" directory under /usr/src.
* 5. Put a fresh copy of the linux kernel in /usr/src/rtlinux-3.2-pre1/linux
* Use the following commands.
* cd /usr/src/rtlinux-3.2-pre1
* tar xjf linux-2.4.18.tar.bz2
* This will create \linux" directory under /usr/src/rtlinux-3.2-pre1.
* 6. Patch the linux kernel with the rtlinux patch
* cd /usr/src/rtlinux-3.2-pre1/patches
* bzip2 -d kernel patch-2.4.18-rtl3.2-pre1.bz2
* cd /usr/src/rtlinux-3.2-pre1/linux
* patch -p1 < /usr/src/rtlinux-3.2-pre1/patches/kernel patch-2.4.18
* 7. Clean all "\.o" files and stale dependencies
* cd /usr/src/rtlinux-3.2-pre1/linux
* make mrproper
* 8. Configure the linux kernel
* cd /usr/src/rtlinux-3.2-pre1/linux
* make config (text mode) or
* make xconfig (X mode)
* 9. Build a new linux kernel
* Use the following commands in order.
* cd /usr/src/rtlinux-3.2-pre1/linux
* make dep
* make bzImage
* make modules
* su
* make modules install
* cp arch/i386/boot/bzImage /boot/rtzImage
* 10. Configure your boot loader
* In the following explanation, we assume that the root file system \/" is
* mapped to /dev/hda3 and the boot file system \/boot" is mapped to /dev/hda2.
* You can check out your mapping using \df" command. If you are using LILO
* as your boot manager, add the following lines to the file \/etc/lilo.conf".
* image = /boot/rtzImage
* label = rtlinux
* read-only
* root = /dev/hda3
* The /dev/hda3 should be the device on which your root file system has been
* installed. Then, use the following command.
* /sbin/lilo
* For more details for your specific setting,
* see [url][/url] and
* [url][/url]
* If your are using GRUB as your boot manager, add the following lines to the file \/etc/grub.conf".
* title rtlinux
* root (hd0, 1)
* kernel /rtzImage ro root = /dev/hda3
* The /dev/hda3 should be the device on which your root file system has been
* installed. (hd0,1) corresponds to the first (0) physical disk derive's
* second (1) partition, which is /dev/hda2. It should be the partition where
* the boot file system \/boot" resides. For more details for
* your specific setting, see [url][/url]
* 11. Reboot and select rtlinux from boot image options
* RTLinux should boot.
* 12. Configure RTLinux
* cd /usr/src/rtlinux-3.2-pre1
* make xconfig (accept default)
* 13. Compile RTLinux Use the following commands in order.
* cd /usr/src/rtlinux-3.2-pre1
* make
* su (become the root to do the followings)
* make devices
* make install
* 14. Reboot and select rtlinux from boot image options
*/
/*
* 运行实时内核
* First of all, you should be the root to do the followings.
* In order to run rtlinux applications given in \examples" directory,
* you first insert dynamic rtlinux kernel modules like mbuff, rtl_fifo,
* rtl, rtl posixio, rtl sched, and rtl time. You can insert each module
* with \insmod modulename.o". Fortunately, the script \rtlinux" can do this
* for you at a single step. Try
* rtlinux status ( look at which module has been loaded )
* rtlinux start ( insert all rtlinux modules )
* rtlinux status ( check whether all rtlinux modules are loaded successfully )
* If you can see all the modules loaded, you are done. Otherwise, you might see
* error messages while you were doing \rtlinux start". This may be due to incorrect
* configuration of linux kernel.
* 运行实时内核模块
* insmod hello.o 载入模块
* lsmod 查看已经载入的模块
*/
#include
#include
#include
#include
#include
#include
#include
#include "rt_com.h"
#include /* printk level */
#include /* kernel version etc. */
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Wind-Son");
MODULE_DESCRIPTION("Pulse-Control system");
typedef unsigned short __u16;
/* 实时应用-IO输出控制部分 */
void io_bit_on(__u16 port, unsigned int pos, __u16 *status)
{
__asm__ __volatile__(
"movl %1,%%edx\n\t"
"movl %0,%%ecx\n\t"
"btsl %2,(%%ecx)\n\t"
"mov (%%ecx),%%al\n\t"
"out %%al,(%%dx)\n\t"
"out %%al,$0x80\n\t"
:
:"m"(status), "rm"(port), "Ir"(pos)
);
}
void io_bit_off(__u16 port, unsigned int pos, __u16 *status)
{
__asm__ __volatile__(
"movl %1,%%edx\n\t"
"movl %0,%%ecx\n\t"
"btrl %2,(%%ecx)\n\t"
"mov (%%ecx),%%al\n\t"
"out %%al,(%%dx)\n\t"
"out %%al,$0x80\n\t"
:
:"m"(status), "rm"(port), "Ir"(pos)
);
}
/*
* 实时应用-以实时线程模拟定时器产生脉冲输出部分
*/
#define dbg_print rtl_printf
#define MIN_TIME 5000
static void get_time_interval(void)
{
}
void* pulse_generate_thread(void *arg)
{
static __u16 io_status = 0;
struct sched_param p;
hrtime_t current_time;
REAL_TIME_GET_ENABLE;
int intrrupt_sched_period = 180000;
p.sched_priority = 1; /* 设置实时线程的优先级 */
struct timespec resolution;
/* RT时钟设置 */
rtl_setclockmode(CLOCK_REALTIME, RTL_CLOCK_MODE_PERIODIC,
intrrupt_sched_period);
clock_getres(rtl_getschedclock(), &resolution);
intrrupt_sched_period = timespec_to_ns(&resolution);
/* 设置RT-调度参数 */
pthread_make_periodic_np(pthread_self(), clock_gethrtime(rtl_getschedclock()),
intrrupt_sched_period);
pthread_setschedparam (pthread_self(), SCHED_FIFO, &p);
for (;;) {
dbg_print("debug entry\n");
while (!ready) /* 空闲等待 */
pthread_wait_np(); /* 空闲状态一定调用该函数让出对cpu的控制,否则死机!*/
dbg_print("debug exit\n");
if (!init_rt_clock) {
/* 初始化或重新设置RT时钟 */
init_rt_clock = 1;
pthread_wait_np();
current_time = clock_gethrtime(CLOCK_REALTIME);
} else {
if (intrrupt_sched_period < MIN_TIME)
intrrupt_sched_period = MIN_TIME;
current_time += intrrupt_sched_period;
/*
* 这一步很关键!clock_nanosleep()使本线程直接睡眠在定时器上,
* 睡眠时间current_time(ns),然后被唤醒。实验结果,这种方式
* 不但保证实时性且基本上接近于硬件定时器精度了。
* 而通过pthread_wait_np()实时调度虽然在实时性上也是有保证的,
* 但在精度上是有限的。
*/
clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, hrt2ts(current_time), NULL);
}
/* 脉冲输出控制…… */
io_bit_on(IO_PORT_OUT, XPULSE, &io_status);
/*
* 获取下一个脉冲间隔,从而产生精确的脉冲控制序列
* Note! 该实时线程用于模拟定时器输出,因此不能有太复杂的计算。
* 一般情况下将脉冲间隔时间的计算以及其他控制部分放到另外一个独立的线程。
* 在本系统中time_interval_calc_thread即用于该目的。
*/
intrrupt_sched_period = get_time_interval();
}
return 0;
}
/*
* 实时应用-动态内存申请部分。
* 直接在RT-thread中申请内存实际结果证明很不稳定,时不时的crash,
* 大概是因为alloc过程被高优先级RT-thread强制中断产生异常。
* 因此需要动态申请内存时开辟独立的内核线程专门负责内存申请。
*/
static void init_for_rt_mm(void)
{
}
static void rt_alloc_mm(void)
{
thread_wait_np();
buf = kmalloc(size, GFP_ATOMIC);
}
static int kmalloc_thread(void * kthread_arg)
{
unsigned long timeout = HZ;
init_for_rt_mm();
for (;;) {
while (!get_flag(MM_ALLOC_FLAG)) {
/* 没有内存申请任务则睡眠 */
if( signal_pending(current))
return 0;
timeout = interruptible_sleep_on_timeout(&wq, timeout);
}
rt_alloc_mm();
clear_flag(MM_ALLOC_FLAG);
}
return -1;
}
/* 实时应用-主程序-脉冲控制部分 */
wait_queue_head_t wq;
static pid_t kmalloc_kthread_id;
static int kmalloc_kthread_state = 1;
static int pulse_generate_thread_created = 0;
static int main_ctrl_thread_created = 0;
static pthread_t pulse_generate_pthread;
static pthread_t main_ctrl_pthread;
static pthread_mutex_t cache_mutex;
void rt_mm_request(void)
{
set_flag(MM_ALLOC_FLAG);
/*
* 通过设置标志位通知kmalloc_thread内核线程申请内存
*/
while(get_flag(MM_ALLOC_FLAG))
pthread_wait_np();
}
void* main_ctrl_thread(void *arg)
{
int work_sched_period = 160000;
struct timespec resolution;
int ret1 = rtl_setclockmode(rtl_getschedclock(), RTL_CLOCK_MODE_PERIODIC,
work_sched_period);
if (ret1) {
dbg_print("seting periodic mode failed\n");
clear_flag(WORK_SCHED_MODE);
}
clock_getres(rtl_getschedclock(), &resolution);
work_sched_period = timespec_to_ns(&resolution);
pthread_make_periodic_np(pthread_self(), clock_gethrtime(rtl_getschedclock()),
work_sched_period);
init_task();
for (;;) {
if (work) {
dbg_print("work\n");
rt_mm_request();
calc_time_interval();
if (exit)
break;
} else
pthread_wait_np();
}
exit_task();
return 0;
}
int init_module(void)
{
pthread_attr_t attr;
struct sched_param p;
int ret;
rtf_destroy(0);
rtf_destroy(1);
rt_com_clr_in(0);
rt_com_clr_out(0);
/* 创建实时管道,用于RT模块和普通应用程序之间的通信 */
int fifo_status = rtf_create(0,100);
if(fifo_status)
dbg_print("FIFO Create failed!");
fifo_status = rtf_create(1, 4000);
if(fifo_status)
dbg_print("FIFO Create failed!");
/* 设置实时串口,用于RT模块控制串口输出 */
rt_com_setup(0, 9600, RT_COM_PARITY_NONE, 1, 8);
hrtime_t now = gethrtime();
pthread_attr_init(&attr);
pthread_mutex_init(&cache_mutex, NULL);
pthread_attr_setfp_np(&attr, 1);
/* pulse_generate_thread */
ret = pthread_create(&pulse_generate_pthread, &attr,
pulse_generate_thread, (void *)0);
if (!ret)
pulse_generate_thread_created = 1;
pthread_make_periodic_np (pulse_generate_pthread, now + 2 * 240000, 80000);
p . sched_priority = 1;
pthread_setschedparam (pulse_generate_pthread, SCHED_FIFO, &p);
/* main_ctrl_thread */
ret = pthread_create(&main_ctrl_pthread, &attr, main_ctrl_thread, (void *)1);
if (!ret)
main_ctrl_thread_created=1;
pthread_make_periodic_np (main_ctrl_pthread, now + 2 * 160000, 30000);
p . sched_priority = 2;
pthread_setschedparam (main_ctrl_pthread, SCHED_FIFO, &p);
init_waitqueue_head(&wq);
kmalloc_kthread_id = kernel_thread(kmalloc_thread, NULL, 0);
if (kmalloc_kthread_id < 0) {
printk(KERN_ERR "fork failed, errno %d\n", -kmalloc_kthread_id);
return kmalloc_kthread_id;
}
return ret;
}
void cleanup_module(void)
{
/* send a term signal to the kthread */
int ret = kill_proc(kmalloc_kthread_id, SIGKILL, 1);
if (!ret) {
int count = 10 * HZ;
/* wait for the kthread to exit befor terminating */
while (kmalloc_kthread_state && --count) {
current->state = TASK_INTERRUPTIBLE;
schedule_timeout(1);
}
}
if (main_ctrl_thread_created) {
pthread_cancel(main_ctrl_pthread);
pthread_join(main_ctrl_pthread, NULL);
pthread_delete_np(main_ctrl_pthread);
}
if (pulse_generate_thread_created) {
pthread_cancel(pulse_generate_pthread);
pthread_join(pulse_generate_pthread, NULL);
pthread_delete_np(pulse_generate_pthread);
}
rt_com_setup(0, -1, 0, 0, 0);
rtf_destroy(0);
rtf_destroy(1);
pthread_mutex_destroy (&cache_mutex);
}
[/code]
其实现在有关Linux实时应用的原理和应用方面的介绍已经不少,所以我主要是想从自己的亲身实践中的经验教训出发总结一下。
我遇到的主要问题主要有以下几个:
1、硬实时调度精度不够的问题。刚开始产生脉冲驱动的线程我按照例子程序采样如下方式
pthread_make_periodic_np(); //设置调度方式和周期等参数
pthread_setschedparam (pthread_self(), SCHED_FIFO, &p);
pthread_wait_np(); //让出cpu进入睡眠
可实际情况总是不理想,输出波形不够稳定,离预想的效果也很远。试着将调度策略SCHED_FIFO改其他几种方式,也一样。最后尝试用clock_nanosleep()才达到了比较理想的效果。理论上clock_nanosleep()应该达到ns级别的精度,当然实际精度还要取决于硬件。
2、实时线程所能到达的实时效果和精度极限也就是定时器本身的精度了。有过在51上做开发经验的都有这样一个意识:定时器中断处理例程里尽量只做最简单、最必须的工作,但毕竟还是有开销的。如果你对精度还有更高的要求,可在main_ctrl_thread()即负责计算脉冲间隔时间的例程中加入补偿值,以抵消脉冲输出例程中的时间开销。
3、实时线程中频繁的动态申请内存时常宕机。后来经过实验摸索才采取了上面代码中所述的拐弯抹角的办法。如果谁碰到过类似问题有更好的办法,还望指出。
4、应用程序直接向串口输出时总出错。
开始方法是system("/bin/ls ./data/ >> /dev/ttyS0";在没有实时线程的影响的情况下, 这样是没有问题。开启实时线程后就老出错。
后改成如下方式就好了:由实时模块通过实时调用rt_com_write()和rt_com_read()读写串口;再通过实时管道rtl_fifo转发到应用程序
另外,纯粹经验谈,实时线程如果不主动让出cpu,任何用户程序无法运行,包括你的键盘响应!如果你的某个环节可能陷入循环,你能做的就只有poweroff了 ;被迫重启后在ext2文件系统上随之而来的是漫长的fscheck……所以我在调试阶段,基本上是只要有循环的的方,就加上pthread_wait_np();以后再慢慢把不必要的去掉。