Unix環境高階程式設計——第一章-UNIX基礎知識

Arturia發表於2019-05-13

1.2 UNIX體系結構

1、體系結構:
(1)核心 (2)系統呼叫 (3)Shell、公共函式庫 (4)應用程式


1.4 檔案和目錄

1、例:ls(l)命令的簡要實現

#include "apue.h"
#include <dirent.h>
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中包含了此標頭檔案)及兩個常量STDIN_FILENO和STDOUT_FILENO是POSIX標準的一部分。標頭檔案unistd.h包含了很多UNIX系統服務的函式原型,包括read和write。
(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函式之一),將程式讀入記憶體,並執行程式。

2、程式和程式ID
(1)程式執行的例項被成為程式(process)/任務(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資料型別。我們不知道它的大小,僅知道是標準會保證它在一個長整型中。
(3)雖然大多數程式ID可以用整型表示,但用長整型可以提高移植性。

3、程式控制
(1)有3個用於程式控制的主要函式:fork、exec和waitpid。(exec函式有7種變體,但經常把它們統稱為exec函式。)

(2)例:該程式從標準輸入讀取命令,然後執行這些命令。

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

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)通常,一個程式只有一個控制執行緒(thread)——某一時刻執行的一組機器指令。
(2)一個程式內的所有執行緒共享同一地址空間、檔案描述符、棧以及程式相關的屬性。因為它們能訪問同一儲存區,所以各執行緒在訪問共享資料需要採取同步措施以避免不一致性。

1.7 出錯處理

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

2、標頭檔案errno.h中定義了errno以及可以賦予它的各種常量。

3、對於errno應當注意兩條規則
(1)如果沒有出錯,其值不會被例程清楚。因此,僅當函式的返回之指明出錯時,才檢驗其值。
(2)任何函式都不會將errno值設為0而且在errno.h中定義的所有常量都不為0。

4、C標準定義了兩個函式,用於列印出錯資訊:

#include <string.h>
char * strerror(int errnum);
//strerror函式將errnum(通常為errno值)對映為一個出錯資訊字串,並返回此字串的指標。

#include <stdio.h>
void perror(const char *msg);
//perror函式基於errno的當前值,在標準錯誤上產生一條出錯資訊,並返回。首先輸出由msg指向的字串,然後是一個:,一個空格,接著對應errno值的出錯資訊,最後是一個換行符。

5、例:程式顯示兩個出錯函式的使用方法

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

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

//man errno檢視錯誤的型別

6、出錯恢復
(1)致命性錯誤:無法執行恢復動作,最多就列印出一條錯誤資訊或將錯誤資訊寫進日誌檔案。
(2)非致命性錯誤可妥善進行處理。

1.8 使用者標識

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、限制:當向一個程式傳送訊號時,我們必須是程式的user或者root使用者。

6、例:為了捕捉SIGINT訊號,程式需要呼叫signal函式,其中指定了當產生SIGINT訊號時要呼叫的函式的名字,函式名為sig_int。

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

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系統使用過兩種不同時間值
(1)日曆時間,該值是子協調世界時(Coordinated Universal Time,UTC)1970年1月1日 00:00:00這個特定時間以來所經過的秒數累計值。
(系統基本資料型別time_t用於儲存這種時間值)
(2)程式時間。也被稱為CPU時間,以度量程式使用中央處理器資源。程式時間以始終滴答計算。
(系統基本資料型別clock_t)用於儲存這種時間值

2、UNIX系統為一個程式維護了3個程式時間值:
(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、系統呼叫和庫函式之間的另一個差別:系統呼叫通常提供最小的介面,而庫函式提供比較複雜的功能。

相關文章