第一章 UNIX 基礎知識

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)。

相關文章
相關標籤/搜索