C99に対応した標準Cライブラリの実装レポートを行っていきます。

プロフィール 

高木信尚

Author:高木信尚

ホームページ
ブログ

最近の記事 

最近のコメント 

最近のトラックバック 

月別アーカイブ 

カテゴリー 

ブロとも申請フォーム 

この人とブロともになる

ホーム 全記事一覧

 

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
--/--/-- --:--|スポンサー広告

 

raise関数は、明示的にシグナルを発生させるための関数です。非同期シグナルに対する処理とは異なり、raise関数と、raise関数を内部的に呼び出すabort関数によって呼び出されたシグナル処理ルーチンは、そこで行うことができる処理にほとんど制約がありません。

非同期シグナルの場合、シグナル処理ルーチンの中で呼び出せる標準関数は、abort関数と_Exit関数、そして同じシグナルに対するsignal関数だけです。今回の実装では、非同期シグナルは、タスク例外処理ルーチンからraise関数を呼び出すことで実現しますが、非同期の場合はraise関数を使っていても上記の制約があるものとします。

つまり、タスク例外処理ルーチンを介して呼び出されたシグナル処理ルーチンからは、ほとんどの標準関数を呼び出すことができず、longjmp関数を用いて脱出することもできなくなります。

ところで、前回のsignal関数の回には、raise関数をsignal関数と同じ翻訳単位に入れることを想定していましたが、よく考えてみるとその必要はなさそうです。では、実装をご覧ください。

#include <kernel.h>
#include <signal.h>

int raise(int sig)
{
  void (*func)(int) = signal(sig, SIG_DFL);
  uintptr_t f = (uintptr_t)func;
  if (f <= 2) {
    switch (f) {
    case 0: // SIG_DFL
      ext_tsk();
      // break;
    case 1: // SIG_IGN
      break;
    default:
      return -1;
    }
  }
  else {
    (*func)(sig);
  }
  return 0;
}

今回の実装では、SIG_DFLの処理は、問答無用でタスク終了としています。最初からswitch文で処理を分けてもよかったのですが、ユーザー定義のシグナル処理ルーチンを最速の方法で呼び出すには、いったんユーザー定義のものが登録されているのか、SIG_DFLやSIG_IGNが登録されているのかを「ふるい」にかけた方が得策と考えて、このように実装してみました。
スポンサーサイト
2006/05/22 03:15|シグナル処理TB:0CM:0

 

前回は、シグナル処理ルーチンの方針について書きました。今回と次回は、その方針に基づいて、実際にsignal関数とraise関数を実装することにします。

今回の関数は、初めてOSに依存したコードになります。といっても、簡単な排他制御だけですので、普通に割り込みを禁止してもよいのですが、他のターゲットへの移植性を考慮して、μITRONの機能を使うことにします。

それでは、実装を見てみましょう。

#include <signal.h>
#include <errno.h>

#define SIGMAX  6
static void (*signal_handler_table[SIGMAX])(int);

void (* signal(int sig, void (*func)(int)) )(int)
{
  if (sig < 1 || SIGMAX < sig || func == SIG_ERR) {
    errno = EDOM;
    return SIG_ERR;
  }

  BOOL f = sns_loc();
  loc_cpu();
  void (*result)(int) = signal_handler_table[sig-1];
  signal_handler_table[sig-1] = func;
  if (!f) unl_cpu();

  return result;
}

前半部は引数のチェックを行っています。sigで指定したシグナル番号の範囲と、念のためfuncにSIG_ERRを誤って指定していないかどうかだけのチェックにとどめています。funcに空ポインタを指定した場合、SIG_DFLとの区別が付きませんので、特に何もしていません。

エラーを検出した場合、SIG_ERRを返すとともに、errnoに正の値を設定しなければならないため、ここではEDOMを設定しています。他のエラー番号を設けてもよいのですが、今回はそこまでする必要もないと判断しました。

シグナル処理ルーチンは、signal_handler_table配列に格納されます。静的な配列ですので、その内容は空ポインタ(=SIG_DFL)で初期化されることが期待できます。また、raise関数も同じ翻訳単位に収めることにすれば、この配列は内部結合となり、外部から参照されるおそれがなくなります。

signal_handler_table配列の操作は、排他制御が必要ですので、sns_loc、loc_cpu、およびunl_cpuというサービスコールを使用しています。これらはCPUロック状態の制御を行うもので、すごく乱暴に言ってしまえば、割り込み禁止の制御を行っています。

signal関数は、非タスクコンテキストで呼ばれることを想定していません。そのため、sns_ctxでコンテキストの状態を調べてることなく、(iloc_cpuやiunl_cpuではなく)loc_cpuやunl_cpuを問答無用で呼び出しています。非タスクコンテキストに対応するするには、SIL*1を使った方が便利かと思います。


*1 ITRONデバイスドライバ設計ガイドラインの一部分として検討されているシステムインタフェースレイヤのこと。TOPPERS/JSPカーネルは、そのサブセットをサポートしています。
2006/05/16 00:07|シグナル処理TB:0CM:0

 

個々の関数の実装に入る前に、シグナル処理ルーチンをどう扱うべきかについて、決めておきたいと思います。

そしてsignal関数の第2引数が関数を指している(すなわち、SIG_DFLやSIG_IGNではない)場合、シグナル sig が発生すると、シグナル処理ルーチンが呼び出される前に、次に挙げる動作のいずれかが行われます(処理系定義)。
  1. signal(sig, SIG_DFL);と同等のことを実行する。
  2. 少なくとも sig を含むシグナルの処理系定義の組に対して、処理系がそれらの発生を遮る(この状態は、その時点のシグナル処理ルーチンが完了された時点で元に戻される)。
多くの処理系では1.が採択されています。というより、C99より前の規格では、1.しかなかったように記憶しています。1.では、通常シグナル処理ルーチンの中で再度signal関数が呼び出されることになりますが、それより前に同じシグナルが発生してしまうことを防げないため、いろいろと問題があります。

可能であれば2.の方が優れている気もしますが、2.を実現するにはやや問題があります。というのは、raiseまたはabort関数の結果としてシグナル処理ルーチンが呼び出された場合、longjmp関数で脱出できる必要があるわけですが、シグナルを遮ってしまうと、それを元に戻すことができなくなるからです。

2.の方法でlongjmp関数を使った場合の問題点を回避するには、longjmp関数自体を修正する必要がありますが、下手なことをやるといろいろなところへの悪影響が懸念されるので、今回は素直に1.の方法を採用することにします。

次に、シグナル処理ルーチンが呼び出されるコンテキストをどうするかですが、今回は、すべてタスクコンテキストで呼び出されるものとします。割り込みハンドラやCPU例外ハンドラ(H8はCPU例外をサポートしませんが)などの非タスクコンテキストから直接呼ぶのではなく、いったんiras_texでタスク例外を発生させてから、シグナル例外処理ルーチンを呼び出すものとします。

このように、タスク例外処理ルーチンからシグナル処理ルーチンが呼ばれることになるわけですが、raiseやabort関数の結果としてシグナル処理ルーチンを呼び出す場合は、タスク例外を介さずに、直接シグナル処理ルーチンを呼び出すことにします。

というのは、タスク例外処理ルーチンがすべてのタスクに対して定義されているとは限らず、また、特定のタスクの例外処理ルーチンだけを呼び出すのも問題があるからです。しかも、タスク例外処理ルーチンが定義されているかどうかを調べることは(タスク初期化ブロックを直接覗かない限り)できないので、エラー検出ができないことも理由の一つです。

最後に残った課題は、タスク例外禁止状態の操作です。raiseやabort関数の結果としてシグナル処理ルーチンが呼ばれる場合には、タスク例外禁止状態を操作する必要は特にありません。タスク例外処理ルーチンから呼び出す場合が問題ですが、signal(sig, SIG_DFL);さえ実行しておけば、タスク例外禁止状態のままにしておいて、重複して発生したタスク例外を保留しても、規格に反するわけではなさそうなので、深く考えずに放置することにします。

タスク例外禁止状態で、タスク例外処理ルーチンからシグナル処理ルーチンを呼び出した場合、longjmp関数で脱出できなくなりますが、raiseやabort関数の結果としてシグナル処理ルーチンが呼ばれたのでない限り、abort、_Exit、およびsignal関数以外のライブラリ関数を呼び出した場合の動作は未定義なので、特に問題はなさそうです*1

今回は、思いつくままダラダラと書いてみました。ちょっとわかりにくかったかもしれませんが、次回以降で各関数の実装を行っていきますので、その際に適宜参照していただければと思います。


*1 μITRON 4.0仕様では、タスク例外処理ルーチンからlongjmp関数で脱出することを許していますが、シグナル処理ルーチンが呼び出された以降は、μITRON 4.0仕様ではなく、標準Cライブラリの仕様に従うことになります。
2006/05/09 23:11|シグナル処理TB:0CM:0

 

前回までで、ようやく<string.h>ヘッダを終えることができました。今回からは<signal.h>ヘッダの実装に入ります。

<signal.h>ヘッダで扱うシグナル処理は、最低限の実装だけで済ませると、ほとんど使い物にならず、規格との互換性のためだけに存在することになってしまいます。かといって、POSIX互換のような実装を行うのは、今回の目的を大きく超えています。

そこで、μITRON 4.0仕様に元々備わっているタスク例外の機能と組み合わせることで、(他の実行環境との)移植性を保ちながら、アプリケーションを作成できる程度のものにとどめることにします。すなわち、タスク例外を直接扱うと、μITRON以外の実行環境への移植性が損なわれますので、それをうまく回避できるようなものにしていきます。

とりあえずは、型とマクロの定義から行います。

まずは、sig_atomic_t型の定義です。この型は、アトミックオペレーションとして読み書きを行うことができる整数型ですが、以前、<stdint.h>ヘッダで定義されるSIG_ATOMIC_MAXおよびSIG_ATOMIC_MINの実装で、H8/300のときはshort型、それ以外はint型にすると決めました。

そこで、次のように型定義を行うことにします。

#ifdef __H8300__
typedef short sig_atomic_t;
#else
typedef int sig_atomic_t;
#endif

次に、signal関数の第2引数または返却値となるSIG_~マクロの定義です。これは、他の関数へのポインタと同じ値にならなければよいので、次のように定義します。

#define SIG_DFL ((void(*)(int))0)
#define SIG_IGN ((void(*)(int))1)
#define SIG_ERR ((void(*)(int))2)

SIG_ERRは-1に定義される場合が多いのですが、ユーザ定義のシグナル処理ルーチンかどうかの判定を簡略化し、呼び出し効率を上げるためにあえてこのようにしています。また、H8の場合、0番地からベクタテーブルが割り付けられているため、これらの値が他の関数へのポインタと合致することもありません。

定義すべきマクロの最後は、signal関数およびraise関数の第1引数となるSIG~マクロです。SIG~マクロは、処理系が独自に追加してもよいのですが、ここでは最小限の内容にとどめます。これらのマクロの値は、1以上の整数でなければならないので、単純に連番をあてることにします。

#define SIGABRT 1
#define SIGFPE 2
#define SIGILL 3
#define SIGINT 4
#define SIGSEGV 5
#define SIGTERM 6

後は、signal関数とraise関数の宣言が必要です。関数の詳細については、例によって、個別にお話したいと思います。
2006/05/05 12:07|シグナル処理TB:0CM:0

ホーム 全記事一覧

ブログ内検索 

お勧め書籍 

RSSフィード 

リンク 

このブログをリンクに追加する

Copyright(C) 2006 TAKAGI Nobuhisa All rights reserved.
Powered by FC2ブログ. template designed by 遥かなるわらしべ長者への挑戦.
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。