天天看點

Linux第十一次學習筆記

異常控制流

  • 異常控制流(ECF)發生在計算機系統的各個層次
    • 在硬體層,硬體檢測到的事件會觸發控制突然轉移到異常處理程式。
    • 在作業系統層,核心通過上下文轉換将控制從一個使用者程序轉移到另一個使用者程序。
    • 在應用層,一個程序可以發送信号到另一個程序,而接收者會将控制突然轉移到它的一個信号處理程式。
  • ECF是作業系統用來實作I/O、程序和虛拟存儲器的基本機制。
  • 應用程式通過使用陷阱(trap)或者系統調用的ECF形式,向作業系統請求服務。
  • ECF是計算機系統中實作并發的基本機制。

異常

  • 異常就是控制流中的突變,用來響應處理器狀态中的某些變化。
  • 狀态變化成為事件。
    • 事件可能和目前指令的執行直接相關。
    • 當處理器檢測到有事件發生時,它會通過異常表的跳轉表,進行一個間接過程調用,到一個專門涉及用來處理這類事件的作業系統子程式(異常處理程式)。
  • 當異常處理程式完成處理後,根據引起異常的事件的類型,會發生以下三種情況的一種:
    • 處理程式将控制傳回給目前指令Icurr,即當事件發生時正在執行的指令。
    • 處理程式将控制傳回Inext,即如果沒有發生異常将會執行的下一條指令。
    • 處理程式終止被中斷的程式。

異常處理

  • 系統中可能的每種類型的異常都配置設定了一個唯一的非負整數的異常号。
    • 包括被零除、缺頁、存儲器通路違例、斷點以及算術溢出。
  • 其他号碼是由作業系統核心的設計者配置設定的。
    • 包括系統調用和來自外部I/O裝置的信号。
  • 異常号是到異常表中的索引。
  • 異常表的起始位址放在異常表機制寄存器的特殊CPU寄存器裡。
  • 過程調用時,在跳轉到處理程式之前,處理器将傳回位址壓入棧中。根據異常的類型,傳回位址要麼傳回目前指令,要麼下一條指令。
  • 處理器把額外的處理器狀态壓到棧中,在處理程式傳回時,重新開始被終端的程式會需要這些狀态。
  • 如果控制從一個使用者程式轉移到核心,那麼所有這些項目都被壓倒核心棧中,而不是壓到使用者棧中。
  • 異常處理程式運作在核心模式下,意味着它們對所有的系統資源都有完全的通路權限。
  • 一旦硬體觸發了異常,剩下的工作就是由異常處理程式在軟體中完成。

異常的類别

  • 異常的四種類别
    • 中斷
      • 由異步發生,來自處理器外部的I/O裝置的信号的結果。
      • 總是傳回到下一條指令
      • 硬體中斷的異常處理程式通常稱為終端處理程式
    • 陷阱
      • 由同步發生,來自有意的異常
      • 在使用者程式和核心之間提供一個像過程一樣的接口,叫做系統調用。
        • 系統調用運作在核心模式中,核心哦屎允許系統調用執行指令,并通路定義在核心中的棧。
    • 故障
      • 由同步發生,來自潛在可恢複的錯誤
      • 可能傳回到目前指令
      • 根據故障是否能夠被修複,故障處理程式要麼重新執行引起故障的指令,要麼終止(abort)。
    • 終止
      • 由同步發生,來自不可恢複的錯誤
      • 不會傳回
      • 直接傳回給一個abort例程,該例程會終止這個應用程式。

Linux/IA32

  • IA32系統定義有高達256種不同的異常類型。
    • 0~31的号碼對應的是由Intel架構師定義的異常。
    • 32~255的号碼對應的是作業系統定義的終端和陷阱。
      Linux第十一次學習筆記
    • 除法故障
      • 當應用試圖除以零時,或者當一個除法指令的結果對于目标操作數過大時,就會發生除法錯誤(異常0)。
      • UNIX不會試圖從除法錯誤中恢複,而是選擇中止程式。
      • Linux外殼通常會把除法錯誤報告為“浮點異常”
    • 一般保護故障
      • 因為一個程式引用了一個未定義的虛拟存儲器區域,或者因為程式試圖寫一個隻讀的文本段。
      • Linux不會嘗試恢複這類故障,通常成為“段故障”
    • 缺頁
      • 處理程式将磁盤上實體存儲器相應的頁面映射到虛拟存儲器的一個頁面,然後重新開始這條産生故障的指令。
    • 機器檢查
      • 導緻故障的指令執行中檢測到緻命的硬體錯誤時發生的。

Linux/IA32系統調用

Linux第十一次學習筆記
  • 我們将系統調用和它們相關聯的包裝函數稱為系統級函數。
  • 所有的到Linux系統調用的參數都是通過通用寄存器而不是棧傳遞的。
  • IA32系統調用是通過一條成為int n的陷阱指令來提供的。

程序

  • 異常是允許作業系統提供程序的概念所需要的基本構造塊。
  • 程序是一個執行中的程式的執行個體。
    • 系統中的每個程式都是運作在某個程序的上下文中的。
    • 上下文是由程式正确運作所需的狀态組成的。
      • 該狀态包括存放在存儲器中的程式和代碼和資料,它的棧、通用目的寄存器的内容、程式計數器、環境變量以及打開檔案描述符的集合。
  • 程序提供給應用程式的關鍵抽象
    • 一個獨立的邏輯控制流,它提供一個假象。
    • 一個私有的抵制空間,它提供一個假象。

邏輯控制流

  • 用調試器單步執行程式,會看到一系列的程式計數器(PC),這個PC值的序列叫做邏輯控制流或者簡稱邏輯流。

并發流

  • 一個邏輯流的執行在事件上與另一個流重疊,成為并發流,這兩個流被成為并發地運作。
  • 多個流并發地執行的一般現象成為并發。
  • 一個程序和其他程序輪流運作的概念成為多任務(時間分片)。
  • 一個程序執行它的控制流的一部分的每一事件段叫做時間片。

私有位址空間

  • 程序為程式提供的假象,好像它獨占的使用系統位址空間。一般而言,和這個空間中某個位址相關聯的那個存儲器位元組是不能被其他程序讀寫的。

使用者模式和核心模式

  • 處理器通常是用某個控制寄存器中的一個模式位來提供這種功能的,該寄存器描述了程序目前享有的特權。
  • 當設定了模式位時,程序就運作在核心模式中(超級使用者模式)。
  • 沒有設定模式位時,程序就運作在使用者模式中。
  • 程序從使用者模式變為核心模式的唯一方法是通過中斷、故障或者陷入系統調用這樣的異常實作的。
  • Linux提供/proc檔案系統,允許使用者模式程序通路内和資料結構的内容。

上下文切換

  • 上下文:核心重新啟動一個被搶占的程序所需的狀态
  • 作業系統核心使用上下文切換的較高層形式的異常控制流來實作多任務。
  • 核心為每個程序維持一個上下文。
  • 在程序執行的某些時刻,核心可以決定搶占目前程序,并重新開始一個先前被搶占的程序,成為排程。
    • 由核心中成為排程器的代碼處理的。
  • 使用上下文切換的機制來控制轉移到新的程序
    • 儲存目前程序的上下文
    • 恢複某個先前被搶占的程序被儲存的上下文
    • 将控制傳遞給這個新恢複的程序

系統調用錯誤處理

  • 當Unix系統級函數遇到錯誤時,它們典型地會傳回-1,并設定全局證書變量errno來表示什麼出錯了。

程序控制

擷取程序ID

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

	pid_t getpid(void);	傳回調用程序的PID
	pid_t getppid(void);傳回父程序的PID(建立調用程序的程序)
           
  • getpid和getppid函數傳回一個類型為pid_t的整數值,在Linux系統上它在types.h中被定義為int

建立和終止程序

  • 程序總是處于下面三種狀态之一
    • 運作
    • 停止:被挂起且不會被排程
    • 終止:程序永遠停止
      • 終止原因:
        • 收到一個信号,該信号的預設行為是終止程序
        • 從主程式傳回
        • 調用exit函數
  • 父程序通過調用fork函數建立一個新的運作子程序:
    #include <sys/types.h>
      #include <unistd.h>
      
      pid_t fork(void);
               
  • 子程序傳回0,父程序傳回子程序的PID,如果出錯,則為-1.
  • 父程序和新建立的子程序之間最大的差別:
    • 它們有不同的PID
  • fork函數隻被調用一次,卻傳回兩次。
    • 一次是在父程序中
    • 一次是在新建立的子程序中
    • 在父程序中,fork傳回子程序的PID
    • 在子程序中,fork傳回0.
    • 子程序的PID總是非零的。
  • fork函數的并發執行
    • 父程序和子程序是并發運作的獨立程序
  • fork函數的相同的但是獨立的位址空間
  • fork函數的共享檔案
  • 調用fork函數n次,産生2的n次方個程序

回收子程序

  • 一個終止了但還未被回收的程序稱為僵死程序。
  • 如果父程序沒有回收它的僵死子程序就終止了,那麼核心就會安排init程序來回收它們。
  • 一個程序可以通過調用waitpid函數來等待它的子程序終止或者停止。
    • 成功傳回子程序PID,如果WNOHANG,傳回0,其他錯誤傳回-1.
  • 判定等待集合的成員
    • 如果pid>0,那麼等待集合就是一個單獨的子程序,它的程序ID等于pid。
    • 如果pid=-1,那麼等待集合就是由父程序所有的子程序組成的。
  • 修改預設行為
    • 可以通過将optioins設定為常量WNOHANG和WUNTRACED的各種組合。
      • WNOHANG:如果等待集合中的任何子程序都還沒有終止,那麼就立即傳回(傳回值為0)。預設的行為是挂起調用程序,直到有子程序終止。
      • WUNTRACED:挂起調用程序的執行,直到等待集合中的一個程序變成已終止或者被停止。傳回的PID為導緻傳回的已終止或被停止子程序的PID。預設的行為是隻傳回已終止的子程序。
      • WNOHANG|WUNTRACED:i級傳回如果等待集合中沒有任何子程序被停止或已終止的子程序。檢查已終止和被停止的子程序時,該選項會有用。
  • 檢查已回收子程序的退出狀态
    • wait.h頭檔案定義了結束status參數的幾個宏:
      • WIFEXITED:如果子程序通過調用exit或者一個傳回正常終止,就傳回真。
      • WEXXITSTATUS:傳回一個正常終止的子程序的退出狀态。隻有在WIFEXITED傳回為真時,才會定義這個狀态。
      • WIFSIGNALED:如果子程序時因為一個未被捕獲的信号終止的,那麼就傳回真。
      • WTERMSIG:傳回導緻子程序終止的信号的數量。隻有在WIFSIGNALED傳回為真時,才定義這個狀态。
      • WIFSTOPPED:如果引起傳回的子程序目前是被停止的,那麼就傳回真。
      • WSTOPSIG:傳回引起子程序停止的信号的數量。隻有在WIFSTOPPED傳回為真時,才定義這個狀态。
  • 錯誤條件
    • 如果調用程序沒有子程序,那麼waitpid傳回-1,并且設定errno為ECHILD。
    • 如果waitpid函數被一個信号中斷,那麼它傳回-1,并設定errno為EINTR。

wait函數

  • 其是waitpid函數的簡單版本
  • 調用wait(&status)等價于調用waitpid(-1,&status,0)

讓程序休眠

  • sleep函數将一個程序挂起一段指定的時間
  • 如果請求的時間量已經到了,sleep傳回0,否則傳回還剩下的要休眠的秒數。
  • pause函數讓調用函數休眠,直到該程序收到一個信号。

加載并運作程式

  • execve函數加載并運作可執行目标檔案filename,且帶參數清單argv和環境變量清單envp。
  • 隻有出現錯誤時,execve才會傳回到調用程式。
  • 調用一次并從不傳回。
  • getenv函數在環境資料中搜尋字元串"name=value",如果找到了,它就傳回一個指向value的指針,否則它就傳回NULL。
  • 如果環境資料包含一個形如“name=oldvalue”的字元串,那麼unsetenv會删除它,而setenv會用newvalue代替oldvalue,但是隻有在overwrite非零時才會這樣。如果name不存在,那麼setenv就把“name=newvalue”添加到數組中。

信号

  • Unix信号:更高層的軟體形式的異常允許程序中斷其他程序。
    Linux第十一次學習筆記

信号術語

  • 傳遞一個信号到目的程序的兩個步驟:
    • 發送信号
    • 接收信号
  • 發送信号的原因:
    • 核心檢測到一個系統事件
    • 一個程序調用了kill函數,顯式的要求核心發送一個信号給目的程序。
  • 一個程序可以發送信号給它自己。
  • 接收信号:
    • 1.忽略
    • 2.終止
    • 3.執行信号處理程式,捕獲信号
  • 待處理信号:
    • 隻發出沒有被接收的信号
    • 任何時刻,一種類型至多隻會有一個待處理信号,多的會被直接丢棄
    • 一個程序可以選擇性的阻塞接受某種信号,被阻塞仍可以被發送,但是不會被接收
    • 一個待處理信号最多隻能被接收一次。
    • pending:待處理信号集合
    • blocked:被阻塞信号集合。

發送信号——基于程序組

  • 程序組
    • 每個程序都隻屬于一個程序組。
    • 程序組ID:正整數
    • 一個子程序和他的父程序屬于同一程序組。
    • 檢視程序組id:getpgrp
    • 修改程序組:setpgid
  • 用/bin/kill程式發送信号
    • /bin/kill程式可以向另外的程序發送任意的信号,格式是:
    • /bin/kill -n m
    • n是信号,m是程序或程序組
    • 當n>0時,發送信号n到程序m
    • 當n<0時,使信号|n|發送到程序組m中的所有程序。
  • 從鍵盤發送信号
    • 在鍵盤輸入ctrl-c會導緻一個SIGINT信号被發送到外殼。
    • 外殼捕獲該信号,然後發送SIGINT信号到這個前台程序組中的每個程序。
    • 在預設情況下,結果是終止前台作業。
    • 輸入ctrl-z會發送一個SIGTSTP信号到外殼,外殼捕獲這個信号,并發送SIGTSTP信号給前台程序組中的每個程序。
    • 在預設情況下結果時停止前台作業。
  • 用kill函數發送信号
    • 程序通過調用kill函數發送信号給其他程序(包括它們自己)
  • 用alarm函數發送信号
    • 程序可以通過調用alarm函數向它自己發送SIGALRM信号。
    • 信号類型的預定義的預設行為:
      • 程序終止
      • 程序終止并轉儲存儲器
      • 程序停止直到被SIGCONT信号重新開機
      • 程序忽略該信号
    • signal函數通過下列三種方法來改變和信号signum相關聯的行為:
      • 如果handler是SIG_IGN,那麼忽略類型為signum的信号。
      • 如果handler是SIG_DFL,那麼類型為signum的信号行為恢複為預設行為。
      • 否則,handler就是使用者定義的函數的位址,這個函數稱為信号處理程式,隻要程序接收到一個類型為signum的信号,就會調用這個程式。
      • 通過把狐狸程式的抵制傳遞到signal函數進而改變預設行為,稱為設定信号處理程式。
      • 調用信号處理程式稱為捕獲信号
      • 執行信号處理程式稱為處理信号

信号處理問題

* 待處理信号被阻塞
* 帶處理信号不會排隊等待
* 系統調用可以被中斷
* 不可以用信号來對其他程序中發生的事件計數
           

可移植的信号處理

  • Signal包裝函數設定了一個信号處理程式,其信号處理語義如下:
    • 隻有這個處理程式目前正在處理的那種類型的信号被阻塞。
    • 和所有信号實作一樣,信号不會排隊等待。
    • 隻要可能,被中斷的系統調用會自動重新開機。
    • 一旦設定了信号處理程式,它就會移至保持,直到Signal帶着handler參數為SIG_IGN或者SIG_DFL被調用。

顯式地阻塞和取消阻塞信号 p517

  • sigprocmask函數改變目前已阻塞信号的集合,具體行為依賴于how值:
    • SIG_BLOCK:添加set中的信号到blocked中(blocked = blocked | set)
    • SIG_UNBLOCK:從blocked中删除set的信号(blocked = blocked &~ set)
    • SIG_SETMASK:blocked = set
    • 如果oldset非空,blocked位向量以前的值會儲存在oldset中。

非本地跳轉

  • 使用者級的異常控制流形式,通過setjmp和longjmp函數提供。
  • setjump函數在env緩沖區中儲存目前調用環境,以供後面longjmp使用,并傳回0.
  • 調用環境:程式計數器,棧指針,通用目的寄存器
  • longjmp函數從env緩沖區中恢複調用環境,然後觸發一個從最近一次初始化env的setjmp調用的傳回。然後setjmp傳回,并帶有非零的傳回值retval。
  • setjmp函數隻被調用一次,但傳回多次;
  • longjmp函數被調用一次,但從不傳回。

操作程序的工具

  • STRACE:列印一個正在運作的程式和他的子程式調用的每個系統調用的痕迹
  • PS:列出目前系統中的程序,包括僵死程序
  • TOP:列印出關于目前程序資源使用的資訊
  • PMAP:顯示程序的存儲器映射

參考文獻

1.《Computer.Systems.A.Programmer's.Perspective.2nd.CN》教材

2.《linux的中斷和異常》 http://blog.chinaunix.net/uid-20556798-id-176960.html

心得體會

後面的内容真的是很難了解,花了那麼多的時間看也沒有看太清楚,還是需要通過視訊來學習一下,看下是否能夠加深一點了解。