510 likes | 616 Views
プログラミングの基礎 第 2 回 systemcall. workshop 資料作成委員会. 本ワークショップの目標. システムコールの仕組みを理解する カーネルモードとユーザモード ソフトウェア割り込み 演習 実 際にシステムコールを直接呼んでみる システムコールを使ったプログラミング. table of contents. システムコール概要 カーネルモードユーザーモード ソフトウェア割り込み システムコール実例 演習:. プログラミングとは?. コンピュータのデバイスを操作する命令手順を書くこと. Hello!. メモリ. ディスプレイ
E N D
プログラミングの基礎 第2回systemcall workshop 資料作成委員会
本ワークショップの目標 • システムコールの仕組みを理解する • カーネルモードとユーザモード • ソフトウェア割り込み • 演習 • 実際にシステムコールを直接呼んでみる • システムコールを使ったプログラミング
table of contents • システムコール概要 • カーネルモードユーザーモード • ソフトウェア割り込み • システムコール実例 • 演習:
プログラミングとは? • コンピュータのデバイスを操作する命令手順を書くこと Hello! メモリ ディスプレイ デバイス ディスプレイに 出力する命令手順 プログラミング
OSはすべてのデバイスを管理する • OS上でプログラミングするには? • OSに対してデバイスを操作する命令を発行 OS 画面に文字を 出力する プログラム
プログラム例 • 画面に”Hello!”を出力するprintf プログラミング 出力命令を発行 Hello! write() 実際に制御 出力関数(OSの機能) ディスプレイ デバイス
書いたプログラムを動かす! 実行プログラム ソースコード オブジェクトコード a.out hello.c hello.o リンク コンパイル 実行 Hello!
a.outの構成 • 元のhello.oよりもサイズが大きくなる a.out Cランタイムオブジェクト libCへの参照 hello.o 自分で書いた部分
前準備 • Hello, world! を作ってね! • FreeBSD か Linux ホストでお願いします. #include <stdio.h> #define MESSAGE “Hello, world!¥n” int main() { printf(MESSAGE); return(123); } Example program ちなみに,この時点でコードサイズは… 11368 バイト!
これから… • 邪魔なリンクファイル (crt*Cランタイムファイル) を消そう • そのかわり、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'
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() がない?? • -lgcc -lc を削除したことに注目しましょう。 • 前者はgcc固有ライブラリ、後者はlibcライブラリをリンクするためのものです。 • ですが今回はとっちゃいます。なぜでしょう? • ライブラリとは • ライブラリは、便利な関数を集めていつでも使えるようにしておいたファイルです。 • libcにはさまざまなC言語の標準関数が含まれています。 • printf,もその一つなのです。 • 逆に言うと、Hello World!を表示するだけのプログラムでは、printfの機能だけ使えればいいので、ライブラリ全てをリンクするのは無駄です。 • そこで • printfに相当する機能を自分で作ります。 -> そうだ! write() だ!
CランタイムとLibCの役割 • Cランタイム • C言語で書かれたプログラムを動作させる環境 • LibC • C言語からOSの機能を抽象化して使いやすくする USE Cランタイム
LibCの役割 • OSの機能を抽象化してくれる! printfは僕が持ってます LibC OSとのインタフェース (窓口)です システムコール OS
Cライブラリとシステムコール • 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 プロセス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ライブラリはシステムコールのラッパーを提供する • より使いやすく可搬性を高めるため 普通はCライブラリの中でシステムコールを呼んでくれる プロセス プロセス プロセス libc ユーザモード システムコール カーネル デバイス
システムコール リスト • 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_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出力(前回既にできた人は必要なし)
#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: /usr/include/asm/unistd.h • FreeBSD: /usr/include/sys/syscall.h
Linux FreeBSD システムコール〜引数の渡し方〜 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
Linux FreeBSD システムコールでhelloworld 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() { int ret; ret = writes(message, sizeof(message)); return(ret); } 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() { int ret; ret = writes(message, sizeof(message)); return(ret); }