一、體系結構:
(1)內核 (2)系統調用 (3)Shell、公共函數庫 (4)應用程序shell
一、例: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
一、文件描述符(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)程序(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)一個進程內的全部線程共享同一地址空間、文件描述符、棧以及進程相關的屬性。由於它們能訪問同一存儲區,因此各線程在訪問共享數據須要採起同步措施以免不一致性。
一、當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)非致命性錯誤可妥善進行處理。
一、例:用於打印用戶ID組ID
#include "apue.h" int main(void) { printf("uid = %d, gid = %d\n",getuid(),getgid()); exit(0); } //man getuid 查看getuid函數原型 //man getgid 查看getgid函數原型
一、信號(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%% "); }
一、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時間
一、全部的操做系統都提供多種服務的入口,這些入口被稱爲系統調用(system call)。
二、系統調用和庫函數都是以C函數的形式出現,二者都爲應用程序提供服務。
三、咱們能夠替換庫函數,但系統調用一般是不能被替換的。
四、以malloc庫函數爲例,UNIX系統調用處理存儲分配的是sbrk(2),sbrk在內核中分配一塊空間給進程,而庫函數malloc則在用戶層次管理這一空間。
五、系統調用和庫函數之間的另外一個差異:系統調用一般提供最小的接口,而庫函數提供比較複雜的功能。