作業系統基礎
作業系統通常被分為核心和使用者空間。
核心為軟體提供了一個與硬體互動的層。它對硬體進行了抽象,使得許多軟體可以在截然不同的硬體上以相同的方式執行。核心提供系統呼叫,允許使用者空間與之互動。核心處理許多內容,包括檔案系統(並非總是如此,但通常如此)、裝置和程序控制。
使用者空間存在於核心之外的所有內容。使用者建立的所有程序,包括終端,都存在於使用者空間。顯示程式的圖形使用者介面 (GUI) 也位於使用者空間。
Unix shell 是一個命令直譯器程式,它在命令列環境(例如終端或終端模擬器)中充當使用者與作業系統之間的主要介面。Shell 是一個必不可少的(通常是首選的)工具,但它只是一個普通的使用者程式,它使用系統呼叫來完成大部分工作 - 所以它只是一個“外殼”。
現代社會存在著許多 Shell,每個 Shell 都有其自身的特性集。最常見的是 Bourne Shell。Bourne Shell(在 POSIX 位置中俗稱為 /bin/sh)已經存在了幾十年,並且基本上可以在任何 Unix 計算機上找到。雖然它缺乏某些互動功能,但它非常普遍,因此任何為它編寫的指令碼都可以在任何 Unix 系統上執行。
Shell 的主要職責是向用戶提供命令提示符(例如 $),等待命令,然後執行命令。
Shell 也可用於編寫程式,方法是將 Shell 命令寫入文字檔案。必須在檔案頂部包含直譯器,形式為 #!interpreter(例如:#!/bin/sh)。執行該檔案時,Unix 讀取直譯器,從而知道使用該 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() 系統呼叫複製當前程序
/* 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() 函式呼叫(系統呼叫)被呼叫 **一次**,但返回 **兩次**,因為當呼叫完成時,有兩個程序在執行相同的程式碼。
/* 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;
}
/* 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() 無法正常工作。
#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;
}
/* 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 值。這使我們能夠使用兩個引數列表來執行由管道連線的兩個獨立命令/程式。
程序是正在執行的程式。它是作業系統管理的實體的隱喻。程序具有自己的地址空間以及作業系統管理資料結構中的其他資訊。

輸入和輸出可能是 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-節點、長檔名 檔案共享:符號連結與硬連結 磁碟塊大小:權衡和折衷,浪費磁碟空間與效能(資料速率) 跟蹤空閒塊:連結列表與點陣圖 系統備份:快取:在路徑下查詢檔案的步驟