tshell_blog

ソフトウェアと車輪がついた乗り物のはなし

RealTime処理のサンプルプログラム

tshell.hatenablog.com

前回の記事ではDockerコンテナ上でcyclictestを実行しました。
今回は HOWTO build a simple RT applicationを参考にリアルタイムアプリケーションを作成してみます。

Exampleに載っている内容をsample.cとして保存し,以下のコマンドでコンパイルします。

$ gcc -pthread sample.c

Dockerコンテナを起動して実行してみます。生成されたa.outがあるディレクトリ内で以下を実行します。

$ docker run --rm -it --cpu-rt-runtime=950000 --ulimit rtprio=99 --cap-add=sys_nice -v `pwd`:/home ubuntu

コンテナが起動したら/homeに移動し,./a.outを実行します。
エラーがあった場合にはエラー箇所が表示されますが,正常に動作した場合は何も表示されません。あまり面白くないですね...

周期実行サンプル

HOWTO build a basic cyclic applicationを参考に以下のようなファイルをsample_cyclic.cとして作成します。

/*                                                                  
 * POSIX Real Time Example
 * using a single pthread as RT thread
 */
 
#include <limits.h>
#include <pthread.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <time.h>
 
struct period_info {
        struct timespec next_period;
        long period_ns;
};

static void inc_period(struct period_info *pinfo) 
{
        pinfo->next_period.tv_nsec += pinfo->period_ns;
 
        while (pinfo->next_period.tv_nsec >= 1000000000) {
                /* timespec nsec overflow */
                pinfo->next_period.tv_sec++;
                pinfo->next_period.tv_nsec -= 1000000000;
        }
}
 
static void periodic_task_init(struct period_info *pinfo)
{
        /* for simplicity, hardcoding a 1ms period */
        pinfo->period_ns = 1000000;
 
        clock_gettime(CLOCK_MONOTONIC, &(pinfo->next_period));
}
 
static void do_rt_task()
{
        struct timespec ts;
        clock_gettime(CLOCK_REALTIME, &ts);
        printf("time: %10ld.%09ld\n", ts.tv_sec, ts.tv_nsec);
        
        /* Do RT stuff here. */
}
 
static void wait_rest_of_period(struct period_info *pinfo)
{
        inc_period(pinfo);
 
        /* for simplicity, ignoring possibilities of signal wakes */
        clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &pinfo->next_period, NULL);
}

void *thread_func(void *data)
{
        /* Do RT specific stuff here */
        
        struct period_info pinfo;
 
        periodic_task_init(&pinfo);
 
        while (1) {
                do_rt_task();
                wait_rest_of_period(&pinfo);
        }
 
        return NULL;
}
 
int main(int argc, char* argv[])
{
        struct sched_param param;
        pthread_attr_t attr;
        pthread_t thread;
        int ret;
 
        /* Lock memory */
        if(mlockall(MCL_CURRENT|MCL_FUTURE) == -1) {
                printf("mlockall failed: %m\n");
                exit(-2);
        }
 
        /* Initialize pthread attributes (default values) */
        ret = pthread_attr_init(&attr);
        if (ret) {
                printf("init pthread attributes failed\n");
                goto out;
        }
 
        /* Set a specific stack size  */
        ret = pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN);
        if (ret) {
            printf("pthread setstacksize failed\n");
            goto out;
        }
 
        /* Set scheduler policy and priority of pthread */
        ret = pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
        if (ret) {
                printf("pthread setschedpolicy failed\n");
                goto out;
        }
        param.sched_priority = 80;
        ret = pthread_attr_setschedparam(&attr, &param);
        if (ret) {
                printf("pthread setschedparam failed\n");
                goto out;
        }
        /* Use scheduling parameters of attr */
        ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
        if (ret) {
                printf("pthread setinheritsched failed\n");
                goto out;
        }
 
        /* Create a pthread with specified attributes */
        ret = pthread_create(&thread, &attr, thread_func, NULL);
        if (ret) {
                printf("create pthread failed\n");
                goto out;
        }
 
        /* Join the thread and wait until it is done */
        ret = pthread_join(thread, NULL);
        if (ret)
                printf("join pthread failed: %m\n");
 
out:
        return ret;
}

最初のsample.cと同様に以下のようにしてコンパイルします。

$ gcc -pthread -o cyclic sample_cyclic.c

Dockerコンテナ上で./cyclicを実行すると以下のように1msごとに時刻が表示されます。

time: 1580301619.999005823
time: 1580301620.000007537
time: 1580301620.001007728
time: 1580301620.002007485
time: 1580301620.003007308
time: 1580301620.004011275
time: 1580301620.005009829
time: 1580301620.006012556
time: 1580301620.007010412
time: 1580301620.008012001
time: 1580301620.009012342

リアルタイム処理の記述

以上でとりあえずサンプルは動きました。
自分の好きなことを好きな間隔で実行させるには以下の箇所を修正します。

static void periodic_task_init(struct period_info *pinfo)
{
        /* for simplicity, hardcoding a 1ms period */
        pinfo->period_ns = 1000000;
 
        clock_gettime(CLOCK_MONOTONIC, &(pinfo->next_period));
}
 
static void do_rt_task()
{
        struct timespec ts;
        clock_gettime(CLOCK_REALTIME, &ts);
        printf("time: %10ld.%09ld\n", ts.tv_sec, ts.tv_nsec);
        
        /* Do RT stuff here. */
}

periodic_task_init内のpinfo->period_nsを変更すれば実行周期を変えられます。

do_rt_task()の中には周期ごとに実行したい処理を記述しましょう。

以上で最低限のリアルタイムは実行できるようになります。