CreateProcess fork() exit() wait() execev() 簡介 fork() parent process經由呼叫fork()建立一個 child process child獲得parent的stack segment、data segment、heap segment、text segment 可說是把parent process一分為二 exit() terminate a process 將佔用的所有資源歸還給kernel parent 可以利用 wait()來取得結束的狀態(status) wait() 若child process還未呼叫exit(),那wait()會suspend parent process,直到有任一child process terminated 可以取得status execve() Load a new program到目前process的記憶體 丟去現存的text segment 重新建立 stack segment、data segment、heap segment 大致流程圖
fork() 1 2 3 #include <unistd.h>pid_t fork(void); In parent: returns process ID of child on success, or –1 on error; in successfully created child: always returns 0 呼叫後將存在兩個process,且每個process都會從fork()的返回處繼續執行 兩個process擁有不同的stack segment、data segment、heap segment、text segment副本(child可以再修改,且不影響parent) 返回pid == 0是child process 返回pid !
Backpack 0 - 1 Problem 主問題:從n個物品選一些物品,在不超過最大容量下,使得價值最大。 解空間:{x1,x2….,xn} xi : 0 or 1 (表示取或不取) 共有 $2^n$ 可能的解 限制條件: $\sum_{i=1}^n$ $w_ix_i$ <= W 採用回溯法 限界條件: 對於任何一個中間節點z,從root到z的分支所代表的狀態已經確定,從z到子孫的節點還未確定。如果z在第t層,說明第1種物品到t-1種物品(是否裝入背包)確定,t可以沿著分支擴展確認狀態,t+1到n不確定。 目前裝入背包的物品總價用cp表示,因為還不確定t+1到n物品的狀態,先假設全部都放入背包,也就是剩餘的總價值,用rp表示。 cp + rp是所有從root出發經過中間節點z的可行解的價值上界。如果價值上界小於或等於目前的最優值,則說明節點z沒有繼續搜尋的必要 即 cp + rp > bestp solution 假設有4個物品,每個物品w [2,5,4,2], 價值v [6,3,5,4], W = 10
初始化:sumw , sumv 統計所有物品的總重和總價 -> sumw = 13, sumw = 18,目前放入背包的物品重量cw = 0, 總價cp = 0, 最優值 bestp = 0 第一層:t = 1, 判斷cw + w[1] = 2 < W (滿足限制條件)向左擴展分支,令x[1] = 1, cw = cw + w[1] = 2, cp = cp+v[1] = 6,生成2好節點
Linux Chapter 21 Signal 處理常式 訊號處理常式設計 設定一個全域flag,接著離開。主程式會週期性的檢測此flag,若有被設定為flag,會進行適當的反應 會做出幾種類型的清理動作,接著結束行程或使用非區域跳躍(nonlocal goto)解開unwind the stack,並將控制權交回給主程式(是先定義的位置) 可重入函式與非同步訊號安全函式 可重入與不可重入 訊號處理常式與multiple thread的概念有關,前者可能會在任意時間點非同步的中斷程式執行,所以主程式與訊號處理常式會變成在同一個行程中,2個獨立的thread(非同步執行) 若函式可以在同一個行程的各thread中同步且安全的執行,此函式稱為是可重入的(reentrant)
SUSv3對可重入的定義是: 當兩條或多條thread呼叫函式時,即便是彼此互相交叉執行,也能保證效果與各thread以未定義順序呼叫時一致 若函式會更新全域或靜態的資料結構,可能就是不可重入的函式(只使用local變數的函式保證是可重入)。
可能發生的情況: 若主程式在呼叫malloc()期間,受到一個同樣呼叫malloc()的訊號處理常式中斷,則此linklist可能會遭到破壞,因此malloc()的函式家族與使用這些函式的其他函式庫函式都是不可重入。 其他會傳回靜態配置的記憶體,也是不可重入的,crypt() getpwnam() gethostbyname() getservbyname() 函式的內部紀錄是使用靜態的資料結構也是不可重入的,例如scanf() printf(),他們會有緩衝的I/O更新內部資料結構,所以在訊號處理常式使用printf()也在主程式呼叫printf(),緩衝區的資料會交錯,導致得到非期望的輸出結果,甚至是整個程式crush 標準的非同步訊號安全函式(async-signal-safe functions) 指可以安全地從訊號處理常式進行呼叫。 當函式是reentrant函式,或是不會受到訊號處理常式中斷,可以稱函式為async-signal-safe function man page: https://man7.org/linux/man-pages//man7/signal-safety.7.html 在訊號處理常式內使用errno 因為可能會更新errno變數,會導致函式reentrant,因為可能會覆寫主程式之前呼叫函式設定的errno值。 所以可以先儲存errno的值,並在函式執行完畢之後,回存errno
1 2 3 4 5 6 7 void handler(int sig) { int sErrno; sErrno = errno; /* can execute a function that might modify errno */ errno = sErron; } 全域變數與sig_atomic_t資料型別 全域變數有reetrant問題,但有時需要在主程式與訊號處理常式之間共用全域變數,要能正確處理即可以保證安全。 常見的設計是:
Linux Chapter 20 Signal 基本概念 概念 signal 可以通知行程(process)有事件發生,也可以稱為軟體中斷,多數情況下都無法預測訊號抵達的時間。
行程可以送出訊號給另外一個行程,在此可以做為synchronization 或者 IPC 的技術。
訊號類型與預設行為 可以在signal(7)使用手冊列出訊號名稱,或是參考書上p.430 下圖列出簡易版訊號表: 改變訊號處置 Unix系統提供兩種 1.signal() 2. sigaction()
signal()提供設定訊號的原始API 介面比sigaction()簡單 sigaction()是建立訊號處置常式首推的API signal() 是基於 sigaction()實作的函式 1 2 3 #include<signal.h>void (*signal(int sig, void (*handler)(int))); Return previous signal disposition on success,or SIG_ERR on error handler可以用以下的值取代 SIG_DFL:將訊號處置 重新設定為預設值,適用於還原先前signal()呼叫改變的訊號處置 SIG_IGN:忽略該訊號,行程也不會知道有此訊號 訊號處理常式 範例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include<signal.
檔案I/O深入探討 原子與競速條件 以互斥的方式建立檔案 在open()中設定O_EXCL與O_CREAT可以在檔案已存在的時候回傳錯誤,確保process本身是檔案的建立者
將資料附加到檔案 當我們有多個process要增加資料到同一個檔案 會有下列這種寫法
1 2 3 4 if(lseek(fd,0,SEEK_END)==-1) errExit("lseek"); if(write(fd, buf, len)!=len) fatal("Partial/failed write"); 會有race condtion問題,第一個process在lseek()與write()之間被第二個相同的行程中斷,那兩個行程會在寫入之前將他們的file offset設定到相同位置,因此會產生互相覆蓋的現象
為了避免此問題,必須讓移往檔案結尾下一個byte操作與寫入操作都是原子操作,我們可以使用O_APPENDflag達成 檔案操作控制 1 2 3 #include<fcntl.h>int fcntl(int fd, int cmd,...); Return value on success depends on cmd, returns -1 on error 用途之一是用來取得或修改access mode,以及一個開啟檔案的開啟檔案狀態flag 取得設定的cmd : F_GETFL
1 2 3 int flags, accessMode; flags = fcntl(fd, F_GETFL); if(flags == -1) errExit("fcntl"); 可以使用 & 操作去判斷目前的開啟檔案狀態flag
Linux 嵌入式開發 – 並行開發— System V IPC System V IPC 包含共享內存、消息對列、和信號燈集 每個IPC對象有唯一的ID IPC對象創建後一直存在,直到被顯式的刪除(主動刪除) 每個IPC對象有一個關聯的KEY ipcs / ipcrm 實現進程間通訊 Key ftok 1 2 3 #include<sys/types.h>#include<sys/ipc.h>key_t ftok(const char *path,int proj_id); 成功時返回合法的key值,失敗時返回EOF path存在且可以被訪問文件的路徑 proj_id 用於生成key的數字,不能為0 範例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include<stdio.h>#include<stdlib.h>#include<sys/types.h>#include<sys/ipc.h>#include<unistd.h> int main(int argc, char *argv[]) { key_t key; if((key = ftok(".",'a')) == -1) { perror("key"); exit(-1); } } 共享內存 是一種最高效的進程間通信方式,進程可以直接讀寫內存,而不需要任何數據的拷貝 在內核空間創建,可以被進程映射到用戶空間訪問,使用靈活 由於多個進程可以同時訪問共享內存,因此需要同步和互斥機制配合使用 使用步驟 創建/打開共享內存 映射共享內存,即把指定的共享內存映射到進程的地址空間用於訪問 讀寫共享內存 撤銷共享內存映射 刪除共享內存對象 創建 1 2 3 #include<sys/ipc.