版權(quán)聲明:本文為 cdeveloper 原創(chuàng)文章,可以隨意轉(zhuǎn)載,但必須在明確位置注明出處旧烧!
什么是守護(hù)進(jìn)程特咆?
守護(hù)進(jìn)程可以簡(jiǎn)單的理解為后臺(tái)的服務(wù)進(jìn)程季惩,很多上層的服務(wù)器都是以守護(hù)進(jìn)程為基礎(chǔ)開(kāi)發(fā)的。例如 Linux 上運(yùn)行的 Apache 服務(wù)器腻格,Android 系統(tǒng)的 Service 服務(wù)画拾,它們的底層都由 Linux 的守護(hù)進(jìn)程提供服務(wù)。
這篇文章介紹的是在 Linux 編寫(xiě)守護(hù)進(jìn)程的方法菜职。
編寫(xiě)守護(hù)進(jìn)程的 6 個(gè)步驟
先來(lái)看看整體的編寫(xiě)步驟:
- 重新設(shè)置 umask(0)
- 執(zhí)行 fork 并脫離父進(jìn)程
- 重啟 session 會(huì)話
- 改變當(dāng)前工作目錄
- 關(guān)閉文件描述符
- 固定文件描述符 0, 1, 2 到
/dev/null
下面我們來(lái)寫(xiě)一個(gè)守護(hù)進(jìn)程的初始化程序 daemon_init.c
來(lái)學(xué)習(xí)這 6 個(gè)步驟青抛。
1. 重新設(shè)置 umask
進(jìn)程從創(chuàng)建他的父進(jìn)程那里繼承文件創(chuàng)建的掩碼,它可能修改守護(hù)進(jìn)程所創(chuàng)建的文件的權(quán)限酬核,這里清除它:
void daemon_init(void) {
// 1. 重新設(shè)置 umask
umask(0);
}
2. 執(zhí)行 fork 并脫離父進(jìn)程
既然是服務(wù)進(jìn)程蜜另,意味著是可以獨(dú)立運(yùn)行的适室,不會(huì)因?yàn)楦高M(jìn)程退出而銷(xiāo)毀,在 Linux 系統(tǒng)中有一個(gè)進(jìn)程號(hào)為 1 的 init 進(jìn)程举瑰,這個(gè)進(jìn)程一直存在與系統(tǒng)中捣辆,當(dāng)我們的子進(jìn)程從父進(jìn)程脫離后,子進(jìn)程變?yōu)楣聝哼M(jìn)程此迅,隨之系統(tǒng)的 init 進(jìn)程會(huì)接管對(duì)這個(gè)進(jìn)程的控制汽畴。我們看看代碼:
void daemon_init(void) {
// 1. 重新設(shè)置 umask
// 2. 脫離父進(jìn)程
pid_t pid = fork() ;
if(pid < 0)
exit(1); // 進(jìn)程創(chuàng)建失敗
else if(pid > 0)
exit(0); // 退出父進(jìn)程
return 0;
}
3. 重啟 session 會(huì)話
使用 setsid
重新開(kāi)啟一個(gè) session 會(huì)話,防止脫離父進(jìn)程的子進(jìn)程重新受控于字符終端進(jìn)程耸序,盡量加上這一步防備工作讓 fork 的子進(jìn)程直接受控于 init 進(jìn)程忍些,要知道編寫(xiě)守護(hù)進(jìn)程的核心是讓子進(jìn)程直接受控于 init 進(jìn)程:
void daemon_init(void) {
// 1. 重新設(shè)置 umask
// 2. 脫離父進(jìn)程
// 3. 重啟 session 會(huì)話
setsid();
}
4. 改變工作目錄
進(jìn)程活動(dòng)時(shí),其工作目錄所在的文件系統(tǒng)不能卸載坎怪,一般需要將守護(hù)進(jìn)程工作目錄改變到根目錄 /
:
void daemon_init(void) {
// 1. 重新設(shè)置 umask
// 2. 脫離父進(jìn)程
// 3. 重啟 session 會(huì)話
// 4. 改變工作目錄
chdir("/");
}
5. 關(guān)閉文件描述符
進(jìn)程從創(chuàng)建它的父進(jìn)程哪里繼承了打開(kāi)的文件描述符罢坝,若不關(guān)閉將會(huì)造成資源浪費(fèi),造成進(jìn)程所在的文件系統(tǒng)無(wú)法卸下以及引起無(wú)法預(yù)料的錯(cuò)誤:
void daemon_init(void) {
// 1. 重新設(shè)置 umask
// 2. 脫離父進(jìn)程
// 3. 重啟 session 會(huì)話
// 4. 改變工作目錄
// 5. 得到并關(guān)閉文件描述符
struct rlimit rl;
getrlimit(RLIMIT_NOFILE, &rl);
if (rl.rlim_max == RLIM_INFINITY)
rl.rlim_max = 1024;
for(int i = 0; i < rl.rlim_max; i++)
close(i);
}
6. 固定文件描述符 0, 1, 2 到 /dev/null
守護(hù)進(jìn)程在后臺(tái)運(yùn)行搅窿,不會(huì)與用戶(hù)發(fā)生直接的交互嘁酿,我們不希望在終端上看到守護(hù)進(jìn)程的輸出,用戶(hù)也不期望他們?cè)诮K端上的輸入被守護(hù)進(jìn)程讀取戈钢,因此我們將文件描述符 0, 1, 2 定位到 /dev/null
:
void daemon_init(void) {
// 1. 重新設(shè)置 umask
// 2. 脫離父進(jìn)程
// 3. 重啟 session 會(huì)話
// 4. 改變工作目錄
// 5. 得到并關(guān)閉文件描述符
// 6. 固定文件描述符 0, 1, 2 到 /dev/null
int fd0 = open("/dev/null", O_RDWR);
int fd1 = dup(0);
int fd2 = dup(0);
}
這樣痹仙,我們 daemon_init 就寫(xiě)好了,可以用這個(gè)函數(shù)來(lái)初始化一個(gè)守護(hù)進(jìn)程了殉了,我們來(lái)測(cè)試測(cè)試开仰。
測(cè)試 daemon_init 初始化函數(shù)
我們編寫(xiě)一個(gè) printlg.c
循環(huán)向文件中輸出信息:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
void daemon_init(void);
int main(void) {
// 初始化守護(hù)進(jìn)程
daemon_init();
// 守護(hù)進(jìn)程邏輯
char *msg = "I'm printlg process...\n" ;
int msg_len = strlen(msg);
int fd = open("/tmp/test_printlg.log", O_RDWR | O_CREAT | O_APPEND, 0666);
if(fd < 0) {
printf("open /tmp/test_printlg.log fail.\n");
exit(1);
}
while(1) {
// 每隔 3s 輸出 msg 到 /tmp/test_printlg.log 文件中
write(fd, msg, msg_len);
sleep(3);
}
close(fd);
return 0;
}
完整的代碼參考:printlg.c,下面我們來(lái)測(cè)試這個(gè)守護(hù)進(jìn)程能夠工作薪铜。
測(cè)試 printlg.c
守護(hù)進(jìn)程
編譯
gcc printlg.c -o printlg
運(yùn)行
./printlg
結(jié)果最終寫(xiě)入到 /tmp/test_printlg.log
文件中众弓,我們每隔 3 s 查看這個(gè)文件的內(nèi)容,發(fā)現(xiàn)內(nèi)容在不斷增多的:
cat /tmp/test_printlg.log
# 結(jié)果
I'm printlg process...
I'm printlg process...
I'm printlg process...
到此為止隔箍,一個(gè)守護(hù)進(jìn)程就寫(xiě)好了谓娃,但是很多的守護(hù)進(jìn)程都可以開(kāi)機(jī)自啟動(dòng),我們的可不可以呢蜒滩?
讓守護(hù)進(jìn)程開(kāi)機(jī)自啟動(dòng)
很多的守護(hù)進(jìn)程都設(shè)置了開(kāi)機(jī)自啟動(dòng)滨达,我們也來(lái)讓 printlg
能夠開(kāi)機(jī)自啟動(dòng),先來(lái)了解自啟動(dòng)的原理俯艰。
每個(gè)系統(tǒng)啟動(dòng)級(jí)別的守護(hù)進(jìn)程分別在 /etc/rcN.d
下捡遍,比如我的圖形界面的啟動(dòng)級(jí)別是 5,那么在這個(gè)啟動(dòng)級(jí)別下自動(dòng)運(yùn)行和禁止啟動(dòng)守護(hù)進(jìn)程都在 /etc/rc5.d
下竹握。這里我的 ubuntu 系統(tǒng)的守護(hù)進(jìn)程目錄是 /etc/rcN.d
画株,如果你是其他的 Linux 可能會(huì)不太一樣,你可以使用 whereis rc[N].d
來(lái)看看具體的目錄位置,不要死記硬背谓传。
這是我的 ubuntu 的 /etc/rc5.d
下的守護(hù)進(jìn)程:
知道了守護(hù)進(jìn)程的位置蜈项,現(xiàn)在就可以把 printlg
放在 /etc/rc5.d/
下,并且還要改名稱(chēng)续挟,因?yàn)橄到y(tǒng)需要根據(jù)指定的名稱(chēng)來(lái)使用 for 循環(huán)來(lái)啟動(dòng)或者關(guān)閉每個(gè)程序紧卒,命名規(guī)則如下:
-
S[num][name]
:?jiǎn)?dòng)守護(hù)進(jìn)程 name,例如:S01printlg
-
K[num][name]
:禁止啟動(dòng)守護(hù)進(jìn)程 name诗祸,例如:K01printlg
我們這里肯定要啟動(dòng)了常侦,所以將 printlg
命名為 S01printlg
:
mv printlg /etc/rc5.d/S01printlg
之后我們重啟機(jī)器,再次查看 /tmp/test_printlg
文件贬媒,就可以看到服務(wù)已經(jīng)啟動(dòng)了,這樣守護(hù)進(jìn)程就成功自啟動(dòng)啦肘习。
結(jié)語(yǔ)
這次我們學(xué)習(xí)了如何在 Linux 編寫(xiě)一個(gè)守護(hù)進(jìn)程际乘,守護(hù)進(jìn)程其實(shí)就是一個(gè)后臺(tái)的服務(wù)程序,學(xué)習(xí)如何在 Linux 創(chuàng)建服務(wù)程序還是非常有必要的漂佩,因?yàn)槲覀儫o(wú)時(shí)無(wú)刻都在使用很多系統(tǒng)提供的服務(wù)脖含,了解原理以后再使用 Linux 的服務(wù)會(huì)更加得心應(yīng)手。
最后投蝉,感謝你的閱讀养葵,我們下次再見(jiàn) :)