1.1 Unix體系結構shell
OS定義爲一種軟件,它控制計算機硬件資源,提供程序運行環境,通常稱其爲內核(kernel),它體積小,位於環境中心。多線程
內核的接口爲系統調用(system call),共用函數庫構建在系統調用上,應用軟件既可使用公用函數庫,也可使用系統調用。shell是一種特殊的應用程序,它爲運行其餘應用程序提供一個接口。函數
下圖爲 UNIX 體系結構:ui
廣義上,OS包括內核和一些軟件,例如 Linux 是 GNU 操做系統使用的內核,能夠稱這種操做系統爲 GNU/Linux,可是一般簡稱爲 Linux。因此 Linux 自己有雙重含義,內核和操做系統。加密
1.2 登錄spa
(1)登陸名操作系統
用戶登錄 UNIX 系統,鍵入 登陸名,再鍵入 口令。系統在其口令文件(一般是/etc/passwd文件)中查看登陸名。線程
我文件中的內容:設計
口令文件中的登錄項由7個以冒號分隔的字段組成,他們分別是:3d
登錄名、加密口令、數值用戶ID、數值組、註釋字段、起始目錄、shell程序
其中,全部OS已將加密口令移到另外一個文件中,第6章將說明這種文件以及訪問他們的函數。
(2)shell
用戶登錄後,用戶能夠向shell程序鍵入命令,某些系統會啓動一個視窗管理程序,但 最終總會有一個shell程序運行在一個視窗中。
shell是一個命令解釋器,它讀取用戶輸入,而後執行命令。
用戶一般用終端(交互式shell),有時經過文件(shell腳本,shell script)向shell進行輸入。
下圖是常見的shell
Steve Bourne在貝爾實驗室開發的 Bourne shell。
Bourne-again shell 是GNU shell,全部Linux系統都提供這種shell,它被設計遵循 POSIX 的。
1.3 文件和目錄
(1)文件系統
UNIX文件系統是目錄和文件組成的一種層次結構,目錄的起點稱爲根(root),名字是 / 。
目錄(directory)是一個包含許多目錄項的文件。
在邏輯上,能夠認爲每一個目錄項都包含一個文件名,文件屬性信息(文件類型,文件大小...),stat 和 fstat 能夠返回文件屬性的一個信息結構。
目錄項的邏輯視圖與實際存放在磁盤上的方式是不一樣的。UNIX 文件系統的大多數實現並不在目錄項中存放屬性,這是由於當一個文件具備多個硬連接時,很難保持多個屬性副本之間的同步。到第4章討論硬連接時,這個問題將很好理解。
(2)文件名
目錄中各個名字稱爲文件名(filename)。
文件名中不能出現斜線(/)和空操做符(null)。由於謝賢用於分隔各文件名構成路徑名。空操做符用於終止一個路徑名。
建立新目錄時會自動建立兩個文件名:. 和 .. ,點指向當前目錄,點一點指向父目錄。在最高層次的根目錄中,點一點和點相同。
如今,全部的UNIX系統支持至少 255 各字符的文件名。
(3)路徑名
一個或多個斜線分隔的文件名序列構成路徑名(pathname),以斜線開頭的路徑稱爲絕對路徑(absolute pathname),不然稱爲相對路徑(relative pathname)。相對路徑名引用相對於當前目錄的文件。
// 列出一個目錄中全部文件 #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->name); closedir(dp); exit(0); }
由於各類不一樣 UNIX 系統目錄項的實際格式是不同的,因此使用函數 opendir, readdir, closedir對目錄進行處理。
opendir 函數返回指向 DIR 結構的指針,將這個指針傳給 readdir 函數,咱們不關心 DIR 結構中包含了什麼。而後,在循環中調用 readdir 來讀每一個目錄項。
readdir 函數返回一個指向 dirent 結構的指針,而當目錄中已無可讀的目錄項時則返回 null 指針。在dirent 結構中取出的是每一個目錄項的名字(d_name)。使用該名字,此後可調用 stat 函數以得到該文件的全部屬性。
當程序將結束,它以參數0調用函數 exit,exit終止程序,按慣例,參數0表示正常結束,參數1-255表示出錯。
struct dirent 結構以下:
(4)工做目錄
每一個進程 都有一個工做目錄(working directory),有時稱爲當前工做目錄(current working directory)。全部相對路徑名都從工做目錄開始解釋。進程能夠用chdir函數更改其工做目錄。
(5)起始目錄
登錄時,工做目錄設置爲起始目錄(home directory),該起始目錄從口令文件中相應用戶的登錄項中取得。
1.4 輸入和輸出
(1)文件描述符
文件描述符(file descriptor)一般時一個小的非負整數,內核用它標識一個特定進程正在訪問的文件。當內核打開一個已有文件或建立一個新文件時,它返回一個文件描述符。在操做文件時,可使用。
(2)標準輸入、標準輸出和標準出錯
按慣例,每當運行一個新程序時,全部shell都爲其打開三個文件描述符:標準輸入(standard input)、標準輸出(standard output)以及標準出錯(standard error)。
若是項 ls 那樣沒有作什麼特殊處理,則這三個描述符都鏈向終端。
大多數 shell 都提供一種方法,使其中 任何一個或全部這三個描述符都能重定向到某個文件,如:
ls > file.list
(3)不用緩衝的 I/O
函數 open、read、write、lseek以及close提供了不用緩衝的 I/O。這些函數都使用文件描述符。
// 將標準輸入複製到標準輸出 #include "apue.h"
#define BUFFSIZE 1
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); }
因爲鍵入值的傳遞是 FIFO結構的,因此不管 BUFFSIZE 設置爲何值,程序都能正常執行,可是執行效率不一樣。
(2)標準 IO
標準 I/O 函數提供一種對不用緩衝 I/O 函數的帶緩衝接口。使用標準 I/O 函數無需擔憂如何選取最佳的緩衝區大小,例如上面程序中的 BUFFSIZE 常量的大小。
使用標準 I/O 函數的另外一個優勢是簡化了對輸入行的處理。例如,fgets函數讀一完整的行,而read函數讀指定字節數。
在5.4節中,咱們將瞭解到,標準 I/O 函數庫提供了使咱們可以控制該庫所使用的緩衝風格的函數。
// 用標準 I/O 將標準輸入複製到標準輸出 #include "apue.h" int main() { int c; while ((c = getc(stdin)) != EOF) if (putc(c, stdout) == EOF) err_sys("output error"); if (ferror(stdin)) err_sys("input_err"); exit(0); }
EOF是一個常量,在stdio.h 中定義,使用 ctrl + D鍵入。 標準輸入/標準輸出 stdin 和 stdout 定義在 stdio.h 中,表示標準輸入和標準輸出文件。
1.5 程序和進程
(1)程序
程序(program)是存放在磁盤上、處於某個目錄中的一個可執行文件。使用6個exec函數中的一個由內核將程序讀入存儲器,並使其執行。
(2)進程和進程ID
程序的執行實例被稱爲進程(process)。某些操做系統用任務(task)表示正被執行的程序。
UNIX系統確保每一個進程都有一個惟一的數字標識符,稱爲進程ID(process ID)。進程ID老是一非負整數。
(3)進程控制
有三個用於進程控制的主要函數:fork、exec和waitpid。
// 從標準輸入讀命令並執行 #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; /* replace newline with null */ 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); }
fgets從標準輸入一次讀一行,當鍵入文件按結束字符EOF(使用 ctrl + D)做爲行的第一個字符時,fgets返回一個 null 指針,程序退出。
因爲 execlp 函數要求參數以 null 結尾,而不是換行符,因此須要進行替換。
(4)線程和線程ID
一般,一個進程只有一個控制線程(thread),同一時刻只執行一組機器指令,對於某些問題,若是不一樣部分各使用一個控制線程,那麼可簡化問題解決。另外,多個控制線程能充分利用多處理器系統的並行性。
同一個進程的線程共享同一地址空間,因此各線程在訪問共享數據時須要採起同步措施以免不一致性。
與進程相同,線程也用ID標識,可是線程ID只在它所屬的進程內起做用。
1.6 出錯處理
UNIX 函數出錯時,一般返回一個負值,或者 null,並且整形變量 errno 一般被設置爲函數有附加信息的一個值。
文件<errno.h>中定義了符號errno和能夠賦予它的各類常量。
errno 之前的定義是:
extern int errno;
可是在支持線程的環境中,多個線程共享進程地址,每一個線程都有屬於本身的局部errno以免一個線程干擾另外一個線程。例如 Linux支持多線程存取 errno,將其定義爲:
extern int * __errno_location(void); #define errno (*__errno_location())
對於errno應當知道兩條規則。
第一:若是沒有出錯,則其值不會被一個例程清除。所以,僅當函數的返回值指明出錯時,才檢驗其值。
第二:任一函數都不會將errno值設置爲0,在<errno.h>中定義的全部常量都不爲0
C標準定義了兩個函數,它們幫助打印出錯信息。
#include <string.h> char *strerror(int errnum);
#include <stdio.h> void perror(const char *msg);
它首先輸出msg指向的字符串,而後一個冒號,一個空格,接着時errno值對應的出錯信息,最後是一個換行符。
// 示例strerror和perror #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); }
出錯恢復:
可將<errno.h>中定義的各類出錯分紅致命性和非致命性兩類。對於致命性錯誤,沒法執行恢復動做,最多隻能在用戶屏幕上打印一條出錯信息,或寫入日誌,而後終止。而對於非致命性出錯,能夠進行處理,大多數非致命性出錯本質上是暫時的,如資源短缺。
與資源相關的非致命性出錯包括 EAGAIN、ENFILE、ENOBUFS、ENOLCK、ENOSPC、ENOSR、EWOULDBLOCK,有時 ENOMEM也是非致命性,當EBUSY指明共享資源正在使用時,能夠將他做爲非致命性出錯處理,當EINTR中斷一慢速系統調用時,可 將它做爲非致命性出錯處理。
對於資源相關的非致命性出錯,通常恢復動做時延遲一些時間,而後再試。
1.7 用戶標識
(1)用戶ID
口令文件登錄項中的用戶ID(user ID)是個數值,它向系統標識各個不一樣的用戶。
系統管理員在肯定一個用戶登錄名同時,肯定用戶ID,用戶不能更改用戶ID。
用戶ID爲0,是超級用戶。
(2)組ID
口令文件登錄項也包括用戶的組ID(group ID),它是一個數值。
組被用於將若干用戶分到不一樣的項目組或者部門中去。這種機制容許同組各個成員之間共享資源,而組外用戶則不能。
組文件將組名映射爲數字組ID,它一般是 /etc/group
使用數字ID是歷史上造成的,爲的是節省磁盤空間,另外權限校驗也比字符串更省時。對於用戶而言使用字符串更方便,因此口令文件包含了登錄名和用戶ID之間的映射關係。
(3)附加組ID
大多數UNIX系統容許用戶屬於多個組。
1.8 信號
信號(signal)是通知進程已發生某種狀況的一種技術。
進程對於信號有三種選擇:忽略,默認方式處理,捕捉。
1.9 時間值
UNIX系統一直使用兩種不一樣的時間值:
(1)日曆時間,該值是自 1970年1月1日00:00:00以來國際標準時間(UTC)所通過的秒數累計值(早期稱爲格林尼治標準時間)。
系統基本數據類型 time_t 用於保存這種事件值。
(2)進程時間,也被稱爲 CPU 時間,用於度量進程使用CPU資源。進程時間以時鐘滴答計算。歷史上有每秒50,60或100個滴答。
系統基本數據類型 clock_t 用於保存這種時間值。
當度量一個進程的執行時間時,UNIX系統使用三個進程時間值:
時鐘時間,用戶CPU時間,系統CPU時間。
時鐘時間:進程運行時間總量。
用戶CPU時間:執行用戶指令所用的時間。(進程在用戶空間的時間)
系統CPU時間:執行內核程序所經歷的時間。(進程在內核空間的時間)
要獲進程的三種時間,只須要執行命令 time(1)。