Unix環境高級編程——第一章-UNIX基礎知識

1.2 UNIX體系結構

一、體系結構:
(1)內核 (2)系統調用 (3)Shell、公共函數庫 (4)應用程序shell


1.4 文件和目錄

一、例: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\n",dirp->d_name);
    closedir(dp);
    exit(0);
}

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


1.5 輸入和輸出

一、文件描述符(file descriptor):一般是一個小的非負整數,內核用以標識特定進程正在訪問的文件。當內核打開一個現有文件或建立一個新文件時,它都返回一個文件描述符,在讀、寫文件時,可使用這個文件描述符。操作系統

二、每當運行一個新程序時,全部的shell都爲其打開3個文件描述符,即標準輸入(standard input)、標準輸出(standard output)以及標準錯誤(standard error)。線程

三、不帶緩衝的I/O
(1)函數open、read、write、lseek以及close提供了不帶緩衝I/O。這些函數都使用文件描述符。指針

四、若是願意從標準輸入中讀,並向標準輸出寫,則下列程序可用於複製任一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。code

五、標準I/O
(1)標準I/O庫函數爲那些不帶緩衝的I/O函數提供了一個帶緩衝的接口。使用標準I/O函數無需擔憂如何選取最佳的緩衝區大小。
(2)使用標準I/O函數還簡化了對輸入行的處理,如fgets函數讀取一個完整的行,而read函數讀取指定字節數。orm

(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)程序(program)是一個存儲在磁盤上某個目錄中的可執行文件。內核使用exec函數(7個exec函數之一),將程序讀入內存,並執行程序。

二、進程和進程ID
(1)程序執行的實例被成爲進程(process)/任務(task)。

(2)UNIX系統確保每一個進程都有一個惟一的數字標識符,將進程ID(process ID)。進程ID老是一個非負整數。

例:用於打印進程的ID。

#include "apue.h"

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

//使用man 2 getpid()查看getpid()函數原型

(1)此程序運行時,它調用函數getpid獲得其進程ID。
(2)getpid返回一個pid_t數據類型。咱們不知道它的大小,僅知道是標準會保證它在一個長整型中。
(3)雖然大多數進程ID能夠用整型表示,但用長整型能夠提升移植性。

三、進程控制
(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]=='\n')
            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變量)。

四、線程和線程ID
(1)一般,一個進程只有一個控制線程(thread)——某一時刻執行的一組機器指令。
(2)一個進程內的全部線程共享同一地址空間、文件描述符、棧以及進程相關的屬性。由於它們能訪問同一存儲區,因此各線程在訪問共享數據須要採起同步措施以免不一致性。

1.7 出錯處理

一、當UNIX系統函數出錯時候,一般會返回一個負值,而整型變量errno一般被設置爲具備特定信息的值。

二、頭文件errno.h中定義了errno以及能夠賦予它的各類常量。

三、對於errno應當注意兩條規則
(1)若是沒有出錯,其值不會被例程清楚。所以,僅當函數的返回之指明出錯時,才檢驗其值。
(2)任何函數都不會將errno值設爲0並且在errno.h中定義的全部常量都不爲0。

四、C標準定義了兩個函數,用於打印出錯信息:

#include <string.h>
char * strerror(int errnum);
//strerror函數將errnum(一般爲errno值)映射爲一個出錯信息字符串,並返回此字符串的指針。

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

五、例:程序顯示兩個出錯函數的使用方法

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

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

//man errno查看錯誤的類型

六、出錯恢復
(1)致命性錯誤:沒法執行恢復動做,最多就打印出一條錯誤信息或將錯誤信息寫進日誌文件。
(2)非致命性錯誤可妥善進行處理。

1.8 用戶標識

一、例:用於打印用戶ID組ID

#include "apue.h"

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

//man getuid 查看getuid函數原型
//man getgid 查看getgid函數原型

1.9 信號

一、信號(signal)用於通知進程發生了某種狀況。

二、進程有如下3種處理信號的方式:
(1)忽略信號
(2)按系統默認方式處理
(3)提供一個函數,信號發生時調用該函數,這被成爲捕捉該信號

三、終端上的鍵盤有兩重產生信號的方法,分別成爲中斷鍵(interrupt key,一般是Delete鍵或Ctrl+C)和退出鍵(quit key,一般是Ctrl+),它們被用於中斷當前進程。

四、另外一種產生信號的方法是調用kill函數。在一個進程中調用此函數就可向另外一個進程發送信號。

五、限制:當向一個進程發送信號時,咱們必須是進程的user或者root用戶。

六、例:爲了捕捉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]=='\n')
            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\n%% ");
}

1.10 時間值

一、UNIX系統使用過兩種不一樣時間值
(1)日曆時間,該值是子協調世界時(Coordinated Universal Time,UTC)1970年1月1日 00:00:00這個特定時間以來所通過的秒數累計值。
(系統基本數據類型time_t用於保存這種時間值)
(2)進程時間。也被稱爲CPU時間,以度量進程使用中央處理器資源。進程時間以始終滴答計算。
(系統基本數據類型clock_t)用於保存這種時間值

二、UNIX系統爲一個進程維護了3個進程時間值:
(1)時鐘時間
(2)用戶CPU時間
(3)系統CPU時間

三、時鐘時間又成爲牆上時鐘時間(wall clock time),它是進程運行的時間總量,其值與系統中同時運行的進程數有關。

四、用戶CPU時間是指執行用戶指令所用的時間量。

五、系統CPU時間是該進程執行內核程序所經歷的時間

六、用戶CPU時間和系統CPU時間之和被成爲CPU時間

1.11 系統調用和庫函數

一、全部的操做系統都提供多種服務的入口,這些入口被稱爲系統調用(system call)。

二、系統調用和庫函數都是以C函數的形式出現,二者都爲應用程序提供服務。

三、咱們能夠替換庫函數,但系統調用一般是不能被替換的。

四、以malloc庫函數爲例,UNIX系統調用處理存儲分配的是sbrk(2),sbrk在內核中分配一塊空間給進程,而庫函數malloc則在用戶層次管理這一空間。

五、系統調用和庫函數之間的另外一個差異:系統調用一般提供最小的接口,而庫函數提供比較複雜的功能。

相關文章
相關標籤/搜索