最終更新 2006/12/01
RTAI - Real Time Application Interface API解説(6) 
 
6 タスク生成とタイマー
 前章でリアルタイムタスクを作成する基本的なスタートアップ rt_task_initを導入した。次の節でカーネルとユーザ両モードで、リアルタイムタスクを作成し初期化する方法とタイマを取り扱う方法を示す。
6.1 RTAI リアルタイムタスク作成
 既に、RTAIにはユーザモードとカーネルモードのリアルタイムタスクがあることを述べた。この両者は別世界にあり、異なる環境にあると思われているが、ここでは、そうではないことを示す。RTAIではその差を、タスク生成に限れば、Makefileだけである。
6.1.1 カーネルモードリアルタイム タスク
どんなカーネルモジュールでも init_module () と呼ばれるエントリーポイントより入り、 cleanup_module ()によって 終了します。詳しくはWEB情報で。

nit_module ()関数は、 insmod ユーティリティーを使用して実行中のカーネルにモジュールを挿入すると、必ず呼ばれます。
RTAIでもAPIを使用して特定機能の設定、リソースの割り当てを行いリアルタイムタスクを始める準備をする理想的な場所である。
 cleanup_module () 関数は、モジュールがカーネルから削除されるとき実行されます(rmmod ユーティリティー参照)。

Example 1 rtai_kernel_mode_task.c:

#include <linux/module.h>
#include <asm/io.h>
MODULE_LICENSE(“GPL”);
int init_module(void)
{
    printk(“Init module function starting point\n”);
    rerturn 0;
}
void cleanup_module(void)
{
    printk(“Cleanup module function starting point\n”);
}

上記の、単純な、テストファイルをコンパイルするためにも、メイクファイルが必要になる(kernel2.6では必須?)、始めるべき最も良い方法はshowroomリポジトリにあるメイクファイルの1つからMakefileを持ってくることです。

 カーネル空間アプリケーションを作成するときライセンスに注意しなければなりません。毎日たくさん非GPLのモジュールがカーネルライセンスを侵害しています。そこでRTAIはユーザモードアプリケーションとして開発を考慮して、あなたのプロジェクトを売るためにLGPLにしています。
6.1.2 ユーザモードリアルタイムタスク
 ユーザモードリアルタイムタスクを作成するにはカーネルモードと関連させて作るのが簡単です。簡単なmain()関数を持つGNU/linuxのタスクをつくり、いくつかの特定の RTAI API を呼び出すことによって、それはユーザースペースで走るリアルタイムタスクに変わる。

 カーネルモードタスクは、おそらくレイテンシーを最小限に減少させて、より高い頻度で実行することが可能とするにために必要です。
ユーザモードタスクはより容易にLinuxワールドの多くのリソースにアクセスが可能であり、集積を許す。
 両方ともハードウェアにアクセスを許します。しかしカーネルモードでは割り込みを持っているハードウェアアクセスはより多くのアクセスが可能です。
そのためにRTAI はユーザースペースの中で ISRへのアクセス suppoort をしている。

 最後に、ユニークな RTAI 能力であるユーザモードリアルタイムタスクがリアルタイムタスクを作成する際に優先すべき方法であると思ってください。もし特別な理由がないなら、常にユーザースペースリアルタイムを使うべきです。 この章を読んで、2つの戦略が多くの共通点があることに注意してください。それは1つのモードから他のモードに移行することは複雑な仕事ではない。




 
6.1.3 リアルタイム能力を可能にする方法
 選択(カーネルあるいはユーザモード)に関わらず、タスクに RTAI リアルタイム能力にアクセスする方法を導入する。それは専用の rt_task_init 関数で特定のタスクを作成することだけです。作成されたタスクは、前章で定義さた RT_TASK タイプです。

カーネルモード用
int rt_task_init(RT_TASK *task, void (*rt_thread)(long), long data, int stack_size, int priority, int uses_fpu, void(*signal)(void))

int rt_task_init_cpuid(RT_TASK *task, void (*rt_thread)(long), long data, int stack_size, int priority, int uses_fpu, void(*signal)(void), unsigned int cpuid)

 両方ともはカーネルスペースで新しいリアルタイムタスクを作成します。
パラメータ:
*task: はそのスペースがアプリケーションによって提供されなくてはならなくて、そしてリアルタイムタスクの生存期間全体の間に実在していなければならない RT_TASK 構造体へのポインタです。
rt_thread: はタスクのインプリメンテーションであり、 rt_thread はタスク関数のエントリーポイントです。
data: 親タスクは関数に一つの整数値を渡すことができます。dataは生成タスクに送るべきデータです。dataをポインタとして使用すれば、どのようなデータ構造も送付することが可能です。
stack_size: 生成するタスクのスタックの大きさを設定する。リアルタイム割込みハンドラのために充分な容量を用意しなければならない。従って控えめな容量ではなく充分な大きさを指定してください。
priority: は最高優先順位が0であり、最低は RT_LOWEST_PRIORITY です。
uses_fpu: ゼロ以外の値がタスクが浮動小数点装置を使うことを示します。
signal(void): タスクがコンテキストスイッチの後にカレントタスクになるとき、割り込み禁止で呼び出される関数です。
(注意)
しかし、タスクが初めてのスケジューリングにおいて呼び出されません。
この関数は、必要とされるときはいつでも動的に変えらことができます。関数 rt_task_signal_handlerを参照してください。

rt_task_init_cpuid はrt_task_initに加えてただ1つのパラメータがある。最初から一つの特定のCPUを割り当てる。
もし rt_task_init API が使われるなら RTAI は;
  MUP スケジューラーで使われるとき、 rt_task_init が自動的にタスクがどのCPU上で走るであろうか選択します、
 他方 SMP スケジューラーではタスクはデフォルトで利用可能なCPUのいずれかを使うことになります。
 この割り当ては rt_set_runnable_on_cpus あるいは rt_set_runnable_on_cpuidの呼び出しによって変えられるかもしれません。
 もし cpuid が無効であるなら、 rt_task_init_cpuid が自動CPU選択になります。

戻り値:
0: 正常
負の値: エラー
EINVAL タスク構造はすでに使用中です
ENOMEM stack_size バイトがスタックに割り当てられることができませんでした

 rtai_lxrt モジュールが使用中であるとき、これらの2つの APIは rtai_kthread_init を隠します、すでに説明したようにスタックは定義されます。

 この api を導入して、リアルタイム能力にアクセスすることができるようにするために、どのように前の例を修正するべきか理解することは簡単です。

Example 2 rtai_kernel_mode_task.c:

#include <linux/module.h>
#include <asm/io.h>
#include <rtai.h>
#include <rtai_sched.h>
static RT_TASK Simple_Task;
MODULE_LICENSE(“GPL”);
static void Simple_Thread(int t)
{
    //This is the real-time thread
}

int init_module(void)
{
    printk(“Init module function starting point\n”);
    rt_task_init(&Simple_Task, Simple_Thread, 0, 3000, 0, 0, 0);
    rerturn 0;
}

void cleanup_module(void)
{
    printk(“Cleanup module function starting point\n”);
}

ユーザモード用
RT_TASK* rt_task_init(unsigned long name, int priority, int stack_size, int max_msg_size)

RT_TASK*rt_task_init_schmod(unsigned long name, int priority, int stack_size, int max_msg_size, int policy, int cpus_allowed)

RTAI_PROTO(RT_TASK *,rt_task_init,(unsigned long name, int priority, int stack_size, int max_msg_size))
{
return rt_task_init_schmod(name, priority, 0, max_msg_size, SCHED_FIFO, 0xFF);
}

パラメータ:
name: はユニークな識別子
priority: は RTAI タスク拡張のプライオリティです
stack_size: はレガシーなパラメータで使われていない。互換性が目的です。
max_msg_size: はがゼロも許す、その場合デフォルトの内部の値が使われます。 デフォルトメッセージ(256)の大きさに注意してください。 より大きい値が必要とされる場合、適切にマクロ MSG_SIZE をセットして sys.c をリコンパイルするか、あるいは明示的にここでより大きい大きさを割り当ててください。
policy: デフォルトは SCHED_FIFOです。、それは RT_SCHED_RRと同様である( sched_get_priority_max (polisy)を参照)
同じプライオリティで、CPUリリース(rt_task_yield ではない)なしで走っている、いくつかのタスクを持っている場合に限り、ラウンドロビンpolicyは使われるように意図されます。
cpus_allowed: はタスクの実行を望む cpuを示す(ビット対応です)。RTAI でタスクが静的に特定のCPU上で走ることを望むとき、これは必須です。

スタックを拡張するに必要なサポートマクロがあります:

#define rt_grow_and_lock_stack(incr) \
do { \
char buf[incr]; \
memset(buf, 0, incr); \
mlockall(MCL_CURRENT | MCL_FUTURE); \
} while (0)


戻り値:
  カーネルスペースで初期化したタスク構造体へのポインタ
  0; エラー

ユーザモードでタスク作成を実行する方法の単純な例です

Example 3 rtai_user_mode_task.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <rtai_lxrt.h>
#include <sys/io.h>
static RT_TASK *main_Task;
int main(void)
{
    RT_TASK *Main_Task;
    if (!(Main_Task = rt_task_init_schmod(nam2num("MNTSK"), 0, 0, 0,    SCHED_FIFO, 0xF))) {
        printf("CANNOT INIT MAIN TASK\n");
        exit(1);
    }
    return 0;
}

この例に同じく新しい機能 nam2num があります。
unsigned long nam2num (const char *name)
void num2nam (unsigned long num, char *name)

前者は文字列(6文字)からユニークなIDに変換
後者はIDから文字列へ変換
 
6.1.4 タイマの始動
 カーネルモードで新規生成されたリアルタイムタスクはサスペンド状態にあります。(ユーザモードではその限りではない)
タスクはrt_task_make_periodic, rt_task_make_periodic_relative_ns 及び rt_task_resumeでアクティブ状態に移行できますが、これらの関数を呼ぶ前にタイマを始動させなければならない。タイマはRTAIのタイミング制約を決定する主なステップです。
タイマは次の関数で始動、停止します。
RTIME start_rt_timer(int period);
void stop_rt_timer(void);

period: は内部カウンタユニットにあり、周期モードのみに必要とされる。oneshotモードではperiodは無視されます。
戻り値: 内部のカウントユニットの周期値

(注意)
 もしプロジェクトが多数のタスクを起動する場合、ただ1度タイマを始動させますが、タイマを停止すると他のタスクもストップするのを避ける注意をしてください。同様に多重にタイマを始動させるなら、2番目のコールがタイマをリセットして、新しい周期ででタイマ動作を始める。ハードタイマが動いているかどうか確かめるために、 rt_is_hard_timer_running () API を使うことができます。

同じくナノセカンド単位を内部のカウント単位に変換する nano2count マクロを用意しています。

Example 4 how to start up a timer:
#define TICK_TIME 1000000
if ((hard_timer_running = rt_is_hard_timer_running()))
{
    printf("Skip hard real_timer setting...\n");
    sampling_interval = nano2count(TICK_TIME);
}else{
    printf("Starting real time timer...\n");
    rt_set_oneshot_mode();
    start_rt_timer(0);
}



 
6.1.5 Oneshot 対周期的なモード
 oneshot あるいは周期モードの中から、どちらのタイマモードで駆動するかを選択します。"rtai_sched.h"で定義されている APIを次に示す:

void rt_set_oneshot_mode (void);
void rt_set_periodic_mode (void);

 もしモードをセットしなければデフォルトで周期モードが選択されます。
 rt_set_oneshot_mode は oneshot モードをセットします。 このモードは本質的にCPUクロック周波数に基づいて可変的なタイミングになります。 これはタスクが任意な時間で実行されることを意味します。時間変換を含めて、全ての時間関連関数を使用前に、呼ばなければならない。
(注意 i386s 、 i486s)
 ペンティアム以前は、 oneshot タイミングのために連続な時間基準として使用されるCPUタイムスタンプ時計(TSC)がない。このようなマシンのために8254のタイマのカウンタ2の連続カウント系で TSC をエミュレートしています。Linuxのjiffiesの生成には影響を与えない。ただし、8254のカウンター読み込みのために多くの時間を要することに注意を払ってください。
 それでこのようなマシンの上では、必要以上に高い周波数で oneshot モードは使うべきではない。
 
 
 
 
 
   



 
6.1.6 周期的にタスクの起動
 この段階で、タイマポリシーに基きタイマは適切なピリオドで動いて、周期モードを選択して、周期的なスケジューリング用にリアルタイムタスクを設定することを可能になる。
これらは次の関数で実現できる
Int rt_task_make_periodic (RT_TASK *task, RTIME start_time, RTTIME period);
Int rt_task_make_periodic (RT_TASK *task, RTIME start_delay, RTTIME period);

 これらは周期起動タスクを作ります、それは rt_task_make_periodic がそれ以前に呼び出された rt_task_init () 、 rt_task_wait_period () が作成したタスクに周期periodで周期起動タスクであることをマークします。
 
最初の実行の時は start_time あるいは start_delay で指定される。
start_timeはclock ticksを単位する絶対時刻です。
start_delayは現在時刻からナノセカンドで設定する。

"rtai_sched.h"で、いつもの通り、定義された2つのマクロがあります。
RTIME count2nano (RTIME timercounts);
RTIME nano2count (RTIME nanosecs);
これらは時間ユニット変換です。カウント単位は (oneshot / periodic)に依存している こと記憶してください。

パラメータ:
 task: は周期起動を望むタスクへのポインタです
 start_time: タスクがスタートする前に待つ絶対時間(clock ticks)です。
 period: タスク周期をclock ticks で指定する。
戻り値: 
 0: 正常
 EINVAL 異常なtask構造体

void rt_make_hard_real_time (void);
void rt_make_soft_real_time (void);

 このAPI がLinuxプロセスpthreadにハードリアルタイム実行能力を与える。より具体的には rt_make_hard_real_time はソフトLinux POSIX リアルタイムプロセスを作ります(ハードリアルタイム LXRT プロセスを呼んで)。この関数はソフトLinux POSIX プロセスがメモリロックを起こす局面でのみ使用する。
 rt_make_hard_real_timeのコールによってハードリアルタイムしたタスクを、rt_make_soft_real_time はプロセスをソフト Linux POSIX リアルタイムに戻す。
 この関数は自プロセスからのみを使うことができます。他のプロセスから遷移を指示することはできません。例外としてrt_force_task_soft(int pid)があります。

Example 5 making a task periodic (in user mode):
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <rtai_lxrt.h>
#include <sys/io.h>
#define TICK_TIME 1000000
#define CPUMAP 0x1
static RT_TASK *main_Task;
static RT_TASK *loop_Task;
int keep_on_running = 1;
static pthread_t main_thread;
static RTIME expected;
static RTIME sampling_interval;

static void *main_loop()
{
    if (!(loop_Task = rt_task_init_schmod(nam2num("RTAI01"), 2, 0, 0,     SCHED_FIFO, CPUMAP))) {
        printf("CANNOT INIT PERIODIC TASK\n");
        exit(1);
    }
    expected = rt_get_time() + 100*sampling_interval;
    rt_task_make_periodic(loop_Task, expected, sampling_interval);
    rt_make_hard_real_time();
    while (keep_on_running)
    {
        //insert your main periodic loop here
        rt_task_wait_period();
       //set keep_on_running to 0 if you want to exit
    }
    rt_task_delete(loop_Task);
    return 0;
}
int main(void)
{
    RT_TASK *Main_Task;
    if (!(Main_Task = rt_task_init_schmod(nam2num("MNTSK"), 0, 0, 0, SCHED_FIFO, 0xF))) {
        printf("CANNOT INIT MAIN TASK\n");
        exit(1);
    }
    if ((hard_timer_running = rt_is_hard_timer_running()))
    {
        printf("Skip hard real_timer setting...\n");
        sampling_interval = nano2count(TICK_TIME);
    }else{
        printf("Starting real time timer...\n");
        rt_set_oneshot_mode();
        start_rt_timer(0);
    }
    sampling_interval = nano2count(TICK_TIME);
    pthread_create(&main_thread, NULL, main_loop, NULL);
    while (keep_on_running)
        sampling_interval = sampling_interval; //do nothing!
    rt_task_delete(Main_Task);
    return 0;
}




 
6.1.7 RTAI スレッド生成のショートカット
 同じく標準POSIX 関数 pthread_create、 pthread_init と pthread_join を使用しないでスレッドを作成する、若干の実用的な機能があります。

int rt_thread_create (void *fun, void * args, int stack_size);
int rt_thread_join (int thread);
int rt_thread_init (unsigned long name, int priority, int max_msg_size, int policy, int
cpus_allowed);

 これらはrtai_lxrt.hに定義されているが、いずれも透過的に標準POSIX関数を呼び出している。加えていくつかのパラメータを受け入れることにより、rt_thread_initからrt_thread_create あるいは直接 rt_task_init_schmod を呼び出す。
 基本的に RTAI タスクを作成するために、これらを快適で安全なショートカットである。
 いずれにしても、前の例で見られるように、これらなしでタスクを作成することができ、これら API の使用が任意であることは明白です。しかし、すぐにこれらのショートカットを使い始めて、 POSIX 方法に戻ることは無くなる。

 
6.2 何時ですか?
 時間の取り扱いで、単位(ナノセカンドであるいは内部のカウンター単位)はリアルタイムタスクでは重要です。実のところこれを専門に行なっているいくつかの API があります、これらは"rtai_sched.h"で定義されています。

RTIME rt_get_time (void);
RTIME rt_get_time_cpuid (int cpuid);
RTIME rt_get_time_ns (void);
RTIME rt_get_cpu_time_ns (void);
RTIME rt_get_time_ns_cpuid (int cpuid);

rt_get_time は、 start_rt_timer が呼び出されたときから、内部のカウントユニットの時間を返します。周期的なモードでは数は周期倍になります。
rt_get_time_ns は rt_get_time と同じですが、返送された時間はナノセカンドに変換されています。
リターン:
内部のカウントユニット / ナノセカンドでの現在時刻は返されます。

Guest No.