1、使用範圍
SylixOS是一款爲嵌入式系統設計的硬實時系統。爲了保證系統的實時性,系統創建子進程時不做頁表切換(頁表切換很耗時間,不利於實時性的體現),即父子進程共享同一個頁表,而對於Linux下fork函數創建的父子進程是需要進行頁表複製和切換的。爲了在SylixOS下實現Linux的fork函數功能,本文總結了如何使用posix標準的posix_spawn函數替換fork函數。
2、原理介紹
SylixOS的posix_spawn函數是基於posix標準的,是fork函數和exec函數的功能合二爲一,它只是創建一個新進程並執行指定程序。
2.1、Linux下fork函數功能淺析
由fork函數創建的子進程獲得父進程的堆棧、數據段和執行文本段的拷貝,實質上我們可以這樣理解:即子進程完全複製父進程的頁表(對於有寫時拷貝機制的fork函數我們也可以這麼理解)。
父子進程均可修改各自的棧數據、以及堆段中的變量,而不影響另一進程。程序可以通過fork函數的返回值來區別父子進程,在父進程中fork函數將返回子進程的進程ID。鑑於父進程可能需要創建,進而追蹤多個子進程(通過wait函數或類似的方法)。fork函數在子進程中返回0,子進程可以通過getpid函數以獲得自身ID,可以使用getppid函數獲得父進程的ID。
父子進程之間文件共享,子進程獲得父進程所有文件描述符號的副本(副本的創建類似於dup函數的功能),且父子進程中對應的描述符均指向相同的文件句柄(即公用一個文件結構和文件節點),共享同一個文件句柄,這樣公用的文件偏移量保證了父子進程寫同一個文件時不會覆蓋彼此寫入的內容(但是父子進程的輸出會隨意混雜在一起,可用進程間同步來規避)。
總的來說fork函數創建子進程,複製父進程的信息可分爲主要三個方面:
1、複製父進程的文件描述符表;
2、複製父進程數據段;
3、複製父進程的環境變量。
對於SylixOS系統來說不提供fork函數,也沒有提供與fork函數相同功能的函數。而SylixOS提供一個posix標準的posix_spawn函數,我們可以根據fork函數功能進行使用posix_spawn函數替換fork函數。
2.2、posix_spawn原型介紹
#include <spawn.h>
int posix_spawn(pid_t *pid,
const char *path,
constposix_spawn_file_actions_t *file_actions,
constposix_spawnattr_t *attrp,
char *const argv[],
char *const envp[]);
函數成功返回 0,失敗返回-1 並設置錯誤碼。函數參數如表2-1函數參數對比:
表2-1 函數參數
項目 | posix_spawn |
進程號pid | 新的進程號 |
文件操作集file_actions | 新進程啓動時需要處理的文件操作集, 爲 0 表示不進行任何文件操作 |
新進程初始化屬性attrp | 新進程初始化屬性,爲 NULL 表示不設置 |
命令行參數字符串數組argv | 命令行參數組成的字符串數組,數組以可執行文件名開始以 NULL結束,爲 NULL 表示不使用命令行參數最多64個 |
進程環境變量字符串數組envp | 需要預先設置的進程環境變量字符串數組,數組以 NULL 結束,爲NULL 表示不需要設置環境變量不超過64個 |
可執行文件路徑path | path 是可執行文件路徑 |
根據posix_spawn函數原型和功能可知,針對fork函數創建子進程時複製的主要三個方面有如下的方法替代:
1. 複製文件描述符表:posix_spawn函數自身在創建子進程時也複製父進程所有的文件描述符,父子進程的文件描述符表指向同一個文件結構,這個和fork函數一樣;
2. 複製數據段:posix_spawn函數可以通過第五個參數指針數組argv[ ]進行數據傳遞,對於對父進程的代碼段的複製只能通過再次編寫來解決;
3. 複製壞境變量:posix_spawn函數可以使用第六個參數數指針數組envp[ ]來傳遞父進程的環境參數,並且可以認爲設置envp[ ]參數來設置子進程的環境參數。
注:因爲在SylixOS父子進程因爲是共享同一張頁表的,所以對於SylixOS操作系統的父子進程天生就有共享內存(即共享同一張頁表地址),不過需要注意如果父子進程通過使用同一張頁表同一個地址來傳遞數據很危險,需謹慎使用(比如子進程到共享頁表地址進行讀寫操作,不小心踩到父進程的堆棧空間會造成不可估量的危險)。
3、技術實現
3.1、文件描述符
例程描述:
父進程創建子進程前已經打開一個文件,父進程將文件描述符值傳遞給子進程,父、子進程共同對同一文件進行寫操作。
替換框圖如圖3-1所示:
圖3-1 文件描述符
例程總結:
父進程在創建子進程時,子進程複製父進程的文件描述符表,所以只需要通過傳參的方式把文件描述符告知給子進程,子進程就能夠使用這個文件描述符,深入瞭解後發現使用posix_spawn函數創建子進程,父子進程也是公用一個文件結構和文件節點。posix_spawn函數也支持執行時關閉(close-on-exec) 標誌,即文件描述符設置FD_CLOEXEC標誌,在創建子進程時子進程不能複製該文件描述符。
注:close-on-exec標誌可通過fnctl函數對文件描述符設置,具體實現可參考附錄。
3.2、全局變量
例程描述:
父進程創建子進程時將全局變量值傳遞給子進程,隨後父進程對全局變量進行加1操作,子進程對接收到的全局變量值也進行加1操作。
替換框圖如圖3-2所示:
圖3-2 全局數據
例程總結:
父進程可通過傳遞參數的方式將子進程需要的數據傳遞給子進程,這樣父子進程都有自己的數據,且互不干擾。
3.3、守護進程
例程描述:
父進程創建子進程,子進程執行daemon函數將自己設爲守護進程,父進程手動退出。
替換框圖如圖3-3所示:
圖3-3 守護進程
例程總結:
SylixOS提供了設置守護進程daemon函數,該函數內部包含了部分fork函數創建守護進程的相關操作。
daemon原型介紹
SylixOS提供API接口daemon函數,可以設置當前進程爲守護進程。
#include <unistd.h>
int daemon(int nochdir, int noclose);
函數 daemon 原型分析:
l此函數成功返回 0,失敗返回-1 並設置錯誤碼;
l參數 nochdir 表示是否切換進程當前工作目錄到根目錄“ /”。 0 表示切換,其他表
示不切換;
l參數 noclose 表示是否重定向標準輸入、標準輸出、標準錯誤輸出到“ /dev/null”
文件。 0 表示重定向,其他表示不重定向
4、posix_spawn函數源碼實現框圖
如圖4-1所示:
圖4-1 posix_spawn函數中的流程實現
如圖4-2所示對於posix_spawn函數實現的核心步驟__processStart的實現流程:
圖4-2 _processStart函數的實現流程
5、參考資料
《 SylixOS 應用開發手冊》
附錄置文件描述符不可複製
1.在open函數打開時設置標誌位:
iFd = open("./filetest", O_RDWR | O_CREAT | O_CLOEXEC , FILE_MODE);
加上這個標誌:O_CLOEXEC
2.使用fcntl函數設置文件描述符不可複製:
fcntl(iFd, F_SETFD, 0);
注:0表示不設置標誌位,1表示設置標誌位