システム・プログラム 電子・情報工学系 新城 靖 <yas@is.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.hlla.is.tsukuba.ac.jp/~yas/coins/syspro-2002/2002-04-15
あるいは、次のページから手繰っていくこともできます。
http://www.hlla.is.tsukuba.ac.jp/~yas/coins/
http://www.is.tsukuba.ac.jp/~yas/index-j.html
この講義では、Unix の API (Application Programming Interface) を利用し てプログラムを作成する。API は、次の3つに分類される。
この講義で扱う Unix の API は、他のものと比較すると簡単になっている。 複雑なものには、Xウインドウ・システム、MS-Windows (Win32 API) などが ある。
「雰囲気」から「仕組みの理解」へ。
カーネル ( kernel ) とは、直接ハードウェアを制御する、オペレーティング・システムの中心部 である。たとえば、ハードディスクやキーボードとの入出力を行ったり、ネットワー クを制御したりする。
UNIXのカーネルは、ファイル、プロセス、プロセス間通信といったシステムの 基本的な機能を提供する。具体的には、ファイルのアクセス権の確認、プロ セスの保護、CPU資源のスケジューリング、パイプ、ソケット、 TCP/IPによるプロセス間通信機能等がある。
シェル ( shell ) とは、カーネルを取り囲んでいる殻という意味である。シェルは、利用者と対話 を行い、利用者からの入力を解析し、それにしたがってプログラム(コマンド) を実行します。シェルのことを、 コマンド・インタプリタ (command interpreter ) と呼ぶこともある。
実験では、シェルを作ることで、システムの仕組みを学ぶ。
標準入力、標準出力、標準エラー、パイプによる結合、入出力の切り換えは、 シェルの仕事である。
コマンド ( command ) とは、利用者がシェルに与える指令である。UNIXのシェルは、指令を受け取っ ても、具体的な仕事をほとんど何もしない。cd, exit, set, umask のような、 自分自身の状態を変えたりプログラムの実行環境を変えるものを除いて、全部 外部のプログラムを実行する。シェル自身が実行するコマンドを 組込みコマンド ( built-in command )、 そうでないものを 外部コマンド ( ) とう。
UNIXでは、ディレクトリの一覧表、ファイルのコピー、ファイルの画面への表 示といった基本的な仕事も、それぞれ ls, cp, cat といった名前の外部コマ ンドにより実行される。
UNIXを使って仕事をする人(利用者、ユーザ)にとって大事なものは、シェル とコマンドである。UNIXの上でプログラムを書く人 ( プログラマ ) プログラマにとって大事なことは、 システム・コール ( system call ) と ライブラリ ( library ) である。
システム・コールというのは、カーネル(システム)の機能を呼出すことである。 たとえば、ファイルの入力出力でいうと、単純な読込み read() や、単純な書 込み write() がシステム・コールである。システム・コールをより使いやすい 形にしたものが、ライブラリである。たとえば、ファイルの内容を1行読み込む fgets()や、書式付きの出力を行うprintf() などがライブラリになっている。
システム・コールもライブラリも、C言語からは、両方とも関数呼出しの形で 使えるようになっていて、一見、区別がつかない。ただしマニュアルを見ると、 システム・コールは2章、ライブラリは、3章に入っている。man コマンドで 見た時に、最初の行に"READ(2)"のように、"(2)" となっていれば、システム・ コール、"PRINTF(3)" のように、"(3)" になっていれば、ライブラリである。
システム・コールは、トラップ命令(trap instruction)を含んでいる。これ は、カーネルに制御を移す働きがある。この部分は、アセンブリ言語で書かれ ている。ライブラリ関数は、アセンブリ言語で書かかれているものもあるが、 大部分はC言語で書かれている。
システム・コールとライブラリの集合を API (Application Programming Interface) という。 たとえC言語で書かれていても、API が違うとコンパイルして機械語に変換す ることはできても、実行できない。
ライブラリやシステム・コールは、コンパイルされてオブジェクト・コードの 形で保存されている。これらは、実行形式を作る時に 抜き出され、アプリケーション・プログラマから作成した オブジェクト・コードと結合される。この操作を、 リンク(link) ( リンケージ・エディット(linkage edit)、 結合編集 ) という。
リンクには、静的リンクと動的リンクの2種類がある。 静的リンク(static link) では、プログラム実行する前(実行形式を作成する時点)で、ライブラリ関数の 呼出しをすべて機械語の手続き呼び出し命令にしたり、データへの参照を絶対 番地に変換する。 動的リンク(dynamiclink) では、プログラム実行中に必要に応じてリンク処理を行う。
動的リンクを行う時には、しばしば 共有ライブラリ(shared library) を使う。これは、複数のプロセスで共通に使うライブラリを動的リンクで共有 するものである。動的リンクを使うことで、実行時の環境に応じてプログラム の動きを変えることができる。この機能を使って、たとえば、古いバージョン のシステムでコンパイルされた実行形式をバージョンのシステムで動作させる ことができる。また、共有ライブラリを使うと、1つひとつの実行形式が小さ くなるので、メモリの節約にななる。
デーモン ( daemon ) とは、本来オペレーティング・システムの一部であるが、他のプログラムと 同じようにプロセスとして活動しているプログラムである。たとえば、プリント・ デーモンは、カーネルと同じように、いろいろな利用者からの仕事(プリント) を請け負う。デーモンというのはUNIXの言葉で、一般的には、 サーバ・プロセス ( server process ) とう。これは、サービスを提供するプロセスを意味する。
最近では、 GUI(Graphical User Interface) を提供するコンピュータが普通である。GUIを提供するための中心的なシス テムであるウィンドウ・システムである。ウィンドウ・システムも、シェルに 並んでコンピュータの利用者に対する見え方を決定する重要な要素である。
ウィンドウ・システムは、オペレーティング・システムとは独立した存在とし て扱わることもありますが、目的や役割を考えるとオペレーティング・システ ムと共通している。 O2 で動いているウィンドウ・システムは、Xウィンドウである。Xウィンド ウでは、モニタやキーボードを管理するサーバ・プロセスとそれらを利用する クライアント・プロセスに分かれている。プロセスとは、一言で言えば、プログラムの実行時のイメージ。ここで、 プ ログラムというのは、CPUが実行できる機械命令の集まり(実行形式、ロー ド・モジュール)のことである。
1つのプログラムを同時に2個動かすことを考えるとプログラムとプロセスの 違いがはっきりする。プロセス(process)は、プロセッサ(processor,CPU)と関 係ある。
プロセスには、「利用者の代理」として計算機の中に入り込むものという側面 がある。
プロセスの操作
UNIXでプロセスを観察するには、ps コマンドを使う。
---------------------------------------------------------------------- % psPID TTY TIME CMD 26619 pts/3 00:00:00 tcsh 26664 pts/3 00:00:00 emacs 26667 pts/3 00:00:00 ps % ps ux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND yas 16586 0.0 0.4 3872 1112 pts/4 S Apr12 0:00 -tcsh yas 26619 0.0 0.7 4100 2020 pts/3 S 16:50 0:00 -tcsh yas 26664 4.2 1.7 8844 4544 pts/3 T 16:56 0:00 emacs -nw yas 26668 0.0 0.3 2840 896 pts/3 R 16:56 0:00 ps ux %
----------------------------------------------------------------------
図2 ソース・プログラムから実行形式まで
cc -c
で実行されるプログラム
cpp(the C language preprocessor)
cc1(C Compiler)
as(Assembler)
図3 cc -c が実行するプログラム
コンパイルの観察。---------------------------------------------------------------------- % nl -ba today.c1 #define TODAY "Monday" 2 main() 3 { 4 printf("Today is %s.\n",TODAY ); 5 } % cc today.c
% ./a.out
Today is Monday. %
----------------------------------------------------------------------
---------------------------------------------------------------------- % cc -v today.cReading specs from /usr/lib/gcc-lib/i386-redhat-linux/2.96/specs gcc version 2.96 20000731 (Red Hat Linux 7.1 2.96-85) /usr/lib/gcc-lib/i386-redhat-linux/2.96/cpp0 -lang-c -v -D__GNUC__=2 -D__GNUC_MINOR__=96 -D__GNUC_PATCHLEVEL__=0 -D__ELF__ -Dunix -Dlinux -D__ELF__ -D__unix__ -D__linux__ -D__unix -D__linux -Asystem(posix) -D__NO_INLINE__ -Acpu(i386) -Amachine(i386) -Di386 -D__i386 -D__i386__ -D__tune_i386__ today.c /tmp/ccBBSvXh.i GNU CPP version 2.96 20000731 (Red Hat Linux 7.1 2.96-85) (cpplib) (i386 Linux/ELF) ignoring nonexistent directory "/usr/i386-redhat-linux/include" #include "..." search starts here: #include <...> search starts here: /usr/local/include /usr/lib/gcc-lib/i386-redhat-linux/2.96/include /usr/include End of search list. /usr/lib/gcc-lib/i386-redhat-linux/2.96/cc1 /tmp/ccBBSvXh.i -quiet -dumpbase today.c -version -o /tmp/cc8l7Fmp.s GNU C version 2.96 20000731 (Red Hat Linux 7.1 2.96-85) (i386-redhat-linux) compiled by GNU C version 2.96 20000731 (Red Hat Linux 7.1 2.96-85). as -V -Qy -o /tmp/ccB4lWuy.o /tmp/cc8l7Fmp.s GNU assembler version 2.10.91 (i386-redhat-linux) using BFD version 2.10.91.0.2 /usr/lib/gcc-lib/i386-redhat-linux/2.96/collect2 -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 /usr/lib/gcc-lib/i386-redhat-linux/2.96/../../../crt1.o /usr/lib/gcc-lib/i386-redhat-linux/2.96/../../../crti.o /usr/lib/gcc-lib/i386-redhat-linux/2.96/crtbegin.o -L/usr/lib/gcc-lib/i386-redhat-linux/2.96 -L/usr/lib/gcc-lib/i386-redhat-linux/2.96/../../.. /tmp/ccB4lWuy.o -lgcc -lc -lgcc /usr/lib/gcc-lib/i386-redhat-linux/2.96/crtend.o /usr/lib/gcc-lib/i386-redhat-linux/2.96/../../../crtn.o %
----------------------------------------------------------------------
---------------------------------------------------------------------- % cc -E today.c > today.i% nl -ba today.i
1 # 2 "today.c" 2 main() 3 { 4 printf("Today is %s.\n","Monday" ); 5 } %
----------------------------------------------------------------------
---------------------------------------------------------------------- % cc -S today.c% nl -ba today.s
1 .file "today.c" 2 .version "01.01" 3 gcc2_compiled.: 4 .section .rodata 5 .LC0: 6 .string "Monday" 7 .LC1: 8 .string "Today is %s.\n" 9 .text 10 .align 4 11 .globl main 12 .type main,@function 13 main: 14 pushl %ebp 15 movl %esp, %ebp 16 subl $8, %esp 17 subl $8, %esp 18 pushl $.LC0 19 pushl $.LC1 20 call printf 21 addl $16, %esp 22 leave 23 ret 24 .Lfe1: 25 .size main,.Lfe1-main 26 .ident "GCC: (GNU) 2.96 20000731 (Red Hat Linux 7.1 2.96-85)" %
----------------------------------------------------------------------
様々なライブラリ(-lgcc -lc -lgcc) が参照されている。---------------------------------------------------------------------- % cc -c today.c% ls -l today.o
-rw-r--r-- 1 yas lab 932 4月 14 17:11 today.o % nm today.o
00000000 t gcc2_compiled. 00000000 T main U printf % cc -v -o a.out today.o
Reading specs from /usr/lib/gcc-lib/i386-redhat-linux/2.96/specs gcc version 2.96 20000731 (Red Hat Linux 7.1 2.96-85) /usr/lib/gcc-lib/i386-redhat-linux/2.96/collect2 -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o a.out /usr/lib/gcc-lib/i386-redhat-linux/2.96/../../../crt1.o /usr/lib/gcc-lib/i386-redhat-linux/2.96/../../../crti.o /usr/lib/gcc-lib/i386-redhat-linux/2.96/crtbegin.o -L/usr/lib/gcc-lib/i386-redhat-linux/2.96 -L/usr/lib/gcc-lib/i386-redhat-linux/2.96/../../.. today.o -lgcc -lc -lgcc /usr/lib/gcc-lib/i386-redhat-linux/2.96/crtend.o /usr/lib/gcc-lib/i386-redhat-linux/2.96/../../../crtn.o %
----------------------------------------------------------------------
open(),close(),read(),write()
ライブラリ関数の例
fopen(),fclose(),fread(),fwrite(),printf(),scanf(),getchar(),putchar()
マクロ定義の例
getc(),putc(),isalpha(),isdigit(),
(stdin,stdout,stderr,getchar(),putchar() がマクロにもなっているシステムも多い。)
ヘッダ・ファイル stdio.h の観察
---------------------------------------------------------------------- % egrep getc /usr/include/stdio.hextern int fgetc (FILE *__stream) __THROW; extern int getc (FILE *__stream) __THROW; extern int getchar (void) __THROW; #define getc(_fp) _IO_getc (_fp) extern int getc_unlocked (FILE *__stream) __THROW; extern int getchar_unlocked (void) __THROW; extern int fgetc_unlocked (FILE *__stream) __THROW; extern int ungetc (int __c, FILE *__stream) __THROW; % cat getchar.c
#include
main() { printf("%c\n",getc(stdin) ); printf("%c\n",getchar() ); } % cc -E getchar.c > getchar.i % tail -5 getchar.i
main() { printf("%c\n",_IO_getc (stdin) ); printf("%c\n",getchar() ); } %
----------------------------------------------------------------------
---------------------------------------------------------------------- 1: /* 2: write-hello-c.c -- writeシステムコールの利用 3: ~yas/syspro/file/write-hello-c.c 4: Start: 2001/04/15 21:00:35 5: */ 6: 7: char hello[] = { 'h','e','l','l','o','\n' }; 8: 9: main() 10: { 11: write( 1,hello,6 ); 12: } ----------------------------------------------------------------------C言語からの利用する時には、関数呼び出しの形になる。ライブラリ関数との 違いは、一見しただけではわからない。
実行例。
---------------------------------------------------------------------- % cp ~yas/syspro/file/write-hello-c.c .% make write-hello-c
cc write-hello-c.c -o write-hello-c % ./write-hello-c
hello %
----------------------------------------------------------------------
---------------------------------------------------------------------- 1: # 2: # write-hello-s.s -- writeシステムコールの利用(アセンブリ言語) 3: # ~yas/syspro/file/write-hello-s.s 4: # Start: 2002/04/14 18:15:08 5: 6: .text 7: .align 2 8: .globl main 9: main: 10: pushl %ebp 11: movl %esp, %ebp 12: pushl %ebx 13: pushl %eax 14: 15: movl $1, %ebx 16: movl $hello, %ecx 17: movl $6, %edx 18: movl $4, %eax 19: int $0x80 20: 21: movl -4(%ebp), %ebx 22: leave 23: ret 24: 25: .data 26: .align 0 27: hello: 28: .byte 104 29: .byte 101 30: .byte 108 31: .byte 108 32: .byte 111 33: .byte 10 ----------------------------------------------------------------------
実行例。
---------------------------------------------------------------------- % cp ~yas/syspro/file/write-hello-s.s .% make write-hello-s
cc write-hello-s.s -o write-hello-s % ./write-hello-s
hello %
----------------------------------------------------------------------
Java では、char は、整数型とはまったく違う。相互に代入できない。 (Java の char は、内部的には、16 ビット。Unicodeで符号化される。)
int i ; char c; i = c ; /* C では OK. Java ではエラー */
図2-1 バイト・オーダ(その1) 図2-2 バイト・オーダ(その2)
---------------------------------------------------------------------- 1: /* 2: byte-order.c -- バイト・オーダを調べるプログラム 3: ~yas/syspro/cc/byte-order.c 4: Start: 2001/04/22 19:01:41 5: */ 6: 7: int i=0x12345678 ; 8: 9: main() 10: { 11: char *p,c ; 12: printf("&i == 0x%x\n",&i ); 13: p = (char *)&i ; 14: printf(" p == 0x%x\n",p ); 15: c = *p ; 16: printf(" c == 0x%02x\n",c ); 17: printf("{0x%02x,0x%02x,0x%02x,0x%02x} \n",p[0],p[1],p[2],p[3] ); 18: } ----------------------------------------------------------------------実行結果。gdb で変数の番地を調べて、その番地の内容を x コマンドで表示 する。
---------------------------------------------------------------------- % cc -g byte-order.c -o byte-order% ./byte-order
&i == 0x80495f0 p == 0x80495f0 c == 0x78 {0x78,0x56,0x34,0x12} % gdb byte-order
GNU gdb Red Hat Linux (5.1-0.71) Copyright 2001 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-redhat-linux"... (gdb) r
Starting program: /home/lab/Denjo/yas/syspro-2002/cc/byte-order &i == 0x80495f0 p == 0x80495f0 c == 0x78 {0x78,0x56,0x34,0x12} Program exited with code 027. (gdb) p &i
$1 = (int *) 0x80495f0 (gdb) x 0x80495f0
0x80495f0 <i>: 0x12345678 (gdb) x/4b 0x80495f0
0x80495f0 <i>: 0x78 0x56 0x34 0x12 (gdb) q
%
----------------------------------------------------------------------
main() { char c = 'A' ; f( c ); /* ここで符合拡張が行われる */ } f(char c) /*int c でも受けられる。*/ { .... }
% man システムコール名% man コマンド名
% man -k キーワード
![]()
% xman &![]()
% man 2 write![]()
Unix System V (Solaris など) では、2章の write を読むには、次のように -s オプションを使う。
% man -s2 write![]()
各章の説明は、intro を読むとよい。たとえば、システムコールの場合は、次 のように打つ。
% man 2 intro![]()
xgdb ではなく、xxgdb。x が2回。 Emacs (Mule) の中から gdb を呼ぶ。ソース・プログラム上でどこを実行して いるかを追うことができる。 使い方。% xxgdb progname &%
![]()
gdb progname リターン
」と打つ。
---------------------------------------------------------------------- int myputchar(int c) { ... write(1,...,...); ... } int myputs(char *s) { ... write(1,...,...); ... } ----------------------------------------------------------------------main() 関数を付けて、正しく動いていることがわかるようにしなさい。たと えば、次のようになる。
---------------------------------------------------------------------- main() { myputchar('h'); myputchar('e'); myputchar('l'); myputchar('l'); myputchar('o'); myputchar('\n'); } main() { myputs("hello\n"); } ----------------------------------------------------------------------注意:myputchar() では、 符合拡張にも気をつける。
myputchar() では、番地を調べて、その番地から1 バイトだけ write する。 myputs() では、文字列の長さを調べて、その長さ分のバイトだけ write する。
---------------------------------------------------------------------- int mygetchar() { ... read(0,...,...); ... } ----------------------------------------------------------------------main() 関数を付けて、正しく動いていることがわかるようにしなさい。
注意:プログラムを実行する時に、stty cbreak; をした後に実行すると オペレーティング・システムによるバッファリングを抑止できる。
通常の実行では、^D (Control D, Control キーを押しながら d キーを押す) で、プログラムに EOF (end of file) が伝わる。stty cbreak では、^D は効 かないので、^C でプログラムを強制終了させる。---------------------------------------------------------------------- % cathello
hello ^D % stty cbreak;cat
hheelllloo
^C %
----------------------------------------------------------------------
できれば、myputchar() と共に解きなさい。
注意:ライブラリ関数 gets() には、バッファの長さ以上のデータが入力され た時に、問題がある。今後、決して使ってはいけない。
man で表示された #includeの部分や定数を、Xウインドウ、または、mule の コピー&ペースト機能を使ってソース・プログラムに取り込みなさい。