20

LKD 1のいくつかの段落を読みまし たが、以下の内容が理解できません。

ユーザー空間からのシステム コールへのアクセス

一般に、C ライブラリはシステム コールをサポートしています。ユーザー アプリケーションは、標準ヘッダーから関数プロトタイプを取得し、C ライブラリとリンクして、システム コール (または、syscall 呼び出しを使用するライブラリ ルーチン) を使用できます。ただし、システム コールを作成したばかりの場合、glibc が既にそれをサポートしているかどうかは疑わしいです。

ありがたいことに、Linux には、システム コールへのアクセスをラップするための一連のマクロが用意されています。レジスタの内容を設定し、トラップ命令を発行します。これらのマクロには という名前が付けられます。ここで、は 0 ~ 6 です。この数は、syscall に渡されるパラメーターの数に対応します。これは、マクロが期待するパラメーターの数を認識し、その結果、レジスターにプッシュする必要があるためです。たとえば、次のように定義されたシステム コールを考えてみましょう。_syscalln()nopen()

long open(const char *filename, int flags, int mode)

明示的なライブラリ サポートなしでこのシステム コールを使用する syscall マクロは、次のようになります。

#define __NR_open 5
_syscall3(long, open, const char *, filename, int, flags, int, mode)

次に、アプリケーションは単純に を呼び出すことができますopen()

各マクロには、2+2×n 個のパラメーターがあります。最初のパラメーターは、syscall の戻り値の型に対応します。2 番目は、システム コールの名前です。次に、各パラメータの型と名前がシステム コールの順に続きます。__NR_open定義は<asm/unistd.h>;にあります。システムコール番号です。マクロは、インライン アセンブリを使用_syscall3して C 関数に展開されます。アセンブリは、前のセクションで説明した手順を実行して、システム コール番号とパラメーターを正しいレジスターにプッシュし、ソフトウェア割り込みを発行してカーネルにトラップします。このマクロをアプリケーションに配置するだけで、open()システム コールを使用できます。

素晴らしい新しいfoo()システム コールを使用するマクロを作成し、テスト コードを作成して、私たちの努力を披露しましょう。

#define __NR_foo 283
__syscall0(long, foo)

int main ()
{
        long stack_size;

        stack_size = foo ();
        printf ("The kernel stack size is %ld\n", stack_size);
        return 0;
}

アプリケーションは単に呼び出すことができます とはopen()どういう意味ですか?

また、コードの最後の部分では、? の宣言はどこにありfoo()ますか? このコードをコンパイル可能にして実行可能にするにはどうすればよいでしょうか? インクルードする必要があるヘッダー ファイルは何ですか?

__________
1 Linux カーネル開発、Robert Love 著。  wordpress.com の PDF ファイル(81 ページに移動)。Google ブックスの結果

4

3 に答える 3

24

まず、 Linuxカーネルの役割と、アプリケーションがシステムコールを介してのみカーネルと対話することを理解する必要があります。

事実上、アプリケーションはカーネルによって提供される「仮想マシン」で実行されます。アプリケーションはユーザースペースで実行され、ユーザーCPUモードで許可された一連のマシン命令のみを実行できます(最下位のマシンレベルで)。例SYSENTERまたはINT 0x80...)システムコールを行うために使用されます。したがって、ユーザーレベルのアプリケーションの観点からは、syscallはアトミックな疑似マシン命令です。

Linux Assembly Howtoは、アセンブリ(つまりマシン命令)レベルでシステムコールを実行する方法を説明しています。

GNU libcは、syscallに対応するC関数を提供しています。したがって、たとえば、open関数は、番号のシステムコールの上にある小さな接着剤(つまりラッパー)です(システムコールNR__openを作成してから更新しますerrno)。アプリケーションは通常、syscallを実行する代わりに、libcでそのようなC関数を呼び出します。

他のを使用することができますlibc。たとえば、MUSL libcはなんとなく「シンプル」であり、そのコードはおそらく読みやすいでしょう。また、生のシステムコールを対応するC関数にラップしています。

独自のシステムコールを追加する場合は、同様のC関数も(独自のライブラリに)実装することをお勧めします。したがって、ライブラリのヘッダーファイルも必要です。

intro(2)syscall(2)およびsyscalls(2)のマニュアルページ、およびsyscallsでのVDSOの役割も参照してください。

システムコールはC関数ではないことに注意してください。それらは呼び出しスタックを使用しません(スタックなしで呼び出すこともできます)。syscallは、基本的にNR__openfromのような数値<asm/unistd.h>であり、SYSENTERどのレジスタがsyscallの引数の前に保持され、どのレジスタがsyscallの結果(失敗の結果を含む)の後に保持さerrnoれ、Cライブラリのラッピングに設定されるかについての規則があります。システムコール)。syscallの規則は、ABI仕様( x86-64 psABIなど)のC関数の呼び出し規則ではありません。したがって、Cラッパーが必要です。

于 2012-07-23T09:27:58.873 に答える
5

最初に、システムコールの定義をいくつか提供したいと思います。システム コールは、ユーザー空間アプリケーションから特定のカーネル サービスを同期的に明示的に要求するプロセスです。同期とは、システムコールの動作が命令シーケンスを実行することによって事前に決定されていることを意味します。割り込みは、プロセッサで実行されているコードとは完全に独立してカーネルに到達するため、非同期システム サービス リクエストの例です。システム コールとは対照的に、例外は同期的ですが、カーネル サービスに対する暗黙的な要求です。

システムコールは次の 4 つの段階から構成されます。

  1. プロセッサをユーザー モードからカーネル モードに切り替えてカーネル内の特定のポイントに制御を渡し、プロセッサをユーザー モードに切り替えて制御を戻します。
  2. 要求されたカーネル サービスの ID の指定。
  3. 要求されたサービスのパラメーターの受け渡し。
  4. サービスの結果をキャプチャします。

一般に、これらすべてのアクションは、実際のシステム コールの前後に多数の補助アクションを実行する 1 つの大きなライブラリ関数の一部として実装できます。この場合、システム コールがこの関数に埋め込まれていると言えますが、一般に関数はシステム コールではありません。別のケースでは、この 4 つのステップのみを実行する小さな関数を作成できます。この場合、この関数はシステムコールであると言えます。実際には、上記の 4 つの段階すべてを手動で実装することにより、システム コール自体を実装できます。この場合、すべての手順が完全にアーキテクチャに依存するため、アセンブラを使用する必要があることに注意してください。

たとえば、Linux/i386 環境には次のシステム コール規則があります。

  1. ユーザー モードからカーネル モードへの制御の受け渡しは、番号 0x80 のソフトウェア割り込み (アセンブリ命令 INT 0x80)、SYSCALL 命令 (AMD)、または SYSENTER 命令 (Intel) によって行うことができます。
  2. 要求されたシステム サービスの ID は、カーネル モードに入るときに EAX レジスタに格納された整数値によって指定されます。カーネル サービス ID は、 _NRの形式で定義する必要があります。すべてのシステム サービス ID は Linux ソース ツリーの path にありますinclude\uapi\asm-generic\unistd.h
  3. 最大 6 つのパラメーターをレジスター EBX(1) 、ECX(2)、EDX(3)、ESI(4)、EDI(5)、EBP(6) を介して渡すことができます。括弧内の数字は、パラメータの連番です。
  4. カーネルは、EAX レジスタで実行されたサービスのステータスを返します。この値は通常、glibc が errno 変数を設定するために使用します。

最新バージョンの Linux には、_syscall マクロはありません (私の知る限り)。代わりに、Linux カーネルのメイン インターフェイス ライブラリである glibc ライブラリは、特別なマクロ - を提供します。このマクロはINTERNAL_SYSCALL、インライン アセンブラ命令によって設定された小さなコードに展開されます。このコードは特定のハードウェア プラットフォームを対象としており、システム コールのすべての段階を実装しているため、このマクロはシステム コール自体を表しています。別のマクロもあります - INLINE_SYSCALL. 最後の 1 つのマクロは、glibc のようなエラー処理を提供します。これに従って、システム コールが失敗すると -1 が返され、エラー番号がerrno変数に格納されます。どちらのマクロもsysdep.hglibc パッケージで定義されています。

次の方法でシステム コールを呼び出すことができます。

#include <sysdep.h>

#define __NR_<name> <id>

int my_syscall(void)
{
    return INLINE_SYSCALL(<name>, <argc>, <argv>);
}

ここで<name>、syscall 名の文字列、<id>- 必要なシステム サービス番号 ID、<argc>- パラメータの実際の数 (0 ~ 6)、および<argv>- コンマで区切られた実際のパラメータ (パラメータが存在する場合はコンマで始まる) に置き換える必要があります。 .

例えば:

#include <sysdep.h>

#define __NR_exit 1

int _exit(int status)
{
    return INLINE_SYSCALL(exit, 1, status); // takes 1 parameter "status"
}

または別の例:

#include <sysdep.h>

#define __NR_fork 2 

int _fork(void)
{
    return INLINE_SYSCALL(fork, 0); // takes no parameters
}
于 2013-12-01T23:11:47.287 に答える