操作系統初步

系統調用實驗

系統調用:
Linux內核中設置了一組用于實現各種系統功能的子程序,稱為系統調用。用戶可以通過系統調用命令在自己的應用程序中調用它們。從某種角度來看,系統調用和普通的函數調用非常相似。區別僅僅在于,系統調用由操作系統核心提供,運行于核心態;而普通的函數調用由函數庫或用戶自己提供,運行于用戶態。
系統調用的用法
進程是不能訪問內核的。它不能訪問內核所占內存空間也不能調用內核函數。系統調用是這些規則的一個例外。其原理是進程先用適當的值填充寄存器,然后調用一個特殊的指令,這個指令會跳到一個事先定義的內核中的一個位置。在Intel CPU中,這個由中斷0x80實現。進程可以跳轉到的內核位置叫做sysem_call。這個過程檢查系統調用號,這個號碼告訴內核進程請求哪種服務。然后,它查看系統調用表(sys_call_table)找到所調用的內核函數入口地址。接著,就調用函數,等返回后進行系統檢查,最后返回到進程。

實驗內容

  1. 運行用API接口函數getpid()直接調用方式調用Linux操作系統的系統調用getpid的程序。
    編寫c程序,功能為利用getpid()函數獲得進程號同時打印輸出進程號。

    利用gcc命令編譯運行,得到進程號。

    根據查詢可得到getpid的系統調用號是39。
    [站外圖片上傳中...(image-4dfff1-1552727238551)]
    /usr/include/unistd.h中可以找到該函數
  2. 運行匯編中斷調用方式調用Linux操作系統的系統調用getpid的程序。
    編寫c程序,在c語言中內嵌匯編代碼實現系統調用。此處系統調用是一個軟中斷,既然是中斷那么一般就具有中斷號和中斷處理程序兩個屬性,Linux使用0x80號中斷作為系統調用的入口,而中斷處理程序的地址放在中斷向量表里,通過代碼可知getpid的中斷向量號為14。

    利用gcc命令編譯運行,得到進程號。

    分別運行程序1.1.1和1.1.2可獲得兩個相鄰的進程號。
  3. 完成習題1.13:命令:printf(“Hello World!\n”) 分別用相應的linux系統調用的C函數形式和匯編代碼兩種形式來實現上述命令。
    1)以C函數形式實現printf

    編譯運行1.1.3.c,可見打印輸出“Hello World”

    2) 以匯編代碼形式實現printf

    編寫好上述代碼,然后將其保存為printf.asm,先使用NASM編譯 hello.asm程序: nasm –f elf64 printf.asm。之后使用 ld –s –o printf printf.o 指令連接程序,最后運行程序。
    可見程序輸出:“Hello World”
  4. 畫出系統調用實現的流程圖
    Pintos是80*86架構的簡單操作系統框架。支持內核線程,加載和運行用戶程序以及文件系統。
    下載Pintos解壓縮后可以看到src下的目錄:

    在lib/user/syscall.h中可以看到定義了20個系統調用函數
    image

    在syscall.c中可見有四個函數:

    系統調用流程圖:

問題總結

  1. 系統調用號的查詢
    在Linux中,每個系統調用被賦予一個系統調用號。這樣,通過這個獨一無二的號就可以關聯系統調用。當用戶空間的進程執行一個系統調用的時候,這個系統調用號就被用來指明到底是要執行哪個系統調用。
    在linux查看32位的系統調用號:cat /usr/include/asm/unistd_32.h
    在linux查看64位的系統調用號:cat /usr/include/asm/unistd_64.h
  2. linux下的系統調用
    系統調用可以通過syscall()函數發起,或者調用每個對應的一個C函數,這些函數定義在<syscall.h> 或者<unistd.h> 頭文件中。應用程序通過應用編程接口(API)而不是直接通過系統調用來編程。因為應用程序使用的這種編程接口實際上并不需要和內核提供的系統調用一一對應。一個API定義了一組應用程序使用的編程接口。它們可以實現成一個系統調用,也可以通過調用多個系統調用來實現,而完全不使用任何系統調用也不存在問題。
    用戶空間的程序無法直接執行內核代碼。它們不能直接調用內核空間中的函數,因為內核駐留在受保護的地址空間上。如果進程可以直接在內核的地址空間上讀寫的話,系統安全就會失去控制。所以,通知內核的機制是靠軟件中斷實現的。首先,用戶程序為系統調用設置參數。其中一個參數是系統調用編號,如本實驗中的getpid為39。參數設置完成后,程序執行“系統調用”指令。x86系統上的軟中斷由int產生。這個指令會導致一個異常:產生一個事件,這個事件會致使處理器切換到內核態并跳轉到一個新的地址,并開始執行那里的異常處理程序。此時的異常處理程序實際上就是系統調用處理程序。
    Linux下的系統調用主要通過以下幾個步驟:
    (1)將你的系統調用號放進EAX中。
    (2)設置系統調用參數,并且依次將參數放進EBX、ECX、EDX、ESX、EDI和EBP。
    (3)調用相關中斷(對應Linux來說是 80h)。
    (4)最后的調用結果會返回到EAX中保存。

并發實驗

實驗內容

  1. 編譯運行cpu.c,觀察輸出結果,說明程序功能。

    [站外圖片上傳中...(image-cef4bc-1552727238551)]
    編譯并運行cpu.c文件,可見連續輸出指定字符。該程序功能為每隔一秒輸出一次命令行參數,若沒有正確接受參數則提示用戶正確輸入參數。僅運行一個cpu程序時:循環輸出“A”
  2. 同時運行多個cpu程序

    同時運行四個cpu程序時,可見同時有四個進程:30879、30880、30881、30882,在運行時可以看到是四個程序交替運行,四個進程輪流運行,順序并無一定的規律。同時進行任務A、B、C、D,CPU會輪流地分配給A、B、C、D使用,只是之間的時間間隔太小,用戶不能感覺出來,就覺得是多個任務同時進行一樣,這就是并發性。在本實驗中,通過sleep函數加長時間間隔。

    通過命令行去隨機化操作后,再次運行程序,可以到的以下結果:

    程序均已“BACD”的順序輸出,可見此時已經不再是之前的隨機算法。

問題總結

  1. gcc編譯時 –Wall 的含義
    -Wall選項意思是編譯后顯示所有警告;-w的意思是關閉編譯時的警告,也就是編譯后不顯示任何warning,因為有時在編譯之后編譯器會顯示一些例如數據轉換之類的警告,這些警告是我們平時可以忽略的。-W選項類似-Wall,會顯示警告,但是只顯示編譯器認為會出現錯誤的警告。
  2. 操作系統的并發和并行
    并行性和并發性是既相似又有區別的兩個概念。并行性是指兩個或多個事件在同一時刻發生。而并發性是指兩個或多個事件在同一時間間隔發生。在多道程序環境下,并發性是指在一段時間內宏觀上有多個程序在同時運行,例如本例中A、B、C、D四個cpu程序在同時運行,但在單處理機系統中,每一時刻卻僅能有一道程序執行,故微觀上這些程序只能是分時地交替執行。

內存分配實驗

實驗內容

  1. 編譯運行mem.c,觀察輸出結果,說明程序功能。


    編譯并運行mem.c文件,可見運行./mem時程序輸出運行進程的進程號以及所指向地址的初始位置,并循環使下一指針所指向的內容加1,以檢驗是否共享同一塊物理內存區域。
    僅運行一個mem.c程序時:初始狀態p = 0x1ce2010 *p = 0,可見該進程循環使得p指向的值逐次累加1。
  2. 運行兩個mem程序

    同時運行兩個mem.c程序時,可以看到,此時擁有兩個進程:31314和31315。其中進程31315分配的內存地址為:0x1c3e010,進程31314分配的內存地址為0x2200010,所以兩個分別運行的程序飛配的內存地址不相同。
    之后兩個進程開始進入循環部分,可見進程31314每循環一次,指針p所指向的內容加1,此后進程31315每循環一次,指針p在原有的基礎上所指向內容加1,并未受到31314進程的影響。所以可知兩個分別運行的程序不共享同一塊物理內存區域。

問題總結

  1. C語言中p=p+1的含義,以及各類指針操作辨析
    p=p+1的效果:指針不移動,但指針所指的數據有+1效果。
    *p++的效果:指針向下移動一個單位(對于int變量的數組,指針移動了4字節),指針內的內容并未變化。
    *(p+1)的效果:p指向的內存地址的下一個地址的數據
  2. 輸出地址的方法
    %p格式符會輸出指針本身的值,也就是指針指向的地址值。該輸出為16進制形式,具體輸出值取決于指針指向的實際地址值。%p一般僅用于printf及同類函數中。形式為printf("%p", varp); 其中后續參數varp為某一個指針變量。

共享實驗

實驗內容

  1. 編譯運行thread.c,觀察輸出結果,說明程序功能。

    在Linux系統下,與線程相關的函數都定義在pthread.h頭文件中。
    創建線程函數是pthread_create函數。線程等待函數pthread_join函數,調用該函數的線程將掛起等待,直到線程終止。thread.c程序功能是將輸入的命令行參數傳入,創建兩個線程,并在指定線程運行worker函數完成計數。由此判斷全局變量在不同的線程當中訪問全局變量是否是共享的。

    執行測試,發現得到的統計值為輸入值的兩倍。

    多次測試發現仍然符合該規律,final value的值總是為輸入參數的二倍。在這個函數中,worker函數是兩個線程共有的運行函數,全局變量loops,counter是兩個線程共享的,loops值等于輸入的參數值,而兩個線程執行時,由結果可見counter值是可累加的,所以可知全局變量在多個線程中是共享的
    雖然線程共享全局變量相對于進程通信會給線程通信帶來巨大的方便,但是不做控制的進行訪問全局變量也是致命的,帶來巨大程序bug,并且難以發現。當多個線程同時對一個全局變量操作,會出現資源競爭問題,從而導致數據結不正確,即遇到線性安全問題。

問題總結

1. 進程與線程的區別
進程:程序的一個動態運行實例,承擔分配系統資源的實例。(Linux實現進程的主要目的是資源獨占)
線程:在進程的內部運行(進程的地址空間)運行的一個分支,也是調度的基本單位。(Linux實現線程的主要目的是資源共享)。線程所有的資源由進程提供。
2. gcc中-pthread和-lpthread的區別
編譯選項中指定 -pthread 會附加一個宏定義 -D_REENTRANT,該宏會導致libc頭文件選擇那些thread-safe的實現;鏈接選項中指定 -pthread 則同 -lpthread 一樣,只表示鏈接 POSIX thread 庫。由于 libc 用于適應 thread-safe 的宏定義可能變化,因此在編譯和鏈接時都使用 -pthread 選項而不是傳統的 -lpthread 能夠保持向后兼容,并提高命令行的一致性。

實驗總結

通過本次實驗,我對于操作系統的系統調用,并發性,內存分配,共享問題等有了進一步的理解,同時也對于linux系統掌握了基本的操作。在這四個不同的實驗過程中,出現了很多知識上的短缺和疑惑,通過查詢學習即時的填補了知識空缺。在完成實驗后更深的理解了課堂上所講的概念,尤其直觀的看到關于并發的交替進行和內存分配等比較抽象的問題,也加深了記憶。此外由于之前欠缺了匯編方面的知識,在本次實驗中通過了解學習我也掌握了一些關于匯編知識的運用。希望之后能夠繼續加強實驗動手能力,加油~

github實驗代碼

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。