MIT6.S081學習筆記--lec 1

来源:https://www.cnblogs.com/jiaweixie/archive/2023/07/18/17561914.html
-Advertisement-
Play Games

# 引言 ## 操作系統的目標 + abstract H/W `抽象化硬體` + multiplex `多路復用` + isolation `隔離性` + sharing `共用(進程通信,數據共用)` + security / access control `安全性/許可權控制` + perform ...


引言

操作系統的目標

  • abstract H/W 抽象化硬體
  • multiplex 多路復用
  • isolation 隔離性
  • sharing 共用(進程通信,數據共用)
  • security / access control 安全性/許可權控制
  • performance 性能/內核開銷
  • range of applications 多應用場景

操作系統概覽

操作系統應該提供的功能:1. 多進程支持 2. 進程間隔離 3. 受控制的進程間通信

  • xv6:一種在本課程中使用的類UNIX的教學操作系統,運行在RISC-V指令集處理器上,本課程中將使用QEMU模擬器代替

  • kernel(內核):為運行的程式提供服務的一種特殊程式。每個運行著的程式叫做進程,每個進程的記憶體中存儲指令、數據和堆棧。一個電腦可以擁有多個進程,但是只能有一個內核

    每當進程需要調用內核時,它會觸發一個system call(系統調用),system call進入內核執行相應的服務然後返回。

操作系統的組織結構如圖1所示

OS Organization

內核提供的一系列系統調用就是用戶程式可見的操作系統介面,xv6 內核提供了 Unix 傳統系統調用的一部分,它們是:

image-20230717090404859

進程和記憶體

每個進程擁有自己的用戶空間記憶體以及內核空間狀態,當進程不再執行時xv6將存儲和這些進程相關的CPU寄存器直到下一次運行這些進程。kernel將每一個進程用一個PID(process identifier)指代。在進程執行中,常常會使用forkexec系統調用來創建新的進程。如下麵代碼所示:

fork and wait

  • fork:形式:int fork()。其作用是讓一個進程生成另外一個和這個進程的記憶體內容相同的子進程。在父進程中,fork的返回值是這個子進程的PID,在子進程中,返回值是0
  • exit:形式:int exit(int status)。讓調用它的進程停止執行並且將記憶體等占用的資源全部釋放。需要一個整數形式的狀態參數,0代表以正常狀態退出,1代表以非正常狀態退出
  • wait:形式:int wait(int *status)。等待子進程退出,返回子進程PID,子進程的退出狀態存儲到int *status這個地址中。如果調用者沒有子進程,wait將返回-1
  • pipe:形式:int pipe(int p[])。創建一個管道,將讀/寫文件描述符放在p[0]和p[1]中
  • sbrk:形式:char *sbrk(int n)。將進程的記憶體增加n位元組。返回新記憶體的起始位置。
int pid = fork();
if (pid > 0) {
    printf("parent: child=%d\n", pid);
    pid = wait((int *) 0);
    printf("child %d is done\n", pid);
} else if (pid == 0) {
    printf("child: exiting\n");
    exit(0);
} else {
    printf("fork error\n");
}

前兩行輸出可能是

parent: child=1234
child: exiting

也可能是

child: exiting
parent: child=1234

這是因為在fork了之後,父進程和子進程將同時開始判斷PID的值,在父進程中,PID為1234,而在子進程中,PID為0。看哪個進程先判斷好PID的值,以上輸出順序才會被決定。

最後一行輸出為

parent: child 1234 is done

子進程在判斷完pid == 0之後將exit,父進程發現子進程exit之後,wait執行完畢,列印輸出。

儘管fork了之後子進程和父進程有相同的記憶體內容,但是記憶體地址和寄存器是不一樣的,也就是說在一個進程中改變變數並不會影響另一個進程。

fork and exec

exec:形式:int exec(char *file, char *argv[])。載入一個文件,獲取執行它的參數,執行。如果執行錯誤返回-1,執行成功則不會返回,而是開始從文件入口位置開始執行命令。文件必須是ELF格式。

xv6 shell使用以上四個system call來為用戶執行程式。在shell進程的main中主迴圈先通過getcmd來從用戶獲取命令,然後調用fork來運行一個和當前shell進程完全相同的子進程。父進程調用wait等待子進程exec執行完(在runcmd中調用exec

/* sh.c */
int
main(void)
{
  static char buf[100];
  int fd;

  // Ensure that three file descriptors are open.
  while((fd = open("console", O_RDWR)) >= 0){
    if(fd >= 3){
      close(fd);
      break;
    }
  }

  // Read and run input commands.
  while(getcmd(buf, sizeof(buf)) >= 0){
    if(buf[0] == 'c' && buf[1] == 'd' && buf[2] == ' '){
      // Chdir must be called by the parent, not the child.
      buf[strlen(buf)-1] = 0;  // chop \n
      if(chdir(buf+3) < 0)
        fprintf(2, "cannot cd %s\n", buf+3);
      continue;
    }
    if(fork1() == 0)
      runcmd(parsecmd(buf));
    // parent wait the child exit
    wait(0);
  }
  exit(0);
}

你可能會想,既然forkexec總是一起使用,為什麼不合併成一個呢?實際上我們可以在fork 之後,對子進程進行一些設置,比如輸入/輸出重定向,然後再執行exec,註意exec並不會改變子進程的file table。

當我們不需要進行額外的設置時,fork 複製記憶體,exec替換記憶體,這意味著記憶體的浪費,有什麼辦法可以優化這種情況麽?答案是肯定的,之後的4.6節我們會講到 COW (copy-on-write)機制。

I/O 和文件描述符

  • file descriptor:文件描述符,用來表示一個被內核管理的、可以被進程讀/寫的對象的一個整數,表現形式類似於位元組流,通過打開文件、目錄、設備等方式獲得。一個文件被打開得越早,文件描述符就越小。

    每個進程都擁有自己獨立的文件描述符列表,其中0是標準輸入,1是標準輸出,2是標準錯誤。shell將保證總是有3個文件描述符是可用的

    while((fd = open("console", O_RDWR)) >= 0)
    {    
        if(fd >= 3)
        {        
            close(fd);        
            break;    
        } 
    }
    
  • readwrite:形式int write(int fd, char *buf, int n)int read(int fd, char *bf, int n)。從/向文件描述符fd讀/寫n位元組bf的內容,返回值是成功讀取/寫入的位元組數。每個文件描述符有一個offset,read會從這個offset開始讀取內容,讀完n個位元組之後將這個offset後移n個位元組,下一個read將從新的offset開始讀取位元組。write也有類似的offset

    /* essence of cat program */ 
    char buf[512]; 
    int n; 
    
    for (;;) 
    {    
        n = read(0, buf, sizeof buf);    
        if (n == 0)        
        break;    
        if (n < 0)
        {        
            fprintf(2, "read error\n");        
            exit(1);    
        }    
        if (write(1, buf, n) != n)
        {        
            fprintf(2, "write error\n");        
            exit(1);    
        } 
    }
    
  • close。形式是int close(int fd),將打開的文件fd釋放,使該文件描述符可以被後面的openpipe等其他system call使用。

    使用close來修改file descriptor table能夠實現I/O重定向

    /* implementation of I/O redirection, * more specifically, cat < input.txt */ 
    char *argv[2]; 
    argv[0] = "cat"; 
    argv[1] = 0; 
    if (fork() == 0) {   // in the child process    
        close(0);  // this step is to release the stdin file descriptor    
        open("input.txt", O_RDONLY);  // the newly allocated fd for input.txt is 0, since the previous fd 0 is released    
        exec("cat", argv);  // execute the cat program, by default takes in the fd 0 as input, which is input.txt 
    }
    

    父進程的fd table將不會被子進程fd table的變化影響,但是文件中的offset將被共用。

  • dup。形式是int dup(int fd),複製一個新的fd指向的I/O對象,返回這個新fd值,兩個I/O對象(文件)的offset相同

    e.g.

    fd = dup(1);
    write(1, "hello ", 6); 
    write(fd, "world\n", 6); 
    // outputs hello world
    

    除了dupfork之外,其他方式不能使兩個I/O對象的offset相同,比如同時open相同的文件

Pipes

pipe:管道,暴露給進程的一對文件描述符,一個文件描述符用來讀,另一個文件描述符用來寫,將數據從管道的一端寫入,將使其能夠被從管道的另一端讀出。

我們之前有提到過pipe是一個system call,形式為int pipe(int p[])p[0]為讀取的文件描述符,p[1]為寫入的文件描述符。

xv6中有這樣一個例子,通過寫管道將參數傳遞給wc程式

/* run the program wc with stdin connected to the read end of pipe, parent process able to communicate with child process */
int p[2];
char *argv[2];

argv[0] = "wc";
argv[1] = 0;

pipe(p); // read fd put into p[0], write fd put into p[1]
if (fork() == 0) {
    close(0);
    dup(p[0]); // make the fd 0 refer to the read end of pipe
    close(p[0]); // original read end of pipe is closed
    close(p[1]); // fd p[1] is closed in child process, but not closed in the parent process. 註意這裡關閉p[1]非常重要,因為如果不關閉p[1],管道的讀取端會一直等待讀取,wc就永遠也無法等到EOF
    exec("/bin/wc", argv); // by default wc will take fd 0 as the input, which is the read end of pipe in this case
} else {
    close(p[0]); // close the read end of pipe in parent process will not affect child process
    write(p[1], "hello world\n", 12); 
    close(p[1]); // write end of pipe closed, the pipe shuts down
}

在xv6的shell實現中即sh.c也是類似的實現,關於pipe系統調用的源碼閱讀,我也總結了一份代碼講解。

case PIPE:
pcmd = (struct pipecmd*)cmd;
if(pipe(p) < 0)
    panic("pipe");
if(fork1() == 0){
    // in child process
    close(1); // close stdout
    dup(p[1]); // make the fd 1 as the write end of pipe
    close(p[0]);
    close(p[1]);
    runcmd(pcmd->left); // run command in the left side of pipe |, output redirected to the write end of pipe
}
if(fork1() == 0){
    // in child process
    close(0); // close stdin
    dup(p[0]); // make the fd 0 as the read end of pipe
    close(p[0]);
    close(p[1]);
    runcmd(pcmd->right); //  run command in the right side of pipe |, input redirected to the read end of pipe
}
close(p[0]);
close(p[1]);
wait(0); // wait for child process to finish
wait(0); // wait for child process to finish
break;

文件系統

xv6文件系統包含了文件(byte arrays)和目錄(對其他文件和目錄的引用)。目錄生成了一個樹,樹從根目錄/開始。對於不以/開頭的路徑,認為是是相對路徑

  • mknod:創建設備文件,一個設備文件有一個major device #和一個minor device #用來唯一確定這個設備。當一個進程打開了這個設備文件時,內核會將readwrite的system call重新定向到設備上。
  • 一個文件的名稱和文件本身是不一樣的,文件本身,也叫inode,可以有多個名字,也叫link,每個link包括了一個文件名和一個對inode的引用。一個inode存儲了文件的元數據,包括該文件的類型(file, directory or device)、大小、文件在硬碟中的存儲位置以及指向這個inode的link的個數
  • fstat。一個system call,形式為int fstat(int fd, struct stat *st),將inode中的相關信息存儲到st中。
  • link。一個system call,將創建一個指向同一個inode的文件名。unlink則是將一個文件名從文件系統中移除,只有當指向這個inode的文件名的數量為0時這個inode以及其存儲的文件內容才會被從硬碟上移除

註意:Unix提供了許多在用戶層面的程式來執行文件系統相關的操作,比如mkdirlnrm等,而不是將其放在shell或kernel內,這樣可以使用戶比較方便地在這些程式上進行擴展。但是cd是一個例外,它是在shell程式內構建的,因為它必須要改變這個calling shell本身指向的路徑位置,如果是一個和shell平行的程式,那麼它必須要調用一個子進程,在子進程里起一個新的shell,再進行cd,這是不符合常理的。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • # 什麼?博客園主題比我的個人博客好看?😮 # 夢開始的地方🛸 最近逛博客園,發現我的園子還挺好看,但是還不夠好看,所以通過我百度發現SimpleMemory主題還可以繼續添加新的東西,當然這些東西不一定非得用SimpleMemory主題才行,但是搭配SimpleMemory主題是真的好看呀(比 ...
  • 前段時間小編檢查同事代碼,發現居然寫的太複雜看不太懂,代碼命名不規範,重覆冗長代碼一堆,這時候就可以通過重構來改進代碼的質量。代碼重構是提高代碼質量和可維護性的關鍵過程,它旨在通過優化代碼結構和設計來提高代碼的可讀性、可理解性和可擴展性。本文講述在C#中重構代碼的幾個案例,供大家參考。 **1、命名 ...
  • ## 一:背景 ### 1. 講故事 前幾天寫了一篇 `如何洞察 .NET程式 非托管句柄泄露` 的文章,文中使用 WinDbg 的 `!htrace` 命令實現了句柄泄露的洞察,在文末我也說了,WinDbg 是以侵入式的方式解決了這個問題,在生產環境中大多數情況下是不能走附加進程的模式,所以這也是 ...
  • 文章代碼分析基於linux-5.19.13,架構基於aarch64(ARM64)。 # 1. 前言 複雜IC內部有很多具有獨立功能的硬體模塊,例如CPU cores、GPU cores、USB控制器、MMC控制器、等等,出於功耗、穩定性等方面的考慮,有些IC在內部為這些硬體模塊設計了複位信號(res ...
  • 以下內容為本人的學習筆記,如需要轉載,請聲明原文鏈接[ 微信公眾號「ENG八戒」](https://mp.weixin.qq.com/s/w8YV_TUb4QwsgChu3AspHg)[https://mp.weixin.qq.com/s/w8YV\_TUb4QwsgChu3AspHg](https ...
  • Atom N2600, N2800 的某些舊型號機器, 安裝 Ubuntu 時在安裝界面選擇安裝後, 啟動過程中會卡住, 或者數秒即黑屏, 再無反應. 這個問題對於Debian系的其他發行版 Lubuntu, Linux Mint, Debian 都一樣, 但是用Win PE盤可以正常啟動. 經各種... ...
  • ## 文件操作和用戶 ### 複製移動和刪除 在Windows中我們可以通過快捷鍵 ctrl + c 複製,ctrl + v 粘貼,在 linux 中需要使用命令。 #### 複製移動 `cp` 就是 copy 的意思。請看示例: ```javascript // 將 a.txt 複製一份,重命名為 ...
  • 之前將自己所有的 `Http` 站點全部更新為 `Https` 站點,但是在請求後臺介面服務的時候還是 `Http` 請求,導致部署之後,直接在控制台報 `This request has been blocked; the content must be served over HTTPS;` 的... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...