跳轉到內容

作業系統基礎

100% developed
來自華夏公益教科書,開放的世界,開放的書籍

什麼是作業系統

[編輯 | 編輯原始碼]

作業系統通常被分為核心和使用者空間。

核心為軟體提供了一個與硬體互動的層。它對硬體進行了抽象,使得許多軟體可以在截然不同的硬體上以相同的方式執行。核心提供系統呼叫,允許使用者空間與之互動。核心處理許多內容,包括檔案系統(並非總是如此,但通常如此)、裝置和程序控制。

使用者空間存在於核心之外的所有內容。使用者建立的所有程序,包括終端,都存在於使用者空間。顯示程式的圖形使用者介面 (GUI) 也位於使用者空間。

Unix Shell

[編輯 | 編輯原始碼]

Unix shell 是一個命令直譯器程式,它在命令列環境(例如終端或終端模擬器)中充當使用者與作業系統之間的主要介面。Shell 是一個必不可少的(通常是首選的)工具,但它只是一個普通的使用者程式,它使用系統呼叫來完成大部分工作 - 所以它只是一個“外殼”。

[編輯 | 編輯原始碼]

現代社會存在著許多 Shell,每個 Shell 都有其自身的特性集。最常見的是 Bourne Shell。Bourne Shell(在 POSIX 位置中俗稱為 /bin/sh)已經存在了幾十年,並且基本上可以在任何 Unix 計算機上找到。雖然它缺乏某些互動功能,但它非常普遍,因此任何為它編寫的指令碼都可以在任何 Unix 系統上執行。

Shell 的功能

[編輯 | 編輯原始碼]

Shell 的主要職責是向用戶提供命令提示符(例如 $),等待命令,然後執行命令。

Shell 也可用於編寫程式,方法是將 Shell 命令寫入文字檔案。必須在檔案頂部包含直譯器,形式為 #!interpreter(例如:#!/bin/sh)。執行該檔案時,Unix 讀取直譯器,從而知道使用該 Shell 來解釋所有命令。

建立 Shell

[編輯 | 編輯原始碼]

Shell 的整體結構可以是

 repeat forever
   read one line 
   parse the command into a list of arguments
   if the line starts with a command name (e.g. cd and exit)
   then 
     perform the function (if it's exit, break out of the loop)
   else (it invokes a program, e.g. ls and cat) 
     execute the program 

為了讀取命令,我們一次讀取一行,並將該行標記化為標記。

為了執行程式,需要執行以下操作:使用 fork() 系統呼叫複製當前程序

使用 fork() 克隆程序

[編輯 | 編輯原始碼]
/* modified from fork.c in Advanced Linux Programming (page 49) */
/* http://www.makelinux.net/alp/024.htm */

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main(){
  pid_t child_pid;
  
  printf("the main program process ID is %d\n", (int) getpid());

  child_pid = fork();
  if(child_pid != 0){
    printf("this is the parent process, with id %d\n", (int)getpid());
    printf("child_pid=%d\n", child_pid);
  }else{
    printf("this is the child  process, with id %d\n", (int)getpid());
    printf("child_pid=%d\n", child_pid);
  }
}

此示例顯示瞭如何透過派生當前程序來建立新程序。請注意,fork() 函式呼叫(系統呼叫)被呼叫 **一次**,但返回 **兩次**,因為當呼叫完成時,有兩個程序在執行相同的程式碼。

使用 execvp() 在後臺執行新程式

[編輯 | 編輯原始碼]
/* from Advanced Linux Programming (page 51) */
/* http://www.makelinux.net/alp/024.htm */
#include <stdio.h> 
#include <stdlib.h> 
#include <sys/types.h> 
#include <unistd.h> 

/* Spawn a child process running a new program. PROGRAM is the name 
   of the program to run; the path will be searched for this program. 
   ARG_LIST is a NULL-terminated list of character strings to be 
   passed as the program's argument list. Returns  the process ID of 
   the spawned process.  */ 

int spawn(char* program, char** arg_list) {
  pid_t child_pid; 

  /* Duplicate this process. */ 
  child_pid = fork(); 

  if (child_pid != 0){
    /* This is the parent process. */ 
    return child_pid; 
  }else {
    /* Now execute PROGRAM, searching for it in the path.  */ 
    execvp(program, arg_list); 
    /* The execvp  function returns only if an error occurs.  */ 
    fprintf (stderr, "an error occurred in execvp\n"); 
    abort(); 
  } 
} 

int main() {
  /*  The argument list to pass to the "ls" command.  */ 
  char* arg_list[] = {
    "ls",     /* argv[0], the name of the program.  */ 
    "-l", 
    "/", 
    NULL      /* The argument list must end with a NULL.  */ 
  }; 

  /* Spawn a child process running the "ls" command. Ignore the 
     returned child process ID.  */ 
  spawn("ls", arg_list); 
  printf("done with main program\n"); 
  return 0; 
}

使用 execvp() 在前臺執行新程式

[編輯 | 編輯原始碼]
/* from Advanced Linux Programming (page 51) */
#include <stdio.h> 
#include <stdlib.h> 
#include <sys/types.h> 
#include <sys/wait.h>
#include <unistd.h> 

/* Spawn a child process running a new program. PROGRAM is the name 
   of the program to run; the path will be searched for this program. 
   ARG_LIST is a NULL-terminated list of character strings to be 
   passed as the program's argument list. Returns  the process ID of 
   the spawned process.  */ 

int spawn(char* program, char** arg_list) {
  pid_t child_pid; 

  /* Duplicate this process. */ 
  child_pid = fork(); 

  if (child_pid != 0){
    /* This is the parent process. */ 
    return child_pid; 
  }else {
    /* Now execute PROGRAM, searching for it in the path.  */ 
    execvp(program, arg_list); 
    /* The execvp  function returns only if an error occurs.  */ 
    fprintf(stderr, "an error occurred in execvp\n"); 
    abort(); 
  } 
} 

int main() {
  /*  The argument list to pass to the "ls" command.  */ 
  char* arg_list[] = {
    "ls",     /* argv[0], the name of the program.  */ 
    "-l", 
    "/", 
    NULL      /* The argument list must end with a NULL.  */ 
  }; 

  /* Spawn a child process running the "ls" command. Ignore the 
     returned child process ID.  */ 
  pid_t pid = spawn("ls", arg_list); 

  /* Wait for the child process to complete.  */ 
  int child_status;
  waitpid(pid, &child_status, 0); 
  
  if (WIFEXITED(child_status)){
    printf ("the child process exited normally, with exit code %d\n", 
	     WEXITSTATUS(child_status)); 
  }else{ 
    printf("the child process exited abnormally\n"); 
  }
  
  return 0; 
}

請注意,引數列表必須以程式名稱作為第一個引數,並且 **必須** 以 NULL 結尾,這表示列表的結尾。否則,execvp() 無法正常工作。

使用 dup2() 重定向標準輸出

[編輯 | 編輯原始碼]
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
 
int main(){
  int overwrite = 0;
  int fd;
  if(overwrite){
    printf("open test.txt to overwrite.\n");
    fd =  open("test.txt", O_WRONLY | O_CREAT, S_IRWXU);
  }else{
    printf("open test.txt to append.\n");
    fd =  open("test.txt", O_WRONLY | O_APPEND | O_CREAT, S_IRWXU);
  }
  dup2 (fd, STDOUT_FILENO);
  printf("hello world!");
}

此示例以寫模式開啟一個檔案(“test.txt”),並將標準輸出與開啟的檔案同義 - 傳送到標準輸出的位元組將轉到該檔案。“O_WRONLY | O_CREAT” 標記會導致檔案以寫模式開啟,並且如果檔案不存在,則建立檔案。

有關每個開啟檔案的資訊記錄在作業系統管理的表中。每個條目對應於一個開啟的檔案,每個條目的索引是一個整數(檔案描述符),它作為 open() 系統呼叫的返回值返回給開啟檔案的程序。標準輸入、標準輸出和標準錯誤的條目使用預定義的索引保留:STDIN_FILENO、STDOUT_FILENO 和 STDERR_FILENO(在 <unistd.h> 中定義)。dup2(index1, index2) 函式將複製 index1 處的條目內容到 index2 處的條目,這使得檔案描述符 index2 與檔案描述符 index1 同義。

在子程序中重定向標準輸出

[編輯 | 編輯原始碼]

“dup2 (fd, STDOUT_FILENO)” 行(在前面的示例中)將當前(主)程序的標準輸出重定向到開啟的檔案。如果您想將子程序的標準輸出重定向到檔案,則需要等到子程序建立 - 在 fork() 函式呼叫之後。以下示例演示了該想法。您將看到,來自父程序的“hello”訊息仍然會發送到標準輸出,但來自子程序的標準輸出將被重定向到檔案。

#include <stdio.h> 
#include <stdlib.h> 
#include <sys/types.h> 
#include <sys/wait.h>
#include <unistd.h> 
#include <sys/stat.h>
#include <fcntl.h>

int main() {
  /*  The argument list to pass to the "ls" command.  */ 
  char* arg_list[] = {
    "ls",     /* argv[0], the name of the program.  */ 
    "-l", 
    "/", 
    NULL      /* The argument list must end with a NULL.  */ 
  }; 

  /* Spawn a child process running the "ls" command. */
  pid_t child_pid = fork(); 

  if (child_pid == 0){
    /* This is the child process. */ 
    char * filename = "test.txt";
    int outfile = open(filename, O_CREAT | O_WRONLY, S_IRWXU);
    if (outfile == -1){
      fprintf(stderr, "Error: failed to create file %s\n", filename);
    }else{
      /* redirect the standard output from this process to the file. */
      if(dup2(outfile, STDOUT_FILENO) != STDOUT_FILENO){
        fprintf(stderr, "Error: failed to redirect standard output\n");
      }

      /* Now execute PROGRAM, searching for it in the path.  */ 
      execvp(arg_list[0],  arg_list); 
      /* The execvp  function returns only if an error occurs.  */ 
      fprintf(stderr, "an error occurred in execvp\n"); 
      abort(); 
    }
  }
  /* only the parent process executes the following code. */
  fprintf(stdout, "Hello from the parent process.\n");

  /* Wait for the child process to complete.  */ 
  int child_status;
  waitpid(child_pid, &child_status, 0); 
  
  if (WIFEXITED(child_status)){
    printf ("the child process exited normally, with exit code %d\n", 
	     WEXITSTATUS(child_status)); 
  }else{ 
    printf("the child process exited abnormally\n"); 
  }
  
  return  0; 
}

使用 pipe() 和 dup2() 建立管道

[編輯 | 編輯原始碼]
/* from Advanced Linux Programming (page 113) */
/* http://www.makelinux.net/alp/038.htm */
#include <stdio.h> 
#include <sys/types.h> 
#include <sys/wait.h> 
#include <unistd.h> 

int main () {
  int fds[2]; 
  pid_t pid; 

  /* Create a pipe. File descriptors for the two ends of the pipe are 
     placed in fds.  */ 
  pipe(fds); 

  printf("fds[0]=%d, fds[1]=%d\n", fds[0], fds[1]);

  /* Fork a child process.  */ 
  pid = fork(); 

  if (pid == (pid_t) 0) {
    /* This is the child process. Close our copy of the write end of 
       the file descriptor.  */ 
    close(fds[1]); 

    /* Connect the read end of the pipe to standard input.  */ 
    dup2(fds[0], STDIN_FILENO); 

    /* Replace the child process with the "sort" program.  */ 
    execlp("sort", "sort", NULL); 
  } else {
    /* This is the parent process.  */ 
    FILE* stream; 

    /* Close our copy of the read end of the file descriptor.  */ 
    close(fds[0]); 

    /* Connect the write end of the pipe to standard out, and write 
       to it.  */ 
    dup2(fds[1], STDOUT_FILENO);
    printf("This is a test.\n"); 
    printf("Hello, world.\n"); 
    printf("My dog has fleas.\n"); 
    printf("This program is great.\n"); 
    printf("One fish, two fish.\n"); 
    fflush(stdout);
    close(fds[1]);
    close(STDOUT_FILENO);

    /* Wait for the child process to finish.  */ 
    waitpid(pid, NULL, 0); 
    printf("parent process terminated.\n");
  } 
  return 0; 
}

連線兩個命令的管道示例

[編輯 | 編輯原始碼]
#include <stdio.h> 
#include <stdlib.h> 
#include <sys/types.h> 
#include <unistd.h> 

int run_command(char** arg_list, int rd, int wd) {
  pid_t child_pid; 

  /* Duplicate this process. */ 
  child_pid = fork(); 

  if (child_pid != 0){
    /* This is the parent process. */ 
    return child_pid; 
  }else {
    if (rd != STDIN_FILENO){
      if(dup2(rd, STDIN_FILENO) != STDIN_FILENO){
	fprintf(stderr, "Error: failed to redirect standard input\n");
        return -1;
      }
    }

    if (wd != STDOUT_FILENO){
      printf("redirect stdout to %d.", wd);
      if(dup2(wd, STDOUT_FILENO) != STDOUT_FILENO){
        fprintf(stderr, "Error: failed to redirect standard output\n");
        return -1;
      }
    }
    /* Now execute PROGRAM, searching for it in the path.  */ 
    execvp (arg_list[0], arg_list); 
    /* The execvp  function returns only if an error occurs.  */ 
    fprintf(stderr, "an error occurred in execvp\n"); 
    abort(); 
  } 
} 

int main() {
  /*  The argument list to pass to the "ls" command.  */ 
  char* arg_list[] = {
    "ls",     /* argv[0], the name of the program.  */ 
    "-l", 
    "|",      /* the pipe symbol is at index 2 */
    "wc",
    "-l", 
    NULL      /* The argument list must end with a NULL.  */ 
  }; 
 
  int pipe_index = 2;
  int rd = STDIN_FILENO;
  int wd = STDOUT_FILENO;
  int fds[2];
  if (pipe(fds) != 0) {
    fprintf(stderr, "Error: unable to pipe command '%s'\n", arg_list1[0]);
    return -1;
  }
  
  wd = fds[1]; /*file descriptor for the write end of the pipe*/

  // delete the pipe symbol and insert a null to terminate the
  // first command's argument list
  args[pipe_index] = NULL;

  // run first command: read from STDIN and write to the pipe
  run_command(arg_list, rd, wd);
  close(fds[1]);

  rd = fds[0];
  wd = STDOUT_FILENO;

  // run the second command: read from the pipe and write to STDOUT
  // the argument for this command starts at pipe_index+1
  run_command(arg_list+pipe_index+1, rd, wd);

  fprintf(stderr, "done with main program\n"); 
  return 0; 
}

在此示例中,原始引數列表在管道符號處被拆分為兩個列表,管道符號被替換為 null 值。這使我們能夠使用兩個引數列表來執行由管道連線的兩個獨立命令/程式。

程序是正在執行的程式。它是作業系統管理的實體的隱喻。程序具有自己的地址空間以及作業系統管理資料結構中的其他資訊。

輸入和輸出

[編輯 | 編輯原始碼]
The Von Neumann Architecture.
馮·諾依曼體系結構方案

輸入和輸出可能是 Unix 中最普遍的概念。在 Unix 中,一切都是檔案,這是有意為之,以便程式可以以通用方式與不同的裝置互動。可以將檔案作為程式的輸入,也可以從程式的輸出建立檔案。

每個程序都有一組輸入和輸出流。雖然程序可以擁有開發者想要的任意數量的流,但所有程序至少擁有 3 個流。這些流被稱為標準輸入、標準輸出和標準錯誤。許多程式使用這些簡單的流,以便使用者可以輕鬆地操作它們。例如,程式 cat 和 grep。cat 將給定檔案傳送到標準輸出(通常是您的終端,除非另有說明),grep 在其標準輸入中搜索模式。如果輸入命令 `$cat file.txt | grep "hi"`,這將搜尋檔案中給定的文字 "hi"。

檔案系統

[編輯 | 編輯原始碼]

檔案系統概念

[編輯 | 編輯原始碼]

檔案抽象 - 位元組流,含義由檔案系統使用者指定 檔案系統使用者 - 終端使用者(人類)和直接使用者(程式,例如應用程式或 shell) 使用者視角 - 一組系統呼叫,例如 creat、open、close、seek、delete ... 檔案屬性 - 元資料:所有者、大小、時間戳 ... 目錄抽象 - 檔案和目錄列表,將檔案/目錄的名稱對映到找到資料所需的資訊。絕對路徑和相對路徑 目錄操作 - 建立、刪除、開啟、關閉、重新命名、連結、取消連結

檔案系統實現

[編輯 | 編輯原始碼]

佈局:分割槽、引導塊、超級塊、... 磁碟塊分配

 contiguous allocation
 linked list allocation: the first word in each block is used as a pointer to the next one.
 file allocation table: linked list allocation using a table in memory. 
 i-node (index-node): an i-node is in memory when the corresponding file is open. With a i-node design we can calculate the largest possible file size.

目錄實現:i-節點、長檔名 檔案共享:符號連結與硬連結 磁碟塊大小:權衡和折衷,浪費磁碟空間與效能(資料速率) 跟蹤空閒塊:連結列表與點陣圖 系統備份:快取:在路徑下查詢檔案的步驟


華夏公益教科書