OS- -系统调用
OS- -系統調用
文章目錄
- OS- -系統調用
- 一、系統調用
- 用于進程管理的系統調用
- 用于文件管理的系統調用
- 用于目錄管理的系統調用
- 其他系統調用
- Win 32 API
一、系統調用
- 我們已經可以看到操作系統提供了兩種功能:為用戶提供應用程序抽象和管理計算機資源。
- 對于大部分 在應用程序和操作系統之間的交互主要是應用程序的抽象,例如創建、寫入、讀取和刪除文件。計算機 的資源管理對用戶來說基本上是透明的。
- 因此,用戶程序和操作系統之間的接口主要是處理抽象。為了 真正理解操作系統的行為,我們必須仔細的分析這個接口。
- 多數現代操作系統都有功能相同但是細節不同的系統調用,引發操作系統的調用依賴于計算機自身的機 制,而且必須用匯編代碼表達。
- 任何單CPU計算機一次執行執行一條指令。如果一個進程在用戶態下 運行用戶程序,例如從文件中讀取數據。那么如果想要把控制權交給操作系統控制,那么必須執行一個 異常指令或者系統調用指令。
- 操作系統緊接著需要參數檢查找出所需要的調用進程。操作系統緊接著進 行參數檢查找出所需要的調用進程。然后執行系統調用,把控制權移交給系統調用下面的指令。
- 大致來 說,系統調用就像是執行了一個特殊的過程調用,但是只有系統調用能夠進入內核態而過程調用則不能 進入內核態。
- 為了能夠了解具體的調用過程,下面我們以read方法為例來看一下調用過程。
- 像上面提到的那樣, 會有三個參數,第一個參數是指定文件、第二個是指向緩沖區、第三個參數是給定需要讀取的字節數。
- 就像幾乎所有系統調用一樣,它通過使用與系統調用相同的名稱來調用一個函數庫,從而從C程序中調 用:read。
-
系統調用在count中返回實際讀出的字節數。這個值通常與nbytes相同,但也可能更小。比如在讀過 程中遇到了文件尾的情況。
-
如果系統調用不能執行,不管是因為無效的參數還是磁盤錯誤,count的值都會被置成-1,然后在全局 變量errno中放入錯誤信號。程序應該進場檢查系統調用的結果以了解是否出錯。
-
系統調用是通過一系列的步驟實現的,為了更清楚的說明這個概念,我們還以read調用為例,在準備 系統調用前,首先會把參數壓入堆棧,如下所示
-
完成系統調用read(fd, buffer, nbytes)的11個步驟
-
C和C++編譯器使用逆序傳參,放在堆棧的頂部)。
-
第一個 參數和第三個參數都是值調用,但是第二個參數通過引用傳遞,即傳遞的是緩沖區的地址(由&指 示),而不是緩沖的內容。
-
然后是C調用系統庫的read函數,這也是第四步。
-
在由匯編語言寫成的庫過程中,一般把系統調用的編號放在操作系統所期望的地方,如寄存器(第五 步)。
-
然后執行一個TRAP指令,將用戶態切換到內核態,并在內核中的一個固定地址開始執行第六 步。
-
TRAP指令實際上與過程調用指令非常相似,它們后面都跟隨一個來自遠處位置的指令,以及供以 后使用的一個保存在棧中的返回地址。
TRAP指令與過程調用指令存在兩個方面的不同:
- ? TRAP指令會改變操作系統的狀態,由用戶態切換到內核態,而過程調用不改變模式
- ?其次,TRAP指令不能跳轉到任意地址上。根據機器的體系結構,要么跳轉到一個單固定地址上,或者指令中有一8位長的字段,它給定了內存中一張表格的索引,這張表格中含有跳轉地址,然 后跳轉到指定地址上。
- 跟隨在TRAP指令后的內核代碼開始檢查系統調用編號,然后dispatch給正確的系統調用處理器, 這通常是通過一張由系統調用編號所引用的、指向系統調用處理器的指針表來完成第七步。
- 此時,系統 調用處理器運行第八步
- 一旦系統調用處理器完成工作,控制權會根據TRAP指令后面的指令中返回給 函數調用庫第九步。
- 這個過程接著以通常的過程調用返回的方式,返回到客戶應用程序,這是第十步。
- 然后調用完成后,操作系統還必須清除用戶堆棧,然后增加堆棧指針(increment stackpointer), 用來清除調用read之前壓入的參數。
從而完成整個read調用過程。
- 在上面的第九步中我們說道,控制可能返回TRAP指令后面的指令,把控制權再移交給調用者這個過程 中,系統調用會發生阻塞,從而避免應用程序繼續執行。
- 這么做是有原因的。例如,如果試圖讀鍵盤, 此時并沒有任何輸入,那么調用者就必須被阻塞。
- 在這種情形下,操作系統會檢查是否有其他可以運行 的進程。這樣,當有用戶輸入時候,進程會提醒操作系統,然后返回第9步繼續運行。
下面,我們會列出一些常用的POSIX系統調用,POSIX系統調用大概有100多個,它們之中最重要 的一些調用見下表:
- 上面的系統調用參數中有一些公共部分,例如pid系統進程id, fd是文件描述符,n是字節數, position是在文件中的偏移量、seconds是流逝時間。
- 從宏觀角度上看,這些系統調所提供的服務確定了多數操作系統應該具有的功能,下面分別來對不同的 系統調用進行解釋
用于進程管理的系統調用
-
在UNIX中,fork是唯一可以在POSIX中創建進程的途徑,它創建一個原有進程的副本,包括所有 的文件描述符、寄存器等內容。
-
在fork之后,原有進程以及副本(父與子)就分開了。在fork過程 中,所有的變量都有相同的值,雖然父進程的數據通過復制給子進程,但是后續對其中任何一個進程的 修改不會影響到另外一個。
-
fork調用會返回一個值,在子進程中該值為0,并且在父進程中等于子進程 的進程標識符(Process IDentified,PID) ,使用返回的PID,就可以看出來哪個是父進程和子進 程。
-
在多數情況下,在fork之后,子進程需要執行和父進程不一樣的代碼。
-
從終端讀取命令,創建一個子 進程,等待子進程執行命令,當子進程結束后再讀取下一個輸入的指令。
-
為了等待子進程完成,父進程 需要執行waitpid系統調用,父進程會等待直至子進程終止(若有多個子進程的話,則直至任何一 個子進程終止)。
-
waitpid可以等待一個特定的子進程,或者通過將第一個參數設為-1的方式,等待任 何一個比較老的子進程。
-
當waitpid完成后,會將第二個參數statloc所指向的地址設置為子進程的 退出狀態(正常或異常終止以及退出值)。有各種可使用的選項,它們由第三個參數確定。
-
例如,如果 沒有已經退出的子進程則立刻返回。
-
那么shell該如何使用fork呢?在鍵入一條命令后,shell會調用fork命令創建一個新的進程。這個子 進程會執行用戶的指令。
-
通過使用execve系統調用可以實現系統執行,這個系統調用會引起整個核 心映像被一個文件所替代,該文件由第一個參數給定。
下面是一個簡化版的例子說明fork、waitpid和 execve的使用:
- —般情況下,execve有三個參數:將要執行的文件名稱,一個指向變量數組的指針,以及一個指向環 境數組的指針。
- 先看一個shell指令
- 此命令把filel復制到file2文件中,在shell執行fork之后,子進程定位并執行文件拷貝,并將源文件 和目標文件的名稱傳遞給它。
- cp的主程序(以及包含其他大多數C程序的主程序)包含聲明
- 其中第一個參 數argc是命令行中參數數目的計數,包括程序名稱。
- 對于上面的例子,argc是3。
- 第二個參 數argv是數組的指針。該數組的元素i是指向該命令行第i個字符串的指針。在上面的例子中, argv[0]指向字符串cp, argv[1]指向字符串filel, argv[2]指向字符串file2。
- main的第三個參數是指向 環境的指針,該環境是一個數組,含有name = value的賦值形式,用以將諸如終端類型以及根目錄 等信息傳送給程序。這些變量通常用來確定用戶希望如何完成特定的任務(例如,使用默認打印機)。
- 在上面的例子中,沒有環境參數傳遞給execve ,所以環境變量是0 ,所以execve的第三個參數為0
- 可能你覺得execve過于復雜,這時候我要鼓勵一下你,execve可能是POSIX的全部系統調用中最復 雜的一個了,其他都比較簡單。
- 作為一個簡單的例子,我們再來看一下exit ,這是進程在執行完成 后應執行的系統調用。這個系統調用有一個參數,它的退出狀態是0 - 255之間,它通過waitpid系統 調用中的statloc返回給父級。
- UNIX中的進程將內存劃分成三個部分:text segment,文本區,例如程序代碼,data segment, 數據區,例如變量,stack segment ,棧區域。數據向上增長而堆棧向下增長,如下圖所示
- 上圖能說明三個部分的內存分配情況,夾在中間的是空閑區,也就是未分配的區域,堆棧在需要時自動 的擠壓空閑區域,不過數據段的擴展是顯示地通過系統調用brk進行的,在數據段擴充后,該系統調 用指向一個新地址。
- 但是,這個調用不是POSIX標準中定義的,對于存儲器的動態分配,鼓勵程序員 使用malloc函數,而malloc的內部實現則不是一個適合標準化的主題,因為幾乎沒有程序員直接 使用它。
用于文件管理的系統調用
-
許多系統調用都與文件系統有關,要讀寫一個文件,必須先將其打開。
-
這個系統調用通過絕對路徑名或 指向工作目錄的相對路徑名指定要打開文件的名稱,而代碼O-RDONLY、 O-WRONLY或O_RDWR 的含義分別是只讀、只寫或者兩者都可以,為了創建一個新文件,使用O-CREATE參數。
-
然后可使用 返回的文件描述符進行讀寫操作。接著,可以使用close關閉文件,這個調用使得文件描述符在后續的 open中被再次使用。
-
最常用的調用還是read和write ,我們再前面探討過read調用,write具有與read相同的參 數。
-
盡管多數程序頻繁的讀寫文件,但是仍有一些應用程序需要能夠隨機訪問一個文件的任意部分。
-
與每個 文件相關的是一個指向文件當前位置的指針。
-
在順序讀寫時,該指針通常指向要讀出(寫入)的下一個 字節。Iseek調用可以改變該位置指針的值,這樣后續的read或write調用就可以在文件的任何地 方開始。
-
Iseek有三個參數,position = iseek(fd,offset,whence),第一個是文件描述符,第二個是文 件位置,第三個是說明該文件位置是相對于文件起始位置,當前位置還是文件的結尾。
-
在修改了指針之 后,Iseek所返回的值是文件中的絕對位置。
-
UNIX為每個文件保存了該文件的類型(普通文件、特殊文件、目錄等)、大小,最后修改時間以及其他信息,程序可以通過stat系統調用查看這些信息。
-
s = stat(name,&buf),第一個參數指定了 被檢查的文件;第二個參數是一個指針,該指針指向存放這些信息的結構。對于一個打開的文件而言, fstat調用完成同樣的工作。
用于目錄管理的系統調用
- 下面我們探討目錄和整個文件系統的系統調用,上面探討的是和某個文件有關的系統調用。
- mkdir和 rmdir分別用于創建s = mkdir(nname,mode)和刪除s = rmdir(name)空目錄
- 下一個調用是s = link(namel,name2)它的作用是允許同一個文件以兩個或者多個名稱出現,多數情況下是在 不同的目錄中使用link
下面我們探討一下link是如何工作的
- 圖中有兩個用戶ast和jim ,每個用戶都有他自己的一個目錄和一些文件,如果ast要執行一個包 含下面系統調用的應用程序
- jim中的memo文件現在會進入到ast的目錄中,在note名稱下。
- 此后,/usr/jim/memo和 /usr/ast/note會有相同的名稱。
- 用戶目錄是保存在/usr, /user, /home還是其他位置,都是由本地系統管理員決定的。
- 要理解link是如何工作的需要清楚link做了什么操作。
- UNIX中的每個文件都有一個獨一無二的版本, 也稱作i - number, i-編號,它標示著不同文件的版本。這個i -編號是i-nodes,i-節點 表的索 引。
- 每個文件都會表明誰擁有這個文件,這個磁盤塊的位置在哪,等等。目錄只是一個包含一組(i編 號,ASCII名稱)對應的文件。
- UNIX中的第一個版本中,每個目錄項都會有16個字節,2個字節對應i -編號和14個字節對應其名稱。現在需要一個更復雜的結構需要支持長文件名,但是從概念上講一個 目錄仍是一系列(i-編號,ASCII名稱)的集合。
- 在上圖中,mail的i-編號為16,依此類推。link只 是利用某個已有文件的i-編號,創建一個新目錄項(也許用一個新名稱)。
- 在上圖b中,你會發現有兩 個相同的70 i-編號的文件,因此它們需要有相同的文件。
- 如果其中一個使用了 unlink系統調用的 話,其中—個會被移除,另一個將保留。如果兩個文件都移除了,則UNIX會發現該文件不存在任何沒 有目錄項(i-節點中的一個域記錄著指向該文件的目錄項),就會把該文件從磁盤中移除。
- 就像我們上面提到過的那樣,mount系統s = mount(special,name,flag)調用會將兩個文件系 統合并為一個。
- 通常的情況是將根文件系統分布在硬盤(子)分區上,并將用戶文件分布在另一個子)分區上,該根文件系統包含常用命令的二進制(可執行)版本和其他使用頻繁的文件。然后,用 戶就會插入可讀取的USB硬盤。
通過執行mount系統調用,USB文件系統可以被添加到根文件系統中,
- 如果用C語言來執行那就是
- 這里,第一個參數是USB驅動器0的塊特殊文件名稱,第二個參數是被安裝在樹中的位置,第三個參 數說明將要安裝的文件系統是可讀寫的還是只讀的。
- 當不再需要一個文件系統時,可以使用amount移除之。
其他系統調用
- 除了進程、文件、目錄系統調用,也存在其他系統調用的情況,下面我們來探討一下。
- 我們可以看到上 面其他系統調用只有四種,首先來看第一個chdir, chdir調用更改當前工作目錄
- 在調用后,打開xyz文件,會打開/usr/ast/test/xyz文件,工作目錄的概念消除了總是需要輸入長文件 名的需要。
- 在UNIX系統中,每個文件都會有保護模式,這個模式會有一個讀-寫-執行位,它用來區分所有者、 組和其他成員。chmod系統調用提供改變文件模式的操作。
- 例如,要使一個文件除了對所有者之外的 用戶可讀,你可以執行
-
kill系統調用是用戶和用戶進程發送信號的方式,如果一個進程準備好捕捉一個特定的信號,那么在信號捕捉之前,會運行一個信號處理程序。
-
如果進程沒有準備好捕捉特定的信號,那么信號的到來會 殺掉該進程(此名字的由來)。
-
POSIX定義了若干時間處理的進程。例如,time以秒為單位返回當前時間,0對應著1970年1月1日。在一臺32位字的計算機中,time的最大值是(2人32) - 1秒,這個數字對應136年多一點。
-
所以在 2106年,32位的UNIX系統會發飆。如果讀者現在有32位UNIX系統,建議在2106年更換位64位 操作系統(偷笑?)。
Win 32 API
上面我們提到的都是UNIX系統調用,現在我們來聊聊Win 32中的系統調用。
- Windows和UNIX在各 自的編程方式上有著根本的不同。UNIX程序由執行某些操作或執行其他操作的代碼組成,進行系統調用以執行某些服務。
- Windows系統則不同,Windows應用程序通常是由事件驅動的。主程序會等待一 些事件發生,然后調用程序去處理。
- 最簡單的事件處理是鍵盤敲擊和鼠標滑過,或者是鼠標點擊,或者是插入USB驅動,然后操作系統調用處理器去處理事件,更新屏幕和更新程序內部狀態。這是與UNIX 不同的設計風格。
- 當然,Windows也有系統調用。在UNIX中,系統調用(比如read)和系統調用所使用的調用庫(例 如read)幾乎是一對一的關系。而在Windows中,情況則大不相同。
- 首先,函數庫的調用和實際的系 統調用幾乎是不對應的。微軟定義了一系列過程,稱為Win32應用編程接口(Application Programming Interface),程序員通過這套標準的接口來實現系統調用。這個接口支持從Windows 95版本以來所有的Windows版本。
- Win32 API調用的數量是非常巨大的,有數千個多。但這些調用并不都是在內核態的模式下運行時,有 —些是在用戶態的模型下運行。
- Win32 API有大量的調用,用來管理視窗、幾何圖形、文本、字體、滾 動條、對話框、菜單以及GUI的其他功能。為了使圖形子系統在內核態下運行,需要系統調用,否則 就只有函數庫調用。
我們把關注點放在和Win32系統調用中來,我們可以簡單看一下Win32 API中的系統調用和UNIX中 有什么不同(并不是所有的系統調用)
- 上表中是UNIX調用大致對應的Win32 API系統調用,簡述一下上表
- CreateProcess用于創建一 個新進程,它把UNIX中的fork和execve兩個指令合成一個,一起執行。它有許多參數用來指定新創 建進程的性質。
- Windows中沒有類似UNIX中的進程層次,所以不存在父進程和子進程的概念。在進 程創建之后,創建者和被創建者是平等的。
- WaitForSingleObject用于等待一個事件,等待的事件 可以是多種可能的事件。如果有參數指定了某個進程,那么調用者將等待指定的進程退出,這通過 ExitProcess 來完成。
- 然后是6個文件操作,在功能上和UNIX的調用類似,然而在參數和細節上是不同的。和UNIX中一 樣,文件可以打開,讀取,寫入,關閉。SetFilePointer和GetFileAttributesEx設置文件的 位置并取得文件的屬性。
- Windows中有目錄,目錄分別用CreateDirectory以及RemoveDirectory API調用創建和刪 除。
- 也有對當前的目錄的標記,這可以通過SetCurrentDirectory來設置。使用GetLocalTime 可獲得當前時間。
- Win32接口中沒有文件的鏈接、文件系統的mount、amount和stat ,當然,Win32中也有大量 UNIX中沒有的系統調用,特別是對GUI的管理和調用
總結
- 上一篇: OS- -操作系统概念
- 下一篇: OS- -进程详详解