プログラミングの基礎 第2回 systemcall workshop 資料作成委員会
本ワークショップの目標 システムコールの仕組みを理解する 演習 カーネルモードとユーザモード ソフトウェア割り込み 実際にシステムコールを直接呼んでみる システムコールを使ったプログラミング
table of contents システムコール概要 カーネルモードユーザーモード ソフトウェア割り込み システムコール実例 演習:
プログラミングとは? Hello! コンピュータのデバイスを操作する命令手順を書くこと メモリ ディスプレイ ディスプレイに デバイス 出力する命令手順 プログラミング
OSはすべてのデバイスを管理する OS OS上でプログラミングするには? OSに対してデバイスを操作する命令を発行 画面に文字を 出力する プログラム
プログラム例 Hello! 画面に”Hello!”を出力するprintf プログラミング 出力命令を発行 write() 実際に制御 出力関数(OSの機能) ディスプレイ デバイス
書いたプログラムを動かす! リンク コンパイル hello.c a.out hello.o ソースコード オブジェクトコード 実行プログラム
a.outの構成 元のhello.oよりもサイズが大きくなる Cランタイムオブジェクト libCへの参照 hello.o a.out 自分で書いた部分
実習: a.outからCランタイムとlibcを削除する
前準備 11368 バイト! Hello, world! を作ってね! FreeBSD か Linux ホストでお願いします. #include <stdio.h> #define MESSAGE “Hello, world!¥n” int main() { printf(MESSAGE); return(123); } Example program ちなみに,この時点でコードサイズは… 11368 バイト!
これから… 邪魔なリンクファイル (crt* Cランタイムファイル) を消そう いらないセクションを消そう これにより、プログラムがどのように動く(動かされている)のか知ることができます。 いらないセクションを消そう
邪魔なリンクファイルを消そう! 先ほどの説明で,crt* ファイルがコンパイル時にリンクされていることがわかりました. リンクしないように,-nostartfiles をつけてみましょう.
21:55 [0] skk@aries% gcc -v hello.c Using built-in specs. Configured with: FreeBSD/i386 system compiler Thread model: posix gcc version 3.4.4 [FreeBSD] 20050518 ・・・ /usr/bin/ld -V -dynamic-linker /libexec/ld-elf.so.1 /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/crtbegin.o -L/usr/lib /var/tmp//ccdlOU3m.o -lgcc -lc -lgcc /usr/lib/crtend.o /usr/lib/crtn.o GNU ld version 2.15 [FreeBSD] 2004-05-23 Supported emulations: elf_i386_fbsd
21:55 [0] skk@aries% gcc -v -nostartfiles hello.c Using built-in specs. Configured with: FreeBSD/i386 system compiler Thread model: posix gcc version 3.4.4 [FreeBSD] 20050518 /usr/libexec/cc1 -quiet -v -D_LONGLONG hello.c -quiet -dumpbase hello.c -auxbase hello – ・・・ elf_i386_fbsd /usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000008048200 /usr/lib/libc.so: undefined reference to `environ' /usr/lib/libc.so: undefined reference to `__progname'
プログラムの開始アドレスを _start じゃなく,main にしたい! warning: cannot find entry symbol _start; defaulting to 0000000008048200 「_start というシンボルが見つからないー.仕方ないから,.text セクションの先頭アドレスを開始アドレスにしちゃうもんね.」という意味。 _startはcrt1.o(Cランタイム)の中にある関数で、プログラム実行時に最初に呼び出される。今回はcrt1.oを丸ごと消したいので使えない。 プログラムの開始アドレスを _start じゃなく,main にしたい!
開始アドレスの変更 ld コマンドの -e オプションで変更できます! -e の後ろには,シンボル名を指定します. % gcc -c hello.c % ld -e main -o a.out hello.o hello.o(.text+0x25): In function `main': : undefined reference to `printf'
printf() がない?? -> そうだ! write() だ! -lgcc -lc を削除したことに注目しましょう。 前者はgcc固有ライブラリ、後者はlibcライブラリをリンクするためのものです。 ですが今回はとっちゃいます。なぜでしょう? ライブラリとは ライブラリは、便利な関数を集めていつでも使えるようにしておいたファイルです。 libcにはさまざまなC言語の標準関数が含まれています。 printf,もその一つなのです。 逆に言うと、Hello World!を表示するだけのプログラムでは、printfの機能だけ使えればいいので、ライブラリ全てをリンクするのは無駄です。 そこで printfに相当する機能を自分で作ります。 -c で,何もリンクしないオブジェクトファイルを作成して,自分で実行形式を作成しているもん.Printf とリンクする時間,ないよね. -> そうだ! write() だ!
CランタイムとLibCの役割 Cランタイム C言語で書かれたプログラムを動作させる環境 LibC C言語からOSの機能を抽象化して使いやすくする USE Cランタイム
LibCの役割 OSの機能を抽象化してくれる! printfは僕が持ってます LibC OSとのインタフェース (窓口)です システムコール
普通はCライブラリの中でシステムコールを呼んでくれる より使いやすく可搬性を高めるため 普通はCライブラリの中でシステムコールを呼んでくれる プロセス プロセス プロセス libc ユーザモード システムコール カーネル デバイス
システムコール概要
システムコールとは? OS(OSのカーネル)の機能を呼び出すために使用される機構のこと システムをカーネルに制御を移すための特別な命令を実行し、カーネルの機能を呼ぶ OS資源の利用 (デバイス、メモリ空間など) システムコールの実行 プロセス
プロセスとかカーネルって? プロセス カーネル アプリケーションの実行単位 アプリケーションが動作する実行環境を提供 プロセス プロセス デバイス
システムコールがあると何がうれしいの? システムのセキュリテが向上する プログラミングが楽になる プログラムの可搬性が向上 カーネルが処理を実行する前に処理要求の正当性を確認できる プログラミングが楽になる ハードウェアに関する低水準レイヤについて覚えなくてよくなる プログラムの可搬性が向上 カーネルが同じインタフェースを提供する限りにおいて
普通にプログラミングする場合、システムコールを直接呼ぶことはしません プログラミングの基礎における意味 プログラミングする上で、OSの存在を意識できるようになる このAPIを呼ぶと、カーネルに制御が移りOSの資源を利用できる プロセス カーネル 制御の移り変わり 普通にプログラミングする場合、システムコールを直接呼ぶことはしません
システムコールの特徴 CPUの実行権限を切り替える 割り込みによる実行 OSとアプリケーションの中間層 詳細は、この後に解説
カーネルモードとユーザーモード
概要 カーネルモードとユーザモード 異なるCPUのランレベルを利用する セキュリティと安全性のため異なる特権状態で命令を実行する Linuxでは特権モードと非特権モードの2つを使い分ける
カーネルモード あらゆるハードウェア資源にアクセス可能 オペレーティングシステムの実行モード プロセス プロセス プロセス ユーザモード システムコール カーネル カーネルモード デバイス
ユーザモード ハードウェア資源へのアクセスを制限・監視下でプログラムを実行 通常のプログラムの実行モード プロセス プロセス プロセス システムコール カーネル カーネルモード デバイス
CPUのランレベル 多くのCPUは2つ以上の実行モードを保有 実行レベルを使い分けることで安全性、安定性を向上させる Intel 80x86は4つの実行リング(特権の階層)を持つ ランレベル0をカーネルモード それ以上をユーザモードに割り当てる
CPUランレベル移行 コールゲートによるランレベルの移行 コールゲートを解した呼び出しだけが許される 特権レベルの低いコードセグメントから特権レベルの高いコードセグメントの呼び出し
コールゲート OSがランレベルの移行をコントロールできる機構 ゲートを経由しないと移行できない 呼び出せる特権レベルのゲートは現動作レベル以下だけ 移行先 特権レベル3の コールゲート
システムコールの本質 CPUランレベルを切り替えて、プログラムがデバイスを操作できるようにする仕組み デバイスを操作 できる領域 プロセス1 ユーザモード カーネルモード デバイスを操作 できる領域 システムコール システムコール ハンドラ
ランモードの切り替え方法 ソフトウェア割り込み(システムコールが使う) タイマ割り込み デバイス割り込み 例外割り込み プロセス1 ユーザモード カーネルモード システムコール タイマ割り込み デバイス割り込み システムコール ハンドラ スケジューラ 割り込みハンドラ
ソフトウェア割り込み
概要 システムコールはソフトウェア割り込みで行 システムコールを実行する手順 割り込みベクターは 0x80 システムコールの引数は,すべてレジスタで渡される システムコールを実行する手順 レジスタに必要な値を設定 int 0x80 を実行する
システムコールが利用するレジスタ
システムコールの流れ Linuxの場合 システムコール番号をeaxレジスタにセット 必要に応じてほかの引数もレジスタにセット int 0x80ソフトウェア割り込みを発行 カーネルモードスタック上にレジスタ内容を退避 システムコールサービスルーチンを呼び出す システムコールの実処理 ハンドラから抜ける
システムコールの流れ図 (38: sys_xyzのシステムコール 番号とする) システムコール番号 や引数をセット カーネルモード ユーザモード system_call: ・・・ sys_xyz() iret ・・・ xyz() xyz(){ ・・・ int 0x80 } sys_xyz(){ ・・・ } システムコール ハンドラ システムコール サービスルーチン アプリケーション プログラムからの システムコール発行 libc標準ライブラリ のラッパールーチン
普通はCライブラリの中でシステムコールを呼んでくれる より使いやすく可搬性を高めるため 普通はCライブラリの中でシステムコールを呼んでくれる プロセス プロセス プロセス libc ユーザモード システムコール カーネル デバイス
システムコール実例
システムコール リスト Linux #define __NR_exit 1 #define __NR_fork 2 システムコール リスト Linux #define __NR_exit 1 #define __NR_fork 2 #define __NR_read 3 #define __NR_write 4 #define __NR_open 5 #define __NR_close 6 #define __NR_waitpid 7 #define __NR_creat 8 #define __NR_link 9 #define __NR_unlink 10 #define __NR_execve 11 #define __NR_chdir 12 #define __NR_time 13 #define __NR_mknod 14 #define __NR_chmod 15 #define __NR_lchown 16
システムコール リスト FreeBSD /* 11 is obsolete execv */ #define SYS_syscall 0 システムコール リスト FreeBSD /* 11 is obsolete execv */ #define SYS_chdir 12 #define SYS_fchdir 13 #define SYS_mknod 14 #define SYS_chmod 15 #define SYS_chown 16 #define SYS_break 17 /* 18 is old getfsstat */ /* 19 is old lseek */ #define SYS_getpid 20 #define SYS_mount 21 #define SYS_syscall 0 #define SYS_exit 1 #define SYS_fork 2 #define SYS_read 3 #define SYS_write 4 #define SYS_open 5 #define SYS_close 6 #define SYS_wait4 7 /* 8 is old creat */ #define SYS_link 9 #define SYS_unlink 10
strateでシステムコールをトレースする straceって? システムコールのトレースを行ってくれる! straceの仕組み システムコールのenterとexitをフックして引数と返り値を出力する OSのデバック用インタフェースを用いる Strace フック フック ユーザモード システムコール カーネルモード
strace: emacs on linuxの一部出力 execve("/usr/bin/emacs", ["emacs"], [/* 21 vars */]) = 0 uname({sys="Linux", node="einstein", ...}) = 0 brk(0) = 0x8424000 old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0 x40017000 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) open("/etc/ld.so.preload", O_RDONLY) = -1 ENOENT (No such file or directory) close(3) = 0 read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\220\342"..., 512) = 512
演習:直接システムコールを呼ぼう システムコールを使ったhelloworld出力 (前回既にできた人は必要なし)
システムコールを呼ぼう システムコールには,番号がついています. Linux: /usr/include/asm/unistd.h FreeBSD: /usr/include/sys/syscall.h #define SYS_syscall 0 #define SYS_exit 1 #define SYS_fork 2 #define SYS_read 3 #define SYS_write 4 #define SYS_open 5 #define SYS_close 6 #define SYS_wait4 7 $FreeBSD: src/sys/sys/syscall.h,v 1.178.2.1 2005/11/21 01:36:27 csjp Exp $
システムコール〜引数の渡し方〜 Linux FreeBSD 引数の順番と逆にスタックに積んでいく mov $4, %eax EBX レジスタ 第一引数 ECX レジスタ 第二引数 EDX レジスタ 第三引数 ESI レジスタ 第四引数 EDI レジスタ 第五引数 引数の順番と逆にスタックに積んでいく mov $4, %eax mov $1, %ebx mov buf, %ecx mov length, %edx int 0x80 push length push buf push $1 push $4 int 0x80
システムコールでhelloworld Linux FreeBSD const char message[] = "hello world¥n"; int writes(const char *buf, int len) { int ret; asm("nop" :: "b"(len)); asm ("pushl %ebx"); asm("nop" :: "c"(buf)); asm("pushl %ecx"); asm("pushl $1"); asm("movl $0x4, %eax"); asm("pushl %eax"); asm("int $0x80"); asm("addl $12, %esp"); return(ret); } int main() { ret = writes(message, sizeof(message)); const char message[] = "hello world¥n"; int writes(const char *buf, int len) { int ret; asm( "int $0x80" : "=a" (ret) : "a" (4), "b" (1), "c" (buf), "d" (len) ); return(ret); } int main() { ret = writes(message, sizeof(message));
コンパイル&実行! % gcc -o helloworld helloworld % ./helloworld hello world