Hi,
Are you prefer to read this article in English?
Please refer to a discussion at Intel community.
Intel Edisonのカーネルに,PREEMPT_RTパッチを当てました.
リアルタイムOSとは
皆さんが普段使っているOSは,おそらくリアルタイムシステムではないと思います.
ここで,”リアルタイムシステム”という言葉について,おさらい.一般に,「リアルタイム」というと,「リアルタイム処理」という言葉に引っ張られ,その「入力に対して即座に出力が返ってくること」,「コンピューターの処理が現実の時間と同期していること」という意味から,「つまり,とにかく処理が速ければいいんだよね!!」なぁんて解釈されてしまいがちですが,こと,制御の分野に限っては,もっと意味が厳密に決まっています.それによると,リアルタイムシステムとは,「ある処理について,それが開始されるまでの最悪時間と,それが終了するまでの最悪時間が厳密に決まっているシステム」のことを指します.つまり,”きっちりスケジュール通りに動作するシステム”ということで,実は,処理の速さとは全く関係しません.極端な話,ある入力に対して,出力が1時間後に返ってくるシステムであっても,その処理が「1時間以内に終了する」というスケジュールの元,厳密に動作しているのであれば,それはリアルタイムシステムです.
さて,ここで話がLinuxのRT-Preemptパッチに戻ってくるのですが,このパッチ,適用したからと言って,システムがいきなりリアルタイムシステムになるわけではありません.マルチタスクシステムにおいては,カーネルが処理を短い時間で区切り,CPUを使う時間を割り振って,並列処理を実現しています.この,CPUを使う処理を切り替えることは,”コンテキストスイッチ”と言います.RT-Preemptパッチを適用すると,リアルタイム指定したプロセスのコンテキストスイッチにかかる遅延時間を短くすることができます.しかし,このパッチには,処理が長引いたからといって,途中で処理を打ち切ってくれる機能はありません.また,システムの単位時間あたりの処理能力を超えることは,もちろんできません.そのため,RT-Preemptパッチを使う場合は,実装者が注意して,処理に必要な時間を見積もらなければ,リアルタイムシステムになりません.
(自分の過去の文章より転載)
PREEMPT_RTパッチの当て方
Yocto Linuxのビルドシステムbitbakeでは,カーネルのビルド方法の指定(レシピと言う)を device-software/meta-edison/recipes-kernel/linux/ の中の .bbappend拡張子 のファイルで追加できます.
今回は,ここに元から入っていた linux-yocto_3.10.bbappend のファイルを以下のように編集しました.
FILESEXTRAPATHS_prepend := "${THISDIR}/files:"
COMPATIBLE_MACHINE = "edison"
LINUX_VERSION = "3.10.17"
SRCREV_machine = "c03195ed6e3066494e3fb4be69154a57066e845b"
SRCREV_meta = "6ad20f049abd52b515a8e0a4664861cfd331f684"
SRC_URI += "file://defconfig"
SRC_URI += "file://upstream_to_edison.patch"
SRC_URI += "file://patch-3.10.17-rt12_edison.patch"
SRC_URI += "file://intel_mid_rpmsg.c.patch"
do_configure() {
cp "${WORKDIR}/defconfig" "${B}/.config"
}
do_kernel_configme() {
cp "${WORKDIR}/defconfig" "${B}/.config"
}
do_patch() {
cd ${S}
git am "${WORKDIR}/upstream_to_edison.patch"
git apply "${WORKDIR}/patch-3.10.17-rt12_edison.patch"
git apply "${WORKDIR}/intel_mid_rpmsg.c.patch"
}
LINUX_KERNEL_TYPE = "preempt-rt"
KBRANCH = "standard/preempt-rt/base"
とかすると,yoctoのほうで用意してくれてるPREEMPT_RTのカーネルを指定できるはずなんですけど,よくわからなかったので,自前でパッチを作成しました.
ここのfilesディレクトリの中にパッチを入れ,SRC_URIパラメータにパッチを登録し,do_patch()関数の中で,実際に適用します.
PREEMPT_RTのパッチは,Real-Time Linuxのプロジェクトのページから,適切なものを持って来ます.今回は,3.10.17のカーネルに適用するので,patch-3.10.17-rt12.patch.bz2ですね.こいつを解凍して,filesフォルダの中に入れました.また,このパッチは upstream_to_edison.patch の後に適用され,それに対応する必要があったため, patch-3.10.17-rt12_edison.patch とリネームしておきました.
ここまでできたら,
$ bitbake virtual/kernel
を繰り返して,ビルドが通るようになるまでパッチを修正していきます.
・・修正は終わりましたか?
それでは,修正済みのパッチをこちらに置いておきますね.
カーネルコンフィグの設定
さぁて,お待ちかね,カーネルをリアルタイム指定するパラメータを設定しましょう.
ちゃんとPREEMPT_RTパッチが当たっていれば,
$ bitbake virtual/kernel -c menuconfig
して出てくる,設定ウィンドウの中で, Processor type and features --> Preemption Model に, Fully Preemptible Kernel (RT) が選べるはずです.
その他,Wikiを参考に,High-Resolution-TimerがONになっていること,ACPIやAPMがOFFになっていることを確認しましょう.
できたら,<SAVE>して,全部<EXIT>します.作成したコンフィグを,
$ cp tmp/work/edison-poky-linux/linux-yocto/3.10.17+gitAUTOINC+6ad20f049a_c03195ed6e-r0/linux-edison-standard-build/.config ../device-software/meta-edison/recipes-kernel/linux/files/defconfig
で,コピーしておくのを忘れずに.
カーネルのビルド
ここは,いつも通り.
$ bitbake virtual/kernel
またエラーが出るかもしれません.そうしたら,またパッチを手で修正しましょう.
それから,どうも,PREEMPT_RTパッチのほうで,正しいのか怪しい実装があり,そこでコンパイルがこけていたので,intel_mid_rpmsg.c.patch というパッチも作成しました.
全体のビルドが通るようになったら,
$ bitbake edison-image
で,Edison用のイメージをビルドします.終了したら,
$ ../device-software/utils/flash/postBuild.sh
$ cd toFlash
$ sudo ./flashall.sh
で,Edisonに焼き込みましょう.
チェック
Edisonにログインして,
$ uname -a
して,
と,カーネルのバージョン番号に -rt がついていることと, PREEMPT RT が入っていることを確認してください.
実験
GPIO上げ下げによる評価
以下のプログラムは,500[us]毎にGPIOを上げ下げします.
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <sched.h>
#include <sys/mman.h>
#include <string.h>
#include <mraa.h>
#define MY_PRIORITY (49) /* we use 49 as the PRREMPT_RT use 50
as the priority of kernel tasklets
and interrupt handler by default */
#define MAX_SAFE_STACK (8*1024) /* The maximum stack size which is
guaranteed safe to access without
faulting */
#define NSEC_PER_SEC (1000000000) /* The number of nsecs per sec. */
void stack_prefault(void) {
unsigned char dummy[MAX_SAFE_STACK];
memset(dummy, 0, MAX_SAFE_STACK);
return;
}
int main(int argc, char* argv[])
{
struct timespec t;
struct sched_param param;
int interval = 500*1000; /* 0.5ms = 500us*/
/* Declare ourself as a real time task */
param.sched_priority = MY_PRIORITY;
if(sched_setscheduler(0, SCHED_FIFO, ¶m) == -1) {
perror("sched_setscheduler failed");
exit(-1);
}
/* Lock memory */
if(mlockall(MCL_CURRENT|MCL_FUTURE) == -1) {
perror("mlockall failed");
exit(-2);
}
/* Pre-fault our stack */
stack_prefault();
clock_gettime(CLOCK_MONOTONIC ,&t);
/* start after one second */
t.tv_sec++;
mraa_result_t ret;
mraa_init();
mraa_gpio_context gpio;
gpio = mraa_gpio_init(6);
ret = mraa_gpio_dir(gpio, MRAA_GPIO_OUT);
ret = mraa_gpio_use_mmaped(gpio, 1);
char state = 0;
while(1) {
/* wait until next shot */
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &t, NULL);
/* do the stuff */
if(state == 1){
mraa_gpio_write(gpio, 1);
state = 0;
}else{
mraa_gpio_write(gpio, 0);
state = 1;
}
/* calculate next shot */
t.tv_nsec += interval;
while (t.tv_nsec >= NSEC_PER_SEC) {
t.tv_nsec -= NSEC_PER_SEC;
t.tv_sec++;
}
}
}
このコードの中の, sched_setscheduler で優先度指定を行っている部分を 外して ,リアルタイム指定しなかった場合の波形は,こちらになります.波長が前後に大きくブレていることがわかると思います.
それに対して,ちゃんとリアルタイムプロセスに優先度指定した動画がこちら.まぁ,それなりに良くはなったかと思います.しかし,たまに大きく遅れているように見えるところもあり,もちっと定量的に評価しておきたいところですね.
cyclictestによる評価
リアルタイム性の評価方法として,PREEMPT_RTのページにも載っている,cyclictestを使って評価します.これは,
# opkg install git # gitをインストールしていない場合
# git clone git://git.kernel.org/pub/scm/linux/kernel/git/clrkwllms/rt-tests.git
# cd rt-tests
# make all
# cp ./cyclictest /usr/bin/
で簡単に導入できる・・らしいのですが,Edisonでビルドしようとすると,「numa.hがない」というエラーが出たので,仕方なくMakefileの20行目の指定を,
#NUMA := 1
のようにコメントアウトしました.
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
2019/6/4 追記
「numa.hがない」というエラーが出た場合、libnuma packageをインストールしてください。
CentOS: yum install libnuma-devel
Ubuntu: apt-get install libnuma-dev
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
さて,実際に使ってみます.
これは,インターバル10000[us],5000回,負荷なしの条件.上が非リアルタイムプロセスの結果,下がリアルタイムプロセスの結果になります.数値は全て[us]単位です.
なにやら数値がずらずらと表示されていますが,リアルタイムの評価時に気にすべきところは,以下の「最悪時間」の部分だけです.
この結果からは,非リアルタイムプロセスの場合は処理の開始までに最悪1111[us]かかる場合があるが,リアルタイムプロセスの場合はそれが98[us]まで短縮できるということが読み取れます.
次に,Edisonはデュアルコアなので,2スレッドでも実験してみました.
この結果からは,2スレッドの場合,非リアルタイムプロセスの場合は処理の開始までに最悪2475[us]かかる場合があるが,リアルタイムプロセスの場合はそれが128[us]まで短縮できるということが読み取れます.
以上の結果は,負荷をかけない状態でやった実験ですが,EdisonにWebカメラを接続し,SURFの特徴点抽出を連続して行うという負荷をかけた状態でも実験してみました.特徴点抽出には,だいたい1[s]くらいの時間がかかる環境で行いました.
この結果からは,負荷のある状態だと,非リアルタイムプロセスの場合は処理の開始までに最悪10[ms](2スレッドの場合)かかる場合があるが,リアルタイムプロセスの場合は,それが141[us](2スレッドの場合)まで短縮できるという結果が得られました.
ロボット等の機械ですと,そりゃ制御周期は早けりゃ早いほうが良いですが,一般に1[kHz]くらいの制御周期があれば十分だと言われています.このシステムを1[kHz]で制御を行うシステムに利用する際には,一回の制御の計算周期を1000-141で859[us],コンテキストスイッチ等にかかる時間で余裕を見て,750~800[us]以内くらいに抑えればリアルタイム制御が実現できるということになります.
まとめ
- EdisonのLinuxカーネルにPREEMPT_RTパッチを適用して,リアルタイム化した.
- リアルタイム化したカーネルだと,処理の開始までに10[ms]かかる場合があるところを,最悪140[us]まで短縮することができる.
- 一回の制御にかかる計算時間を750~800[us]以内くらいに収めれば,このシステムで1[kHz]の制御周期のリアルタイムシステムが構築可能.