第四章 RTLinux应用程序设计
4.1 程序结构
每个实时应用程序可以分为两部分:实时部分和非实时部分[2]。非实时部分在用户空间执行,称为用户部分。实时部分要尽可能简单,只包含直接与时间相关的代码;由于硬件对时间的约束,低级的与硬件通信的代码一般也包含在实时部分。用户部分的代码主要实现为数据的处理,包括数据的发布、保存和用户界面。两部分之间的通信采用数据缓冲区。
图4.1所示的数据流程图是依照这个程序模型的典型实时应用程序。
图4.1 程序结构图
4.2 基本API
4.2.1 POSIX线程创建函数
就像前面介绍的那样,一个实时程序是由几个执行的线程组成的。线程是轻量级进程,它们共享共有的地址空间。在RTLinux中,所有的线程共享Linux内核地址空间。
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参数是周期的长度。(有关时钟模式见3.4节的说明)
int pthread_wait_np (void)
当前周期的线程运行结束,总是返回0。
4.2.2 时间相关函数
RTLinux提供了一些时钟函数用于计时功能,包括线程调度,获得TSP(timestamps)等。
下面的是一般的计时函数:
/* #include */
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)的纳秒时间值。
一些时间转换的函数,用于把时间格式转换为另外一种格式。
时间转换函数:
/* #include */
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); /*
下面是一些支持的时钟类型。
时钟类型相关的宏:
l CLOCK_MONOTONIC: POSIX时钟,以恒定速率运行;不会复位和调整
l CLOCK_REALTIME: 标准POSIX实时时钟。目前与CLOCK_MONOTONIC时钟相同
l CLOCK_RTL_SCHED: 调度器用来任务调度的时钟
以下是机器结构相关的时钟:
l CLOCK_8254: 在x86单处理器机器上用于调度的时钟
l CLOCK_APIC: 用在SMP x86机器的时钟
4.2.3 线程调度函数
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可能的值。
4.3 编程示例
前面介绍了RTLinux的基本API,在这里以一个实例来说明RTLinux下的编程方法。这是一以测试RTLinux下中断延迟的程序。正如前面所说的,程序分为两部分,实时部分和非实时部分。实时部分通过使用一个模块,在将实时模块插入后,运行实时任务。对于非实时部分,实现对FIFO设备的读取,完成和实时任务的通信。
图4.2 实时程序结构图
4.3.1 实时部分
init_module完成对实时部分的初始化。cleanup_module实现关闭实时模块的任务。
/* * RTLinux scheduling accuracy measuring example */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "common.h"
int ntests=500;
int period=1000000;
int bperiod=3100000;
int mode=0;
int absolute=0;
int fifo_size=4000;
int advance=0;
MODULE_PARM(period,"i");
MODULE_PARM(bperiod,"i");
MODULE_PARM(ntests,"i");
MODULE_PARM(mode,"i");
MODULE_PARM(absolute,"i");
MODULE_PARM(advance,"i");
pthread_t thread;
int fd_fifo;
void *thread_code(void *param)
{
hrtime_t expected;
hrtime_t diff;
hrtime_t now;
hrtime_t last_time = 0;
hrtime_t min_diff;
hrtime_t max_diff;
struct sample samp;
int i;
int cnt = 0;
int cpu_id = rtl_getcpuid();
rtl_printf ("Measurement task starts on CPU %d/n", cpu_id);
if (mode) {
int ret = rtl_setclockmode (CLOCK_REALTIME, RTL_CLOCK_MODE_PERIODIC, period);
if (ret != 0) {
conpr("Setting periodic mode failed/n");
mode = 0;
}
} else {
rtl_setclockmode (CLOCK_REALTIME, RTL_CLOCK_MODE_ONESHOT, 0);
}
expected = clock_gethrtime(CLOCK_REALTIME) + 2 * (hrtime_t) period;
fd_fifo = open("/dev/rtf0", O_NONBLOCK);
if (fd_fifo < 0) {
rtl_printf("/dev/rtf0 open returned %d/n", fd_fifo);
return (void *) -1;
}
if (advance) {
rtl_stop_interrupts(); /* Be careful with this! The task won't be preempted by anything else. This is probably only appropriate for small high-priority tasks. */
}
/* first cycle */
clock_nanosleep (CLOCK_REALTIME, TIMER_ABSTIME, hrt2ts(expected - advance), NULL);
expected += period;
now = clock_gethrtime(CLOCK_MONOTONIC);
last_time = now;
do {
min_diff = 2000000000;
max_diff = -2000000000;
for (i = 0; i < ntests; i++) {
++cnt;
clock_nanosleep (CLOCK_REALTIME, TIMER_ABSTIME, hrt2ts(expected - advance), NULL);
now = clock_gethrtime(CLOCK_MONOTONIC);
if (absolute && advance && !mode) {
if (now < expected) {
rtl_delay (expected - now);
}
now = clock_gethrtime(CLOCK_MONOTONIC);
}
if (absolute) {
diff = now - expected;
} else {
diff = now - last_time - period;
if (diff < 0) {
diff = -diff;
}
}
if (diff < min_diff) {
min_diff = diff;
}
if (diff > max_diff) {
max_diff = diff;
}
expected += period;
last_time = now;
}
samp.min = min_diff;
samp.max = max_diff;
write (fd_fifo, &samp, sizeof(samp));
} while (1);
return 0;
}
pthread_t background_threadid;
void *background_thread(void *param)
{
hrtime_t next = clock_gethrtime(CLOCK_REALTIME);
while (1) {
hrtime_t t = gethrtime ();
next += bperiod;
/* the measurement task should preempt the following loop */
while (gethrtime() < t + bperiod * 2 / 3);
clock_nanosleep (CLOCK_REALTIME, TIMER_ABSTIME, hrt2ts(next), NULL);
}
}
int init_module(void)
{
pthread_attr_t attr;
struct sched_param sched_param;
int thread_status;
int fifo_status;
rtf_destroy(0);
fifo_status = rtf_create(0, fifo_size);
if (fifo_status) {
rtl_printf("RTLinux measurement test fail. fifo_status=%d/n",fifo_status);
return -1;
}
rtl_printf("RTLinux measurement module on CPU %d/n",rtl_getcpuid());
pthread_attr_init (&attr);
if (rtl_cpu_exists(1)) {
pthread_attr_setcpu_np(&attr, 1);
}
sched_param.sched_priority = 1;
pthread_attr_setschedparam (&attr, &sched_param);
rtl_printf("About to thread create/n");
thread_status = pthread_create (&thread, &attr, thread_code, (void *)1);
if (thread_status != 0) {
rtl_printf("failed to create RT-thread: %d/n", thread_status);
return -1;
} else {
rtl_printf("created RT-thread/n");
}
if (bperiod) {
pthread_create (&background_threadid, NULL, background_thread, NULL);
}
return 0;
}
void cleanup_module(void)
{
rtl_printf ("Removing module on CPU %d/n", rtl_getcpuid());
pthread_cancel (thread);
pthread_join (thread, NULL);
close(fd_fifo);
rtf_destroy(0);
if (bperiod) {
pthread_cancel (background_threadid);
pthread_join (background_threadid, NULL);
}
}
4.3.2 非实时部分
非实时部分实际上建行就是一个应用程序,可以在控制台下编写。不过必须有/dev/rtf0设备的读取权限才能运行。
#include
#include
#include
#include
#include
#include
#include
#include
#include "common.h"
int main()
{
int fd0;
int n;
struct sample samp;
if ((fd0 = open("/dev/rtf0", O_RDONLY)) < 0) {
fprintf(stderr, "Error opening /dev/rtf0/n");
exit(1);
}
while (1) {
n = read(fd0, &samp, sizeof(samp));
printf("min: %8d, max: %8d/n", (int) samp.min, (int) samp.max);
fflush(stdout);
}
return 0;
}
4.3.3 编译和运行程序
在一台Celeron 412MHz,196MB内存,RTLinux3.1的机器上进行如下的Makefile编译:
all: rt_process.o irqsema.o monitor histplot
include ../../rtl.mk
monitor: monitor.c
$(CC) ${USER_CFLAGS} ${INCLUDE} -Wall -O2 -o monitor monitor.c
clean:
rm -f *.o monitor histplot periodic_monitor gnuplot.out
include $(RTL_DIR)/Rules.make
则程序可以测试调度的时间精度,程序的运行结果为:
min: 0, max: 24480
min: 0, max: 10720
min: 0, max: 10912
min: 0, max: 10976
min: 0, max: 11072
min: 0, max: 10656
min: 0, max: 10944
min: 0, max: 11200
min: 0, max: 11200
min: 0, max: 11008
min: 0, max: 10912
.........