Linuxシステムコール:Linuxのシステムコール実装

Linuxのシステムコール実装

 これまで述べてきたようにOS(カーネル)の持つ機能へのインターフェイスがシステムコールですが、システムコールの実態はカーネルの中にあります。このため、ユーザープログラムがシステムコールを呼び出すとユーザーモードからカーネルモードへの切り替わりが発生し、カーネル内部での処理完了でユーザーモードに復帰します。

 例として、ファイルからデータを読み出す場合を見てみましょう。


 ユーザープログラムがファイルを読むため、システムコールを呼び出します。このときCPUのソフトウェア割り込み、またはシステムコール命令が発行されることで、CPUはカーネルモードに移行します。システムコール命令を発行するとき、特定のレジスタに実行したいシステムコールの番号を設定しておきます。CPUはカーネルモードに切り替わるとシステムコール番号からシステムコールテーブルを参照して、カーネル内部の所定の処理を呼び出します。

 この例では、ファイル読み込みが呼ばれ、その中で物理的なファイルを読み込み、ユーザー領域のバッファにデータを転送し、ユーザープログラムがシステムコールを呼んだ場所にリターンします。ユーザー空間にリターンすることで、CPUのモードもカーネルモードからユーザーモードに移行します。

 システムコールを呼び出すことで、これらの一連の処理が実行されています。

 少し余談になりますが、ライブラリもハードウェアとして実態のファイルにアクセスするためにシステムコールを使っています。なぜ同じような目的なのにわざわざライブラリにしているのでしょうか?

 目的は2つあります。1つはシステムコールのラッパーとしての役割、もう一つは抽象化したシステムの提供です。

 OSがWindowsである場合、ファイル読み込みのシステムコールは ReadFile() ですが、Windows上で動作するc言語のstdioで定義されているファイル読み込みは同じ fread() で、Linux上で動作する fread() と同様の動作をします。

 このように、OSが変わっても同様のプログラミングが可能なようにする役割もライブラリは持っています。

 また、抽象化とは、今回の例ではファイルの読み込みですが、ファイル読み込みはシステムコールでは read() です。C言語のライブラリでは fread() です。ライブラリ関数では、ファイルをストリームの概念でアクセスできるようにしています。

 カーネルモードへの移行は、これまではソフトウェア割り込みを発生させることによるカーネルモード移行でしたが、割り込みの場合、割り込み回路を駆動 → 割り込みベクタからジャンプ → システムコールテーブルでジャンプ。。。と、CPUの負荷が大きいため、最近ではCPUのシステムコール命令でカーネルモードへ移行するようになってきています。



さて、ココから先は実際のLinuxコードがどうなっているか?
についてもう少し詳しく見ていきたいと思います。

下記は、テキストファイルを読むだけの簡単なプログラムです。
(エラー処理とかちゃんと入っていません!)

#include <stdio.h> #include <string.h> #include <unistd.h> #include <fcntl.h> int main() {     int fd, ret;     char buf[16];     fd = open("test.txt", O_RDONLY );     memset(buf, 0, sizeof(buf));     read(fd, buf, sizeof(buf));     printf("[%s]\n", buf);     close(fd); }

このソースでは3つのシステムコールを使っています。
   open(), read(), close()

実際にシステムコールがどのように呼ばれるのか?
上記のソースをコンパイラでアセンブラを出力してみます。(gcc -S)

         .arch armv5t         .fpu softvfp         .eabi_attribute 20, 1         .eabi_attribute 21, 1         .eabi_attribute 23, 3         .eabi_attribute 24, 1         .eabi_attribute 25, 1         .eabi_attribute 26, 2         .eabi_attribute 30, 6         .eabi_attribute 34, 0         .eabi_attribute 18, 4         .file        "reader.c"         .section        .rodata         .align        2 .LC0:         .ascii        "test.txt\000"         .align        2 .LC1:         .ascii        "[%s]\012\000"         .text         .align        2         .global        main         .syntax unified         .arm         .type        main, %function main:         @ args = 0, pretend = 0, frame = 24         @ frame_needed = 1, uses_anonymous_args = 0         push        {fp, lr}         add        fp, sp, #4         sub        sp, sp, #24         ldr        r3, .L4         ldr        r3, [r3]         str        r3, [fp, #-8]         mov        r1, #0         ldr        r0, .L4+4         bl        open         str        r0, [fp, #-28]         sub        r3, fp, #24         mov        r2, #16         mov        r1, #0         mov        r0, r3         bl        memset         sub        r3, fp, #24         mov        r2, #16         mov        r1, r3         ldr        r0, [fp, #-28]         bl        read         sub        r3, fp, #24         mov        r1, r3         ldr        r0, .L4+8         bl        printf         ldr        r0, [fp, #-28]         bl        close         mov        r3, #0         mov        r0, r3         ldr        r3, .L4         ldr        r2, [fp, #-8]         ldr        r3, [r3]         cmp        r2, r3         beq        .L3         bl        __stack_chk_fail .L3:         sub        sp, fp, #4         @ sp needed         pop        {fp, pc} .L5:         .align        2 .L4:         .word        __stack_chk_guard         .word        .LC0         .word        .LC1         .size        main, .-main         .ident        "GCC: (Ubuntu/Linaro 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609"         .section        .note.GNU-stack,"",%progbits

アセンブラからのシステムコール呼び出しは、bl命令で呼ばれています。このbl命令はリンクレジスタにbl命令の次のアドレスを設定して分岐します。分岐先でのRET命令でこのbl命令の次に戻ってきます。つまり、サブルーチンの呼び出し(Call)と考えてよいでしょう。
じつは、システムコールもイキナリ、ソフトウェア割り込み命令に展開されるのではなく、システムコールのラッパーを呼び出しています。

システムコールのラッパーでは、ARM の swi 命令を実行してカーネルモードに移行し、システムコールを実行します。

このような実装となっているのは、カーネルモードへの移行方法がCPU依存になっているからです。カーネルとのインターフェイスは、ABI(Application Binary Interface)として定義されていますが、ARMでは2つのABIが定義されています。最近使われているABIは、EABIと呼ばれるインターフェイスで、EはEmbeddedの頭文字です。もう一つはOABIでこちらは古いと言う意味からOldのOが割り当てられています。これらは、クロスコンパイラなどのツールーチェーンのプレフクス表示に確認することができます。


Lightning Brains

コメント

このブログの人気の投稿

Linuxシステムコール、共有メモリの使い方

Linuxシステムコール、メッセージキューの使い方

Linuxシステムコール、セマフォの使い方