從 0 開(kāi)始學(xué)習(xí) Linux 系列之「18.守護(hù)進(jìn)程」

daemon

版權(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ě)步驟:

  1. 重新設(shè)置 umask(0)
  2. 執(zhí)行 fork 并脫離父進(jìn)程
  3. 重啟 session 會(huì)話
  4. 改變當(dāng)前工作目錄
  5. 關(guān)閉文件描述符
  6. 固定文件描述符 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)程:

mydaemon

知道了守護(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ī)則如下:

  1. S[num][name]:?jiǎn)?dòng)守護(hù)進(jìn)程 name,例如:S01printlg
  2. 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) :)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市瘩缆,隨后出現(xiàn)的幾起案子关拒,更是在濱河造成了極大的恐慌,老刑警劉巖庸娱,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件着绊,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡熟尉,警方通過(guò)查閱死者的電腦和手機(jī)归露,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)斤儿,“玉大人剧包,你說(shuō)我怎么就攤上這事⊥” “怎么了疆液?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)棚放。 經(jīng)常有香客問(wèn)我枚粘,道長(zhǎng),這世上最難降的妖魔是什么飘蚯? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任馍迄,我火速辦了婚禮福也,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘攀圈。我一直安慰自己暴凑,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布赘来。 她就那樣靜靜地躺著现喳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪犬辰。 梳的紋絲不亂的頭發(fā)上嗦篱,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音幌缝,去河邊找鬼灸促。 笑死,一個(gè)胖子當(dāng)著我的面吹牛涵卵,可吹牛的內(nèi)容都是我干的浴栽。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼轿偎,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼典鸡!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起坏晦,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤萝玷,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后英遭,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體间护,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年挖诸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了汁尺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡多律,死狀恐怖痴突,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情狼荞,我是刑警寧澤辽装,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站相味,受9級(jí)特大地震影響拾积,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一拓巧、第九天 我趴在偏房一處隱蔽的房頂上張望斯碌。 院中可真熱鬧,春花似錦肛度、人聲如沸傻唾。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)冠骄。三九已至,卻和暖如春加袋,著一層夾襖步出監(jiān)牢的瞬間凛辣,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工职烧, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蟀给,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓阳堕,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親择克。 傳聞我的和親對(duì)象是個(gè)殘疾皇子恬总,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容