守護進程編程規範
-
1.首先要做的是調用umask,重設文件權限掩碼。
文件權限掩碼是指屏蔽掉文件權限中的對應位。比如,有個文件權限掩碼是050,它就屏蔽了文件組擁有者的可讀與可執行權限。
由於使用fork函數新建的子進程繼承了父進程的文件權限掩碼,這就給該子進程使用文件帶來了諸多的麻煩。
因此,把文件權限掩碼設置爲0,可以大大增強該守護進程的靈活性。
設置文件權限掩碼的函數是umask。在這裏,通常的使用方法爲umask(0)。 -
2.調用fork,然後將父進程退出(exit)。
這是編寫守護進程的第一步。由於守護進程是脫離控制終端的,因此,完成第一步後就會在Shell終端裏造成一程序已經運行完畢的假象。
之後的所有工作都在子進程中完成,而用戶在Shell終端裏則可以執行其他命令,從而在形式上做到了與控制終端的脫離。
在Linux中父進程先於子進程退出會造成子進程成爲孤兒進程,而每當系統發現一個孤兒進程是,就會自動由1號進程(init)收養它。這樣,原先的子進程就會變成init進程的子進程。 -
3.調用setsid以創建一個新會話。
由於創建守護進程的第一步調用了fork函數來創建子進程,再將父進程退出。由於在調用了fork函數時,子進程全盤拷貝了父進程的會話期、進程組、控制終端等。
雖然父進程退出了,但會話期、進程組、控制終端等並沒有改變,因此,還還不是真正意義上的獨立開來,而setsid函數能夠使進程完全獨立出來,從而擺脫其他進程的控制。 -
4.當前工作目錄更改爲根目錄。從父進程繼承過來的當前工作目錄可能在一個裝配文件系統中。因爲守護進程可能會在系統再引導之前就一直存在,所以如果守護進程的當前工作目錄。在一個裝配文件系統中,那麼該文件系統就不可拆卸。這與裝配文件系統的原意不符。
-
5.閉不再需要的文件描述符。用fork函數新建的子進程會從父進程那裏繼承一些已經打開了的文件。這些被打開的文件可能永遠不會被守護進程讀寫,但它們一樣消耗系統資源,而且可能導致所在的文件系統無法卸下。
-
6.些守護進程打開/dev/null使其具有文件描述符0、1和2
因爲守護進程並不與終端設備相關聯,所以不能再終端設備上顯示其輸出,也無處從交互式用戶那裏接收輸入。
主機字節序
不同的CPU有不同的字節序類型,這些字節序是指 整數 在內存中保存的順序,這個叫做 主機序。
最常見的有兩種:
- 1.Little endian:將低序字節存儲在起始地址
- 2.Big endian:將高序字節存儲在起始地址
LE little-endian(小端)
- 最符合人的思維的字節序;
- 地址低位存儲值的低位;
- 地址高位存儲值的高位;
- 怎麼講是最符合人的思維的字節序,是因爲從人的第一觀感來說;
- 低位值小,就應該放在內存地址小的地方,也即內存地址低位;
- 反之,高位值就應該放在內存地址大的地方,也即內存地址高位;
BE big-endian(大端)
- 最直觀的字節序;
- 地址低位存儲值的高位;
- 地址高位存儲值的低位;
- 爲什麼說直觀,不要考慮對應關係;
- 只需要把內存地址從左到右按照由低到高的順序寫出;
- 把值按照通常的高位到低位的順序寫出;
- 兩者對照,一個字節一個字節的填充進去;
例子:在內存中雙字 0x01020304(DWORD) 的存儲方式
內存地址
4000 4001 4002 4003
LE 04 03 02 01
BE 01 02 03 04
例子:如果我們將0x1234abcd寫入到以0x0000開始的內存中,則結果爲
big-endian little-endian
0x0000 0x12 0xcd
0x0001 0x23 0xab
0x0002 0xab 0x34
0x0003 0xcd 0x12
x86系列CPU都是little-endian的字節序。
網絡字節序
網絡字節順序是TCP/IP中規定好的一種數據表示格式,它與具體的CPU類型、操作系統等無關,從而可以保證數據在不同主機之間傳輸時能夠被正確解釋。網絡字節序採用big endian排序方式。
網絡字節序和主機字節序的轉換
htons 把unsigned short類型從主機序轉換到網絡序
htonl 把unsigned long類型從主機序轉換到網絡序
ntohs 把unsigned short類型從網絡序轉換到主機序
ntohl 把unsigned long類型從網絡序轉換到主機序
網絡與主機字節轉換函數:htons()、ntohs()、htonl()、ntohl() (注意:s 就是short l是long h是host n是network)
Linux 進程間通訊的各種方式的比較
各種通信方式的比較和優缺點
- 管道:速度慢,容量有限,只有父子進程能通訊
- FIFO:任何進程間都能通訊,但速度慢
- 消息隊列:容量受到系統限制,且要注意第一次讀的時候,要考慮上一次沒有讀完數據的問題
- 信號量:不能傳遞複雜消息,只能用來同步
- 共享內存區:能夠很容易控制容量,速度快,但要保持同步,比如一個進程在寫的時候,另一個進程要注意讀寫的問題,相當於線程中的線程安全,當然,共享內存區同樣可以用作線程間通訊,不過沒這個必要,線程間本來就已經共享了同一進程內的一塊內存
fork 函數的返回值
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
返回值有三種:
- 負數:如果出錯,則fork()返回-1,此時沒有創建新的進程。最初的進程仍然運行。
- 零:在子進程中,fork()返回0
- 正數:在負進程中,fork()返回正的子進程的PID
wait 和waitpid
#include <sys/wait.h>
pid_t wait(int * statloc);
pid_t waitpid(pid_t pid,int *statloc,int options);
waitpid則可以通過設置一個選項來設置爲非阻塞,另外waitpid並不是等待第一個結束的進程而是等待參數中pid指定的進程。
waitpid的option常量:
WNOHANG
waitpid將不阻塞如果指定的pid並未結束
WUNTRACED
如果子進程進入暫停執行情況則馬上返回,但結束狀態不予以理會。
waitpid中pid的含義依據其具體值而變:
- pid==-1 等待任何一個子進程,此時waitpid的作用與wait相同
- pid >0 等待進程ID與pid值相同的子進程
- pid==0 等待與調用者進程組ID相同的任意子進程
- pid<-1 等待進程組ID與pid絕對值相等的任意子進程
waitpid提供了wait所沒有的三個特性:
- 1 waitpid使我們可以等待指定的進程
- 2 waitpid提供了一個無阻塞的wait
- 3 waitpid支持工作控制
XSI信號量函數API
信號量(信號燈)本質上是一個計數器,用於協調多個進程(包括但不限於父子進程)對共享數據對象的讀/寫。它不以傳送數據爲目的,主要是用來保護共享資源(信號量、消息隊列、socket連接等),保證共享資源在一個時刻只有一個進程獨享。
信號量是一個特殊的變量,只允許進程對它進行等待信號和發送信號操作。最簡單的信號量是取值0和1的二元信號量,這是信號量最常見的形式。
通用信號量(可以取多個正整數值)和信號量集方面的知識比較複雜,應用場景也比較少。
下面側重介紹二元信號量。
相關函數
Linux中提供了一組函數用於操作信號量,程序中需要包含以下頭文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
1、semget函數
semget函數用來獲取或創建信號量,它的原型如下:
int semget(key_t key, int nsems, int semflg);
-
1)參數key是信號量的鍵值,typedef unsigned int key_t,是信號量在系統中的編號,不同信號量的編號不能相同,這一點由程序員保證。key用十六進制表示比較好。
-
2)參數nsems是創建信號量集中信號量的個數,該參數只在創建信號量集時有效,這裏固定填1。
-
3)參數sem_flags是一組標誌,如果希望信號量不存在時創建一個新的信號量,可以和值IPC_CREAT做按位或操作。如果沒有設置IPC_CREAT標誌並且信號量不存在,就會返錯誤(errno的值爲2,No such file or directory)。
-
4)如果semget函數成功,返回信號量集的標識;失敗返回-1,錯誤原因存於error中。
PS:
可以使用ftok
函數將文件名轉變成一個key_t
key_t ftok(const char *pathname, int proj_id);
其中參數fname是指定的文件名,這個文件必須是存在的而且可以訪問的。id是子序號,它是一個8bit的整數。即範圍是0~255。當函數執行成功,則會返回key_t鍵值,否則返回-1
示例:
1)獲取鍵值爲0x5000的信號量,如果該信號量不存在,就創建它,代碼如下:
int semid=semget(0x5000,1,0640|IPC_CREAT);
2)獲取鍵值爲0x5000的信號量,如果該信號量不存在,返回-1,errno的值被設置爲2,代碼如下:
int semid= semget(0x5000,1,0640);
2、semctl函數
該函數用來控制信號量(常用於設置信號量的初始值和銷燬信號量),它的原型如下:
int semctl(int semid, int sem_num, int command, ...);
- 1)參數semid是由semget函數返回的信號量標識。
- 2)參數sem_num是信號量集數組上的下標,表示某一個信號量,填0。
- 3)參數cmd是對信號量操作的命令種類,常用的有以下兩個:
IPC_RMID:銷燬信號量,不需要第四個參數;
SETVAL:初始化信號量的值(信號量成功創建後,需要設置初始值),這個值由第四個參數決定。第四參數是一個自定義的共同體,如下:
// 用於信號燈操作的共同體。
union semun
{
int val;
struct semid_ds *buf;
unsigned short *arry;
};
- 4)如果semctl函數調用失敗返回-1;如果成功,返回值比較複雜,暫時不關心它。
示例:
1)銷燬信號量。
semctl(semid,0,IPC_RMID);
2)初始化信號量的值爲1,信號量可用。
union semun sem_union;
sem_union.val = 1;
semctl(semid,0,SETVAL,sem_union);
3、semop函數
該函數有兩個功能:1)等待信號量的值變爲1,如果等待成功,立即把信號量的值置爲0,這個過程也稱之爲等待鎖;2)把信號量的值置爲1,這個過程也稱之爲釋放鎖。
int semop(int semid, struct sembuf *sops, unsigned nsops);
1)參數semid是由semget函數返回的信號量標識。
2)參數nsops是操作信號量的個數,即sops結構變量的個數,設置它的爲1(只對一個信號量的操作)。
3)參數sops是一個結構體,如下:
struct sembuf
{
short sem_num; // 信號量集的個數,單個信號量設置爲0。
short sem_op; // 信號量在本次操作中需要改變的數據:-1-等待操作;1-發送操作。
short sem_flg; // 把此標誌設置爲SEM_UNDO,操作系統將跟蹤這個信號量。
// 如果當前進程退出時沒有釋放信號量,操作系統將釋放信號量,避免資源被死鎖。
};
示例:
1)等待信號量的值變爲1,如果等待成功,立即把信號量的值置爲0;
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = -1;
sem_b.sem_flg = SEM_UNDO;
semop(sem_id, &sem_b, 1);
2)把信號量的值置爲1。
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = 1;
sem_b.sem_flg = SEM_UNDO;
semop(sem_id, &sem_b, 1);