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__

#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.                 实时线程如果不主动让出

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