1、UNIX體系結構shell
全部操做系統都爲他們所運行的程序提供服務,典型的服務包括:執行新程序、打開文件、讀文件、分配存儲區等。嚴格意義上來講,操做系統能夠定義爲一種軟件,它控制計算機硬件資源,提供程序運行環境。咱們也將這種軟件成爲內核,由於它相對較小,而且位於環境的核心。內核的接口被稱爲系統調用。公共函數庫構建在之上,普通的應用程序能夠調用系統調用,也能夠調用公共函數庫。shell是一個特殊的應用程序,位運行其餘應用程序提供了一個接口。函數
2、文件和目錄ui
1.文件系統spa
UNXI文件系統是目錄和文件的一種層次結構,全部東西的起點稱爲根(root)的目錄,這個目錄的名稱是一個字符「/」。目錄是一個包含目錄項的文件,由文件名和文件屬性(文件類型、文件大小、文件全部者、文件權限等)組成。這裏須要注意的是邏輯視圖和實際存放在磁盤上的方式是不一樣的。UNIX文件系統的大多數實現並不在目錄項中存放屬性,這是由於當一個文件具備多個硬連接時,很難保持多個屬性副本之間的同步。操作系統
2.文件名和路徑名線程
建立新目錄時會自動建立兩個文件名:「.」指向當前目錄,「..」指向父目錄,在最高層次,兩者指向相同。由斜線分隔的一個或多個文件名組成的序列構成路徑名,以斜線開頭的路徑名稱爲絕對路徑名,不然稱爲相對路徑名。code
#include<stdio.h> #include<stdlib.h> #include<dirent.h> int main(int argc, char *argv[]) { DIR *dp; struct dirent *dirp; if(2 != argc) { printf("usage: ls directory_name\n"); return -1; } if(NULL == (dp = opendir(argv[1]))) { printf("can't open %s\n", argv[1]); return -1; } while(NULL != (dirp = readdir(dp))) { printf("%s\n", dirp->d_name); } closedir(dp); exit(0); }
1-1:列出一個目錄中的全部文件blog
3.工做目錄和起始目錄接口
每一個進程都有一個工做目錄,全部相對路徑名都從工做目錄開始解釋,進程能夠用chdir函數更改其工做目錄。登錄時,工做目錄設置爲起始目錄,這個目錄是從口令文件中相應用戶的登陸項中獲取的。進程
3、輸入和輸出
1.文件描述符
文件描述符一般是一個小的非負整數,內核用以標識一個特定進程正在訪問的文件。當內核打開一個先用文件或建立一個新文件時,它都返回一個文件描述符,在讀寫文件時,可使用這個文件描述符。按慣例,每當運行一個新程序時,全部的shell都爲其打開3個文件描述符,即標準輸入、標準輸出以及標準錯誤,若是不作特殊處理,這三個文件描述符都連接向終端。
2.不帶緩衝的I/O
函數open、read、write、lseek以及close提供了不帶緩衝的I/O。這些函數都使用文件描述符。
#include<unistd.h> #include<stdio.h> #include<stdlib.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) { printf("write error.\n"); return -1; } } if(n > 0) { printf("read error.\n"); return -1; } exit(0); }
1-2:將標準輸入複製到標準輸出
註釋:1.頭文件<unistd.h>包含了不少UNIX系統服務的函數原型,STDID_FILENO/STDOUT_FILENO,以及read/write都定義在其中。2.read函數返回其讀取到的字節數,當發生錯誤時,read返回-1;3輸入ctrl+D做爲文件結束符,終止程序;
3.標準I/O
標準I/O函數爲那些不帶緩衝I/O函數提供了一個帶緩衝的接口。使用標準I/O函數無需擔憂如何選取最佳的緩衝區大小,不用像上例那樣須要定義BUFFSIZE。而且還簡化了對輸入行的處理。
#include<unistd.h> #include<stdio.h> #include<stdlib.h> int main(void) { int c; while((c = getc(stdin)) != EOF) { if(putc(c, stdout) == EOF) { printf("output error.\n"); return -1; } } if(ferror(stdin)) { printf("input error.\n"); } exit(0); }
1-3:用標準I/O將標準輸入複製到標準輸出
4、程序和進程
1.程序、進程和進程ID
程序是一個存儲在磁盤上某個目錄中的可執行文件。內核使用exec函數將程序讀入內存,並執行程序。程序的執行實例稱爲進程,每個進程都有一個惟一的數字標識符,被稱爲進程ID(非負整數)。
#include<unistd.h> #include<stdio.h> #include<stdlib.h> int main(void) { printf("Hello world from process ID %ld\n", (long)getpid()); exit(0); }
1-4:打印進程ID
2.進程控制
進程控制主要函數:fork、exec和waitpid。
#include<stdlib.h> #include<stdio.h> #include<string.h> #include<unistd.h> #include<sys/wait.h> #define MAXLINE 1024 int main(void) { char buff[MAXLINE]; pid_t pid; int status; printf("%% "); while(NULL != (fgets(buff, MAXLINE, stdin))) { if('\n' == buff[strlen(buff) - 1]) { buff[strlen(buff) - 1] = 0; } if(0 > (pid = fork())) { printf("fork error\n"); return -1; } else if(0 == pid) { execlp(buff, buff, (char *) 0); printf("couldn't execute:%s\n", buff); return -1; } if(0 > (pid == waitpid(pid, &status, 0))) { printf("wait pid error\n"); } printf("%% "); } }
1-5:從標準輸入讀命令並執行
註釋:1.fgets是一個標準I/O函數,一次讀取一行。和read的區別在於本身管理緩衝區,不返回讀取的長度,在讀取的末尾加入"\n"。而execlp要求參數以null結束,因此須要進行替換;2.fork調用一次,返回兩次,在子進程中返回0,父進程返回子進程PID;3.execlp以執行從標準輸入讀取的命令,這就用新的程序文件替換了子進程原先執行的程序文件;4.父進程和子進程同步是經過waipid實現的。
3.線程和線程ID
一個進程內的全部線程共享同一地址空間、文件描述符、棧以及與進程相關的屬性。由於他們能訪問贊成存儲區,因此各線程在訪問共享數據時須要採起同步措施以免不一致性。和進程ID相同, 線程也有ID,可是線程ID只在它所屬的進程內其做用。
5、出錯處理
當UNIX系統函數出錯時,一般會返回一個負值,並且整形變量errno一般被設置爲具備特定信息的值。對於errno應當注意兩條規則:1.若是沒有出錯,其值不會被例程清楚,所以,僅當函數的返回值指明出錯時,才檢驗其值;2.任何函數都不會將errno值設爲0.
#include<unistd.h> #include<stdlib.h> #include<string.h> #include<stdio.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-6:例示strerror和perror
註釋:1.咱們將程序名(argv[0])做爲參數傳遞給main,這是一個標準的UNIX慣例。
6、用戶標識
口令文件登陸項中的用戶ID是一個數值,它向系統標識不一樣的用戶。根用戶的ID爲0.
#include<unistd.h> #include<stdlib.h> #include<stdio.h> int main(void) { printf("uid = %d, gid = %d\n", getuid(), getgid()); exit(0); }
1-7:打印用戶ID和組ID
7、信號
信號(signal)用於通知進程發生了某種狀況。進程有如下三種處理方式:1.忽略信號;2.按系統默認方式處理;3.提供一個函數。該信號發生時調用該函數。當向一個進程發送信號時,咱們必須是那個進程的全部者或者是超級用戶。
#include<stdlib.h> #include<stdio.h> #include<string.h> #include<unistd.h> #include<sys/wait.h> #define MAXLINE 1024 static void sig_int(int); int main(void) { char buff[MAXLINE]; pid_t pid; int status; if(signal(SIGINT, sig_int) == SIG_ERR) { printf("signal error\n"); return -1; } printf("%% "); while(NULL != (fgets(buff, MAXLINE, stdin))) { if('\n' == buff[strlen(buff) - 1]) { buff[strlen(buff) - 1] = 0; } if(0 > (pid = fork())) { printf("fork error\n"); return -1; } else if(0 == pid) { execlp(buff, buff, (char *) 0); printf("couldn't execute:%s\n", buff); return -1; } if(0 > (pid == waitpid(pid, &status, 0))) { printf("wait pid error\n"); } printf("%% "); } } void sig_int(int signo) { printf("interrupt \n%% "); }
1-8:從標準輸入讀命令並執行
8、時間值
UNIX系統爲一個進程維護了3個進程時間值:1.時鐘時間:進程運行的時間總量,其值與系統中同時運行的進程數有關;2.用戶CPU時間:執行用戶指令所用的時間量;3.系統CPU時間:爲該進程執行內核程序所經歷的時間。2和3之和又稱爲CPU時間。
9、系統調用和庫函數
從實現者的角度來看,系統調用和庫函數之間有根本的區別,但從用戶的角度來看,其區別不重要。用戶能夠替換庫函數,可是不能替換系統調用。而且系統調用一般提供一種最小接口,而庫函數一般提供比較複雜的功能。