カーネルとユーザースペースの相互作用

 

URLhttp ://www.kerneltravel.net/jiaoliu/005.htmから   

ユーザープログラムとカーネル間の情報交換は双方向です。つまり、ユーザースペースからカーネルスペースに情報をアクティブに送信するか、カーネルスペースからユーザースペースにデータを送信することができます。もちろん、ユーザープログラムはカーネルからデータを積極的に抽出することもできます。以下に、カーネルとユーザーの相互作用データのメソッドを要約して要約します。

   情報伝達の開始者によると、情報の相互作用は2つのカテゴリに分類できます。ユーザーがデータをカーネルに転送/抽出することと、カーネルがユーザースペースに要求を送信することです。まず、ユーザーが開始する情報の相互作用-レベルプログラム

ユーザーレベルのプログラムによって開始される情報の相互作用

独自のシステムコールを書く

    システムコールは、ユーザーレベルのプログラムがカーネルにアクセスするための最も基本的な方法です。現在、Linuxはおよそ200を超える標準システムコールを提供しており(カーネルコードツリーのinclude / asm-i386 /unistd.hおよびarch / i386 / kernel / entry.Sファイルを参照)、独自のシステムを追加できます。カーネルとの情報交換を実現するために呼び出します。たとえば、侵入検知のためにすべてのシステムコールアクションを記録するシステムコールログシステムを確立したいと考えています。この時点で、カーネルサービスプログラムを作成できます。プログラムは、すべてのシステムコール要求を収集し、これらの呼び出し情報をカーネル内の自己構築バッファに記録する役割を果たします。カーネルに複雑な侵入検知プログラムを実装することはできないため、バッファ内のレコードをユーザースペースに抽出する必要があります。最も簡単な方法は、バッファリングされたデータを抽出するこの関数を実装するための新しいシステムコールを作成することです。カーネルサービスプログラムと新しいシステムコールが実装されると、ユーザースペースにユーザープログラムを記述して侵入検知タスクを実行できます。侵入検知プログラムは、データを抽出する必要があるときに、スケジュール、ローテーション、または新しいシステムコールの呼び出しを行うことができます。カーネル、次に侵入検知が実行されます。

B書き込みドライバー

    Linux / UNIXの機能は、すべてがファイルである(すべてがファイルである)ことです。システムは簡潔で完全なドライバーインターフェイスを定義し、クライアントプログラムはこのインターフェイスを介して統一された方法でカーネルドライバーと対話できます。ほとんどのシステムユーザーと開発者は、このインターフェイスと対応する開発プロセスにすでに精通しています。

ドライバープログラムはカーネルスペースで実行され、ユーザースペースアプリケーションプログラムはファイルシステムの/ dev /ディレクトリにあるファイルを介してドライバープログラムと対話します。これは、私たちが精通しているファイル操作プロセスです。open()-read()-write()-ioctl()-close()。(すべてのカーネルドライバーがこのインターフェイスを備えているわけではないことに注意してください。ネットワークドライバーとさまざまなプロトコルスタックの使用はあまり一貫していません。たとえば、ソケットプログラミングにもopen()close()などの概念がありますが、カーネルと外部の使用法は、通常のドライバーとは大きく異なります。)

この記事では、割り込み応答、デバイス管理、データ処理、およびデバイスドライバーがカーネルで実行する必要のあるその他のタスクについては考慮していません。ここでは、ユーザーレベルのプログラムと対話する部分に焦点を当てます。オペレーティングシステムは、この目的のために、前述のopen()、read()、write()、ioctl()、close()などの統合された対話型インターフェイスを定義します。各ドライバーは、独自のニーズに応じて個別に実装され、この統合されたインターフェイスの下で提供される機能とサービスを隠します。クライアントレベルのプログラムは、必要なドライバーまたはサービスを選択し(実際には、/ dev /ディレクトリ内のファイルを選択します)、上記のインターフェイスとファイル操作プロセスに従って、カーネル内のドライバーと対話します。その実用的なオブジェクト指向の概念は説明が簡単で、システムは抽象インターフェース(抽象インターフェース)を定義し、特定の各ドライバーはこのインターフェースの実装(実装)です。

したがって、ドライバーは、ユーザースペースとカーネル情報の相互作用のための重要な方法の1つでもあります。実際、ioctl、read、writeは基本的にシステムコールを介して実行されますが、これらの呼び出しはカーネルによって標準化および定義されています。したがって、ユーザーはカーネルコードを変更して、新しいシステムコールを入力するように新しいカーネルを再コンパイルする必要はありません。仮想デバイスは、モジュールメソッドを介して新しい仮想デバイスをカーネル(insmod)にインストールするだけで、簡単に実行できます。使用する。この点に関する設計の詳細については参考文献5を参照し、プログラミングの詳細については参考文献6を参照してください。

Linuxでは、デバイスは大きく分けて、文字デバイス、ブロックデバイス、ネットワークインターフェイス(文字デバイスには、文字端末、シリアルポートなど、バイトストリームのように順番にアクセスする必要があるデバイスが含まれます)。ブロックデバイスとは、ランダムな方法でデータのブロック全体の単位でアクセスできる、ハードディスクなどのデバイスを指します。ネットワークインターフェイスとは、ネットワークカードやプロトコルスタックなどの複雑なネットワーク入力および出力サービスを指します。システムコールログシステムをキャラクタードライバーとして実装すれば、リラックスできる仕事になります。カーネル内の情報を収集して記録するキャラクターデバイスドライバーを作成できます。実際に対応する物理デバイスはありませんが、問題はありません。Linuxデバイスドライバーは元々ソフトウェアの抽象化であり、ハードウェアと組み合わせてサービスを提供できます。また、純粋なソフトウェアとしてサービスを提供することもできます(もちろん、メモリの使用はやむを得ない。の)。ドライバーでは、openを使用してサービスを開始し、read()を使用して処理済みレコードを返し、ioctl()を使用してレコード形式を設定するなど、close()を使用してサービスを停止できます。write()は使用されません。 、それから私たちはそれを達成することはできません。次に、新しく追加したカーネルシステムコールログシステムドライバーに対応するデバイスファイルを/ dev /ディレクトリに作成します。

C:procファイルシステムを使用する

    Procは、Linuxが提供する特別なファイルシステムです。その目的は、ユーザーとカーネル間の便利な対話方法を提供することです。ユーザーインターフェイスとしてファイルシステムを使用するため、アプリケーションは、システムの現在の実行状態やその他のカーネルデータ情報をファイル操作の方法で安全かつ便利に取得できます。

procファイルシステムは主に監視、管理、デバッグシステムに使用されます。ps、topなど、私たちが使用する管理ツールの多くは、カーネル情報を読み取るためにprocを使用します。カーネル情報の読み取りに加えて、procファイルシステムは書き込み機能も提供します。したがって、これを使用してカーネルに情報を入力することもできます。たとえば、procファイルシステムの下のシステムパラメータ構成ファイル(/ proc / sys)を変更することにより、実行時にカーネルパラメータを動的に直接変更できます。別の例では、次の手順を実行します。

エコー1> / proc / sys / net / ip_v4 / ip_forward

カーネルでIP転送を制御するスイッチをオンにすることで、実行中のLinuxシステムでルーティング機能を有効にできます。同様に、procファイルシステムを介して直接クエリおよび調整できるカーネルオプションが多数あります。

システムによってすでに提供されているファイルエントリに加えて、procには、ユーザープログラムと情報やデータを共有するために、カーネルに新しいエントリを作成できるインターフェイスもあります。たとえば、システムコールログプログラムのprocファイルシステムに新しいファイルエントリを作成できます(ドライバとして、または純粋なカーネルモジュールとして)。このエントリには、システムコールが使用された回数が表示されます。単一のシステムコールの使用頻度など。オープンシステムコールの使用状況を記録しないなど、ログルールを設定するためのエントリを追加することもできます。procファイルシステムの使用方法の詳細については、参考文献7を参照してください。

D:仮想ファイルシステムを使用する

一部のカーネル開発者は、ioctl()システムコールを使用すると、システムコールが不明確になり、制御が困難になることが多いと考えています。情報をprocファイルシステムに入れると、情報の編成に混乱が生じるため、過度の使用はお勧めしません。彼らは、ioctl()や/ procの代わりに分離された仮想ファイルシステムを実装することを提案しています。これは、ファイルシステムインターフェイスが明確で、ユーザースペースから簡単にアクセスできるためです。同時に、仮想ファイルシステムを使用すると、スクリプトを使用して実行できます。より便利で効果的なシステム管理タスク。

たとえば、仮想ファイルシステムを介してカーネル情報を変更する方法を見てみましょう。sagafsと呼ばれる仮想ファイルシステムを実装できます。ファイルログは、カーネルによって保存されたシステムコールログに対応します。ログ情報は、次のようなファイルアクセス方法で取得できます。

#猫/ sagafs / log

仮想ファイルシステム-VFSを使用して情報の相互作用を実現すると、システム管理がより便利で明確になります。ただし、一部のプログラマーは、VFSのAPIインターフェイスは複雑で習得が難しいと言うかもしれません。2.5カーネルには、ファイルシステムに精通していないユーザーが実装の一般的な操作をカプセル化するのに役立つlibfsというサンプルプログラムが用意されていることを心配しないでください。 VFS。相互作用を実現するためにVFSを使用する方法については、参考資料を参照してください。

E:メモリイメージを使用する

    Linuxは、メモリマッピングメカニズムを介してメモリに直接アクセスするユーザープログラムの機能を提供します。メモリマッピングとは、カーネル内のメモリ空間の特定の部分をユーザーレベルのプログラムのメモリ空間にマッピングすることを意味します。つまり、ユーザースペースとカーネルスペースは同じメモリを共有します。これの直感的な効果は明ら​​かです。カーネルは変更されたデータをこのアドレスに保存し、ユーザーはデータをまったくコピーせずにすぐに見つけて使用できます。システムコールを使用して情報を操作する場合、操作全体でデータコピーのステップが必要です-カーネルデータをユーザーバッファーにコピーするか、ユーザーデータをカーネルバッファーにコピーするだけです-これは多くのデータアプリケーションに当てはまります大量の送信と長い時間の要件は間違いなく致命的な打撃です。多くのアプリケーションは、データのコピーによって消費される時間とリソースを単に許容できません。

高速サンプリングデバイス用のドライバを開発しました。このデバイスには、20メガバイトのサンプリングレートで1KHzの繰り返しレートで16ビットのリアルタイムサンプリングが必要です。サンプリングする必要のあるデータ量、DMA、およびミリ秒ごとに処理されるのは驚くべきことです。データコピーを使用する場合この方法では、要件を満たすことができません。現時点では、メモリマッピングが唯一の選択肢になります。メモリ内にスペースを予約し、サンプリングデバイスDMAがデータを出力するための循環キューとして構成します。次に、このメモリ空間をユーザー空間で実行されているデータ処理プログラムにマップします。これにより、サンプリングデバイスによって取得されてホストに送信されたデータを、ユーザー空間のプログラムですぐに処理できます。

実際、メモリマッピング方式は通常、カーネルとユーザースペースが大量のデータをすばやく操作する必要がある場合、特に強力なリアルタイムパフォーマンスを必要とするアプリケーションで使用されます。Xウィンドウシステムのサーバーの仮想メモリ領域は、メモリマッピングの使用法の典型的な例と見なすことができます:Xサーバーはビデオメモリ上で大量のデータを交換する必要があります.lseek / writeと比較して、グラフィックディスプレイメモリユーザースペースに移動すると、パフォーマンスが大幅に向上します。

シリアルポートやマウスなどのストリームデータに基づくキャラクターデバイスなど、すべてのタイプのアプリケーションがmmapに適しているわけではありません。mmapはあまり役に立ちません。さらに、このメモリ共有方法には、同期が不十分であるという問題があります。ユーザープログラムとカーネルプログラムが共有する特別な同期メカニズムがないため、干渉が発生しないように、データの読み取りと書き込みを行う際には非常に注意深い設計が必要です。

mmapは完全に共有メモリの概念に基づいているため、さらに便利になりますが、制御が特に困難です。

カーネルによって開始される情報の相互作用

Aカーネル空間からユーザープログラムを呼び出す

    カーネル内でも、特定のデータを読み取るためにファイルを開く、関数を完了するためにユーザープログラムを実行するなど、ユーザーレベルで提供されるいくつかの操作を実行する必要がある場合があります。多くのデータと機能が存在するか、ユーザースペースに実装されているため、繰り返すために多くのリソースを費やす必要はありません。さらに、カーネルを設計する場合、未知ではあるが可能性のある変更をサポートするための柔軟性またはパフォーマンスを向上させるために、カーネル自体がタスクを完了するために協力するためにユーザースペースリソースを使用する必要があります。たとえば、モジュールを動的にロードするカーネルの部分は、kmodを呼び出す必要があります。ただし、kmodをコンパイルする場合、すべてのカーネルモジュールをサブスクライブすることは不可能であるため(この場合、モジュールの動的ロードは無意味です)、その後に表示されるモジュールの場所とロード方法を知ることはできません。したがって、モジュールの動的ロードは次の戦略を採用しています。ロードタスクは実際にはユーザースペースにあるmodprobeプログラムの助けを借りて行われます-最も単純なケースでは、modprobeはカーネルから渡されたモジュール名を使用してinsmodを呼び出しますパラメータ。この方法を使用して、必要なモジュールをロードします。

カーネルでユーザープログラムを起動するには、execveシステムコールプロトタイプを使用する必要がありますが、この時点での呼び出しはカーネルスペースで発生し、一般的なシステムコールはユーザースペースで実行されます。システムコールがパラメータを受け取る場合、問題が発生します。パラメータの正当性はシステムコールの特定の実装コードでチェックする必要があるため、チェックではすべてのパラメータをユーザースペースに配置する必要があります。アドレスは0x0000000および0xC0000000であるため、カーネルからパラメーター(0xC0000000より大きいアドレス)を渡すと、チェックは呼び出し要求を拒否します。この問題を解決するために、set_fsマクロを使用してチェック戦略を変更し、パラメーターアドレスがカーネルアドレスになるようにすることができます。このようにして、カーネルはシステムコールを直接使用できます。

次に例を示します。kmodがexecveを呼び出してmodprobeコードを実行する前に、set_fs(KERNEL_DS)が必要です。

……。

set_fs(KERNEL_DS);

/ * Go、go、go ... * /
if(execve(program_path、argv、envp)<0)
return-errno;
再代码中program_path是 "/ sbin / modprobe"、argv是{modprobe_path、 "-s" 、 "-k"、 "-"、(char *)module_name、NULL}、envp是{"HOME = /"、 "TERM = linux"、 "PATH = / sbin:/ usr / sbin:/ bin:/ usr / bin "、NULL}。

カーネルからファイルを開くには、パラメーターを指定したopenシステムコールも使用されます。必要なのは、最初にset_fsマクロを呼び出すことです。

Bbrkシステムコールを使用してカーネルデータをエクスポートする

カーネルとユーザースペースは、主にget_user(ptr)およびput_user(datum、ptr)ルーチンを介してデータを転送します。したがって、データを渡す必要のあるほとんどのシステムコールで見つけることができます。ただし、ユーザープログラムを介して、つまりユーザースペースにバッファーの場所を明示的に指定せずにシステムコールを開始しない場合、カーネルデータをユーザースペースに渡すにはどうすればよいでしょうか。

明らかに、put_user()の宛先バッファーを指定する方法がないため、put_user()を直接使用することはできなくなりました。したがって、brkシステムコールと現在のプロセススペースを借用する必要があります。brkは、プロセスのヒープスペースのサイズを設定するために使用されます。各プロセスには独立したヒープスペースがあり、mallocなどの動的メモリ割り当て関数は実際にはプロセスのヒープスペースからメモリを取得します。brkを使用して、現在のプロセスのヒープスペースに新しい一時バッファーを展開し、次にput_userを使用してカーネルデータをこの特定のユーザースペースにエクスポートします。

カーネルでユーザープログラムを呼び出すプロセスを今覚えていますか?ここで、パラメーターチェックをスキップする操作があります。このメソッドを使用すると、別の方法を見つけることができます。現在のプロセスのヒープ上のスペースを拡張し、システムコールで使用されるパラメーターをput_userを介して新しいパラメーターにコピーします。 ()拡張によって得られたユーザースペースでは、execveが呼び出されたときに、新しく開いたスペースアドレスがパラメーターとして使用されるため、パラメーターチェックの障害はなくなりました。

char * program_path = "/ bin / ls";

/ *ヒープの先頭の現在の位置を検索します* / 
mmm = current-> mm-> brk;
/ * brkを使用して、ヒープの先頭にある新しい256バイトのバッファーを展開します* /
ret = brk(*( void)(mmm +256));
/ * execveに必要なパラメーターを新しいバッファーにコピーします* /
put_user((void *)2、program_path、strlen(program_path)+1);
/ * / bin / lsを正常に実行しますプログラム!* / 
execve((char *)(mmm + 2));
/ *シーンを復元します* /
tmp = brk((void *)mmm);

この方法は一般的ではなく(具体的には、この方法は悪影響を及ぼしますか)、テクニックとしてのみ使用できますが、見つけるのは難しくありません。カーネル構造に精通している場合は、予期しないことがたくさんあります。 !!

C:使用信号:

    カーネル内のシグナルの目的は、主にプログラムに重大なエラーがあることをユーザーに通知し、現在のプロセスを強制的に強制終了することです。このとき、カーネルはSIGKILLシグナルを送信して、プロセスの終了を通知します。カーネルsend_sign(pid、sig)ルーチンを使用してシグナルを送信すると、シグナルを確認できます。送信では、着信プログラム番号(pid)を事前に知っている必要があるため、ユーザープロセスに非同期で通知して、特定のタスクを実行するように通知する場合は、カーネルの場合、ユーザープロセスのプロセス番号を事前に知っておく必要があります。カーネルの実行中に特定のプロセスのプロセス番号を検索するのは面倒な作業ですが、プロセス制御ブロックのリンクリスト全体をトラバースする必要がある場合があります。したがって、特定のユーザープロセスに信号を送る方法は非常に悪く、一般的にカーネルでは使用されません。カーネルでのシグナルの使用は、終了操作などの特定の一般的な操作を実行するように現在のプロセスに通知する場合にのみ発生します(現在の変数からpidを簡単に取得できます)。したがって、この方法はカーネル開発者にとってあまり役に立ちません。同様の状況でのメッセージ操作もあります。ここでは言葉ではありません。

 

まとめユーザーレベルのプログラムによって開始される情報のやり取りは、標準の呼び出し方法であろうとドライバーインターフェイスを介したものであろうと、通常はシステムコールを使用します。ただし、カーネルが積極的に情報の相互作用を開始するケースは多くありません。標準のインターフェースがないため、操作が非常に不便になります。したがって、一般的に、情報交換には、この記事で説明されている最初のいくつかの方法を可能な限り使用してください。結局のところ、設計のルートでは、カーネルはクライアントレベルのプログラムに関連するパッシブサービスプロバイダーとして定義されています。したがって、私たち自身の開発は、可能な限りこの設計原則に従う必要があります。

おすすめ

転載: blog.csdn.net/u014426028/article/details/110519387