/****************** * Linux カーネルの時間管理 *********************/ (1)カーネルにおける時間の概念 時間管理は Linux カーネルにおいて非常に重要な役割を果たします。 イベント駆動型と比較すると、カーネルには時間駆動型の関数が多数あります。 10 ミリ秒ごとに画面を更新するなど、一部の機能は定期的に実行されます。 カーネルが 500 ミリ秒後にタスクを実行するなど、一部の関数は一定時間後に実行されます。 区別するには:
定期的なイベントはシステムタイマーによって駆動されます (2)HZ値 カーネルは、時間を計算および管理するためにハードウェア タイマーの助けを借りる必要があります。 タイマーが割り込みを生成する頻度は、ティック レートと呼ばれます。 カーネルには変数 HZ が指定されており、カーネルの初期化時にこの値に基づいてタイマーのビート レートが決定されます。 HZ は <asm/param.h> で定義されています。i386 プラットフォームでは、現在使用されている HZ 値は 1000 です。 つまり、クロック割り込みは 1 ミリ秒の周期で 1 秒あたり 1000 回発生します。今すぐ: 知らせ! HZ は固定値ではなく、変更可能であり、カーネル ソース コードを構成するときに入力できます。 アーキテクチャによって HZ 値は異なります。たとえば、arm では 100 が使用されます。 ドライバーでシステム割り込み周波数を使用する場合は、100または1000の代わりにHZを直接使用します。 a. 理想的なHZ値 i386 の HZ 値は、バージョン 2.5 以降で 1000 に変更されるまで、常に 100 でした。 ティック レートを上げると、クロック割り込みがより頻繁に生成され、割り込みハンドラがより頻繁に実行されます。 利点は次のとおりです。
(短縮されたスケジューリング遅延。プロセスに 2 ミリ秒のタイムスライスが残っている場合、10 ミリ秒のスケジューリング サイクルでは、プロセスは 8 ミリ秒長く実行されます。 欠点は次のとおりです。 ※ビートレートが高くなるほど、システムへの負担が大きくなります。 割り込みハンドラはより多くのプロセッサ時間を消費します。 (3)ジフィー グローバル変数 jiffies は、システムの起動後に生成されたビートの合計数を記録するために使用されます。 起動時に、jiffies は 0 に初期化され、その後はクロック割り込みハンドラによって毎回増加されます。 このように、システム起動後の実行時間はjiffies/HZ秒です。 Jiffies は <linux/jiffies.h> で定義されています: jiffies 変数は常に unsigned long 型です。 したがって、32 ビット アーキテクチャでは 32 ビット、64 ビット アーキテクチャでは 64 ビットになります。 32 ビット jiffy の場合、HZ が 1000 であれば、49.7 日後にオーバーフローします。オーバーフローはまれですが、タイムアウトを検出すると、ラップによってプログラムがエラーを引き起こす可能性があります。 Linux には、ビートカウントを比較するための 4 つのマクロが用意されており、ビートカウントのラップアラウンドを正しく処理できます。 #include <linux/jiffies.h> #define time_after(unknown, known) // 不明 > 既知 #define time_before(unknown, known) // 不明 < 既知 #define time_after_eq(unknown, known) // 不明 >= 既知 #define time_before_eq(unknown, known) // 不明 <= 既知 不明は通常 jiffies を指し、既知は比較される値です (通常は jiffies を加算または減算して計算される相対値)。例: unsigned long timeout = jiffies + HZ/2; /* 0.5秒後にタイムアウト */ ... if (time_before(jiffies, タイムアウト)) { /* タイムアウトなし、良好 */ }それ以外{ /* タイムアウト、エラーが発生しました */ time_beforeはタイムアウト前に完了したと解釈できます(before) *システムでは、64 ビット値 jiffies_64 も宣言されています。64 ビット システムでは、jiffies_64 と jiffies は同じ値です。 この値は get_jiffies_64() を通じて取得できます。 *使用 u64j2; j2 = get_jiffies_64(); (4)現在の時刻を取得する 通常、ドライバーは実際の時間 (つまり、年、月、日の時刻) を知る必要はありません。しかし、ドライバーは絶対時間を処理する必要があるかもしれません。 構造体timeval{ time_t tv_sec; /* 秒 */ suseconds_t tv_usec; /* マイクロ秒 */ }; // 古いですが、人気があります。秒とミリ秒を使用して、1970年1月1日0:00からの秒数を格納します。struct timespec { time_t tv_sec; /* 秒 */ long tv_nsec; /* ナノ秒 */ }; // 新しいバージョンでは、時間を節約するために秒とナノ秒を使用します。 do_gettimeofday() この関数は、構造体 timeval を指すポインタ変数に通常の秒またはマイクロ秒を入力します。プロトタイプは次のとおりです。 #include <linux/time.h> void do_gettimeofday(構造体timeval *tv); current_kernel_time() この関数はtimespecを取得するために使用できます #include <linux/time.h> 構造体timespeccurrent_kernel_time(void); /******************** *決められた時間の遅延実行********************/ デバイス ドライバーは、通常、ハードウェアが何らかのタスクを完了できるようにするために、特定のコードの実行を一定期間遅らせる必要があることがよくあります。 タイマー期間 (クロック ティックとも呼ばれる) よりも長い遅延はシステム クロックを使用して実現できますが、非常に短い遅延はソフトウェア ループを使用して実現できます。 (1)短い遅延 最大数十ミリ秒の遅延の場合、システム タイマーを使用する方法はありません。 システムは、ソフトウェア ループを通じて次の遅延機能を提供します。 #include <linux/delay.h> /* 実際には <asm/delay.h> にあります */ void ndelay(unsigned long nsecs); /*遅延ナノ秒*/ void udelay(unsigned long usecs); /*マイクロ秒単位の遅延*/ void mdelay(unsigned long msecs); /*ミリ秒単位の遅延*/ これら 3 つの遅延機能はすべてビジー待機機能であり、遅延プロセス中は他のタスクを実行できません。 実際、ナノ秒の精度は現時点ではすべてのプラットフォームで実現できるわけではありません。 (2)長時間の遅延 a. 遅延が切れる前にプロセッサを放棄する while(time_before(jiffies, j1)) スケジュール(); 待機期間中にプロセッサを解放することはできますが、システムはアイドル モードに入ることができません (このプロセスは常にスケジュールされているため)。これは、省電力にはつながりません。 b. タイムアウト機能 #include <linux/sched.h> 符号付き long schedule_timeout(符号付き long タイムアウト); 方向: 現在の状態を設定します(TASK_INTERRUPTIBLE); schedule_timeout(2*HZ); /* 2秒間スリープする*/ プロセスは 2 秒後に起動されます。ユーザー空間によって中断されたくない場合は、プロセス状態を TASK_UNINTERRUPTIBLE に設定できます。 眠い sleep // 秒 (3)待機列 待機キューを使用すると、長い遅延を実現することもできます。 遅延中、現在のプロセスは待機キューでスリープします。 プロセスがスリープ状態の場合、待機しているイベントに基づいて待機キューにリンクする必要があります。 a. 待機キューを宣言する 待機キューは実際にはプロセスにリンクされたリストであり、特定のイベントを待機しているすべてのプロセスが含まれます。 #include <linux/wait.h> 構造体__wait_queue_head { spinlock_t ロック; 構造体 list_head タスクリスト; }; typedef 構造体 __wait_queue_head wait_queue_head_t; 待機キューにプロセスを追加するには、ドライバーはまずモジュール内で待機キュー ヘッドを宣言し、それを初期化する必要があります。 静的初期化 DECLARE_WAIT_QUEUE_HEAD(名前); 動的初期化 キューの先頭を wait_queue_head_t します。 init_waitqueue_head(&my_queue); b. 待機機能 次の関数を呼び出すことで、プロセスは待機キューで一定時間スリープすることができます。 #include <linux/wait.h> long wait_event_timeout(wait_queue_head_t q、条件、長いタイムアウト); long wait_event_interruptible_timeout(wait_queue_head_t q、条件、長いタイムアウト); これら 2 つの関数を呼び出した後、プロセスは指定された待機キュー q でスリープしますが、タイムアウトが経過すると戻ります。 タイムアウトが経過した場合は 0 を返し、プロセスが他のイベントによって起動された場合は残り時間を返します。 待機条件がない場合は、条件を0に設定します 方向: wait_queue_head_t 待機; init_waitqueue_head(&wait); wait_event_interruptible_timeout(待機、0、2*HZ); /*現在のプロセスは待機キューで2秒間スリープします */ (4) カーネルタイマー タスクの実行を遅らせる別の方法は、カーネル タイマーを使用することです。以前の遅延方法とは異なり、カーネル タイマーは現在のプロセスをブロックしません。カーネル タイマーを開始すると、タスクが将来のある時点で実行されることが宣言されるだけで、現在のプロセスは引き続き実行されます。リアルタイムタスクにはタイマーを使用しない タイマーは、<linux/timer.h>で定義されている構造体timer_listで表されます。 構造体タイマーリスト{ struct list_head entry; /* タイマーリンクリスト */ unsigned long expires; /* 時間値(jiffies 単位)*/ spinlock_t ロック; void(*function)(unsigned long); /* タイマー処理関数*/ unsigned long data; /* タイマー処理関数に渡されるパラメータ*/ } カーネルは、<linux/timer.h> でタイマーを管理するための一連のインターフェースを提供します。 a. タイマーを作成する
b. タイマーを初期化する init_timer(&my_timer); /* データ構造を埋める */ my_timer.expires = jiffies + delay; my_timer.data = 0; my_timer.function = my_function; /*タイマーが切れたときに呼び出される関数*/ c. タイマー実行機能 タイムアウト処理関数のプロトタイプは次のとおりです。 void my_timer_function(符号なしロングデータ); データ パラメータを使用すると、1 つの処理関数で複数のタイマーを処理できます。データを0に設定できます d. タイマーを起動する
タイマーは起動するとすぐに実行を開始します。 e. アクティブ化されたタイマーのタイムアウト期間を変更する mod_timer(&my_timer, jiffies+ney_delay); 初期化されているがまだアクティブ化されていないタイマーに使用できます。呼び出し時にタイマーがアクティブ化されていない場合は 0 を返し、それ以外の場合は 1 を返します。 mod_timer が返されると、タイマーがアクティブになります。 f. タイマーを削除する
アクティブまたは非アクティブのタイマーを使用できます。呼び出されたときにタイマーが非アクティブの場合は 0 を返し、それ以外の場合は 1 を返します。タイムアウトしたタイマーを呼び出す必要はありません。自動的に削除されます。 g. 同期削除
smp システムでは、すべてのタイマー ハンドラーが戻るときに終了することを確認します。割り込みコンテキストでは使用できません。 /******************** *不確定な時間の遅延実行********************/ (1)不確定遅延とは何ですか? 前のセクションでは、一定時間の遅延実行について紹介しましたが、この状況はドライバーの作成プロセスでよく発生します。ユーザー空間プログラムは読み取り関数を呼び出してデバイスからデータを読み取りますが、現在デバイスにはデータが生成されていません。この時点で、ドライバーの読み取り機能のデフォルトの操作は、スリープ モードに入り、デバイスにデータが存在するまで待機することです。 この種の待機は無期限の遅延であり、通常はスリープ メカニズムを使用して実現されます。 (2)睡眠 スリープは待機キューに基づいています。これまでに wait_event シリーズの関数を紹介しましたが、今回は固定のスリープ時間は設定されません。 プロセスがスリープ状態になると、特別な状態としてマークされ、スケジューラの実行キューから削除されます。 デバイスがデータを受信するなどの特定のイベントが発生するまで、プロセスは実行状態にリセットされ、スケジュールのために実行キューに入ります。 スリープ関数のヘッダー ファイルは <linux/wait.h> で、具体的な実装関数は kernel/wait.c にあります。 a. 休眠のルール
b. 待機キューの初期化 前の記事を見る c. スリープ機能 Linux で最も単純なスリープ モードは wait_event マクロです。このマクロは、スリープを実装している間にプロセスが待機している条件をチェックします。 1. void wait_event() 待機キューヘッド q、 int 条件); 2. int wait_event_interruptible( 待機キューヘッド q、 int 条件);
d. ウェイクアップ機能 プロセスがスリープ状態になると、他の実行スレッド (別のプロセスまたは割り込み処理ルーチン) によってプロセスを起動する必要があります。ウェイクアップ機能: #include <linux/wait.h> 1. void ウェイクアップ( wait_queue_head_t *キュー); 2. void wake_up_interruptible( wait_queue_head_t *キュー); wake_up は、指定されたキューで待機しているすべてのプロセスを起動します。また、wake_up_interruptible は、割り込み可能なスリープを実行しているプロセスを起動します。実際には、wait_event を使用する場合は wake_up を使用し、wait_event_interruptible を使用する場合は wake_up_interruptible を使用するのが慣例です。 要約する 以上がこの記事の全内容です。この記事の内容が皆様の勉強や仕事に何らかの参考学習価値をもたらすことを願います。123WORDPRESS.COM をご愛顧いただき、誠にありがとうございます。これについてもっと知りたい場合は、次のリンクをご覧ください。 以下もご興味があるかもしれません:
|
<<: Centos7でのMySQLインストールチュートリアル
>>: JavaScript を使用してページに動的な検証コードを実装する例
新しいテーブルを作成する テーブル「人」を作成します( `id` int NOT NULL COMM...
DockerにNginxをインストールするNginx は、IMAP/POP3/SMTP サービスも提...
なぜ Nexus プライベート サーバーを構築する必要があるのでしょうか。その理由は非常に簡単です。...
この記事の例では、検証コードを実装するためのjsの具体的なコードを参考までに共有しています。具体的な...
yum install httpd php mariadb-server –yランプの動作環境を設定...
目次序文Websocketの使用Websocketオブジェクトの構築Websocket ステータスW...
ソフトウェア バージョンとプラットフォーム: MySQL-5.7.17-winx64、win7 Ho...
目次背景探検する要約する背景テーブルでは、dataTime フィールドは varchar 型に設定さ...
CSS を使用する場合は、DOCTYPE (ドキュメント タイプ定義) を記述することを忘れないでく...
opencv2 の簡単なインストール: conda インストール --channel https:/...
1. はじめに外部キー制約を使用するかどうかという話題は、すでに決まり文句になっています。学校では、...
多くの場合、Web デザインが完成した後でデザイナーの無知が露呈し、批判されることがあります。彼らは...
この記事では、VMware Workstation14 ProにUbuntu 16.04をインストー...
説明するこのインターフェースを呼び出すときは、次の点に注意する必要があります。パブリック IP アド...
序文以前のプロジェクトでは、SQL の CASE WHEN ソート関数が使用されました。ではブログメ...