時間関連処理
1)、時間経過の計測
一般には「jiffies」の使用をお勧めします。
①、「jiffies」カウンタを使う
キャッシュしたjiffiesと現在の値の比較は、
int time_after(unsigned long a, unsigned long b);
int time_before(unsigned long a, unsigned long b);
int time_after_eq(unsigned long a, unsigned long b);
int time_before_eq(unsigned long a, unsigned long b);
jiffiesとstruct timeval / struct timespecの変換は、
unsigned long timespec_to_jiffies(struct timespec *value);
void jiffies_to_timespec(unsigned long jiffies, struct timespec *value);
unsigned long timeval_to_jiffies(struct timeval *value);
void jiffies_to_timeval(unsigned long jiffies, struct timeval *value);
64ビットのjiffiesカウンタにアクセスするには、
u64 get_jiffies_64(void);
2)、現在時刻を知る方法
方法一、「jiffies」を調べて現在時刻を表したものを得ることができます。
通常、この値は最後に起動されてからの経過時間ですが、ドライバにとっては十分です。
方法二、「struct timespec current_kernel_time(void)」を使って、
現在時刻を(jiffy粒度、即ち、クロックティック粒度)変数xtimeから得ることもできます。
実時刻を「jiffies」に変換するには、
unsigned long mktime (unsigned int year,
unsigned int mon,
unsigned int day,
unsigned int hour,
unsigned int min,
unsigned int sec);
3)、実行を遅らせる
考える必要な重要なポイントの1つは、どうやってクロックティックから必要な遅延を作り出すかという点です。
・1クロックティックより十分に長い遅延であり、粗い粒度でも困らない場合は、システムクロックを使えます。
・非常に短い遅延には、一般にソフトウェアループを実装します。
①長い遅延 (1クロックティック以上)
・ビジーな待ち
それほどの精度が求められず、クロックティックの整数倍の時間だけ遅延する場合、
最も簡単な実装はjiffyカウンタを監視するループになります。
実装例:
while (time_before(jiffies, j1)) {
cpu_relax();
}
この類の実装は可能であれば絶対に避けるべきです。
・プロセッサの譲渡
必要ないときにCPUを明示的に開放します。
実装例:
while (time_before(jiffies, j1)) {
schedule();
}
「schedule」でプロセスがプロセッサを解放すると、プロセスがプロセッサをいつでもすぐに取り戻せる保証がありません。
ドライバのニーズに対する安全な解決策ではありません。
・タイムアウト
別のイベントを待つためにドライバが待ち列を使っており、一定の時間内に確実にそれを実行したいなら、
long wait_event_timeout(wait_queue_head_t q, condition, long timeout);
long wait_event_interruptible_timeout(wait_queue_head_t q,
condition,
long timeout); // 「/proc/jitqueue」
特定のイベントを待たずに遅延するなら、
signed long schedule_timeout(signed long timeout);
②、短い遅延
数十マイクロ秒見たいな遅延は
void msleep(unsigned int millisecs);
unsigned long msleep_interruptible(unsigned int millisecs);
void ssleep(unsigned int seconds)
4)、カーネルタイマ
後で起こるアクションをスケジュールする必要があり、その時が来るまでカレントプロセスをブロックしたくないなら、カーネルタイマを使います。
(アクションを起こすまでの間、現在の処理を続行したい)
カーネルタイマは、ユーザが指定した時間に、ユーザが指定した引数を使って、ユーザ定義の関数をカーネルに実行させるデータ構造体です。
カーネルタイマは「ソフトウェア割り込み」の結果です。
その1、タイマ関数はアトミックでなければなりません ( 並行&競争の考えが必要 )。
その2、プロセスコンテキストがないことによって下記制限があります。
・ユーザ空間へのアクセスは許されません。
・アトミックモードでは、Currentポインタは意味を持ちませんし、使うこともできません。
・スリープ、スケジュール、セマフォも使えません。 ( 例: kmallocはスリープの可能性があるので、使えません )
SMPシステムでは、タイマを登録したものと、タイマ自体は、必ず同じCPU上で実行されます。
①、タイマのAPI
struct timer_list
{
/* ... */
unsigned long expires; // タイマハンドラを実行したい時点のjiffies値
void (*function)(unsigned long);
unsigned long data;
};
void init_timer(struct timer_list *timer);
struct timer_list TIMER_INITIALIZER(_function, _expires, _data);
void add_timer(struct timer_list * timer);
int del_timer(struct timer_list * timer);
// タイマの満了時間を変更します
int mod_timer(struct timer_list *timer, unsigned long expires);
//SMP版の「del_timer」、ロックを保持している間は、同じロックを獲得しないように要注意。
int del_timer_sync(struct timer_list *timer);
//タイマがスケジュールされているか否かを論理値で返します。
int timer_pending(const struct timer_list * timer);
②、カーネルタイマの実装
略。
5)、タスクレット
タスクレットはたいていは割り込み管理のために使われます。
カーネルタイマとは異なり、特定の時間に関数を実行させることはできませんが、スケジュールすることによって、
後で、カーネルが選択した時間に実行するようにできるだけです。
カーネルタイマと同じように、「ソフトウェア割り込み」のコンテキストの中で(アトミックモードで)実行されます。
タスクレットはデータ構造体として存在します。
struct tasklet_struct {
/* ... */
void (*func)(unsigned long);
unsigned long data;
};
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);
DECLARE_TASKLET(name, func, data);
DECLARE_TASKLET_DISABLED(name, func, data);
タスクレットの特徴:
・タスクレットをいったん無効にして、後で再び有効にすることができます。
・タイマとまったく同じように、タスクレットも自分自身を再登録できます。
・標準の優先度か 高い優先度で実行するようにスケジュールできます。
・システムに非常に負荷がかかった状態でなければ、タスクレットは直ちに実行されます。
・同じタスクレットは複数のプロセッサ上で、同時には実行されません。
関連関数:
void tasklet_disable(struct tasklet_struct *t);
void tasklet_disable_nosync(struct tasklet_struct *t);
void tasklet_enable(struct tasklet_struct *t);
void tasklet_schedule(struct tasklet_struct *t);
// 高い優先度でタスクレットの実行をスケジュールします。
void tasklet_hi_schedule(struct tasklet_struct *t);
void tasklet_kill(struct tasklet_struct *t);
6)、作業待ち列
目ためはタスクレットに似ていますが、大きな違いがいくつかあります。
・作業待ち列の関数は特殊なカーネルプロセスのコンテキスト内で実行されますから、アトミックである必要がありません。
・カーネルコードは作業待ち列関数の実行を、指定した時間遅延をさせることができます。
・作業待ち列はスリープできます。
・タスクレットが短い期間に素早く、アトミックモードで実行されるのに対して、
作業待ち列の関数は待ち時間がばらつきますが、アトミックでなくでもよいです。
関連関数:
struct workqueue_struct *create_workqueue(const char *name);
struct workqueue_struct *create_singlethread_workqueue(const char *name);
DECLARE_WORK(name, void (*function)(void *), void *data); //タスクを作業待ち列に登録
int queue_work(struct workqueue_struct *queue, struct work_struct *work);
int queue_delayed_work(struct workqueue_struct *queue, struct work_struct *work, unsigned long delay);
INIT_WORK(struct work_struct *work,
void (*function)(void *),
void *data); //実行時にwork_struct構造体を設定、完全初期化
PREPARE_WORK(struct work_struct *work,
void (*function)(void *),
void *data); //作業待ち列に登録した構造体を変更
int cancel_delayed_work(struct work_struct *work); //作業待ち列エントリをキャンセル
void flush_workqueue(struct workqueue_struct *queue); //ワーク関数が実行されていないことを保証するには、この関数
void destroy_workqueue(struct workqueue_struct *queue);
共有待ち列は、カーネルが提供する共有の作業待ち列であり、ドライバ自身の作業待ち列ではありません。
・待ち列を誰かと共有することに注意を払う
・待ち列を長時間独占してはいけない
・プロセッサの中でタスクの順番が来るのにも時間がかかります
int schedule_work(struct work_struct *work);
int schedule_delayed_work(struct work_struct *work, unsigned long delay);
void flush_scheduled_work(void);
int cancel_delayed_work(struct work_struct *work);