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

システム・プログラム

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

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

■印刷配布資料

■今日の目標

■講義の目的

システム・プログラムでは、オペレーティング・システムが提供する次のよう な機能や考え方を理解することを目的とする。

このような基本的な考え方は、時間が経過しても変わらない。 この講義で得られた知識は、10年後も十分通用する。

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

この講義では、Unix の API (Application Program 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 )、 そうでないものを 外部コマンドexternal 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 Program 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
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
% []
----------------------------------------------------------------------

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

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

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

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

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

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

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

コンパイルの観察。

◆cc


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

◆cc -v


----------------------------------------------------------------------
% cc -v today.c  [←]
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/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

プリプロセッサだけ動かして、結果を標準出力へ出す。

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

アセンブラを実行しないで、アセンブリ言語のプログラムを .s ファイルに残 す。

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

◆リンクの観察

リンクは、ld というプログラムで行われることが多い(下は違う)。

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

様々なライブラリ(-lgcc -lc -lgcc) が参照されている。

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

C言語でプログラムを作る時に、次の3つを使うことになる。 システム・コールの例
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.h  [←]
extern 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() );
}
% []
----------------------------------------------------------------------

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

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

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

機械語序論でやった xspim とは、引数の与え方が違う。
----------------------------------------------------------------------
   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
% []
----------------------------------------------------------------------

◆writeシステムコールを使いやすくしたライブラリ関数

どういう時にライブラリ関数を使って、どういう時にシステムコールを使えば よいか。

■メモリ中のデータの表現

◆バイト・アドレッシング

メモリには、1バイトごとに番地が振られている。 (1ワードごと、1ビットごとにアドレスを振る方法も考えられる。)

◆整数

C言語では、いろいろなビット数(バイト数)の整数が使える。
char
8 ビット
short int
16 ビット
int
32 ビット (処理系依存)
long int
32 ビット
char も、8ビット(1バイト)の整数であることに注意。 unsigned を付けると符合無しになる。

Java では、char は、整数型とはまったく違う。相互に代入できない。 (Java の char は、内部的には、16 ビット。Unicodeで符号化される。)

int i ;
char c;
    i = c ; /* C では OK. Java ではエラー */

◆バイト・オーダ

複数バイトの整数をメモリに置く時に、連続した複数番地の メモリを使う。 どういう順番で置くか。 数の大小比較の時に、最も効いてくる(most significant)なビット(を含むバ イト、上位バイト)の置き方。
ビッグエンディアン。番地が小さい方に上位バイトをを置く。
リトルエンディアン。番地が小さい方に下位バイトを置く。
例:0x12345678の置き方。

図2-1 バイト・オーダ(その1)

図2-1 バイト・オーダ(その1)

CPUのレジスタの中では、バイト・オーダは関係ない。

図2-2 バイト・オーダ(その2)

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

◆符合拡張

C言語では、関数呼出しの時、char も short も、int へ拡張する。符合付な ら、符合をつけたまま拡張する。
main()
{
   char c = 'A' ;
    f( c ); /* ここで符合拡張が行われる */
}

f(char c) /*int c でも受けられる。*/
{
   ....
}

◆C言語の&演算子

C言語の「&」には、2つの意味がある。
x & y
x と y のビット単位の AND
& v
変数 v の番地

◆番地(ポインタ)

read() システム・コールや write() システム・コールでは、たとえ1バイト でも「番地」を与える必要がある。
ssize_t write(int fd, const void *buf, size_t count);
ssize_t read(int fd, void *buf, size_t count);
次のプログラムは、間違い。
----------------------------------------------------------------------
main()
{
    void *buf ;
    read( 0, buf, 1 );
}
----------------------------------------------------------------------
ポインタ buf 変数は、番地を保持する。ポインタを使う時には、どの番地を 指しているかが大事である。初期化されていない。ポインタも、どこかの番地 を差している。そのどこかは、メモリが割り当てられていないかもしれない。

ポインタは、必ず次のいずれかの方法で初期化して使う。

■マニュアルの読み方

プログラムを作る時には、参考書だけでなく、付属のマニュアルを必ず参照す る必要がある。参考書とは微妙にコマンドや関数の使い方が違っていることが ある。(ただし、Linux に付属のマニュアルは、間違いを含んでいることもある。)

◆コマンドと同名のシステムコールのマニュアルを読む

Unix のマニュアルは、章ごとに分割されている。
1 章
コマンド
2 章
システム・コール
3 章
ライブラリ
4 章
デバイス・ファイル
5 章
ファイル形式
6 章
ゲーム
7 章
その他
8 章
管理者用のコマンド
単純に man write と打つと、write(1)、つまり、1章のwrite コマンドのマニュ アルが表示される。write(2) システム・コールを見るには、次のように打つ。
% man 2 write [←]

Unix System V (Solaris など) では、2章の write を読むには、次のように -s オプションを使う。

% man -s2 write [←]

各章の説明は、intro を読むとよい。たとえば、システムコールの場合は、次 のように打つ。

% man 2 intro [←]

■デバッガ

まだ使えない人は、練習すること。

◆gdb 5.0 日本語マニュアル

◆gdb コマンドカード(印刷用)

◆xxgdb

X Window のインタフェース。
% xxgdb progname & [←]
% []
xgdb ではなく、xxgdb。x が2回。

◆emacs ESC x gdb

Emacs (Mule) の中から gdb を呼ぶ。ソース・プログラム上でどこを実行して いるかを追うことができる。 使い方。
  1. emacs を実行する。
  2. ESC x gdb リターン」と打つ。
  3. gdb progname リターン」と打つ。
gdb のウィンドウでよく使うキー
C-c C-n
next
C-c C-s
step
C-c C-l
ウィンドウを割って、ソースの表示
ソース・プログラムのウィンドウで使えるキー
C-x SPC
ブレーク・ポイントの設定。(こちらは使える。)

◆Segmentaton fault(Segmentaton violation)

プログラムを実行して、Segmentaton fault というエラーが出たら、ポインタ の操作が怪しい。どこが怪しいかは、cc -g でコンパイルして、その中で実行 して、バックトレースすれば、だいたい分かる。

■練習問題

★練習問題 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() 関数を付けて、正しく動いていることがわかるようにしなさい。たと えば、次のようになる。

----------------------------------------------------------------------
main()
{
	myputchar('h');
	myputchar('e');
	myputchar('l');
	myputchar('l');
	myputchar('o');
	myputchar('\n');
}

main()
{
	myputs("hello\n");
}
----------------------------------------------------------------------
注意:myputchar() では、 符合拡張にも気をつける。

myputchar() では、番地を調べて、その番地から1 バイトだけ write する。 myputs() では、文字列の長さを調べて、その長さ分のバイトだけ write する。

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

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

注意:プログラムを実行する時に、stty cbreak; をした後に実行すると オペレーティング・システムによるバッファリングを抑止できる。

----------------------------------------------------------------------
% cat [←]
hello[←]
hello
^D
% stty cbreak;cat [←]
hheelllloo[←]

^C
% []
----------------------------------------------------------------------
通常の実行では、^D (Control D, Control キーを押しながら d キーを押す) で、プログラムに EOF (end of file) が伝わる。stty cbreak では、^D は効 かないので、^C でプログラムを強制終了させる。

できれば、myputchar() と共に解きなさい。

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

★練習問題 6 ポインタの誤用の修正

次のプログラムは、read() システム・コールを使って画面から1バイトだけ 読み込むプログラムであるが、誤りを含んでいる。
----------------------------------------------------------------------
main()
{
    char *buf ;
    read( 0, buf, 1 );
    printf("%c\n",*buf );
}
----------------------------------------------------------------------
この誤りを、次の2つの方法で修正しなさい。 2つの方法の利害特質を考えなさい。

★練習問題 7 manの表示

man コマンド、xman 、Emacs の ESC x man の使い方を練習しなさい。

man で表示された #includeの部分や定数を、Xウインドウ、または、mule の コピー&ペースト機能を使ってソース・プログラムに取り込みなさい。

★練習問題 8 デバッガ

cc -g で作ったプログラムを、gdb, xxgdb, Emacs gdb を使って実行しなさい。 デバッガのコマンド実行してみなさい。

★練習問題 9 Segmentation fault

Segmentation fault を起こすプログラムをかきなさい。 それをデバッガで追跡して、どこで起きたかを調べてなさい。