RTlinux下的精确定时

一般来说,普通的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__
#endif

#ifndef MODULE
#  define MODULE
#endif

#include
#if defined(CONFIG_MODVERSIONS) && !defined(MODVERSIONS)
#  define MODVERSIONS
#endif

#ifdef MODVERSIONS
#  include
#endif

#include

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//////////////////////////#include rtl_xxx.h///////////////////////////////
#include
#include
#include
#include
#include
#include

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.                实时线程如果不主动让出cpu,任何用户程序无法运行,包括你的键盘响应!所以在执行完任务时必须加上pthread_wait_np();

你可能感兴趣的:(RTlinux下的精确定时)