1. 程式人生 > >UNIX環境高階程式設計(3) 第一章

UNIX環境高階程式設計(3) 第一章

1.1 引言

所有作業系統都為他們所執行的程式提供服務。典型的服務包括:執行新程式、開啟檔案、讀檔案、分配儲存區以及獲取當前時間等。

1.2 UNIX體系結構

層級從裡向外擴充套件應用。 1. 核心:可將作業系統定義為一種軟體,即稱為核心,它控制計算機硬體資源,提供程式執行環境。 2. 系統呼叫:核心提供的介面。可被應用程式呼叫。 3. shell:是一個特殊的應用程式,為執行其他應用程式提供一個介面。 4. 應用程式。

1.3 登入

1, 登入名 使用者在登入UNIX系統時,先鍵入登入名,然後鍵入口令。系統在其口令檔案(通常是/etc/passwd檔案)中檢視使用者名稱。

2, shell 使用者登入後,系統通常先顯示一些系統資訊,然後使用者就可以向shell程式鍵入命令。

shell是一個命令列直譯器,它讀取使用者輸入,然後執行命令。

1.4 檔案和目錄

目錄是一個包含目錄項的檔案,可以認為每個目錄項都包含一個檔名,同時還包含該檔案屬性的資訊(ls -l可以檢視)。第四章會詳細說明檔案的各種屬性。

1, 檔案系統 UNIX檔案系統是目錄和檔案的一種層次結構,所有東西的起點都是根的目錄,這個目錄名稱是一個字元“/”。

2, 檔名 目錄中的各個名字成為檔名,建立目錄時會自動建立兩個檔名:.(當前目錄)和..(父目錄) (可見為什麼 cd .. 會回到父目錄)

3, 路徑名 已斜線開頭的路徑名稱成為絕對路徑名(可以理解為以根目錄開頭的相對路徑名),否則為相對路徑名。

用系統庫函式可列出一個目錄中所有檔案的名字,下面是ls命令的簡要實現。

#include "apue.h"
#include <dirent.h>

int main(int argc,char *argv[])
{
    DIR *dp;
    struct dirent *dirp;
    if(argc!=2)
        err_quit("usage:ls directory_name");
    if((dp=opendir(argv[1]))==NULL)
        err_sys("can't open %s
"
,argv[1]); while((dirp=readdir(dp))!=NULL) printf("%s\n",dirp->d_name); closedir(dp); exit(0); }

(1)系統標頭檔案dirent.h,以便使用opendir和readdir的函式原型,以及dirent結構的定義。 (2)opendir()函式返回指向DIR結構的指標,我們將該指標傳送給readdir來讀每個目錄項。當目錄已無目錄項可讀時則返回null指標。 (3)函式exit終止程式,引數0為正常結束,引數值1~255表示出錯

4, 工作目錄 每個程序都有一個工作目錄,有時稱其為當前工作目錄,程序可以用chdir函式更改其工作目錄

5, 起始目錄 登入時,工作目錄設定為起始目錄,該起始目錄從口令檔案中相應使用者的登入項中取得。

1.5 輸入與輸出

  1. 檔案描述符 檔案描述符通常是一個小的非負整數,用來標識一個特定程序正在訪問的檔案。當核心開啟一個現有檔案或建立一個新檔案時,它都返回一個檔案描述符,可以通過這個檔案描述符進行讀寫檔案。

  2. 標準輸入、標準輸出和標準錯誤 每當執行一個新程式時,所有的shell都為其開啟3個檔案描述符,即標準輸入(0)、標準輸出(1)和標準錯誤(2),如果不做特殊處理,則這3個描述符都連結到終端。

  3. 不帶緩衝的I/O 函式open、read、write、lseek以及close提供了不帶緩衝的I/O。這些函式都使用檔案描述符

下面程式展示瞭如何複製UNIX下的普通檔案

#include "apue.h"

#define BUFFSIZE 4096

int main(void)
{
    int n;
    char buf[BUFFSIZE];
    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);
}

大多數shell都提供一種方法,使其中任何一個或所有這3個描述符都能重定向到某個檔案。例如ls > file.list 可以將ls的輸出(標準輸出 檔案描述符為1)重定向到名為file.list的檔案。

如下執行上面的程式:./a.out > data 可以把程式的標準輸出重定向到檔案data ./a.out < infile > outfile把程式的標準輸入重定向到infile 把標準輸出重定向到outfile,實現了檔案的複製。

4, 標準I/O 標準I/O 為那些不帶緩衝的I/O函式提供了一個帶緩衝的介面,下面演示使用標準I/O複製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, 程式 程式是一個儲存在磁碟上某個目錄中的可執行檔案

2, 程序和程序ID 程式的執行例項被稱為程序,UNIX系統確保每個程序都有一個唯一的數字識別符號,稱為程序ID。程序ID總是一個非負整數.

下面程式用於列印程序ID

#include "apue.h"
int main(void)
{
    printf("hello world from process ID %ld\n",(long)getpid());
    exit(0);
}

3, 程序控制 有3個用於程序控制的主要函式:fork、exec和waitpid。

UNIX系統的程序控制功能可以用一個簡單得程式說明,下面程式從標準輸入讀取命令,然後執行命令,類似於shell程式的基本實施部分。

#include "apue.h"
#include <sys/wait.h>


int main(void)
{
    char buf[MAXLINE];
    pid_t pid;
    int status;
    printf("%% ");
    while(fgets(buf,MAXLINE,stdin)!=NULL)
    {
        if(buf[strlen(buf)-1]=='\n')
            buf[strlen(buf)-1]=0;
        if((pid=fork())<0)
            err_sys("fork error");
        else if(pid==0)
        {
            execlp(buf,buf,(char *)0);
            err_ret("couldn't execute:%s",buf);
            exit(127);
        }
        if((pid=waitpid(pid,&status,0))<0)
            err_sys("waitpid error");
        printf("%% ");
    }
    exit(0);
}

4, 執行緒和執行緒ID 與程序相同,執行緒也用ID標識。但是,執行緒ID只在它所屬的程序內起作用。

1.7 出錯處理

當UNIX系統函數出錯時,通常會返回一個負值,而且整形變數errno通常被設定為具有特定資訊的值。

#include <string.h>
char *strerror(int errnum);
    return:指向訊息字串的指標

strerror函式將errnm(通常就是error值)對映為一個出錯訊息字串,並且返回此字串的指標。

#include <stdio.h>
void perror(const char *msg);

perror函式基於errno的當前值,在標準錯誤上產生一條出錯資訊,然後返回。

下面程式顯示這兩個出錯函式的使用方法

#include "apue.h"
#include <errno.h>

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

1.8 使用者標識

1, 使用者ID

使用者ID是一個數值,用來確定一個使用者。使用者ID為0為根使用者或超級使用者,超級使用者對系統有自由的支配權。

2, 組ID 組ID是由系統管理員在指定使用者登入名時分配的,可以把多個使用者分成一組。

下面程式用於列印使用者ID和組ID

#include "apue.h"

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

3, 附屬組ID

除了在口令檔案中對一個登入名指定一個組ID之外,大多數UNIX系統版本還允許一個使用者屬於另外一些組。

1.9 訊號

訊號用於通知程序發生了某種情況。例如,若某一程序執行除法操作,其除數為0,則將名為SIGFPE(浮點異常)的訊號傳送給程序。

程序有以下3種處理訊號的方式:

  1. 忽略訊號
  2. 按系統預設方式處理
  3. 提供一個函式,訊號發生時呼叫該函式,這被稱為捕捉該訊號。 終端鍵盤上有兩種產生訊號的方法,分別稱為中斷鍵(通常是Delete或Ctrl+C)和退出鍵(通常是Ctrl+)

修改之前的shell例項,使程式可以捕獲SIGINT訊號,我們會在第10章詳細的介紹訊號。

#include "apue.h"
#include <sys/wait.h>

static void sig_int(int);

int main(void)
{
    char buf[MAXLINE];
    pid_t pid;
    int status;
    if(signal(SIGINT,sig_int)==SIG_ERR);
                err_sys("signal error");
    printf("%% ");
    while(fgets(buf,MAXLINE,stdin)!=NULL)
    {
        if(buf[strlen(buf)-1]=='\n')
            buf[strlen(buf)-1]=0;
        if((pid=fork())<0)
            err_sys("fork error");
        else if(pid==0)
        {
            execlp(buf,buf,(char *)0);
            err_ret("couldn't execute:%s",buf);
            exit(127);
        }
        if((pid=waitpid(pid,&status,0))<0)
            err_sys("waitpid error");
        printf("%% ");
    }
    exit(0);
}
void sig_int(int signo)
{
        printf("interrupt\n%%");
}

1.10 時間值

歷史上,UNIX系統使用過兩種不同的時間值

  1. 日曆時間。該值是從1970年1月1日00:00:00這個特定時間以來所經過的秒數的累計值。
  2. 程序時間。也被稱為CPU時間

當度量一個程序的執行時間時,UNIX系統為一個程序維護了3個程序時間值

程序時間 說明
時鐘時間 即程序執行的時間總量,其值與系統中同時執行的程序數有關
使用者CPU時間 執行使用者指令所用的時間量
系統CPU時間 執行系統呼叫的時間

1.11 系統呼叫和庫函式

Linux不同版本提供了240——260個系統呼叫,應用程式可以呼叫庫函式或者系統呼叫,庫函式也可呼叫系統呼叫;系統呼叫提供了最小的介面,庫函式則提供比較複雜的功能。

系統呼叫提供的函式如open, close, read, write, ioctl等,系統呼叫發生在核心空間

標準C庫函式提供的檔案操作函式如fopen, fread, fwrite, fclose, fflush, fseek等屬於庫函式,底層也是通過系統呼叫來實現的