→I/Oアクセス
→プログラムをroot権限で実行する:chmod +s
→CPU内蔵のカウンタで時間を計る:RDTSC
→プログラムの優先順位を上げる:setpriority
→プログラムの優先順位をさらにに上げる:sched_setscheduler
Linux の場合はというとやぱり in/out の関数が存在します。入出力 それぞれ 1,2,4バイト単位で
しかし、Linuxの場合、ただ、これら命令を実行すると Segmentation fault
が発生してプログラムが停止します。これは保護機能によるものです。
ユーザがかってに I/O 命令を実行できるとなると、マルチタスク・
マルチユーザのOSの信頼性はとたんに無くなります。1ユーザのいたずらで
システムが容易にとまるからです。
そこで、保護を解除する必要があります。方法は2通りあります。
さらに、この保護解除は root 権限でなければできないようになっています
(一般ユーザができたら、保護でもなんでも無くなります)。
ただし、一度確定したプログラムを一般ユーザがroot権限で
実行することは可能で、後述します。
結果的に、I/O アクセスを Linux のプログラムで行うには
なお、メモリに関しては事実上直接は操作できません(/dev/mem で ある程度可能)。メモリ関係のハードをアクセスするには、 メーカがデバイスドライバを供給してくれることを待つか、 自作する必要があります。
具体例を示します。
% ls -l aaa -rwxrwxr-x 1 kumagai users 3976 Sep 18 16:24 aaa % su Password: # chown root:root aaa # ls -l aaa -rwxrwxr-x 1 root root 3976 Sep 18 16:24 aaa # chmod +s aaa # ls -l aaa -rwsrwsr-x 1 root root 3976 Sep 18 16:24 aaaまず、root で chown することでファイルの所有者をrootにします。 次に chmod +s で実行時set userID 属性をつけます。ファイルの 属性の 'x' だったところが 's' になります。
この操作で、いちいち root にならなくとも、I/O 操作のプログラム などを実行できるようになります。この set userID はよくみると /usr/bin などの多くのプログラムで使用されています。しかし、 だれでも root 権限で実行できるようになるということは、ある意味 危険なので、十分注意する必要があるでしょう。
Pentium 以降のCPUの場合には、もう1つ、CPU の内部カウンタを利用する、 という方法があります。実際のところは、Linux が gettimeofday の 時間を得るのにも、この内部カウンタをつかっています。
この内部カウンタを使用する利点は2つあります。第1に1クロックで 済むような命令を使うため、gettimeofday に比べ処理に時間が かからないこと。第2にgettimeofdayは秒以上の桁と秒未満の桁が 別の変数(をまとめた構造体)として帰ってくるため、両方まとめる 処理が必要であるのに対し、64bitの一つの数値として 扱うことができる、というものです。逆に欠点として、CPUの動作 クロックに直接依存するため、変換および変換定数の調整が 必要なことです。
この内部カウンタはCPUのクロックそのものをカウントしています。 400MHzのCPUでは1秒に約400*10^6だけカウントします。 カウンタの大きさは64bitです。こういうものを使うときには いつあふれるか?ということが重要な問題になりますが、試算すると 400MHz の場合で1000年以上もちます。つまり、常識の範囲では あふれません。
このカウンタを読み出す関数はライブラリには存在しないので、 インラインアセンブルで直接Pentiumの命令を仕込みます。 最近のアセンブラを使用すると rdtsc と書くだけですむのですが、 ここでは互換性を考えて、コードを直接埋めます。
inline unsigned long long int RDTSC(void) { unsigned int h,l; /* read Pentium cycle counter */ __asm__(".byte 0x0f,0x31" :"=a" (l), "=d" (h)); return ((unsigned long long int)h<<32)|l; }ここで unsigned long long int は64bitの符合無し整数の型です。 __asm__ の部分でクロックの読み出しを行い、32bit づつ C言語側に 持ってきて、結合します。
変換係数はたとえば、こんなプログラムで 求めることができます。
NAME getpriority, setpriority - get/set program scheduling priority SYNOPSIS #include使い方は setpriority(PRIO_PROCESS,0,prio); と、対象に PRIO_PROCESS を指定の上、優先度を設定します。prio は nice の 数値と同様 0 で標準、20で最低、 -20で最優先になります。 負の数値は root の権限がなければ設定できませんので、 前述のように実行時 set user ID 属性を つけておくとよいでしょう。#include int getpriority(int which, int who); int setpriority(int which, int who, int prio);
具体的には
#includeという部分を追加すれば、さらに優先順位があがります。 sp.sched_priority には 1-99(最大) の優先順位を指定できます。 SCHED_FIFO のかわりに SCHED_RR を使用してもほとんど同じです (同じsched_priority のプロセスがあった場合の動作が若干異なります)。 通常は SCHED_OTHER になっています。: struct sched_param sp; sp.sched_priority=99; sched_setscheduler(0,SCHED_FIFO,&sp);
プロセスの優先度を SCHED_FIFO, SCHED_RR で指定した場合、ふつうの 特に指定していないプロセス(SCHED_OTHER)すべてより優先度が高くなります。 そのため、実験例で示したような周期安定性も、 よくなり、周期をはずすことも、1万回に1〜2回程度とさらに少なくなります。 依然としてはずすことがあるのは、プロセスよりも優先される、 ハードウェアの割り込み処理などの影響と考えられます。 処理内容によっては setpriority では効果が 足りない場合があり、その場合には、この方法が役立つことでしょう。
ただし、重要な注意点がひとつあります。それは、ふつうのプロセス すべてより優先度が高くなるということは、usleep などでOSに CPUを明示的に返さずに無限ループした場合(おそらく意図的には やらないとおもいますが)、kill を実行することはおろか、 kterm 上でControl-Cで止めようにも、X やシェルすら動作しない、という 状態が発生します。OSとしては動いているので、致命的な障害は 発生しないと思われるものの、シャットダウンしなければ戻りません。 十分注意する必要があるでしょう。
補足:
Linuxによる制御の実践のところで述べたような
周期変更を行う場合、注意が必要です。というのは、SCHED_FIFO などの
プロセスがふつうのプロセスより優先される、という機構は
優先順位に下駄を履かせることによって実現されているのですが、
この下駄がたりなくなるためです。もし、HZを1000以上にした上で
sched_setscheduler を使用する場合には、/usr/src/linux/kernel/sched.c
の goodness という関数の最初のほうの
if (p->policy != SCHED_OTHER) { weight = 1000 + p->rt_priority; goto out; }の1000を10000などに上げる必要があります。
サンプルプログラム: