SCHED_FIFOによるリアルタイム制御 ~その2~ FRONT PAGE

コレは何?

Linuxの標準的なスレッドをSCEHD_FIFOに設定し,ちょっとした工夫により, カーネルを一切いじることなく,通常の一般的なLinuxそのまま,しかもユーザ空間で100μs周期のリアルタイム制御が可能になる。 今回はさらにSCHED_FIFOによるリアルタイム制御 ~その1~を改良。

前提条件

実現方法

コンセプト&詳細については SCHED_FIFOによるリアルタイム制御 ~その1~ を参照のこと。

さらなる改良

今回,さらにリアルタイム性能や一部機能を改良した。 主要部分のソースコード(抜粋)は以下の通り。


01 
02 void SFthread::RealTimeThread(SFthread *p){
03     // 実時間スレッド
04     
05     // 時間関連の構造体
06     timespec NextTime = {0};        // お休み時間設定用
07     timespec TimeInWait = {0};      // 待機ループ内で取得する現在時刻
08     timespec PeriodTime = p->nsec_to_timespec(p->Ts);   // 所望の制御周期
09     timespec StartTime = {0};       // 開始時刻格納用
10     timespec StartTimePrev = {0};   // 前回の開始時間格納用
11     timespec EndTime = {0};         // 終了時刻格納用
12     timespec PreventStuck = {0};    // 「BUG: soft lockup - CPU#0 Stuck for 67s!」を回避するためのスリープ用
13     
14     // 動作状態が「開始」に設定されるまで待機
15     while(p->StateFlag != SFID_START){
16         usleep(WAIT_TIME);      // 適当に待機
17     }
18     p->StateFlag = SFID_RUN;    // 動作状態フラグを「動作中」に設定
19     
20     clock_gettime(CLOCK_MONOTONIC, &NextTime);              // 初期開始時刻の取得
21     StartTimePrev = p->timespec_sub(NextTime, PeriodTime);  // 実際の制御周期計算用の初期値設定
22     
23     // 実時間ループ
24     while(p->StateFlag != SFID_STOP){   // 動作状態フラグが「停止」に設定されるまでループ
25         // ここからリアルタイム空間
26         clock_gettime(CLOCK_MONOTONIC, &StartTime);             // 開始時刻の取得
27         
28         p->pRTfunc(p->pRTarg);  // 制御用関数の実行(関数ポインタにより、ここで実際の制御関数が呼ばれる)
29         
30         p->LoopCount++;         // 制御ループカウンタをインクリメント
31         p->ActPeriodicTime = p->timespec_sub(StartTime, StartTimePrev); // 実際の周期時間を計算(timespec構造体は単純に減算できないことに注意)
32         StartTimePrev = StartTime;                                      // 次回用に今回の開始時刻を格納
33         NextTime = p->timespec_add(NextTime, PeriodTime);               // 開始時刻に制御周期を加算して次の時刻を計算
34         clock_nanosleep(CLOCK_MONOTONIC, 0, &PreventStuck, NULL);       // 「BUG: soft lockup - CPU#0 Stuck for 67s!」を回避するためのスリープ
35         clock_gettime(CLOCK_MONOTONIC, &EndTime);                       // 終了時刻の取得
36         p->ComputationTime = p->timespec_sub(EndTime, StartTime);       // 消費時間を計算(timespec構造体は単純に減算できないことに注意)
37         
38         // 次の時刻になるまで待機
39         // clock_nanosleep関数を使うとジッタが酷すぎてお話にならないので独自に実装
40         // (スレッドを休ませないことが肝。ただし、“割り当てられたCPUコアにおいて” 実時間スレッド以外のタスクはほぼ実行できなくなる諸刃の剣。)
41         while(p->StateFlag != SFID_STOP){
42             clock_gettime(CLOCK_MONOTONIC, &TimeInWait);    // 現在時刻の取得
43             if(p->timespec_lessthaneq(NextTime, TimeInWait) == true){
44                 // 現在時刻と予め計算した次の時刻とを比較して,超えたら待機終了
45                 break;
46             }
47         }
48         // 一般的には,下記のように clock_nanosleep関数で待機するが,
49         // 100μsの制御周期で動かすにはジッタが大きすぎて使いものにならない。
50         // 「clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &NextTime, NULL);」
51         
52         // リアルタイム空間ここまで
53     }
54     
55     p->StateFlag = SFID_EXCMPL;         // 動作状態フラグを「終了動作完了」に設定
56 }
57 

大きな変更点は,gettimeofday関数をやめて,すべてclock_gettime関数にしたことです。 某NICTのある情報("gettimeofday nict"でググると出てくる)によると,どうもgettimeofday関数は処理が遅くてさらに処理時間が変動するらしい。 ジッタが生じるってことで,リアルタイム性の追求には致命的。

さらに,gettimeofday関数は新規設計非推奨とのこと。 ということで,gettimeofday関数を使うことをやめました。 それに伴い,時間関係の変数をtimeval構造体からtimespec構造体に変更,時刻加算用のtimespec_add関数,時刻減算用のtimespec_sub関数, 時刻比較用のtimespec_lessthaneq関数を新たに作成した。 (せっかくC++なんだから演算子オーバーロードを使えって話もあるが今回は勘弁。。。)

あと,センサ系(特にエンコーダカウンタ)の読み取りタイミングで問題が生じる可能性があったため, 「制御用関数の実行」を可能な限り一番最初に移動した。

使い方の一例

下記に使い方の一例を掲載。使いやすくなるように,今回新たにリアルタイムスレッドを開始させるStart関数と停止させるStop関数, さらにスレッドが完全に終了するまでブロッキング待機するWaitStop関数を用意。


01 
02 #include <cstdio>
03 #include <cstdlib>
04 #include "SFthread.hh"
05 
06 using namespace ARCS;
07 void PeriodicFunction(void*);   // 周期実行関数 プロトタイプ宣言
08 
09 int main(void){
10     // ここがすべての始まり(エントリポイント)
11     printf("Test Code for SCHED_FIFO RealTime Thread\n");
12     
13     // main関数側のCPUコアの割り当て
14     pthread_t main_func = pthread_self();   // main関数のスレッドIDを取得
15     cpu_set_t cpuset;                       // CPU設定用変数
16     CPU_ZERO(&cpuset);                      // まずCPU設定用変数をクリアして,
17     CPU_SET(3,&cpuset);                     // 4個目のCPUコア番号をCPU設定用変数にセットし,
18     pthread_setaffinity_np(main_func, sizeof(cpu_set_t), &cpuset);  // main関数をCPUコアに割り当てる
19     
20     // リアルタイムスレッド側の設定    s  m  u  n
21     const unsigned long PeriodicTime = 1000000000;  // [ns] 周期時間
22     unsigned int CPUno = 2;             // 3個目のコアをリアルタイムスレッドに割り当てる
23     SFfuncPtr pPFunc = PeriodicFunction;// 周期実行関数への関数ポインタを代入
24     
25     SFthread* pSFthread;    // リアルタイムスレッドへのポインタ
26     pSFthread = new SFthread(PeriodicTime, pPFunc, NULL, CPUno);    // スレッド生成
27     
28     pSFthread->Start(); // リアルタイムスレッドに開始信号を送出
29     sleep(10);          // 回したい時間だけ回す
30     pSFthread->Stop();  // リアルタイムスレッドに停止信号を送出
31     
32     pSFthread->WaitStop();  // リアルタイムスレッドが完全に停止するまで待機
33     delete pSFthread;       // スレッド破棄
34     
35     return EXIT_SUCCESS;    // 終了
36     // ここがすべての終わり
37 }
38 
39 void PeriodicFunction(void*){
40     // 周期実行関数(リアルタイムスレッド)
41     
42     const double Ts = 1;            // [s] 周期時間
43     static unsigned long count = 0; // ループカウンタ
44     static double t = 0;            // [s] 経過時刻
45     
46     t = count*Ts;   // 時刻の計算
47     count++;        // ループカウンタを進める
48     
49     printf("Time = %3.1lf\n",t);    // あくまで一例。高速な周期の場合は標準入出力等々の遅い関数は書かないほうが良い。
50 }
51 

SFthreadクラスの実装とサンプルコード

SFthreadクラスと,その使い方を示すサンプルコードを下記よりダウンロードしてネ。

実行結果

サンプルコードを実行すると以下の様な感じになる。 1秒刻みで経過時刻を表示。(ただし,ssh経由だと表示がどうも遅い。バッファの問題か? 原因不明。動作自体に問題はない。)


 [root@robotdvd Yoko]# cd SchedFifoTest/
 [root@robotdvd SchedFifoTest]# ls
 Makefile  SFthread.cc  SFthread.hh  SchedFifoTest.cc
 [root@robotdvd SchedFifoTest]# make
 gcc -I. -I/usr/src/linux/include -Wall -Weffc++ -O3 -c SFthread.cc
 gcc -I. -I/usr/src/linux/include -Wall -Weffc++ -O3 -c SchedFifoTest.cc
 g++ -L. -lpthread -lrt -lm -ltinfo -o SchedFifoTest SFthread.o SchedFifoTest.o
 [root@robotdvd SchedFifoTest]# ./SchedFifoTest
 Test Code for SCHED_FIFO RealTime Thread
 Time = 0.0
 Time = 1.0
 Time = 2.0
 Time = 3.0
 Time = 4.0
 Time = 5.0
 Time = 6.0
 Time = 7.0
 Time = 8.0
 Time = 9.0
 [root@robotdvd SchedFifoTest]# 

まとめと今後の予定

「その1」に比べ,さらにリアルタイム性がうp! 実は「その3」も考え中。 x86系CPUには「RDTSC命令」というのがあり,これを使うとさらにリアルタイム性が上がるかも。。。乞うご期待。

それから,今のLinuxにはCompletely Fair Scheduler(CFS)という,その名もズバリな我々からするとはっきり言って余計なモンがいる。 フェアだと困る。 「sched_rt_runtime_us」を「-1」に設定することでコイツを黙らせて,リアルタイム性をアップさせることをやってみる。 エンコーダの微分値に稀にスパイクノイズが出るのは,ひょっとするとCFSのせいかもしれないので,問題を解決できるかも。(2018/9/27追記)

ライセンス/Licenses

GPLからBSDライセンスに変えました。

Copyright (c) 2011-2018, Yuki YOKOKURA
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project.





- 116305 -

研究室の横の倉庫 - Side Warehouse of Laboratory
Copyright(C), Side Warehouse, All rights reserved.