从 0 开始学习 Linux 系列之「18.守护进程」

daemon

版权声明:本文为 cdeveloper 原创文章,可以随意转载,但必须在明确位置注明出处!

什么是守护进程?

守护进程可以简单的理解为后台的服务进程,很多上层的服务器都是以守护进程为基础开发的。例如 Linux 上运行的 Apache 服务器,Android 系统的 Service 服务,它们的底层都由 Linux 的守护进程提供服务。

这篇文章介绍的是在 Linux 编写守护进程的方法。

编写守护进程的 6 个步骤

先来看看整体的编写步骤:

  1. 重新设置 umask(0)
  2. 执行 fork 并脱离父进程
  3. 重启 session 会话
  4. 改变当前工作目录
  5. 关闭文件描述符
  6. 固定文件描述符 0, 1, 2 到 /dev/null

下面我们来写一个守护进程的初始化程序 daemon_init.c 来学习这 6 个步骤。

1. 重新设置 umask

进程从创建他的父进程那里继承文件创建的掩码,它可能修改守护进程所创建的文件的权限,这里清除它:

void daemon_init(void) {
    // 1. 重新设置 umask
    umask(0);
}

2. 执行 fork 并脱离父进程

既然是服务进程,意味着是可以独立运行的,不会因为父进程退出而销毁,在 Linux 系统中有一个进程号为 1 的 init 进程,这个进程一直存在与系统中,当我们的子进程从父进程脱离后,子进程变为孤儿进程,随之系统的 init 进程会接管对这个进程的控制。我们看看代码:

void daemon_init(void) {
    // 1. 重新设置 umask
    // 2. 脱离父进程
    pid_t pid = fork()  ; 

    if(pid < 0)
        exit(1);    // 进程创建失败
    else if(pid > 0)
        exit(0);    // 退出父进程

    return 0;
}

3. 重启 session 会话

使用 setsid 重新开启一个 session 会话,防止脱离父进程的子进程重新受控于字符终端进程,尽量加上这一步防备工作让 fork 的子进程直接受控于 init 进程,要知道编写守护进程的核心是让子进程直接受控于 init 进程

void daemon_init(void) {
    // 1. 重新设置 umask
    // 2. 脱离父进程
    // 3. 重启 session 会话
    setsid();
}

4. 改变工作目录

进程活动时,其工作目录所在的文件系统不能卸载,一般需要将守护进程工作目录改变到根目录 /

void daemon_init(void) {
    // 1. 重新设置 umask
    // 2. 脱离父进程
    // 3. 重启 session 会话
    // 4. 改变工作目录
    chdir("/");
}

5. 关闭文件描述符

进程从创建它的父进程哪里继承了打开的文件描述符,若不关闭将会造成资源浪费,造成进程所在的文件系统无法卸下以及引起无法预料的错误:

void daemon_init(void) {
    // 1. 重新设置 umask
    // 2. 脱离父进程
    // 3. 重启 session 会话
    // 4. 改变工作目录
    // 5. 得到并关闭文件描述符
    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

守护进程在后台运行,不会与用户发生直接的交互,我们不希望在终端上看到守护进程的输出,用户也不期望他们在终端上的输入被守护进程读取,因此我们将文件描述符 0, 1, 2 定位到 /dev/null

void daemon_init(void) {
    // 1. 重新设置 umask
    // 2. 脱离父进程
    // 3. 重启 session 会话
    // 4. 改变工作目录
    // 5. 得到并关闭文件描述符
    // 6. 固定文件描述符 0, 1, 2 到 /dev/null
    int fd0 = open("/dev/null", O_RDWR);
    int fd1 = dup(0);
    int fd2 = dup(0);
}

这样,我们 daemon_init 就写好了,可以用这个函数来初始化一个守护进程了,我们来测试测试。

测试 daemon_init 初始化函数

我们编写一个 printlg.c 循环向文件中输出信息:

#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) {
    // 初始化守护进程
    daemon_init();
    
    // 守护进程逻辑
    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,下面我们来测试这个守护进程能够工作。

测试 printlg.c 守护进程

编译

gcc printlg.c -o printlg

运行

./printlg

结果最终写入到 /tmp/test_printlg.log 文件中,我们每隔 3 s 查看这个文件的内容,发现内容在不断增多的:

cat /tmp/test_printlg.log

# 结果
I'm printlg process...
I'm printlg process...
I'm printlg process...

到此为止,一个守护进程就写好了,但是很多的守护进程都可以开机自启动,我们的可不可以呢?

让守护进程开机自启动

很多的守护进程都设置了开机自启动,我们也来让 printlg 能够开机自启动,先来了解自启动的原理。

每个系统启动级别的守护进程分别在 /etc/rcN.d 下,比如我的图形界面的启动级别是 5,那么在这个启动级别下自动运行和禁止启动守护进程都在 /etc/rc5.d 下。这里我的 ubuntu 系统的守护进程目录是 /etc/rcN.d,如果你是其他的 Linux 可能会不太一样,你可以使用 whereis rc[N].d 来看看具体的目录位置,不要死记硬背。

这是我的 ubuntu 的 /etc/rc5.d 下的守护进程:

mydaemon

知道了守护进程的位置,现在就可以把 printlg 放在 /etc/rc5.d/ 下,并且还要改名称,因为系统需要根据指定的名称来使用 for 循环来启动或者关闭每个程序,命名规则如下:

  1. S[num][name]:启动守护进程 name,例如:S01printlg
  2. K[num][name]:禁止启动守护进程 name,例如:K01printlg

我们这里肯定要启动了,所以将 printlg 命名为 S01printlg

mv printlg /etc/rc5.d/S01printlg

之后我们重启机器,再次查看 /tmp/test_printlg 文件,就可以看到服务已经启动了,这样守护进程就成功自启动啦。

结语

这次我们学习了如何在 Linux 编写一个守护进程,守护进程其实就是一个后台的服务程序,学习如何在 Linux 创建服务程序还是非常有必要的,因为我们无时无刻都在使用很多系统提供的服务,了解原理以后再使用 Linux 的服务会更加得心应手。

最后,感谢你的阅读,我们下次再见 :)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容