Unix環境高度プログラミング——第一章-UNIX基礎知識

8599 ワード

1.2 UNIXアーキテクチャ
1、アーキテクチャ:(1)カーネル(2)システムコール(3)Shell、共通関数ライブラリ(4)アプリケーション
1.4ファイルとディレクトリ
1、例:ls(l)コマンドの簡単な実現
#include "apue.h"
#include 
int main(int argc,char * argv[])
{
    DIR *dp;
    struct dirent *dirp;
    if(argc!=2)
        err_quit("Usages: ls directory_name");
    
    if((dp=opendir(argv[1])==NULL))
        err_sys("can't open %s,argv[1]");
    
    while((dirp=readdir(dp))!=NULL)
        printf("%s
",dirp->d_name); closedir(dp); exit(0); }

(1)システムヘッダファイルdirent.hはopendirとreaddirの関数プロトタイプとdirent構造の定義を使用するために使用される.(2)opendir()関数はDIR構造へのポインタを返し,このポインタをreaddirに転送して各ディレクトリ項目を読み出す.ディレクトリ・アイテムが読み取り可能でない場合はnullポインタを返します.(3)関数exit終了プログラム,パラメータ0は正常終了,パラメータ値1~255はエラーを示す
1.5入力と出力
1、ファイル記述子(file descriptor):通常、特定のプロセスがアクセスしているファイルを識別するためにカーネルが使用される小さな非負の整数です.カーネルが既存のファイルを開くか、新しいファイルを作成すると、ファイルの読み取り、書き込みに使用できるファイル記述子が返されます.
2、新しいプログラムを実行するたびに、すべてのshellは3つのファイル記述子、すなわち標準入力(standard input)、標準出力(standard output)、標準エラー(standard error)を開きます.
3、バッファなしI/O(1)関数open、read、write、lseekおよびcloseはバッファなしI/Oを提供する.これらの関数は、ファイル記述子を使用します.
4、標準入力から読み、標準出力に書き込みたい場合は、次のプログラムを使用して、いずれかのUNIX標準ファイルをコピーできます.
//  STDIN_FILENO STDOUT_FILENO 
#define  STDIN_FILENO   0       
/* standard input file descriptor */
#define STDOUT_FILENO   1       
/* standard output file descriptor */
#define STDERR_FILENO   2       
/* standard error file descriptor */


#include "apue.h"

#define BUFFSIZE 4096

int main(void)
{
    int n;
    char buf[BUFFISZE];
    while((n=read(STDIN_FILENO,buf,BUFFSIZE))>0)
        if(write(STDOUT_FILENO,buf,n)!=n)
            err_sys("write error");
    if(n<0)
        err_sys("read error");
    exit(0);
}

(1)ヘッダファイルunistd.h(apue.hにこのヘッダファイルが含まれている)と2つの定数stdIN_FILENOとstdOUT_FILENOはPOSIX規格の一部である.ヘッダファイルhはreadとwriteを含む多くのUNIXシステムサービスの関数プロトタイプを含む.(2)2つの定数stdIN_FILENOとstdOUT_FILENO定義unistd.hヘッダファイルでは、標準入力と標準出力のファイル記述子を指定します.POSIX規格では、これらの値はそれぞれ0と1であるが、可読性を考慮して、これらの名前を使用してこれらの変数を表す.(3)read関数は、書き込むバイト数として使用される読み出しバイト数を返す.入力ファイルの末尾に達するとreadは0を返し、プログラムは実行を停止する.読み取りエラーが発生した場合、readは-1を返す.エラーが発生した場合、ほとんどのシステム関数は-1を返します.
5、標準I/O(1)標準I/Oライブラリ関数は、バッファを持たないI/O関数にバッファ付きインタフェースを提供する.標準I/O関数を使用すると、最適なバッファサイズをどのように選択するかを心配する必要はありません.(2)標準I/O関数を使用すると、fgets関数などの入力行の処理も簡略化され、read関数は指定されたバイト数を読み出す.
(2)例:標準入力を標準出力にコピーする,すなわちいずれかのUNIX通常ファイルをコピーできる.
#include "apue.h"

int main(void)
{
    int c;
    
    while((c=getc(stdin))!=EOF)
        if(putc(c,stdout)==EOF)
            err_sys("output error");
    
    if(ferror(stdin))
        err_sys("input error");
    
    exit(0);
}

1.6プログラムとプロセス
1、プログラム(1)プログラム(program)は、ディスク上のディレクトリに格納された実行可能ファイルである.カーネルはexec関数(7つのexec関数の1つ)を使用して、プログラムをメモリに読み込み、プログラムを実行します.
2、プロセスID(1)プログラムが実行する例は、プロセス/タスク(task)となる.
(2)UNIXシステムは、各プロセスに一意のデジタル識別子があり、プロセスID(process ID)を確保する.プロセスIDは常に負の整数ではありません.
例:印刷プロセスに使用するID.
#include "apue.h"

int main(void)
{
    printf("hello world from process ID %ld
", (long)getpid()); exit(0); } // man 2 getpid() getpid()

(1)このプログラムが実行されると、関数getpidを呼び出してプロセスIDを取得する.(2)getpidはpidを返すtデータ型.私たちはその大きさを知らないで、ただ標準が1つの長い整型の中で保証することを知っています.(3)プロセスIDの多くは整数で表すことができるが,ロング整数で移植性を向上させることができる.
3、プロセス制御(1)プロセス制御の主な関数はfork、exec、waitpidの3つある.(exec関数には7つのバリエーションがありますが、よくexec関数と総称されます.)
(2)例:プログラムは、標準入力から読み出しコマンドを入力し、これらのコマンドを実行する.
#include "apue.h"
#include 

int main(void)
{
    char buf[MAXLINE];       /* from apue.h */
    pid_t pid;
    int status;
    printf("%% ");            
/* print prompt (printf requires %% to print %) */
    while(fgets(buf,MAXLINE,stdin)!=NULL){
        if(buf[strlen(buf)-1]=='
') buf[strlen(buf)-1]=0; /* replace newline with null */ if ((pid==fork())<0){ err_sys("fork error"); }else if (pid==0) { /* child */ execlp(buf,buf,(char *)0); exec_ret("could't execute: %s",buf); } /* parent */ if((pid=waitpid(pid,&status,0))<0) err)sys("waitpid error"); printf("%% "); } exit(0); }

1.forkを呼び出して新しいプロセスを作成します.forkは、親プロセスに対して新しいサブプロセスのプロセスID(負の整数ではない)を返し、サブプロセスに対して0を返します.2.サブプロセスでexeclpを呼び出し、標準入力から読み込まれたコマンドを実行します.3.サブプロセスはexeclpを呼び出して新しいプログラムファイルを実行し、親プロセスはwaitpidを呼び出すことによってサブプロセスの終了を待つことを望んでいる.4.waitpid関数は、サブプロセスの終了状態(status変数)を返します.
4、スレッドおよびスレッドID(1)通常、1つのプロセスには1つの制御スレッド(thread)しかありません.ある時点で実行されるマシン命令のセットです.(2)1つのプロセス内のすべてのスレッドは、同じアドレス空間、ファイル記述子、スタック、およびプロセスに関連する属性を共有する.同じストレージ領域にアクセスできるため、各スレッドは共有データにアクセスする際に不一致を回避するために同期措置をとる必要があります.
1.7エラー処理
1.UNIXシステムの関数にエラーが発生した場合、通常は負の値が返されますが、整数変数errnoは通常、特定の情報を持つ値に設定されます.
2、ヘッダファイルerrno.hではerrnoとそれに付与できる様々な定数が定義されている.
3、errnoについては、2つのルール(1)がエラーがなければ、その値はルーチンによって明らかにされないことに注意しなければならない.したがって、関数の戻りがエラーを示す場合にのみ、その値が検証されます.(2)いずれの関数もerrno値を0とするerrno.hで定義された定数はすべて0ではありません.
4、C標準は2つの関数を定義し、エラー情報を印刷する.
#include 
char * strerror(int errnum);
//strerror   errnum(   errno )            ,          。

#include 
void perror(const char *msg);
//perror    errno    ,              ,   。     msg      ,     :,    ,    errno      ,        。

5、例:プログラムは二つのエラー関数の使用方法を表示する
#include "apue.h"
#include 

int main(int argc,char * argv[])
{
    fprintf(stderr,"EACCES: %s
",stderror(EACCES)); errno=ENOENT; perror(argv[0]); exit(0); } //man errno

6、エラー復旧(1)致命的なエラー:復旧動作が実行できず、最大1つのエラー情報を印刷したり、エラー情報をログファイルに書き込んだりします.(2)非致命的なエラーは適切に処理できる.
1.8ユーザーID
1、例:ユーザIDグループIDを印刷する
#include "apue.h"

int main(void)
{
    printf("uid = %d, gid = %d
",getuid(),getgid()); exit(0); } //man getuid getuid //man getgid getgid

1.9信号
1.プロセスに何らかの状況が発生したことを通知するための信号(signal).
2、プロセスは以下の3種類の信号を処理する方式がある:(1)信号を無視する(2)システムのデフォルト方式で処理する(3)関数を提供し、信号が発生した時にこの関数を呼び出し、これはこの信号を捕捉する
3、端末上のキーボードは信号を二重に生成する方法があり、それぞれ中断キー(interrupt key、通常はDeleteキーまたはCtrl+C)と終了キー(quit key、通常はCtrl+)となり、これらは現在のプロセスを中断するために使用される.
4、もう一つの信号を生成する方法はkill関数を呼び出すことである.あるプロセスでこの関数を呼び出すと、別のプロセスに信号を送信できます.
5、制限:1つのプロセスに信号を送信する場合、私たちはプロセスのuserまたはrootユーザーでなければなりません.
6、例:SIGINT信号を捕捉するために、プログラムはsignal関数を呼び出す必要があり、ここでSIGINT信号が発生した時に呼び出す関数の名前を指定し、関数名はsig_int.
#include "apue.h"
#include 

static void sig_int(int); /* out signal-caching functions */
int main(void)
{
    char buf[MAXLINE];   /* from apue.h */
    pid_t pid;
    int status;
    
    if(signal(SIGINT,sig_int)==SIG_ERR)
        err_sys("signal error");
    
    printf("%% "); /* format print */ 
    while(fgets(buf,MAXLINE,stdin)!=NULL){
        if(buf[strlen(buf)-1]=='
') buf[strlen(buf)-1]=0; /* replace newline with null */ if((pid=fork())<0){ err_sys("fork error"); }else if(pid==0){ /* child */ execlp(buf,buf,(char *)0); err_ret("couldn't execute: %s",buf); exit(127); } /* parent */ if((pid=waitpid(pid,&status,0))<0) err_sys("waitpid error"); printf("%% "); } exit(0); } void sig_int(int signo) { printf("interupt
%% "); }

1.10時間値
1、UNIXシステムでは、サブコーディネートワールド時(Coordinated Universal Time,UTC)1970年1月1日00:00:00という特定の時間から経過した秒数積算値の2つの異なる時間値(1)カレンダー時間を使用したことがある.(システム基本データ型time_tは、この時間値を保存するために使用される)(2)プロセス時間.CPU時間とも呼ばれ、メジャープロセスで中央プロセッサリソースを使用します.プロセス時間は常に滴下して計算されます.(システム基本データ型clock_t)この時間値を保存する
2、UNIXシステムは一つのプロセスに対して三つのプロセス時間値を維持した:(1)クロック時間(2)ユーザーCPU時間(3)システムCPU時間
3、クロック時間はまた壁のクロック時間(wall clock time)となり、それはプロセスが実行する時間の総量であり、その値はシステムの中で同時に実行するプロセス数と関係がある.
4、ユーザCPU時間とは、ユーザコマンドを実行するのにかかる時間量をいう.
5、システムCPU時間は当該プロセスがカーネルプログラムを実行した時間である
6、ユーザCPU時間とシステムCPU時間の和がCPU時間となる
1.11システム呼び出しとライブラリ関数
1.すべてのオペレーティングシステムは、システムコール(system call)と呼ばれる複数のサービスのエントリを提供する.
2、システム呼び出しとライブラリ関数はいずれもC関数として現れ、どちらもアプリケーションにサービスを提供する.
3、ライブラリ関数を置き換えることができますが、システム呼び出しは通常置き換えられません.
4、mallocライブラリ関数を例にとると、UNIXシステムはストレージ割り当てを処理するsbrk(2)を呼び出し、sbrkはカーネルにプロセスに空間を割り当て、ライブラリ関数mallocはユーザー階層でこの空間を管理する.
5、システム呼び出しとライブラリ関数の間のもう一つの違い:システム呼び出しは通常最小のインタフェースを提供し、ライブラリ関数は比較的複雑な機能を提供する.