魅力的なLinuxシステムコールの世界について学びましょう

By 清水 孝郎 - MAY 17, 2022

SHARE:

本文の内容は、2014年12月17日にSysdigのCTO Loris Degionanniが投稿したブログ(https://sysdig.com/blog/fascinating-world-linux-system-calls/)を元に日本語に翻訳・再構成した内容となっております。

Linuxのシステムコールについて話しましょう。 簡単に言うと、システムコールは、プログラムがオペレーティングシステムとやり取りするための基本的な方法です。 システムコールの基本的な理解は、どのようなプロ並みのLinuxユーザにとって必要条件である事は言うまでもありません。

システムコールインタフェースには、オペレーティングシステムがその上で実行されているアプリケーションにエクスポートする多数の機能が含まれています。 これらの関数は、ファイルを開く、ネットワーク接続を作成する、ファイルから読み書きするなどの操作を可能にします。 実際には、どのマシンでも起こることのほとんどはシステムコールを通過します。 結果として、システムコールを監視することは、プログラムが何をしているのかについての非常に深い洞察を提供することができ、トラブルシューティング、監視、およびボトルネックの識別に非常に貴重な情報となります。

システムコールインタフェースがLinuxでどのように働くかについて興味がありますか? システムコールをトレースする方法と、それらをトレースすることによって何ができるかを学びたいですか? どのシステムコールが注目に値するのか、その理由はなぜでしょうか?

その理由を知りたい方は、このブログはうってつけです

ちょっとした理論から始めましょう。

Linuxにおけるシステムコールの仕組み

Wikipediaではシステムコールを次のように定義しています。

*コンピューティングにおいて、システムコールは、プログラムがオペレーティングシステムのカーネルにサービスを要求する方法です。 これは、ハードウェア関連サービス(例えば、ハードディスクへのアクセス)、新しいプロセスの作成および実行、および(スケジューリングのような)不可欠なカーネルサービスとの通信を含み得る。 システムコールは、プロセスとオペレーティングシステム間の重要なインタフェースを提供します。 *

システムコールはそれを使用するプログラムへの関数呼び出しのように見えますが、実際にはユーザーモードからカーネルモードへのトランザクションを必要とするため、関数呼び出しよりも少し複雑です。

下図は、C標準ライブラリの基本関数の1つであるfwriteの呼び出しに含まれる一連の手順を示しています(注意、わかりやすくするために少し簡略化しています)。



プロセスが含むステップは以下の通りです:

  1. fwriteは、他のC標準ライブラリと一緒にglibc *で実装されています。glibc *は、Linuxオペレーティングシステムのコアコンポーネントの1つです。
  2. fwriteは本質的にwrite library呼び出しのラッパーです。
  3. writeはシステムコールID(writeの場合は1)と引数をプロセッサレジスタにロードしてから、プロセッサをカーネルレベルに切り替えます。 これがどのように行われるかは、プロセッサアーキテクチャ、そして時にはプロセッサモデルに依存します。 たとえば、x86プロセッサは通常割り込み80を呼び出しますが、x64プロセッサはsyscallプロセッサ命令を使用します。
  4. カーネル空間で実行中のプロセッサは、システムコールIDをsyscallテーブルに渡し、オフセット1の位置にある関数ポインタを抽出して呼び出します。 この関数sys_writeは、ファイルを書くためのカーネル実装です。私を信じていませんか。あなたはこのコードを試すことができます

# ----------------------------------------------------------------------------------------
# helloworld.s. Hello world in assembly!
# ----------------------------------------------------------------------------------------

     .global _start

    .text
_start:
# write(1, message, 13)
    mov     $1, %rax                # system call ID. 1 is write
    mov     $1, %rdi                # file handle 1 is stdout
    mov     $message, %rsi          # address of string to output
    mov     $13, %rdx               # string length
    syscall                         # system call invocation!

# exit(0)
    mov     $60, %rax               # system call ID. 60 is exit
    xor     %rdi, %rdi              # we want return code 0
    syscall                         # system call invocation!
message:
    .ascii  "Hello, worldn"

コンパイルします。

$ gcc -o helloworld -nostdlib helloworld.s


そう、これはx64プロセッサ上で最低レベルのhello worldの実装です。 これはhello worldを標準出力に依存せずに出力します。Linuxカーネルだけです。 :-)

その低レベルであることがあなたを不安にさせるのであれば、glibcはsyscallと呼ばれる関数をあなたに提供します。それはシステムコールインタフェースを探索するために使うことができます。

#include <sys/syscall.h>


int main() {
    syscall(SYS_write, 1, "Hello, worldn", 13);
    return 0;
}



システムコールを監視する方法

システムコールを監視するために使用できるツールはいくつかあります。 最もよく知られているツールである、straceは多くのオペレーティングシステムで利用可能であり、あなたはおそらくすでにあなたのマシン上にあります。 その最も単純な形式では、それを呼び出して、監視したいプロセスの名前を続けます。

> strace myprogram


sysdigは新しい技術です、そして、あなたはこれらの指示に従うことによってインストールできます。 sysdigを使ってできることはたくさんありますが、その最も単純な形式で実行するだけで、システム上で発生しているすべてのシステムコールが表示されます。 これはかなり多くなる傾向があるので、sysdigとフィルタを併用して、その出力をもっと見やすくすることができます。

> sysdig proc.name=myprogram


これはsysdigのブログなので、この記事の残りの部分ではシステムコールキャプチャツールとしてのsysdigに焦点を当てます。 sysdigの出力を解釈するのに手助けが必要な場合は、ブログ記事「Sysdigの出力を解釈する」を参照してください。


見るべきシステムコール

**clone(man page)

****何をするのか?

** 新しいプロセスを作る

**なぜそれを見るべきなのか?

**クローンは本質的にプロセスとスレッドを作成するための唯一のLinuxカーネルエントリポイントです。 これを見ると、すべてのプロセスとスレッドの実行アクティビティを観察することができます。 戻り値は、子スレッドでは0、親スレッドでは子pidです。 クローンは最も複雑なシステムコールの1つであり、作成されている新しいプロセスについて多くのことを知らせることができるたくさんのフラグを持っています。

** sysdig filter

**evt.type=clone

** Notes

** クローンは、親プロセスと子プロセスに1回ずつ、合計2回(!)を返す唯一のシステムコールです。 混乱しないでください。 :-)クローンはリソースを消費する傾向があるので、あなたのアプリがあまり呼んでいない事を確認してください。

execve **(man page)

****何をするのか?

**新しいプログラムを実行する

**なぜそれを見るべきなのか?

** Execveは、プログラムを実行するための唯一のLinuxカーネルエントリポイントです。 ユーザ空間APIにはexeclやfexecveのようないくつかの変種がありますが、それらはすべてexecveシステムコールを呼び出すことになります。 ほとんどの場合、クローン作成後にexecveが実行されます。execveの呼び出しを監視すると、システムで何が実行されたのかがわかります。 すべてのプログラム、スクリプト、またはcronジョブはexecveを実行する必要があります。

**sysdig filter

**evt.type=execve

**chdir (man page)

***何をするのか?

**現在のプロセス作業ディレクトリを変更します

**なぜそれを見るべきなのか?

** execveが実行されたすべてのコマンドラインを表示している場合、chdirはアクセスされたすべてのディレクトリを表示します。 実行してみましょう!

> sysdig evt.type=chdir

その後、別のシェルからディレクトリにアクセスします。 :-)

**sysdig filter

**evt.type=chdir

**open/creat (man page)

****何をするのか?

**ファイルまたはデバイスを開き、場合によっては作成します。

**なぜそれを見るべきなのか?

**このシステムコールをたどることで、ファイルがいつ作成されたのか、またはいつ触れられたのかがわかります。
**sysdig filter

**evt.type=open

evt.type=creat

**Notes

**フィルタを使用すると、sysdigでこのシステムコールをトレースするのがはるかに生産的になります(そして楽しくなります)。 例えば:

> sysdig evt.type=open and proc.name=evilproc and file.name contains /etc

> sysdig evt.type=open and proc.name=niginx and file.name contains .log


**connect (man page)

*****何をするのか?

**ソケットで接続を開始します

**なぜそれを見るべきなのか?

**これは、ネットワーク接続を確立するための唯一のカーネルエントリポイントです。 あなたのマシン上のプロセスが何かに接続しようとする度に、あなたはこのシステムコールを見るでしょう。

**sysdig filter

**evt.type=connect

**Notes

**繰り返しになりますが、フィルタを使用して楽しい要素を増やします。 例えば:


> sysdig evt.type=connect and fd.port=80
> sysdig "evt.type=connect and fd.sip!=127.0.0.1"



**accept (man page)

****何をするのか?

**ソケットで接続を受け入れます

**なぜそれを見るべきなのか?

**このシステムコールは、サーバー接続がマシン上で確立されるたびに表示されるという意味でconnectに接続します。

**sysdig filter

**evt.type=accept

**Notes

**楽しい要因のためのフィルター。 例えば:

> sysdig evt.type=accept and fd.port=80
> sysdig "evt.type=accept and proc.name=nginx and fd.port!=80"



**read (man page) / write (man page)

****何をするのか?

** readとwriteシステムコールはLinuxシステムコールインタフェースに多くのバリエーションがあります:readv、writev、preadv、pwritev、send、recv、sendto、recvfrom、sendmsg、sendmmsg、recvmsg、recvmmsg。 それらはすべてほぼ同じことを行い、データをファイル記述子に読み書きします。これがI / Oを行う際の中心です。

**なぜそれを見るべきなのか?

** Linuxのほとんどすべてがファイル記述子であるため、これらの呼び出しを観察することは非常に便利です。 それらはファイルへのアクセス、ネットワークデータ交換、パイプとunixソケットでの活動を示すでしょう。 ファイルディスクリプタの詳細については、昨日の投稿を参照してください。

**sysdig filter

**evt.is_io=true

**Notes

** sysdigは、入出力活動を本質的に観察するための“chisel”を提供しています。 このようにフィルターと一緒に使用できます。

> sysdig -c echo_fds fd.name=/etc/passwd


またはこんな感じ

> sysdig -c echo_fds proc.name=nginx and fd.port!=80


この”chisel”に精通すると、多くの状況で非常に便利です。

**unlink (man page) / rename (man page)

****何をするのか?

**ファイルの削除、名前変更

**なぜそれを見るべきなのか?

**open / creatは新しいファイルがいつ作成されるのかを示し、read / writeバリアントはいつファイルが修正されるのかを示しますが、unlinkとrenameはいつファイルが削除または名前変更されるのかを示します。

**sysdig filter

**evt.type=unlink

evt.type=rename

**brk (man page) / mmap (man page) / munmap (man page)

****何をするのか?

**メモリの割り当て/解放

**なぜそれを見るべきなのか?

**プロセスがいつメモリを割り当てたり解放したりするかを観察したい場合、これらは監視するシステムコールです。 brkは伝統的にmalloc()によって使用されますが、mmap / mmunmapはより用途が広く、メモリの共有やファイルのメモリへのマッピングなど、いくつかの目的で使用されます。

**sysdig filter

**evt.type=brk

evt.type=mmap

evt.type=munmap

**Notes

**注意:メモリ管理はこのブログ記事の範囲を超えた複雑な問題です。 プログラムでmalloc()またはnew()の結果としてbrkまたはmmapが表示されることはほとんどありません。通常のプログラムでは、メモリを取得する前に少なくとも1つまたは2つのエンティティを通過する可能性が高いからです。 たとえば、glibcには、カーネルに大きなチャンクでメモリを要求し、それをユーザレベルで管理するメモリアロケータがあります。 あなたの好きなVMベースの言語のガベージコレクタは似たようなことをします。

**select (man page) / poll (man page)

****何をするのか?

**何かを待つ

**なぜそれを見るべきなのか?

**プロセスが積極的に何かをしていないときはいつも、これらの2つの呼び出しのうちの1つでスタックする可能性が非常に高いです。 その引数を観察することは、それは、どのくらいの時間を待っているのか把握できます。

**sysdig filter

**evt.type=select

evt.type=poll

**kill (man page)

****何をするのか?

**シグナルを送る

**なぜそれを見るべきなのか?

** killはプロセスが他のプロセスにシグナルを送るために使われます。

**sysdig filter

**evt.type=kill



まとめ

システムコールに慣れることはあなたのツールベルトの中で信じられないほど便利なツールであるだけでなく、それはまたとても楽しいです、それで私はあなたがあなた自身で実験することを勧めます。 ところで、この素敵な印刷可能なチートシートはとても良い参考資料です。

また、sysdig twitterアカウントから便利なsysdigコマンドラインを定期的に投稿していますので、必ずフォローしてください。

*あなたが興味を持って探検したいのであれば、内部名は_IO_file_fwriteです。

** forkとvforkという2つの追加のシステムコールもありますが、それらが使用されることはめったにありません。