Linuxのユーザモードとカーネルモード

こんにちは。構築担当の下地です。

夏休みは名古屋に行って、矢場とんや山本屋本店でグルメめぐりをしていました。

少し暑かったですが名古屋めし美味しかったです。

さて、今日はLinuxにおけるCPUのモードにお話します。

宜しくお願いします。

ユーザモードとカーネルモード

CPUにはユーザモードとカーネルモードの2つのモードがあります。

通常、OSは各種プログラムをプロセスという単位で実行しますが、プロセスはユーザモードで動作します。

そして、プロセスからデバイス(ストレージやネットワークアダプタなど)を操作したい場合に、デバイスドライバを介してデバイスにアクセスします。

デバイスドライバはカーネルモードで動作します。

ユーザモードとカーネルモード.JPG

なぜ2つのモードがあるのか

仮にプロセスが自由にデバイスへアクセスできるとすると、複数のプロセスが1つのデバイスを同時に操作することができてしまいます。

そういった事態を防ぐため、Linuxではプロセスから直接デバイスへアクセスできないようになっています。

具体的には、ユーザモードとカーネルモードという2つのモードを用意し、カーネルモードで動作しているときのみデバイスにアクセスできるようにします。

デバイスの操作以外にも、プロセスから直接操作できると困る処理がたくさんあります。

・プロセスの管理

・プロセススケジューラ

・メモリの管理

などです。

これらもカーネルモードで動作します。

このようなカーネルモードで動作するOSの核となる処理をまとめたプログラムをカーネルと呼んでいます。

システムコールと割り込み

先に書いたように、プロセスは直接デバイスを含むカーネルが提供する機能を使うことはできません。

それらを使いたければ、すべて「システムコール」と呼ばれる特殊な処理を介す必要があります。

システムコールには次のような種類があります。

・プロセス生成、削除

・メモリ確保、解放

・プロセス間通信

・ネットワーク

・ファイルシステム操作

・デバイスアクセス

プロセスは通常ユーザモードで実行していますが、カーネルに処理を依頼するためにシステムコールを発行すると、CPUにおいて「割り込み」というイベントが発生します。

これによりCPUはユーザモードからカーネルモードに遷移して、依頼内容に応じてカーネルモードで処理が開始されます。

カーネルモードでの処理が完了すると再びユーザモードに戻ってプロセスの動作を継続します。

システムコールの様子を実際に見てみる

プロセスがどんなシステムコールを発行するかは、straceコマンドで見ることができます。

実際にやってみましょう。

まず簡単なプログラムを作成します。

いわゆるhello worldです。

#include <stdio.h>
int main(void)
{
    puts("hello world");
    return 0;
}

書けたらコンパイルして動かしてみます。

# cc -o hello hello.c
# ./hello
hello world

OKでした。
ではstraceコマンドで、このプログラムがどんなシステムコールを発行するかを見てみます。

# strace -o hello.log ./hello
hello world
# cat hello.log
execve("./hello", ["./hello"], [/* 22 vars */]) = 0
brk(NULL) = 0x8ac000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1129f99000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=26393, ...}) = 0
mmap(NULL, 26393, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f1129f92000
close(3) = 0
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "177ELF21133>1P%2"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2173512, ...}) = 0
mmap(NULL, 3981792, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f11299ac000
mprotect(0x7f1129b6f000, 2093056, PROT_NONE) = 0
mmap(0x7f1129d6e000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c2000) = 0x7f1129d6e000
mmap(0x7f1129d74000, 16864, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f1129d74000
close(3) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1129f91000 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1129f8f000 arch_prctl(ARCH_SET_FS, 0x7f1129f8f740) = 0
mprotect(0x7f1129d6e000, 16384, PROT_READ) = 0 mprotect(0x600000, 4096, PROT_READ) = 0
mprotect(0x7f1129f9a000, 4096, PROT_READ) = 0 munmap(0x7f1129f92000, 26393) = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1129f98000 write(1, "hello worldn", 12) = 12 exit_group(0) = ? +++ exited with 0 +++

このように私の環境では27回システムコールが発行されました。

この中で見るべきは

write(1, "hello worldn", 12) = 12

の部分です。

ここではデータを画面やファイルなどに出力するwrite()システムコールによって、hello worldという文字列が画面に出力されていることが分かります。

まとめ

OSとプロセスがどのように動作しているかを理解すれば、問題が起きたときの切り分けなどに役立ちます。

このようなハードウェアに近い部分も学習すると面白いですね。

お読み頂きありがとうございました。

返信を残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA