ユーザーがプロセスを所有でき、各プロセスにアドレス空間(有効なメモリロケーションが含まれ、このプロセスが参照できる)があることを理解しています。他のライブラリ関数と同様に、プロセスがシステムコールを呼び出してパラメーターを渡すことができることを知っています。これは、すべてのシステムコールがメモリの共有などによってプロセスのアドレス空間にあることを示唆しているようです。しかし、おそらく、これは、高レベルのプログラミング言語では、システムコールが他の関数のように見えるという事実によって作成された幻想にすぎません。それを呼び出します。
しかし、今、私はさらに一歩踏み込んで、内部で何が起こっているかをより詳しく分析します。コンパイラはどのようにしてシステムコールをコンパイルしますか?それはおそらく、プロセスによって提供されたシステムコール名とパラメーターをスタックにプッシュしてから、アセンブリー命令に「TRAP」などと言います。基本的には、ソフトウェア割り込みを呼び出すアセンブリー命令です。
このTRAPアセンブリ命令は、最初にユーザーからカーネルにモードビットをトグルしてから、割り込みサービスルーチンの開始を示すようにコードポインターを設定することにより、ハードウェアによって実行されます。この時点から、ISRはカーネルモードで実行され、スタックからパラメーターを取得します(これは、カーネルがユーザープロセスが所有するメモリの場所も含めて、任意のメモリロケーションにアクセスできるため可能です)。 endはCPUを解放します。これにより、再びモードビットが切り替わり、ユーザープロセスは中断したところから開始します。
私の理解は正しいですか?
添付は私の理解の大まかな図です:
あなたの理解はかなり近いです。トリックは、プログラムが呼び出す関数(例:getpid(2)
、chdir(2)
など)が実際には標準Cライブラリによって提供されるため、ほとんどのコンパイラがシステムコールを記述しないことです。標準Cライブラリには、_INT 0x80
_またはSYSENTER
を介して呼び出されるかどうかにかかわらず、システムコールのコードが含まれています。ライブラリを使わずにシステムコールを実行するのは、奇妙なプログラムです。 (たとえPerl
が直接システムコールを実行できるsyscall()
関数を提供していますが、クレイジーですよね?)
次に、メモリ。オペレーティングシステムカーネルときどきは、ユーザープロセスメモリへのアドレス空間アクセスを容易にします。もちろん、保護モードは異なり、ユーザーが入力したデータの変更を防ぐために、ユーザーが入力したデータをカーネルの保護されたアドレス空間にコピーする必要がありますシステムコールの実行中 =:
_static int do_getname(const char __user *filename, char *page)
{
int retval;
unsigned long len = PATH_MAX;
if (!segment_eq(get_fs(), KERNEL_DS)) {
if ((unsigned long) filename >= TASK_SIZE)
return -EFAULT;
if (TASK_SIZE - (unsigned long) filename < PATH_MAX)
len = TASK_SIZE - (unsigned long) filename;
}
retval = strncpy_from_user(page, filename, len);
if (retval > 0) {
if (retval < len)
return 0;
return -ENAMETOOLONG;
} else if (!retval)
retval = -ENOENT;
return retval;
}
_
これは、それ自体がシステムコールではありませんが、ファイル名をカーネルのアドレス空間にコピーするシステムコール関数によって呼び出されるヘルパー関数です。ファイル名全体がユーザーのデータ範囲内にあることを確認し、ユーザー空間から文字列をコピーする関数を呼び出して、戻る前にいくつかの健全性チェックを実行します。
get_fs()
および類似の関数は、Linuxのx86-rootの名残りです。関数はすべてのアーキテクチャーで機能する実装を持っていますが、名前は古いままです。
セグメントでの余分な作業はすべて、カーネルとユーザー空間mightが使用可能なアドレス空間の一部を共有するためです。 32ビットプラットフォーム(数値が理解しやすい)では、カーネルは通常1ギガバイトの仮想アドレス空間を持ち、ユーザープロセスは通常3ギガバイトの仮想アドレス空間を持ちます。
プロセスがカーネルを呼び出すと、カーネルはページテーブルのアクセス許可を「修正」して、範囲全体にアクセスできるようにし、ユーザーが提供するメモリに対して事前に入力された TLBエントリ の利点を得ます。 。大成功。ただし、カーネルがコンテキストをユーザー空間に戻す必要がある場合、カーネルはTLBをフラッシュして、カーネルアドレス空間ページのキャッシュされた特権を削除する必要があります。
しかし、秘訣は、1ギガバイトの仮想アドレス空間がnot巨大なマシン上のすべてのカーネルデータ構造に十分であることです。キャッシュされたファイルシステムとブロックデバイスドライバーのメタデータ、ネットワークスタック、およびシステム上のすべてのプロセスのメモリマッピングを維持するには、膨大な量のデータが必要になる場合があります。
したがって、さまざまな「スプリット」を使用できます。ユーザー用に2ギグ、カーネル用に2ギグ、ユーザー用に1ギグ、カーネル用に3ギグなどです。カーネル用のスペースが増えると、ユーザープロセス用のスペースが減ります。つまり、ユーザープロセスに4ギガバイト、カーネルに4ギガバイトを与える _4:4
_ メモリ分割があり、カーネルはセグメント記述子をいじってユーザーメモリにアクセスできる必要があります。 TLBは、システムコールの開始と終了でフラッシュされます。これは、かなり重大な速度ペナルティです。しかし、これによりカーネルは非常に大きなデータ構造を維持できます。
64ビットプラットフォームのページテーブルとアドレス範囲がはるかに大きいため、前述のすべての外観が古臭くなります。とにかくそう願っています。
はい、あなたはそれを大体正しくしています。ただし、コンパイラがシステムコールをコンパイルするときに、nameではなく、システムコールのnumberを使用します。たとえば、以下は Linux syscallsのリスト です(古いバージョンの場合でも、概念は同じです)。
実際には、Cランタイムライブラリを呼び出します。 TRAPを挿入するのはコンパイラではなく、TRAPをライブラリ呼び出しにラップするのはCライブラリです。あなたの理解の残りは正しいです。
通常のプログラムは通常、「syscallをコンパイル」しません。システムコールごとに、通常、対応するユーザースペースライブラリ関数(通常、Unixライクなシステムのlibcに実装されています)。たとえば、mkdir()
関数は、その引数をmkdir
syscallに転送します。
GNUシステム(他の人でも同じだと思います)では、syscall()
関数が 'mkdir()'関数から使用されます。syscall関数/マクロは通常実装されていますCで。たとえば、INTERNAL_SYSCALL
のsysdeps/unix/sysv/linux/i386/sysdep.h
またはsysdeps/unix/sysv/linux/i386/sysdep.S
(glibc)のsyscall
を見てください。
ここで、sysdeps/unix/sysv/linux/i386/sysdep.h
を見ると、カーネルへの呼び出しは、従来はi386 CPUで割り込みENTER_KERNEL
を呼び出す0x80
によって行われていることがわかります。これで関数が呼び出されます(カーネルによってマップされたlinux-gate.so
に実装されていると思いますSOカーネルによってマップされたファイル。これは、あなたのタイプのシステムコールをCPUにする最も効率的な方法を含んでいます)。
プログラムから直接システムコールを実行したい場合は、簡単に実行できます。プラットフォームに依存しますが、ファイルから読み取りたいとしましょう。すべてのシステムコールには番号があります。この場合、read_from_file
システムコールの番号をレジスタEAXに配置します。システムコールの引数は、(システムコールに応じて)異なるレジスタまたはスタックに配置されます。レジスタに正しいデータが入力され、システムコールを実行する準備ができたら、INT 0x80
命令を実行します(アーキテクチャによって異なります)。その命令は、OSに制御を移す原因となる割り込みです。次に、OSはEAXレジスタでシステムコール番号を識別し、それに応じて動作し、システムコールを実行しているプロセスに制御を戻します。
システムコールの使用方法は変更される傾向があり、特定のプラットフォームによって異なります。これらのシステムコールへの簡単なインターフェイスを提供するライブラリを使用することで、プログラムをよりプラットフォームに依存せず、コードの可読性と書き込み速度を大幅に向上させることができます。高水準言語で直接システムコールを実装することを検討してください。データが適切なレジスタに配置されるようにするには、インラインアセンブリなどが必要です。
はい、あなたの理解は完全に正しいです。Cプログラムは直接システムコールを呼び出すことができます。そのシステムコールが発生すると、アセンブリトラップまでの一連の呼び出しになります。私はあなたの理解が初心者に非常に役立つと思います。「システム」システムコールを呼び出しているこのコードを確認してください。
#include < stdio.h >
#include < stdlib.h >
int main()
{
printf("Running ps with "system" system call ");
system("ps ax");
printf("Done.\n");
exit(0);
}