リファレンスガイド
RHEL for Real Time を使用するための中核となる概念および用語
Marie Doleželová
Maxim Svistunov
Radek Bíba
David Ryan
Cheryn Tan
Lana Brindley
Alison Young
概要
前書き
パート I. ハードウェア
第1章 プロセッサーコア
1.1. キャッシュ
1.2. 相互接続
第2章 メモリーの割り当て
2.1. 需要ページング
図2.1 Red Hat Enterprise Linux for Real Time 仮想メモリーシステム
/proc/vmstat
ファイルの pgfault
値を検索することで監視できます。
/proc
ディレクトリー内のプロセス情報を確認することです。特定のプロセス PID については、cat
コマンドを使用して /proc/PID/stat
ファイルを表示します。このファイル内の関連するエントリーは以下のとおりです。
Field 2
: 実行ファイルのファイル名Field 10
: マイナーページフォールトの数Field 12
: メジャーページフォールトの数
例2.1 /proc/PID/stat
ファイルを使用したページ障害の確認
/proc/PID/stat
ファイルを使用して、実行中のプロセスでページ障害の有無を確認します。
cat
コマンドとパイプ関数を使用して、/proc/PID/stat
ファイルの 2 行目、10 行目、および 12 行のみを返します。
~]# cat /proc/3366/stat | cut -d\ -f2,10,12
(bash) 5389 0
bash
で、マイナーページフォールトが 5389 件報告され、主要なページフォールトはありません。
注記
- 『Linux System Programming』 by Robert Love
2.2. ページ I/O を避けるために mlock
を使用
mlock
および mlockall
システムコールは、指定したメモリー範囲にロックし、そのメモリーをページングできないように指示します。つまり、物理ページがページテーブルエントリーに割り当てられると、そのページへの参照は常に高速になります。
mlock
システムコールには、2 つのグループがあります。mlock
および munlock
は、特定のアドレス範囲のロックおよびロック解除を行います。mlockall
および munlockall
は、プログラム領域全体をロックまたはアンロックします。
mlock
慎重に検討し、注意して使用してください。アプリケーションが大きい場合や、大規模なデータドメインがある場合は、システムが他のタスクにメモリーを割り当てできない場合に mlock
呼び出しがスラッシュする可能性があります。
注記
mlock
は常に注意して使用してください。これを過剰に使用すると、メモリー不足 (OOM) エラーが生じる可能性があります。アプリケーションの先頭には mlockall
呼び出しを付けないでください。アプリケーションのリアルタイム部分のデータとテキストのみがロックされることが推奨されます。
mlock
は、このプログラムにページ I/O がないことを保証しません。これは、データがメモリー内に留まるが、同じページに留まることを確認するのに使用されます。move_pages
やメモリー圧縮関数は、mlock
を使用してもデータを移動できます。
重要
mlockall
または mlock
を使用できるようにするために、CAP_IPC_LOCK
機能が必要です。詳細は capabilities(7) man ページを参照してください。
mlock
または mlockall
への呼び出し 2 回ロックされた同じページを共有する場合、一致するページの munlock
への単一の呼び出し、または munlockall
によってアンロックされます。そのため、この二重ロック/シングルロックの問題を防ぐために、アプリケーションがロック解除しているページを認識する必要があります。
- 割り当て済みおよびロックされたメモリー領域を追跡し、ページをロックする前にラッパー機能を作成すると、そのページにあるユーザー数 (割り当て) を確認します。これは、デバイスドライバーで使用されるリソースカウントの原則です。
- 同じページで二重ロックを防ぐために、ページサイズとアライメントを考慮して割り当てを実行します。
mlock
の最適な利用方法は、アプリケーションのニーズとシステムリソースによって異なります。すべてのアプリケーションには単一のソリューションはありませんが、以下のコード例は、メモリーバッファーを割り当て、ロックする関数の実装のスタートポイントとして使用できます。
例2.2 アプリケーションでの mlock
の使用
#include <stdlib.h> #include <unistd.h> #include <sys/mman.h> void * alloc_workbuf(size_t size) { void *ptr; int retval; /* * alloc memory aligned to a page, to prevent two mlock() in the * same page. */ retval = posix_memalign(&ptr, (size_t) sysconf(_SC_PAGESIZE), size); /* return NULL on failure */ if (retval) return NULL; /* lock this buffer into RAM */ if (mlock(ptr, size)) { free(ptr); return NULL; } return ptr; } void free_workbuf(void *ptr, size_t size) { /* unlock the address range */ munlock(ptr, size); /* free the memory */ free(ptr); }
alloc_workbuf
はメモリーバッファーを動的に割り当ててロックします。メモリーの割り当ては、メモリー領域をページに合わせるために posix_memalig
によって行われます。size
変数が小さい場合は、ページサイズよりも小さいと、通常の malloc
割り当てで残りのページを使用できます。ただし、この手法を安全に使用するために、通常の malloc
割り当てでは mlock
呼び出しを行うことができません。これにより、二重ロック/シングルアンロックの問題が回避されます。この関数 free_workbuf
は、メモリー領域のロックを解除し、解放します。
mlock
および mlockall
の使用方法に加えて、MAP_LOCKED
フラグで mmap
を使用してメモリー領域の割り当て、ロックすることもできます。以下の例は、mmap
を使用した前述のコードの実装です。
例2.3 アプリケーションでの mmap
の使用
#include <sys/mman.h> #include <stdlib.h> void * alloc_workbuf(size_t size) { void *ptr; ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_LOCKED, -1, 0); if (ptr == MAP_FAILED) return NULL; return ptr; } void free_workbuf(void *ptr, size_t size) { munmap(ptr, size); }
mmap
をページベースで割り当てると、同じページにロックが 2 つ存在せず、二重ロック/シングルアンロックの問題を防ぐのに役立ちます。一方、size
変数がページサイズの倍数ではない場合は、残りのページが無駄になります。さらに、mmap
によってロックされたメモリーの munlockall
ロックを解除するための呼び出しが必要になります。
mlockall
を呼び出し、その後に時間機密領域の最後に munlockall
を呼び出します。これにより、重要なセクションにおいてページングを減らすことができます。同様に、mlock
は比較的静的または、ページ I/O なしでアクセスが必要な徐々に増大するデータリージョンで使用できます。
注記
- capabilities(7)
- mlock(2)
- mlock(3)
- mlockall(2)
- mmap(2)
- move_pages(2)
- posix_memalign(3)
- posix_memalign(3p)
第3章 ハードウェア割り込み
例3.1 システムにおける割り込みの表示
cat
コマンドで /proc/interrupts
を表示します。
~]$ cat /proc/interrupts
CPU0 CPU1
0: 13072311 0 IO-APIC-edge timer
1: 18351 0 IO-APIC-edge i8042
8: 190 0 IO-APIC-edge rtc0
9: 118508 5415 IO-APIC-fasteoi acpi
12: 747529 86120 IO-APIC-edge i8042
14: 1163648 0 IO-APIC-edge ata_piix
15: 0 0 IO-APIC-edge ata_piix
16: 12681226 126932 IO-APIC-fasteoi ahci, uhci_hcd:usb2, radeon, yenta, eth0
17: 3717841 0 IO-APIC-fasteoi uhci_hcd:usb3, HDA, iwl3945
18: 0 0 IO-APIC-fasteoi uhci_hcd:usb4
19: 577 68 IO-APIC-fasteoi ehci_hcd:usb1, uhci_hcd:usb5
NMI: 0 0 Non-maskable interrupts
LOC: 3755270 9388684 Local timer interrupts
RES: 1184857 2497600 Rescheduling interrupts
CAL: 12471 2914 function call interrupts
TLB: 14555 15567 TLB shootdowns
TRM: 0 0 Thermal event interrupts
SPU: 0 0 Spurious interrupts
ERR: 0
MIS: 0
3.1. レベルシグナル割り込み
3.2. メッセージシグナル割り込み
pci=nomsi
を使用して Linux カーネルを起動することもできます。
3.3. マスク不可割り込み
3.4. システム管理割り込み
注記
rt-tests
パッケージで利用可能な hwlatdetect
ユーティリティーを使用します。このユーティリティーは、CPU が SMI 処理ルーチンによって引き起こされた期間を測定するように設計されています。
3.5. 高度なプログラミング可能割り込みコントローラー
パート II. アプリケーションのアーキテクチャー
第4章 スレッドおよびプロセス
- プロセス
- UNIX® スタイルのプロセスとは、以下を含むオペレーティングシステムのコンストラクトです。
- 仮想メモリーのアドレスマッピング
- 実行コンテキスト (PC、スタック、レジスター)
- 状態/アカウント情報
Linux プロセスは、このスタイルのプロセスとして開始しました。あるアドレス空間内で実行中の 1 つ以上のプロセスの概念が開発されると、Linux は別のプロセスでアドレス空間を共有するプロセス構造に移行ていました。これは、プロセスデータ構造が小さい限り機能します。本書の残りの部分では、プロセス という用語は、複数のスレッドが含まれる可能性がある独立したアドレス空間を指します。 - スレッド
- 厳密には、スレッドは以下が含まれるスケジュール可能なエンティティーです。
- プログラムカウンター (PC)
- レジスターコンテキスト
- スタックポインター
プロセス内に複数のスレッドが存在する可能性があります。
fork
およびexec
関数を使用した新規プロセスの作成- Posix Threads (pthreads) API を使用して、実行中のプロセス内に新しいスレッドを作成します。
注記
注記
- fork(2)
- exec(2)
- 『Programming with POSIX Threads』, David R. Butenhof, Addison-Wesley, ISBN 0-201-63392-2
- 『Advanced Programming in the UNIX Environment』, 2nd Ed., W. Richard Stevens and Stephen A. Rago, Addison-Wesley, ISBN 0-201-43307-9
- 「POSIX Threads Programming」, Blaise Barney, Lawrence Livermore National Laboratory, http://www.llnl.gov/computing/tutorials/pthreads/
第5章 優先順位およびポリシー
SCHED_OTHER
またはSCHED_NORMAL
: デフォルトポリシーSCHED_BATCH
:SCHED_OTHER
と同様ですが、スループットが維持されます。SCHED_IDLE
:SCHED_OTHER
より低い優先度SCHED_FIFO
: 最初にリアルタイムポリシーを出力SCHED_RR
: ラウンドロビンのリアルタイムポリシー
SCHED_OTHER
SCHED_FIFO
および SCHED_RR
です。
SCHED_OTHER
または SCHED_NORMAL
は、Linux スレッドのデフォルトスケジューリングポリシーです。スレッドの特性に基づいてシステムによって変更される動的な優先度があります。SCHED_OTHER
スレッドの優先度に悪影響を与えるもう 1 つのものは、nice 値です。nice 値は、-20 (最も高い優先度) と 19 (最も低い優先度) の数値です。デフォルトでは、SCHED_OTHER
スレッドの適切な値は 0 になります。nice 値を調整すると、スレッドの処理方法が変わります。
SCHED_FIFO
ポリシーのあるスレッドは、SCHED_OTHER
タスクよりも先に実行されます。適切な値を SCHED_FIFO
使用する代わりに、1 (最低) と 99 (最大) の優先度を使用します。優先度が 1 の SCHED_FIFO
スレッドは常に SCHED_OTHER
スレッドよりも先にスケジュールされます。
SCHED_RR
ポリシーは、SCHED_FIFO
ポリシーと非常に似ています。SCHED_RR
ポリシーでは、優先度が等しいスレッドは ラウンドロビン方式 でスケジュールされます。通常、SCHED_FIFO
は、SCHED_RR
よりも優先されます。
SCHED_FIFO
および SCHED_RR
スレッドは以下のイベントのいずれかが発生するまで実行されます。
- スレッドはスリープ状態になるか、またはイベントの待機を開始します。
- 優先度の高いリアルタイムスレッドが実行できるようになります
第6章 アフィニティー
- すべてのシステムプロセス用に CPU コアを 1 つ予約し、残りのコアでアプリケーションを実行できるようにします。
- 同じ CPU でスレッドアプリケーションと指定のカーネルスレッド (ネットワーク softirq やドライバースレッドなど) を許可します。
- 各 CPU のペアプロデューサーとコンシューマースレッド。
Tuna
ツールを使用して実行することも、シェルスクリプトを使用してビットマスクの値を変更することもできます。この taskset
コマンドは、プロセスのアフィニティーを変更するのに使用できますが、/proc
ファイルシステムエントリーを変更すると割り込みのアフィニティーが変更されます。
注記
6.1. taskset
コマンドを使用したプロセッサーアフィニティーの設定
taskset
コマンドは、特定のプロセスのアフィニティー情報を設定し、確認します。これらのタスクは、Tuna ツールを使用して実行することもできます。
-p
または --pid
オプションと、チェックするプロセスの PID を指定して taskset コマンドを使用します。-c
または --cpu-list
オプションは、情報をビットマスクではなく、コアの数値リストとして表示します。
~]# taskset -p -c 1000
pid 1000's current affinity list: 0,1
~]# taskset -p -c 1 1000
pid 1000's current affinity list: 0,1
pid 1000's new affinity list: 1
~]# taskset -p -c 0,1 1000
pid 1000's current affinity list: 1
pid 1000's new affinity list: 0,1
taskset
コマンドは、特定のアフィニティーで新規プロセスを開始するためにも使用できます。このコマンドは、CPU 4 で /bin/my-app
アプリケーションを実行します。
~]# taskset -c 4 /bin/my-app
SCHED_FIFO
ポリシーと 78 の優先度で、CPU 4 で /bin/my-app
アプリケーションを実行します。
~]# taskset -c 5 chrt -f 78 /bin/my-app
6.2. sched_setaffinity()
システムコールを使用したプロセッサーアフィニティーの設定
taskset
コマンドの他に、sched_setaffinity()
システムコールを使用してプロセッサーアフィニティーを設定することもできます。
#define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <sched.h> int main(int argc, char **argv) { int i, online=0; ulong ncores = sysconf(_SC_NPROCESSORS_CONF); cpu_set_t *setp = CPU_ALLOC(ncores); ulong setsz = CPU_ALLOC_SIZE(ncores); CPU_ZERO_S(setsz, setp); if (sched_getaffinity(0, setsz, setp) == -1) { perror("sched_getaffinity(2) failed"); exit(errno); } for (i=0; i < CPU_COUNT_S(setsz, setp); i++) { if (CPU_ISSET_S(i, setsz, setp)) online++; } printf("%d cores configured, %d cpus allowed in affinity mask\n", ncores, online); CPU_FREE(setp); }
注記
- sched_setaffinity(2)
第7章 スレッドの同期
7.1. ミューテックス
mutex
は 相互除外 (mutual-exclusion) という用語から派生しています。mutex は POSIX スレッド構成で、pthread_create_mutex
ライブラリー呼び出しを使用して作成されます。mutex は、コードの各セクションへのアクセスをシリアライズし、アプリケーションのスレッドが同時にコードを実行するようにします。
futex
があります。これはミューテックスの実装に使用する内部メカニズムである Fast User muTEX です。futexes は、カーネルと C ライブラリーの間で共有規則を使用します。これにより、カーネル領域へのコンテキスト切り替えがないと、競合のない mutex がロックまたは解放されます。
7.2. バリア
Barriers
は、他のスレッド同期方法と非常に異なる方法で動作します。コードリージョンへのアクセスをシリアライズする代わりに、事前に決定された数までのスレッドが累積されるまですべてのスレッドをブロックします。次に、バリアにより、すべてのスレッドが続行されます。バリアは、実行を続行する前にすべてのスレッドがタスクを完了していることを確認する必要がある環境で使用されます。
7.3. Condvars
condvar
、または条件変数は、POSIX スレッドの構成で、特定の条件が達成されるのを待機してから続行します。一般的に、シグナル化している条件は、スレッドが別のスレッドと共有するデータの状態を保持します。たとえば、condvar を使用して、データエントリーが処理キューに格納されたことを通知でき、キューからデータを処理するのを待機するスレッドが続行できるようになりました。
7.4. 他のタイプの同期
第8章 ソケット
8.1. ソケットオプション
TCP_NODELAY
および TCP_CORK
があります。
TCP_NODELAY
TCP は最も一般的なトランスポートプロトコルです。つまり、多くの異なるニーズに対応するために使用されます。新しいアプリケーションおよびハードウェア機能が開発され、カーネルアーキテクチャーの最適化が行われると、TCP は変更を効果的に処理するために新しいヒューリスティックを導入する必要がありました。
TCP_NODELAY
は、この動作をオフにするのに指定できるソケットオプションです。以下の機能を使用すると、setsockopt
ソケット API で有効にできます。
int one = 1; setsockopt(descriptor, SOL_TCP, TCP_NODELAY, &one, sizeof(one));
TCP_NODELAY
はその他の最適化ヒューリスティックと対話でき、全体的なパフォーマンスが低下します。
TCP_NODELAY
が有効なソケットを使用して送信できます。
TCP_NODELAY
を有効にしたソケットで writev
を使用してカーネルに渡すことができます。
TCP_CORK
同様の方法で機能する別の TCP ソケットオプションは、TCP_CORK
です。有効にすると、アプリケーションが cork を削除されるまで TCP パケットがすべて遅延し、保存されたパケットが送信されます。これにより、アプリケーションはカーネル領域にパケットを構築できます。これは、異なるライブラリーを使用して層の抽象化を提供する場合に便利です。
TCP_CORK
オプションを有効にできます。
int one = 1; setsockopt(descriptor, SOL_TCP, TCP_CORK, &one, sizeof(one));
TCP_CORK
の有効化は、corking the socket
と呼ばれます。
int zero = 0; setsockopt(descriptor, SOL_TCP, TCP_CORK, &zero, sizeof(zero));ソケットのコードが解除されると、TCP は、アプリケーションからの追加のパケットを待たずに累積された論理パッケージを即座に送信します。
例8.1 TCP_NODELAY
および TCP_CORK
の使用
TCP_NODELAY
と TCP_CORK
による、アプリケーションのパフォーマンスへの影響を示しています。
~]$ ./tcp_nodelay_server 5001 10000
TCP_NODELAY
ソケットオプションを有効にするには、no_delay
オプションを指定します。TCP_CORK
を有効にする場合は、cork
オプションを指定します。すべてのケースで 15 パケットが送信され、それぞれ 2 バイトの送信とサーバーからの応答を待機します。
TCP_NODELAY
も TCP_CORK
も使用されません。これはベースラインの測定です。TCP のコアレッシングは書き込みを行い、アプリケーションがネットワークパケットに最適に適合できる以上のデータがあるかどうかを確認する必要があります。
~]$ ./tcp_nodelay_client localhost 5001 10000
10000 packets of 30 bytes sent in 400129.781250 ms: 0.749757 bytes/ms
TCP_NODELAY
のみを使用します。TCP は小さなパケットを結合しないように指示されますが、バッファーを即座に送信するよう指示されます。これによりパフォーマンスが大幅に改善されますが、各論理パケットに多数のネットワークパケットが作成されます。
~]$ ./tcp_nodelay_client localhost 5001 10000 no_delay
10000 packets of 30 bytes sent in 1649.771240 ms: 181.843399 bytes/ms using TCP_NODELAY
TCP_CORK
は使用します。これは、同じ数の論理パケットを送信するのに必要な時間を 2 倍にします。これは、TCP がバッファーに論理パケット全体を結合し、ネットワークパケット全体を送信するためです。
~]$ ./tcp_nodelay_client localhost 5001 10000 cork
10000 packets of 30 bytes sent in 850.796448 ms: 352.610779 bytes/ms using TCP_CORK
TCP_CORK
が最適な手法です。これにより、アプリケーションはパケットが終了していることを正確に伝えることができ、遅延なく送信する必要があります。プログラムを開発する際にファイルから一括データを送信する必要がある場合は、TCP_CORK
を sendfile
とともに使用することを考慮してください。
注記
- sendfile(2)
- 「TCP nagle サンプルアプリケーション」 (C で書かれた両方のソケットオプションのアプリケーションの例)。ダウンロードするには、以下のリンクから右クリックして保存します。
第9章 共有メモリー
shmem
呼び出しセットでした。これらの呼び出しは極めて優れていますが、ほとんどのユースケースでは非常に複雑で複雑で複雑です。このため、Red Hat Enterprise Linux for Real Time カーネルで非推奨となったため、使用すべきではありません。
shm_open
や mmap
などの POSIX 共有メモリー呼び出しを使用します。
注記
- shm_open(3)
- shm_overview(7)
- mmap(2)
第10章 共有ライブラリ
ld.so
システムローダーによって 1 度読み込まれます。そこから、ライブラリーからのシンボルを必要とするプロセスのアドレス空間にマッピングされます。記号の最初の参照が検出されるまでは、評価できません。シンボルが参照されている場合にのみ、シンボルの評価はレイテンシーのソースになります。これは、メモリーページがディスク上で可能で、キャッシュを無効にすることができるためです。シンボルを事前に評価することは、レイテンシーを改善するのに役立つ安全なサイド手順です。
LD_BIND_NOW
環境変数を使用して実行できます。null 以外の値を設定 LD_BIND_NOW
すると、プログラムの読み込み時にシステムローダーが解決していないシンボルをすべて検索します。
注記
- ld.so(8)
パート III. ライブラリーサービス
第11章 スケジューラーの設定
11.1. chrt
を使用したスケジューラーの設定
chrt
は、スケジューラーポリシーおよび優先順位の確認および調整に使用されます。希望するプロパティーで新しいプロセスを開始するか、実行中のプロセスのプロパティーを変更できます。
--pid
または -p
オプションのみを使用してプロセス ID (PID) を指定します。
~]#chrt -p 468
pid 468's current scheduling policy: SCHED_FIFO pid 468's current scheduling priority: 85 ~]#chrt -p 476
pid 476's current scheduling policy: SCHED_OTHER pid 476's current scheduling priority: 0
表11.1 chrt
コマンドのポリシーオプション
短いオプション | 長いオプション | 詳細 |
---|---|---|
-f | --fifo | スケジュールを SCHED_FIFO に設定します。 |
-o | --other | スケジュールを SCHED_OTHER に設定します。 |
-r | --rr | スケジュールを SCHED_RR に設定します。 |
SCHED_FIFO
に、優先度 50 で設定します。
~]# chrt -f -p 50 1000
SCHED_OTHER
に、優先度 0 で設定します。
~]# chrt -o -p 0 1000
SCHED_FIFO
および優先度が 36 で /bin/my-app を起動します。
~]# chrt -f 36 /bin/my-app
注記
- chrt(1)
11.2. Preemption
/proc/PID/status
の内容を確認します。以下のコマンドは、PID 1000 のプロセスのプリエンプションを確認します。
~]# grep voluntary /proc/1000/status
voluntary_ctxt_switches: 194529
nonvoluntary_ctxt_switches: 195338
11.3. ライブラリー呼び出しを使用した優先度の設定
nice
getpriority
setpriority
重要
sched.h
ヘッダーファイルを含める必要があります。関数から常に戻りコードを確認するようにしてください。適切な man ページには、使用されるさまざまなコードの概要が記載されています。
11.3.1. sched_getscheduler
sched_getscheduler()
関数は、指定 PID のスケジューラーポリシーを取得します。
#include <sched.h> int policy; policy = sched_getscheduler(pid_t pid);
SCHED_OTHER
、SCHED_RR
および SCHED_FIFO
は sched.h
定義されています。これらは定義されたポリシーを確認するか、またはポリシーの設定に使用できます。
#include <stdio.h> #include <unistd.h> #include <sched.h> main(int argc, char *argv[]) { pid_t pid; int policy; if (argc < 2) pid = 0; else pid = atoi(argv[1]); printf("Scheduler Policy for PID: %d -> ", pid); policy = sched_getscheduler(pid); switch(policy) { case SCHED_OTHER: printf("SCHED_OTHER\n"); break; case SCHED_RR: printf("SCHED_RR\n"); break; case SCHED_FIFO: printf("SCHED_FIFO\n"); break; default: printf("Unknown...\n"); } }
11.3.2. sched_setscheduler
sched_setscheduler()
関数を使用して設定できます。現在、リアルタイムポリシーには 1 つのパラメーターsched_priority
があります。このパラメーターは、プロセスの優先度を調整するために使用されます。
sched_setscheduler
関数には、sched_setscheduler(pid_t pid, int policy, const struct sched_param *sp);
の 3 つのパラメーターが必要です。
注記
sched_setscheduler
(2) man ページは、エラーコードを含む sched_setscheduler
の考えられる値を一覧表示します。
pid
が ゼロの場合、sched_setscheduler()
関数は呼び出しプロセスで動作します。
SCHED_FIFO
に、優先度を 50 に設定します。
struct sched_param sp = { .sched_priority = 50 }; int ret; ret = sched_setscheduler(0, SCHED_FIFO, &sp); if (ret == -1) { perror("sched_setscheduler"); return 1; }
11.3.3. sched_getparam
および sched_setparam
sched_setparam()
関数は、特定プロセスのスケジューリングパラメーターを設定するために使用されます。その後、sched_getparam()
関数を使用して確認できます。
sched_getscheduler()
関数とは異なり、sched_getparam()
関数は指定のプロセスのすべてのスケジューリングパラメーターを返します。
struct sched_param sp; int ret; /* reads priority and increments it by 2 */ ret = sched_getparam(0, &sp); sp.sched_priority += 2; /* sets the new priority */ ret = sched_setparam(0, &sp);
重要
11.3.4. sched_get_priority_min
および sched_get_priority_max
sched_get_priority_min
および sched_get_priority_max
関数は、指定のスケジューラーポリシーの有効な優先度範囲を確認するために使用されます。
-1
を返し、EINVAL
は errno
に設定されます。
#include <stdio.h> #include <unistd.h> #include <sched.h> main() { printf("Valid priority range for SCHED_OTHER: %d - %d\n", sched_get_priority_min(SCHED_OTHER), sched_get_priority_max(SCHED_OTHER)); printf("Valid priority range for SCHED_FIFO: %d - %d\n", sched_get_priority_min(SCHED_FIFO), sched_get_priority_max(SCHED_FIFO)); printf("Valid priority range for SCHED_RR: %d - %d\n", sched_get_priority_min(SCHED_RR), sched_get_priority_max(SCHED_RR)); }
注記
SCHED_FIFO
と SCHED_RR
は両方とも、1 から 99 の範囲で数値指定できます。POSIX は、この範囲を適用する保証はありませんが、移植可能なプログラムはこれらの呼び出しを使用する必要があります。
11.3.5. sched_rr_get_interval
SCHED_RR
ポリシーは、SCHED_FIFO
ポリシーとは若干異なります。SCHED_RR
は、ラウンドロビンローテーションで同じ優先順位を持つ同時プロセスを割り当てます。この方法では、各プロセスに複数回割り当てられます。sched_rr_get_interval()
関数は、各プロセスに割り当てられた回数を報告します。
SCHED_RR
プロセスでのみ機能しなければならない必要がありますが、この sched_rr_get_interval()
関数は Linux 上の プロセスの時系列の長さを取得することができます。
timespec
または、ベース時間 1970 年 1 月 1 日 00:00:00:00 GMT以降の秒とナノ秒の数値で返されます。秒数とナノ秒を返します。
struct timespec { time_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ }
sched_rr_get_interval
関数には、プロセスの PID と構造の timespec が必要です。
#include <stdio.h> #include <sched.h> main() { struct timespec ts; int ret; /* real apps must check return values */ ret = sched_rr_get_interval(0, &ts); printf("Timeslice: %lu.%lu\n", ts.tv_sec, ts.tv_nsec); }
sched_03
を実行します。SCHED_FIFO
ポリシーのあるプロセスは、0 秒と 0ナノ秒の回数を返します。これは無限であることを示します。
~]$ chrt -o 0 ./sched_03
Timeslice: 0.38994072
~]$ chrt -r 10 ./sched_03
Timeslice: 0.99984800
~]$ chrt -f 10 ./sched_03
Timeslice: 0.0
注記
- nice(2)
- getpriority(2)
- setpriority(2)
第12章 スレッドおよびプロセスの作成
第13章 Mmap
mmap
システムコールでは、ファイル (またはファイルのパーツ) をメモリーにマップできます。これにより、メモリー操作でファイルコンテンツを変更でき、システムコールや入出力操作を回避できます。
注記
- mmap(2)
- 『Linux System Programming』 by Robert Love
第14章 システムコール
14.1. sched_yield
sched_yield
関数は、最初にプロセッサーが実行中のプロセス以外のプロセスを選択するよう設計されました。このタイプの要求は、書き込みの低いアプリケーション内から発行すると失敗する可能性があります。
sched_yield()
関数がリアルタイム優先度のプロセス内で使用されると、予期しない動作が表示される可能性があります。sched_yield
を呼び出したプロセスは、その優先度で実行中のプロセスのキューの末尾に移動します。これは、同じ優先度で他のプロセスが実行していない状況で発生すると、sched_yield
を呼び出したプロセスの実行は継続されます。プロセスの優先度が高い場合、ビジーループが発生し、マシンが使用できなくなる可能性があります。
sched_yield
を使用しないでください。
14.2. getrusage()
getrusage
関数は、指定のプロセスおよびそのスレッドから重要な情報を取得するために使用されます。これにより、利用可能なすべての情報が提供されるわけではありませんが、コンテキストスイッチやページフォールトなどの情報について報告します。
getrusage()
関数は、指定されたプロセスとそのスレッドから重要な情報を取得するために使用されます。これは、/proc/
ディレクトリー内の複数の異なるファイルからカタログ化する必要がありますが、アプリケーション上の特定のアクションまたはイベントと同期することが困難になります。自発的なコンテキストスイッチおよび自発的なコンテキストスイッチの量、メジャーおよびマイナーページのフォールト、使用中のメモリー量、その他の情報は、getrusage()
関数で取得できます。
注記
getrusage()
結果の報告に使用される構造に含まれるすべてのフィールドがカーネルによって設定される訳ではありません。一部は、互換性の理由でのみ保持されます。
第15章 タイムスタンプ
15.1. ハードウェアクロック
/sys/devices/system/clocksource/clocksource0/available_clocksource
ファイルを表示します。
~]# cat /sys/devices/system/clocksource/clocksource0/available_clocksource
tsc hpet acpi_pm
/sys/devices/system/clocksource/clocksource0/current_clocksource
ファイルを読み取りて検査できます。
~]# cat /sys/devices/system/clocksource/clocksource0/current_clocksource
tsc
/sys/devices/system/clocksource/clocksource0/available_clocksource
ファイルに表示されている一覧から、別のクロックソースを選択できます。これを行うには、クロックソースの名前を /sys/devices/system/clocksource/clocksource0/current_clocksource
ファイルに書き込みます。たとえば、以下のコマンドは、使用中のクロックソースとして HPET を設定します。
~]# echo hpet > /sys/devices/system/clocksource/clocksource0/current_clocksource
重要
idle=poll
パラメーターはアイドル状態にならないようにクロックを強制し、processor.max_cstate=1
パラメーターによりクロックがより深い C 状態に入るのを防ぎます。ただし、どちらの場合も、システムが常に最大速度で実行されるため、電力消費量が増加することに注意してください。
注記
15.1.1. ハードウェアクロックソースの読み取り
例15.1 ハードウェアクロックソースの読み込みコストの比較
cat
コマンドの出力で示されているように、現在使用中のクロックソースは TSC です。この time
コマンドは、クロックソースを 10 万回読み込むのに必要な期間を表示するために使用されます。
~]#cat /sys/devices/system/clocksource/clocksource0/current_clocksource
tsc ~]#time ./clock_timing
real 0m0.601s user 0m0.592s sys 0m0.002s
~]#echo hpet > /sys/devices/system/clocksource/clocksource0/current_clocksource
~]#cat /sys/devices/system/clocksource/clocksource0/current_clocksource
hpet ~]#time ./clock_timing
real 0m12.263s user 0m12.197s sys 0m0.001s
~]#echo acpi_pm > /sys/devices/system/clocksource/clocksource0/current_clocksource
~]#cat /sys/devices/system/clocksource/clocksource0/current_clocksource
acpi_pm ~]#time ./clock_timing
real 0m24.461s user 0m0.504s sys 0m23.776s
time(1)
man ページには、コマンドの使用方法と、その出力の解釈方法の詳細情報が記載されています。上記の例では、以下のカテゴリーを使用しています。
real
: プログラム呼び出しからプロセスが終了するまでに費やされた合計時間。real
には、user
とsys
時間が含まれます。通常は、後の 2 つの合計よりも大きくなります。このプロセスが優先度の高いアプリケーションや、ハードウェア割り込み (IRQ) などのシステムイベントによって中断される場合、この時間も待機に費やされた時間もreal
下で計算されます。user
: プロセスがユーザー空間で費やした時間。カーネルの介入を必要としないタスクを実行します。sys
: ユーザープロセスで必要なタスクの実行中にカーネルが費やした時間。これらのタスクには、ファイルのオープン、ファイルまたは I/O ポートの読み取りおよび書き込み、メモリーの割り当て、スレッドの作成、およびネットワーク関連のアクティビティーが含まれます。
15.2. POSIX クロック
CLOCK_REALTIME
: これは、実際の時間を表します。つまり「wall time」とも呼ばれます。これは、壁の時計からわかる時間を意味します。このクロックは、タイムスタンプイベントとユーザーと対話する場合に使用されます。これは、適切な権限を持つユーザーが変更できます。ただし、クロックの値が 2 つの読み取り間で変更された場合に、誤ったデータが作成される可能性があるため、ユーザーの変更は注意して使用してください。CLOCK_MONOTONIC
: システム起動以降に増加する時間を表します。このクロックはプロセスでは設定できず、イベント間の時間差を計算するのに推奨されるクロックです。このセクションの以下の例は、POSIX クロックとしてCLOCK_MONOTONIC
を使用します。
注記
- clock_gettime()
- 『Linux System Programming』 by Robert Love
clock_gettime()
で、<time.h>
出て異議されます。この clock_gettime()
コマンドは、POSIX クロック ID と timespec 構造の 2 つのパラメーターを取ります。これは、クロックの読み取りに使用する期間が埋められます。以下の例は、クロックの読み取りコストを測定する関数を示しています。
例15.2 clock_gettime()
を使用した読み取り用 POSIX クロックのコストの測定
#include <time.h> main() { int rc; long i; struct timespec ts; for(i=0; i<10000000; i++) { rc = clock_gettime(CLOCK_MONOTONIC, &ts); } }
clock_gettime()
の戻りコードの確認、rc
変数の値の確認、または ts
構造の内容が信頼されるようにすることで改善できます。clock_gettime()
man ページは、より信頼できるアプリケーションを作成するのに役立つより多くの情報を提供します。
重要
clock_gettime()
関数を使用するプログラムは、gcc
コマンドライン '-lrt'
に追加して rt
ライブラリーにリンクする必要があります。
~]$ gcc clock_timing.c -o clock_timing -lrt
15.2.1. CLOCK_MONOTONIC_COARSE
および CLOCK_REALTIME_COARSE
clock_gettime()
やなどの関数 gettimeofday()
は、システムコールの形式でカーネル内の関数です。ユーザープロセスが clock_gettime()
を呼び出す際に、対応する C ライブラリー (glibc
) が要求された操作を実行する sys_clock_gettime()
システムコールを呼び出し、その結果をユーザープロセスに返します。
CLOCK_MONOTONIC_COARSE
および CLOCK_REALTIME_COARSE
POSIX クロックのサポートが VDSO ライブラリー機能の形式で作成されました。_COARSE
バリアントは読み取りが速く、1 ミリ秒 (ミリ秒) の精度 (解像度とも呼ばれます) を持ちます 。
15.2.2. clock_getres()
を使用したクロック解決の比較
clock_getres()
関数を使用すると、特定の POSIX クロックの解決が可能となります。clock_getres()
は、使用される POSIX クロックの ID と、結果が返される timespec 構造のポインターと同じ 2 つのパラメーターを clock_gettime()
として使用します。以下の関数を使用すると、CLOCK_MONOTONIC
と CLOCK_MONOTONIC_COARSE
間の正確性を比較できます。
main() { int rc; struct timespec res; rc = clock_getres(CLOCK_MONOTONIC, &res); if (!rc) printf("CLOCK_MONOTONIC: %ldns\n", res.tv_nsec); rc = clock_getres(CLOCK_MONOTONIC_COARSE, &res); if (!rc) printf("CLOCK_MONOTONIC_COARSE: %ldns\n", res.tv_nsec); }
例15.3 clock_getres
のサンプル出力
TSC: ~]#./clock_resolution
CLOCK_MONOTONIC: 1ns CLOCK_MONOTONIC_COARSE: 999848ns (about 1ms) HPET: ~]#./clock_resolution
CLOCK_MONOTONIC: 1ns CLOCK_MONOTONIC_COARSE: 999848ns (about 1ms) ACPI_PM: ~]#./clock_resolution
CLOCK_MONOTONIC: 1ns CLOCK_MONOTONIC_COARSE: 999848ns (about 1ms)
15.2.3. C コードを使用したクロック解決の比較
CLOCK_MONOTONIC
POSIX クロックから読み取られたデータの形式を確認できます。timespec 構造の tv_nsec
フィールドにある 9 桁すべては、クロックにナノ秒の解像度があるため意味があります。clock_test.c
という名前のサンプル関数は、以下のようになります。
#include <stdio.h> #include <stdlib.h> #include <time.h> main() { int i; struct timespec ts; for(i=0; i<5; i++) { clock_gettime(CLOCK_MONOTONIC, &ts); printf("%ld.%ld\n", ts.tv_sec, ts.tv_nsec); usleep(200); } }
例15.4 clock_test.c
および clock_test_coarse.c
のサンプル出力
~]#gcc clock_test.c -o clock_test -lrt
~]#./clock_test
218449.986980853 218449.987330908 218449.987590716 218449.987849549 218449.988108248
clock_test_coarse.c
に変更し、 CLOCK_MONOTONIC
を CLOCK_MONOTONIC_COARSE
で置き換えることで、以下のような結果になります。
~]# ./clock_test_coarse
218550.844862154
218550.844862154
218550.844862154
218550.845862154
218550.845862154
_COARSE
クロックの精度は 1 ミリ秒であるため、timespec 構造の tv_nsec
フィールドの最初の 3 桁のみが重要になります。上記の結果は、以下のように読み込むことができます。
~]# ./clock_test_coarse
218550.844
218550.844
218550.844
218550.845
218550.845
_COARSE
バリアントは、タイムスタンプをミリ秒単位で実行できる場合に特に便利です。この利点は、ACPI_PM などの読み取り操作にかかる高コストのハードウェアクロックを使用するシステムにおいて、より明確になります。
15.2.4. time
コマンドを使用した読み取りクロックのコストの比較
time
コマンドを使用してクロックソースを 1,000 万回読み取り、利用可能なハードウェアクロックの読み取り CLOCK_MONOTONIC
および CLOCK_MONOTONIC_COARSE
表現のコストを比較することができます。以下の例では、TSC、HPET、および ACPI_PM のハードウェアクロックを使用します。time
コマンドの出力の暗号を解除する方法は、「ハードウェアクロックソースの読み取り」 を参照してください。
例15.5 読み取り用 POSIX クロックのコストの比較
TSC: ~]#time ./clock_timing_monotonic
real 0m0.567s user 0m0.559s sys 0m0.002s ~]#time ./clock_timing_monotonic_coarse
real 0m0.120s user 0m0.118s sys 0m0.001s HPET: ~]#time ./clock_timing_monotonic
real 0m12.257s user 0m12.179s sys 0m0.002s ~]#time ./clock_timing_monotonic_coarse
real 0m0.119s user 0m0.118s sys 0m0.000s ACPI_PM: ~]#time ./clock_timing_monotonic
real 0m25.524s user 0m0.451s sys 0m24.932s ~]#time ./clock_timing_monotonic_coarse
real 0m0.119s user 0m0.117s sys 0m0.001s
_COARSE
クロックが使用されると、sys
時間 (ユーザープロセスで必要なタスクを実行するためにカーネルが費やした時間) が大幅に短縮されます。これは、特に ACPI_PM クロックのタイミングで明確です。これは、POSIX クロックの _COARSE
バリアントにより、読み取りコストが高いクロックのパフォーマンスが向上します。
第16章 詳細情報
16.1. バグの報告
バグレポートを作成する前に、以下の手順に従って、問題発生場所を診断します。これにより、問題解決に大きくサポートします。
- 最新バージョンの Red Hat Enterprise Linux 7 カーネルがあることを確認してから、GRUB メニューから起動します。問題を標準カーネルで再現してみてください。問題が解決しない場合は、Red Hat Enterprise Linux 7 にバグを報告してください。
- 標準カーネルの使用時に問題が発生しなかった場合は、Red Hat Enterprise Linux for Real Time 固有の機能拡張 Red Hat がベースライン (3.10.0) カーネルに適用したバグにより、バグにより変更が加えられる可能性があります。
バグが Red Hat Enterprise Linux for Real Time に固有であると判断した場合は、以下の手順に従ってバグレポートを入力します。
- Bugzilla アカウントがまだない場合には作成します。
- Enter A New Bug Report をクリックします。必要な場合はログインします。
Red Hat
分類を選択します。Red Hat Enterprise Linux 7
製品を選択します。- カーネルの問題である場合は、コンポーネントとして
kernel-rt
を入力します。それ以外の場合は、影響を受けるユーザー空間コンポーネントの名前を入力します。 - 問題を詳細に説明して、バグ情報の入力を継続します。問題の説明を入力する際には、標準の Red Hat Enterprise Linux 7 カーネルで問題を再現できるかどうかの詳細情報が含まれるようにします。
付録A 改訂履歴
改訂履歴 | |||
---|---|---|---|
改訂 1-6 | Tue Aug 6 2019 | Jaroslav Klech | |
| |||
改訂 1-5 | Thu Oct 18 2018 | Jaroslav Klech | |
| |||
改訂 1-4 | Tue Mar 20 2018 | Marie Doleželová | |
| |||
改訂 1-3 | Tue Jul 25 2017 | Jana Heves | |
| |||
改訂 1-2 | Mon Nov 3 2016 | Maxim Svistunov | |
| |||
改訂 1-1 | Fri Nov 06 2015 | Tomáš Čapek | |
| |||
改訂 1-0 | Thu Feb 12 2015 | Radek Bíba | |
|