一般来说,普通的 linux 系统,定时精度只有十毫秒,即使在 2..6.18 以后,其误差也至少在半个毫秒,但在 rtlinux 下,其精度可达到微妙级,甚至纳秒,这对于实时性要求很高的工业控制系统来说,简直是一大福音,因为 rtlinux 是开源的,免费的,不过令人沮丧的是, rtlinux 在 2007 年被 Wind River 公司收购了,从此 rtlinux 的最高版本只到 2.6.9 为止,不过有一个类似的实时系统 RTAI ,其性能也与 rtlinux 不相上下,而且 RTAI 一直有团队在免费维护和升级,配合当今最流行的 ubuntu 系统,相信能够让你的程序跑遍天下无敌手。下面简单说说 rtlinux 下的定时应用,本人在 rtlinux 下开发过程序,写此文的主要目的是为日后留个备案而已,同时亦希望对他人有所帮助。
Rtlinux 下主要有两种定时方式,在这之前,我得首先谈谈 rtlinux 的时钟,下面是一些支持的时钟类型:
CLOCK_MONOTONIC: POSIX 时钟 , 以恒定速率运行 ; 不会复位和调整
CLOCK_REALTIME: 标准 POSIX 实时时钟。目前与 CLOCK_MONOTONIC 时钟相同
CLOCK_RTL_SCHED: 调度器用来任务调度的时钟
以下是机器结构相关的时钟:
CLOCK_8254: 在 x86 单处理器机器上用于调度的时钟
CLOCK_APIC: 用在 SMP x86 机器的时钟
以下是时间相关的一些函数:
int clock_gettime(clockid_t clock_id, struct timespec *ts);
hrtime_t clock_gethrtime(clockid_t clock);
struct timespec {
time_t tv_sec; /* 秒 */
long tv_nsec; /* 纳秒 */
};
clock_gettime :读取当前的时间 , 保存到 clock_id 所指的对象中。
clock_gethrtime :读取当前时间 , 但返回一个 64 位 (hrtime_t) 的纳秒时间值
一些时间转换的函数 , 用于把时间格式转换为另外一种格式。
时间转换函数:
hrtime_t timespec_to_ns(const struct timespec *ts); /* timespec 到纳秒数转换 */
struct timespec timespec_from_ns(hrtime_t t); /* 纳秒数到 timespec 转换 */
const struct timespec * hrt2ts(hrtime_t value);
说完了时钟,下面谈谈定时涉及到的 POSIX 线程属性设置相关函数。
int pthread_create (pthread_t *thread, pthread_attr_t * attr, void * (* start_routine)(void *), void *arg)
这是 RTLinux 的标准 POSIX 线程创建函数。这个线程运行函数指针 start_routine 指向的过程 ,arg 是这个函数的指针的入口参数。线程的属性由 attr 对象决定 , 可以为这个属性设置 CPU 号、堆栈大小等属性。设定若为 NULL, 将会使用默认属性。返回 0 表示成功创建线程
线程号放在 thread 所指向的空间 ; 返回非 0 表示创建失败。线程的属性决定在特定的 CPU 上创建线程 (pthread_attr_setcpu_np), 是否使用 FPU(pthread_attr_setfp_np) 。
int pthread_attr_init (pthread_attr_t *attr)
初始化线程运行的属性。
int pthread_ attr_setschedparam (pthread_attr_t *attr, const struct sched_param *param) 和 int pthread_ attr_setschedparam (const pthread_attr_t *attr, struct sched_param *param)
这两个函数根据程序的需要相应地从 attr 中设定 / 取得线程的运行参数。 param 是为调度的 SCHED_FIFO 和 SCHED_RR 策略定义的属性。
int pthread_attr_setcpu_np (pthread_atte_t *attr, int cpu) 和
int pthread_attr_getcpu_np (pthread_atte_t *attr, int cpu)
设定 / 取得线程运行的 CPU 号。在 SMP 机器上允许线程在一个特定的 CPU 上运行。
int pthread_cancel (pthread_t thread)
取消一个运行的线程。
int pthread_delete_np (pthread_t thread)
删除一个线程 , 并且释放该线程的所有资源。返回 0 表示成功删除 , 非 0 表示删除失败。
pthrad_t pthread_self (void)
获得当前正在运行的线程号。
clockid_t rtl_getschedclock (void)
获得当前调度方法的时钟。
int rtl_setclockmode (clockid_t clock, int mode, hrtime_t mode_param)
设置当前的时钟模式 ,mode=RTL_CLOCK_MODE_ONESHOT 时是非周期(一次性)模式 mode_param 参数无用 ;mode =RTL_CLOCK_MODE_PERIODIC 时是周期模式 ,mode_param 参数是周期的长度。
int pthread_wait_np (void)
当前周期的线程运行结束 , 总是返回 0 。
说完了线程属性设置,要想用好定时,还要谈谈线程调度相关函数:
RTLinux 提供一些调度方式 , 允许线程代码在特定的时刻运行。 RTLinux 使用单纯优先级驱动的调度器 , 更搞优先级的线程总是被选择运行。如果两个线程的优先级拥有一样的优先级 , 选择那一个线程运行是不确定的。 RTLinux 使用下面的调度 API :
int pthread_setschedparam (pthread_t thread, int policy, const struct sched_param *param)
设置一个线程的调度参数 , 用 policy 和 sched_param 两个参数设置 thread 的调度参数属性:
policy=SCHED_RR :使用 Round-Robin 方法调度
policy=SCHED_FIFO :使用先进先出的方法调度
返回 0 表示成功调度 , 非 0 表示失败。
int pthread_getschedparam (pthread_t thread, int policy, const struct sched_param *param)
获得一个线程的调度参数。将获得的 policy 和 sched_param 结构放在入口参数所指向的地址里面。
int pthread_make_periodic_np (pthread_t thread, hrtime start_time, hrtime_t period)
这个函数标记 thread 线程为可运行。线程将在 start_time 时刻开始运行 , 运行的时间间隔由 period 给定。
int pthread_wait_np (void)
pthread_wait_np 函数将挂起当前运行发线程直到下一周期。这个线程必须是 pthread_make_periodic_np 函数标记为可执行。
int sched_get_priority_max (int policy) 和 int sched_get_priority_min (int policy)
确定 sched_priority 可能的值。
下面的程序是可以进行精确定时的,精度可达到纳秒,
#ifndef __KERNEL__
# define __KERNEL__
#ifndef MODULE
# define MODULE
#endif
#include<linux/config.h>
#if defined(CONFIG_MODVERSIONS) && !defined(MODVERSIONS)
# define MODVERSIONS
#endif
#ifdef MODVERSIONS
# include<linux/modversions.h>
#endif
#include<linux/module.h>
#include<asm/io.h>
#include<asm/uaccess.h>
#include <linux/proc_fs.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/ioport.h>
//////////////////////////#include rtl_xxx.h///////////////////////////////
#include <rtl_fifo.h>
#include <pthread.h>
#include <rtl_posixio.h>
#include <rtl.h>
#include <time.h>
#include <math.h>
pthread_t thread_pci;
///////////////////////thread_pci function////////////////////////
void *start_routine(void *arg){
// 设置线程属性
struct sched_param p;
p.sched_priority=1;// 优先级
pthread_setschedparam(pthread_self(),SCHED_FIFO,&p);// 先进先出调度 , 关于调度 , 可见我的其它文章
pthread_setfp_np(pthread_self(),1);
//pthread_make_periodic_np(pthread_self(),gethrtime(),10000000);//10ms
hrtime_t current_time;
int period=10000000;
struct timespec resolution;
int ret=rtl_setclockmode(CLOCK_REALTIME,RTL_CLOCK_MODE_PERIODIC,period);
clock_getres(rtl_getschedclock(),&resolution);
period=timespec_to_ns(&resolution);
pthread_make_periodic_np(pthread_self(),clock_gethrtime(rtl_getschedclock()),period);
current_time=clock_gethrtime(CLOCK_MONOTONIC);
while(1)
{
pthread_wait_np();//挂起线程直到下个周期被唤醒
current_time=clock_gethrtime(CLOCK_MONOTONIC);//(CLOCK_REALTIME);
current_time+=period;
clock_nanosleep(CLOCK_REALTIME,TIMER_ABSTIME,hrt2ts(current_time),NULL);
}
}
////////////////////////module init and clean up//////////////////////////////////////////
void cleanup_module (void)
{
pthread_delete_np(thread_pci);//delete
printk (KERN_NOTICE "Module Unloaded successfully/n");
}
int init_module (void) {
///create thread
pthread_create(&thread_pci,NULL,start_routine,NULL);
rtl_printf(KERN_NOTICE "Module successfully loaded");
return 0;
}
上面程序中的这种定时方式精度很高,即 clock_nanosleep() 可达到纳秒级,但如果仅仅使用 pthread_make_periodic_np(pthread_self(),gethrtime(),10000000) , 其精度只能达到微妙级,误差为几个微妙吧,如果想使上面的程序运行起来,还需要编写 Makefile, 可参考入下(在内核 2.4 下, 2.6 内核的 Makefile 有所不同):
INCLUDEDIR = /usr/src/linux/include
include rtl.mk
INSTALLDIR = /lib/modules/2.4.20-rtl3.2-pre3/pmac
CFLAGS += -Wall -O -D__KERNEL__ -DMODULE -I$(INCLUDEDIR)
all: module.o
module.o:module.c
$(CC) ${INCLUDE} ${CFLAGS} -c -o module_tmp.o module.c
$(LD) -r -static module_tmp.o -o module.o -L/usr/lib -lm
rm -f module_tmp.o
install:
install -d $(INSTALLDIR)
install -c module.o $(INSTALLDIR)
install -c module.h $(INCLUDEDIR)/linux
clean:
rm -f *.o *~ core
rm -f $(INSTALLDIR)/module.o
rm -f $(INCLUDEDIR)/linux/module.h
上面程序中的 rtl.mk 来自于编译实时内核时所产生的关于实时内核的目录信息。中间编译链接库时加入了数学库,这是为在内核中进行数学计算时所必须添加的,当然在本例中用不到。
在编写实时内核程序时会遇到以下几个问题:
1. 实时线程中频繁的动态申请内存时常宕机。例如经常用 printk ()进行输出,或者在参数的位置上采用字符串的形式都会造成死机。
2. 实时线程如果不主动让出