プロセス、プログラム、システム・コール、ライブラリ

システム・プログラム

                                       電子・情報工学系
                                       新城 靖
                                       <yas@is.tsukuba.ac.jp>

このページは、次の URL にあります。
http://www.hlla.is.tsukuba.ac.jp/~yas/coins/syspro-2001/2001-04-16
あるいは、次のページから手繰っていくこともできます。
http://www.hlla.is.tsukuba.ac.jp/~yas/coins/
http://www.is.tsukuba.ac.jp/~yas/index-j.html

■印刷配布資料

■今日の目標

■講義の目的

システム・プログラムでは、オペレーティング・システムが提供する次のよう な機能や考え方を理解することを目的とする。 オペレーティング・システムの「内部の動き」は、2学期の「オペレーティン グ・システム」という講義で扱う。内部の動きの前に、外から眺めてみて、オ ペレーティング・システムの考え方を理解することを目的とする。 3学期には、さらに「オペレーティング・システム II 」、「分散システム」 で最近の話題に繋がる。

この講義では、Unix の API (Application Programming Interface) を利用し てプログラムを作成する。API は、次の3つに分類される。

言語を覚えただけでは、プログラムを書くことができない。さらに、API を知 る必要がある。

この講義で扱う Unix の API は、他のものと比較すると簡単になっている。 複雑なものには、Xウインドウ・システム、MS-Windows (Win32 API) などが ある。

■コンピュータ・システムの構成

■オペレーティング・システムの構成要素

UNIXオペレーティング・システムは、カーネル、サーバ、シェ ル、コマンド群、各種ライブラリ関数、といった要素から構成さ れている。

図? オペレーティング・システムの構成要素

図? オペレーティング・システムの構成要素

◆カーネル

カーネル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 コマンドを使う。

----------------------------------------------------------------------
% ps [←]
   PID TTY     TIME CMD
  5815 ttyq1   0:00 tcsh
  5830 ttyq1   0:00 emacs
  5841 ttyq1   0:00 ps
% ps -f [←]
     UID   PID  PPID  C    STIME TTY     TIME CMD
     yas  5815  5814  1 21:40:00 ttyq1   0:00 -tcsh
     yas  5830  5815  0 21:41:25 ttyq1   0:00 mule -nw
     yas  5842  5815  3 21:42:20 ttyq1   0:00 ps -f
% ps -u $USER [←]
   PID TTY     TIME CMD
  5838 pts/2   0:00 tcsh
  5815 pts/1   0:00 tcsh
  5830 pts/1   0:00 emacs
  5843 pts/1   0:00 ps
% []
----------------------------------------------------------------------

■プログラムのコンパイルと実行

3種類のプログラム コンパイルから実行までに必要なプログラム

図2 ソース・プログラムから実行形式まで

図2 ソース・プログラムから実行形式まで

cc -c で実行されるプログラム
cpp(the C language preprocessor)
プリプロセッサ(前処理プログラム、マクロプロセッサ)
ccom(C Compiler)
Cコンパイラ本体
as(Assembler)
アセンブラ
assembly language で書かれたプログラムを assembler でオブジェクト・プ ログラムに変換する。

図3 cc -c が実行するプログラム

図3 cc -c が実行するプログラム

コンパイルの観察。

◆cc


----------------------------------------------------------------------
% nl -ba today.c [←]
     1  #define TODAY   "Tuesday"
     2  main()
     3  {
     4          printf("Today is %s.\n",TODAY );
     5  }
% cc today.c [←]
% ./a.out [←]
Today is Tuesday.
% []
----------------------------------------------------------------------

◆cc -v


----------------------------------------------------------------------
% cc -v today.c [←]
/usr/lib/cfe -D_MIPS_FPSET=16 -D_MIPS_ISA=2 -D_ABIO32=1 -D_MIPS_SIM=_ABIO32 -D_M
IPS_SZINT=32 -D_MIPS_SZLONG=32 -D_MIPS_SZPTR=32 -D__EXTENSIONS__ -DLANGUAGE_C -D
_LANGUAGE_C -D__INLINE_INTRINSICS -Dsgi -D__sgi -Dunix -Dmips -Dhost_mips -D__un
ix -D__host_mips -D_SVR4_SOURCE -D_MODERN_C -D_SGI_SOURCE -D_PIC -D__DSO__ -DSYS
TYPE_SVR4 -D_SYSTYPE_SVR4 -D_LONGLONG -D__mips=2 -I -D_MIPSEB -DMIPSEB -D__STDC_
_=1 -I/usr/include today.c -Xv -D_CFE -Amachine(mips) -Asystem(unix) -call_share
d -G 0 -std -XS/tmp/ctmsta001TP -mips2 -EB -Xg0 -O1 > /tmp/ctmfa001TP
cfe: main /usr/lib/cfe phase time: 0.03u 0.04s 0:00.1 100%
/usr/lib/ugen -v -G 0 -pic2 -mips2 -EB -g0 -O1 /tmp/ctmfa001TP -o /tmp/ctmca001T
P -t /tmp/ctmsta001TP -temp /tmp/ctmgta001TP
ugen: main
/usr/lib/ugen phase time: 0.01u 0.04s 0:00.1 100%
/usr/lib/as1 -t5_ll_sc_bug -elf -pic2 -v -G 0 -p0 -mips2 -EB -g0 -O1 /tmp/ctmca0
01TP -o today.o -t /tmp/ctmsta001TP
as1: main
/usr/lib/as1 phase time: 0.00u 0.02s 0:00.1 25%
/usr/lib/ld -elf -_SYSTYPE_SVR4 -require_dynamic_link _rld_new_interface -no_unr
esolved -Wx,-G 0 -mips2 -call_shared -g0 -KPIC -L/usr/lib/ -nocount /usr/lib/crt
1.o -count today.o -nocount -lc /usr/lib/crtn.o
/usr/lib/ld phase time: 0.02u 0.04s 0:00.1 67%
% []
----------------------------------------------------------------------

◆cc -E


----------------------------------------------------------------------
% cc -E today.c > today.i [←]
% nl -ba today.i [←]
     1  # 1 "today.c"
     2
     3  main()
     4  {
     5          printf("Today is %s.\n","Tuesday"  );
     6  }
% []
----------------------------------------------------------------------

◆cc -S


----------------------------------------------------------------------
% nl -ba today.s [←]
     1          .verstamp       7 20
     2          .option pic2
     3          .rdata
     4          .align  2
     5          .align  0
     6  $$5:
     7          .ascii  "Today is %s.\X0A\X00"
     8          .rdata
     9          .align  2
    10          .align  0
    11  $$6:
    12          .ascii  "Tuesday\X00"
    13          .text
    14          .align  2
    15          .file   2 "today.c"
    16          .globl  main
    17          .loc    2 3
    18   #   1  #define TODAY   "Tuesday"
    19   #   2  main()
    20   #   3  {
    21          .ent    main 2
    22  main:
    23          .option O1
    24          .set     noreorder
    25          .cpload $25
    26          .set     reorder
    27          subu    $sp, 32
    28          sw      $31, 28($sp)
    29          .cprestore      24
    30          .mask   0x90000000, -4
    31          .frame  $sp, 32, $31
    32          .loc    2 3
    33          .loc    2 4
    34   #   4          printf("Today is %s.\n",TODAY );
    35          la      $4, $$5
    36          la      $5, $$6
    37          .livereg        0x0C00000E,0x00000000
    38          jal     printf
    39          .loc    2 5
    40   #   5  }
    41          move    $2, $0
    42          .livereg        0x2000FF0E,0x00000FFF
    43          lw      $31, 28($sp)
    44          addu    $sp, 32
    45          j       $31
    46          .end    main
% []
----------------------------------------------------------------------

◆リンクの観察


----------------------------------------------------------------------
% cc -c today.c [←]
% nm today.o [←]


Symbols from today.o:

[Index]   Value        Size        Class    Type               Section    Name

[0]     |           0|            |File    |ref=4             |Text     | today.
c
[1]     |           0|            |Proc    |end=3 int         |Text     | main
[2]     |          68|            |End     |ref=1             |Text     | main
[3]     |           0|            |End     |ref=0             |Text     | today.
c
[4]     |           0|            |Proc    |ref=1             |Text     | main
[5]     |           0|            |Proc    |                  |Undefined| printf
[6]     |           0|            |Global  |                  |Undefined| _gp_di
sp
% cc -v -o today today.o [←]
/usr/lib/ld -elf -o today -_SYSTYPE_SVR4 -require_dynamic_link _rld_new_interfac
e -no_unresolved -Wx,-G 0 -mips2 -call_shared -g0 -KPIC -L/usr/lib/ -nocount /us
r/lib/crt1.o -count today.o -nocount -lc /usr/lib/crtn.o
/usr/lib/ld phase time: 0.02u 0.05s 0:00.1 88%
% []
----------------------------------------------------------------------

■ライブラリ関数とシステム・コール

C言語でプログラムを作る時に、次の3つを使うことになる。 システム・コールの例
open(),close(),read(),write()
ライブラリ関数の例
fopen(),fclose(),fread(),fwrite(),printf(),scanf(),getchar(),putchar()
マクロ定義の例
getchar(),putchar(),stdin,stdout,stderr
ヘッダ・ファイル stdio.h の観察
----------------------------------------------------------------------
% egrep getchar /usr/include/stdio.h [←]
extern int      getchar(void);
extern int      getchar_unlocked(void);
#define getchar()       getc_locked(stdin)
#define getchar_locked()        getc_locked(stdin)
#define getchar_unlocked()      getc_unlocked(stdin)
#define getchar()       getc(stdin)
#define getchar_unlocked()      getc_unlocked(stdin)
#define getchar()       getc(stdin)
% egrep stdin /usr/include/stdio.h [←]
#define stdin   (&__iob[0])
#define getchar()       getc_locked(stdin)
#define getchar_locked()        getc_locked(stdin)
#define getchar_unlocked()      getc_unlocked(stdin)
#define getchar()       getc(stdin)
#define getchar_unlocked()      getc_unlocked(stdin)
#define getchar()       getc(stdin)
% []
----------------------------------------------------------------------

◆writeシステムコールの利用(C言語)

----------------------------------------------------------------------
   1:	/*
   2:	        write-hello-c.c -- writeシステムコールの利用
   3:	        ~yas/syspro/file/write-hello-c.c
   4:	        $Header: /home/lab2/OS/yas/syspro-2001/file/RCS/write-hello-c.c,v 1.1 2001/04/15 12:02:00 yas Exp $
   5:	        Start: 2001/04/15 21:00:35
   6:	*/
   7:	
   8:	char hello[] = { 'h','e','l','l','o','\n' };
   9:	
  10:	main()
  11:	{
  12:	        write( 1,hello,6 );
  13:	}
----------------------------------------------------------------------

実行例。

----------------------------------------------------------------------
% 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
% []
----------------------------------------------------------------------

◆writeシステムコールの利用(アセンブリ言語)

機械語序論でやった xspim とは、引数の与え方が違う。
----------------------------------------------------------------------
   1:	 #
   2:	 #      write-hello-s.s -- writeシステムコールの利用(アセンブリ言語)
   3:	 #      ~yas/syspro/file/write-hello-s.s
   4:	 #      $Header: /home/lab2/OS/yas/syspro-2001/file/RCS/write-hello-s.s,v 1.1 2001/04/15 12:20:49 yas Exp $
   5:	 #      Start: 2001/04/15 21:09:47
   6:	
   7:	        .text   
   8:	        .align  2
   9:	        .globl  main
  10:	main:
  11:	        addiu   $sp,$sp,-32
  12:	        sw      $31,28($sp)
  13:	        sw      $gp,24($sp)
  14:	
  15:	        li      $4, 1
  16:	        la      $5, hello
  17:	        li      $6, 6
  18:	
  19:	        li      $2, 1004        # write system call
  20:	        syscall
  21:	
  22:	        lw      $31,28($sp)
  23:	        lw      $gp,24($sp)
  24:	        addiu   $sp,$sp,32
  25:	        jr      $31
  26:	
  27:	        .data
  28:	        .align  2
  29:	hello:
  30:	        .byte   104
  31:	        .byte   101
  32:	        .byte   108
  33:	        .byte   108
  34:	        .byte   111
  35:	        .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
% []
----------------------------------------------------------------------

■練習問題

★練習問題 1 psコマンド

ps コマンドでプロセスを観察しなさい。

★練習問題 2 ccコマンドの観察

cc -v で、cc から実行されるプログラムを観察しなさい。

★練習問題 3 nmコマンドによる実行形式のシンボルテーブルの観察利用

nm で、オブジェクト・プログラムと実行形式のシンボルとそのアドレス を観察しなさい。

★練習問題 4 writeシステムコールによるライブラリの実現

次のライブラリ関数のどれかを write() システムコールを使って実現しなさ い。 writeの第一引数は、1 とする。 標準のライブラリ関数と区別するために、次のような名前にしなさい。 プログラムは、次のような形になる。
----------------------------------------------------------------------
int myputchar(int c)
{
    ...
    write(1,...,...);
    ...
}

int myputs(char *s)
{
    ...
    write(1,...,...);
    ...
}
----------------------------------------------------------------------
main() 関数を付けて、正しく動いていることがわかるようにしなさい。

★練習問題 5 readシステムコールによるライブラリの実現

次のライブラリ関数のどれかを read() システムコールを使って実現しなさ い。 ただし、read()の第一引数は、0 とする。 標準のライブラリ関数と区別するために、次のような名前にしなさい。 プログラムは、次のような形になる。
----------------------------------------------------------------------
int mygetchar()
{
    ...
    read(0,...,...);
    ...
}
----------------------------------------------------------------------
main() 関数を付けて、正しく動いていることがわかるようにしなさい。

注意:ライブラリ関数 gets() には、バッファの長さ以上のデータが入力され た時に、問題がある。今後、決して使ってはいけない。