システムを行ったんレベル0に落し、マルチ・ユーザーで立ち上げる。コマンドを保守モードに入るよう指示する。システムが別の実行レベルから保守モードに入ると、システム・コンソールだけが端末として使用される。シングルユーザモード。内にあるあらゆる停止(K)および起動(S)スクリプトが実行される。以下のファイルにシンボリックリンクしている。シングル・ユーザー状態。全てのファイルシステムがマウントされている。コマンドを保守モードに入るよう指示する。システムが別の実行レベルから保守モードに入ると、システム・コンソールだけが端末として使用される。自動ログインした仮想システムコンソールとなる。シングル・ユーザー状態。全てのファイルシステムがマウントされている。電源を入れ、Boot:という画面が出たらすぐに、-sと入力します。daemonsに関連するデーモンを起動する。errptにてOSのログ参照。今回は、番外編としてLinuxカーネルにおけるスケジューラと割込みハンドラの関係を記事にします。2.6系の頃のカーネルと現在のカーネルでは割込みハンドラの扱いが大きく変化したため、この経緯なども踏まえて説明できればと思います。・Linuxカーネルのコードは5.4を対象としています。・アーキテクチャ依存の処理はArm(32bit)アーキテクチャで説明します。割込みとは初めに用語の定義から入ります。割込みとは外部デバイスからイベントを通知する為の仕組みです。デバイスからキーが押下されたことをCPUへ通知されることを割込みと言います。割込みと似たような仕組みで例外があります。アクセス違反や未定義命令などを実行した時に、イベントとして通知される仕組みです。両者を区別する観点は幾つかあります。・ソフトウェアの実行と非同期で発生するイベントが割込みと区別する事ができます。・それ以外のデバイスから通知されるイベントは割込みと捉えることもできます。テーブルのIRQエントリのアドレスに制御が移ります。テーブルを説明します。テーブルは下記のように定義されており、割込みの場合はオフセット0x18に制御が移ります。テーブルのアドレスは、0x00000000(ローベクタ)、または0xFFFF0000(ハイベクタ)のいずれかに配置されます。オフセット0x18に制御が移った後はソフトウェアで割込み発生時に実行していたコンテキストのレジスタ群を退避し、割込みハンドラを実行した後に、退避したレジスタ群を復元します。その後、元のコンテキストの実行を再開します。割込みハンドラ割込みハンドラは対応する割込みを受けた時に実行する処理のことを指します。基本的に割込みハンドラはリアルタイム応答性能が求められるため、それまで実行していた処理に割り込んで実行する必要があります。Linuxでは、処理の緊急性に応じて「ハードウェア割込みハンドラ」と「ソフトウェア割込みハンドラ」の2種類の割込みハンドラの仕組みが用意されています。ハードウェア割込みハンドラ緊急性の高い必要最低限の処理を実装します。キーボードのキーを押下した場合の割込みを例で考えてみましょう。デバイスの割込みを刈り取った後、押下されたキー情報を取得して、上位レイヤに通知して終了します。押下されたキーに対するアクション(画面に表示する処理など)はハードウェア割込みハンドラでは行いません。私なりの基本的な考え方は、至ってシンプルです。割込みを上げたハードウェアにアクセスする処理はハードウェア割込みハンドラで、それ以外の処理はソフトウェア割込みハンドラで、を基準に考えています。ソフトウェア割込みハンドラLinuxを扱う書籍、Webでは様々な呼び方がされています。ソフトウェア割込み、SoftIRQ、割込みの遅延処理など。本記事ではこのソフトウェア割込みハンドラについてそれほど触れる予定もないので、この機能については別途記事を書きたいと思います。割込み処理のうち、緊急度がそれほど高くなく、遅れて実行される処理と思っていただければ良いと思います。従来の割込みハンドラの仕組み以降はハードウェア割込みハンドラを「割込みハンドラ」と記述します。割込みハンドラが使用するスタックLinuxの割込みハンドラは、自身を実行する為のスタックを持ちません(少なくともx86、Arm(32bit)、Powerアーキテクチャにおいては)。下図の通り、割込みが発生した時点に実行していたプロセスのカーネルスタックを使用します。この図だけではイメージが湧かないと思いますので、実際の割込みハンドラ実行時のスタックの中を確認してみましょう。その時の出力情報を以下に示します。)を呼び出して、本メッセージが出力されていることが判ります。c0af2000"からthread_info構造体の先頭アドレスが判ります。thread_info構造体とカーネルスタック領域は上図の関係にあるため、カーネルスタックが8KBの場合、このアドレスに0x2000を加算した0xc0af3FFFがスタックの底のアドレスになります。余談ですが、カーネルスタックのアドレスに対して0x1FFFをマスクすることで、そのプロセスのthread_info構造体のアドレスを算出できます。次に注目して貰いたい箇所は4行目です。)が呼ばれたことを意味しています。swapperプロセスは、システムがアイドル状態の時に実行されるIDLEクラスのプロセスです。34行目はスタック領域の内容を表示しています。左端の4桁の数字は、スタックアドレスの下位16bitを表しています。49行目はswapperプロセスのコールトレースが表示されています。46行目は割込み発生後のコールトレースが表示されています。)までの一連の割込み処理はswapperプロセスのスタックを用いて動作していることが判ります。]---スケジューラとの関係スケジューラはプロセスをtask_struct構造体を用いて管理します。一方で、割込みハンドラは自分のtask_struct構造体は持たず、割込み発生時に実行していたプロセスのスタックを利用します。このことから、割込みハンドラはスケジューラの管理対象外にであることが理解できます。(例えばschedule())を呼び出してはいけない(休眠してはいけない)と認識されている方も多いと思います。実際に呼び出したコードを実行すると下記メッセージとスタックトレースが出力されます。プロセス名/PID/プリエンプトカウントこれは"atomicなコンテキストから(実行してはいけない)schedule()を実行しようとしているよ。カーネルに不具合があるよ"という警告メッセージです。割込みハンドラからスケジューラを呼び出してはいけない(休眠してはいけない)のでしょうか。この理由をサラッと説明できる方は本節は飛ばしてもらって構いません。基本的なことですが、書籍などではあまり触れられていないので、ここでは割込みハンドラからスケジューラを呼び出してはいけない理由について述べます。)を呼び出した場合を考えます。この時、スケジューラは優先度に基づいて次に実行するプロセスをディスパッチします。上図の場合では、RTクラスSCHED_FIFOのプロセスBが実行されます。プロセスBの実行後は、nice値に基づいてプロセスCが実行されます(プロセスA、プロセスCのvruntimeの値によってプロセスAが先に実行される可能性もありますが、ここでは考慮しません)。スケジューラは、task_struct構造体を持たない割込みハンドラは管理対象外でありプロセスAとして扱うため、割込みハンドラの実行はプロセスAの優先度に引っ張られる形になります。プロセスAが再度ディスパッチされるタイミングまで割込みハンドラの実行が待たされることになります。このような状態では、割込みハンドラの「緊急度の高い処理を優先して行う」という本来の目的を全く達成できません。そもそも緊急性が求められる割込みハンドラで休眠するなという話ですが、割込みハンドラ内でmutexやセマフォなど内部で休眠する可能性がある機能で使用しようとする人もいるかもしれないので...。現在の割込みハンドラの仕組み現在の割込みハンドラの仕組みは大きく変わりました。Linuxというプロジェクトがあります。このプロジェクトではPreempt-RT又はRTパッチと呼ばれるリアルタイム応答性向上に取り組んでいます。私が把握している範囲では少なくとも15年前から活動しています。このプロジェクトの目的は、一言で述べると、リアルタイムで処理したいプロセスのレイテンシをできる限り削減することです。この目的を実現する為に、本プロジェクトではリアルタイム処理をしたいプロセスの実行を妨げるプリエンプション禁止区間を排除する、というアプローチをとっています。フルプリエンプティブ(カーネルのどの区間においてもプリエンプション可能)にすることです。リアルタイム処理をしたいプロセスを即座に実行する為に、場合によっては特定の割込みハンドラより優先して実行しないといけない場合もあります。割込みハンドラより優先して実行するということは、スケジューラが割込みハンドラを制御するということです。前述の通り、既存の仕組みではスケジューラは割込みハンドラは管理対象外としていました。そこで、Preempt-RTでは、割込みハンドラをスレッド化することで、スケジューラから制御できるように仕組みを変更しました。この機能がmainlineカーネルに取り込まれた版数はアーキテクチャによって異なります。x86では2.6.35、Powerでは3.3、Arm(32bit)では3.12から割込みハンドラのスレッド化を採用しています。実際に見てみましょう。以下にpsコマンドの実行結果を示します。★はSDカードの割込みハンドラに対応するカーネルスレッドです。いう命名規則でカーネルスレッドが生成されます。(snip)Preempt-RTでは、この仕組み以外にもリアルタイム応答性を向上させるためにフルプリエンプティブにする機能を提供していますが、詳細は別記事で紹介したいと思います。割込みハンドラの登録処理を読解することで仕組みがおよそ理解できます。実際のコードをポイントを絞って追っていきましょう。)を起点に見ていきます。)のラッパー関数です。)の第3引数にはNULLを設定しています。(snip)}この関数のポイントは、上記で抜粋した部分です。)のポイントとなる部分を抜粋します。)を下記に抜粋します。要点は以下の通りです。)の第3引数flagsにIRQF_NO_THREAD、IRQF_PERCPU、IRQF_ONESHOTのいずれかを指定している場合はその時点で復帰します。すると、本関数ではnew->thread_fnにnew->handlerを設定せずにNULLの状態で復帰します。纏めると下表のとおりです。0;}上記内容を踏まえて、__setup_irqの★2を見てください。)で何をしているか見ましょう。(snip)}4行目でカーネルスレッドを生成します。その時の識別子は、"irq/IRQ番号-識別子"で前述のpsコマンドの結果と一致します。11行目でこのカーネルスレッドのスケジューリングクラスと優先度を設定します。スケジューリングクラスはRTクラスのSCHED_FIFO、優先度はMAX_USER_RT_PRIOが100なので50になります。)の第3引数flagsにIRQF_NO_THREAD、IRQF_PERCPU、IRQF_ONESHOTのいずれも設定されていない場合は、それぞれの割込みに対応する割込みハンドラ用スレッドを生成します。逆にこれらのフラグのいずれかが設定されている場合はスレッドを生成しません。タイマー割込みなどプロセスの実行より常に優先すべき割込みは従来同様に割込み発生時に実行していたプロセスのスタックを使用して実行します。従って緊急度が高い割込みはIRQF_NO_THREADを設定します。割込みハンドラの実行ここでは割込みハンドラの実行時の動作について見ます。ポイントを絞って記載したいと思います。Linuxでは、(1)アーキ固有割込み入口処理(2)アーキ共通割込み入口処理(3)割込みハンドラ(4)アーキ共通割込み出口処理(5)アーキ固有割込み出口処理があります。ここでは(2)の後半と(3)に注目してコードを見てみます。