1:什么是Linux下的守护进程

  Linux daemon是运行于后台常驻内存的一种特殊进程,周期性的执行或者等待trigger执行某个任务,与用户交互断开,独立于控制终端。一个守护进程的父进程是init进程,它是一个孤儿进程,没有控制终端,所以任何输出,无论是向标准输出设备stdout还是标准出错设备stderr的输出都被丢到了/dev/null中。守护进程一般用作服务器进程,如httpd,syslogd等。

2:进程,进程组,会话,控制终端之间的关系

  因为守护进程的创建需要改变这些环境参数,所以了解它们之间的关系很重要:

  上图就描述了它们之间的联系:

  2.1 进程组:它是由一个或多个进程组成,进程组号(GID)就是这些进程中的进程组长的PID。

  2.2 会话:其实叫做会话期(session),它包括了期间所有的进程组,一般一个会话期开始于用户login,一般login的是shell终端,所以shell终端又是此次会话期的首进程,会话一般结束于logout。对于非进程组长,它可以调用setsid()创建一个新的会话。

  2.3 控制终端(tty):一般就是指shell终端,它在会话期中可有也可以没有。

3:创建一个daemon的几个步骤

  3.1 实例(创建一个daemon,每隔10秒向/mydaemon.log文件写入当前时间一共三次)

 void mydaemon(void)
{
pid_t pid;
int fd, i, nfiles;
struct rlimit rl; pid = fork();
if(pid < )
ERROR_EXIT("First fork failed!"); if(pid > )
exit(EXIT_SUCCESS);// father exit if(setsid() == -)
ERROR_EXIT("setsid failed!"); pid = fork();
if(pid < )
ERROR_EXIT("Second fork failed!"); if(pid > )// father exit
exit(EXIT_SUCCESS);
#ifdef RLIMIT_NOFILE
/* 关闭从父进程继承来的文件描述符 */
if (getrlimit(RLIMIT_NOFILE, &rl) == -)
ERROR_EXIT("getrlimit failed!");
nfiles = rl.rlim_cur = rl.rlim_max;
setrlimit(RLIMIT_NOFILE, &rl);
for(i=; i<nfiles; i++)
close(i);
   #endif
/* 重定向标准的3个文件描述符 */
if(fd = open("/dev/null", O_RDWR) < )
ERROR_EXIT("open /dev/null failed!");
for(i=; i<; i++)
dup2(fd, i);
   if(fd > 2) close(fd);
/* 改变工作目录和文件掩码常量 */
chdir("/");
umask();
}

  3.2 解读创建daemon的过程

  A(7~12行):成为后台进程

    用fork创建子进程,父进程退出,子进程成为孤儿进程被init接管,子进程变为后台进程。

  B(14~15行):脱离父进程的控制终端,登陆会话和进程组

    调用setsid()让子进程成为新会话的组长,脱离父进程的会话期。setsid()在调用者是某进程组组长时会失败,但是A已经保证了子进程不会是组长,B之后子进程变成了新会话组的组长。

  C(17~22行):禁止进程重新开启控制终端

    因为会话组的组长有权限重新打开控制终端,所以这里第二次fork将子进程结束,留着孙进程,孙进程不是会话组的组长所以没有权利再打开控制终端,这样整个程序就与控制终端隔离了。

  D(23~31行):关闭文件描述符

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

  E(32~36行):重定向0,1,2标准文件描述符

    将三个标准文件描述符定向到/dev/null中

  F(38~40行):改变工作目录和文件掩码

    进程活动时,其工作目录所在的文件系统不能卸下(比如工作目录在一个NFS中,运行一个daemon会导致umount无法成功)。一般需要将工作目录改变到根目录。对于需要转储核心,写运行日志的进程将工作目录改变到特定目录如chdir("/tmp"),进程从创建它的父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩模清除:umask(0);

  注:D,E,F三步是对当前工作环境的修改,可以先做,因为这些修改都会被子进程继承下来

4:实例运行

#define ERROR_EXIT(m)\
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}\
while()
int main(int argc, char **argv)
{
time_t t;
int fd, i;
mydaemon();
fd = open("./mydaemon.log", O_RDWR|O_CREAT, );
if(fd < )
ERROR_EXIT("open /mydaemon.log failed!");
for(i=; i<; i++)
{
t = time();
char *buf = asctime(localtime(&t));
write(fd, buf, strlen(buf));
sleep();
}
close(fd);
return ;
}

  上图是main函数,运行结果如下图:

  有图可知,在open /mydaemon.log文件没有权限,而切换到root权限后执行成功,文件的内容也是每10秒间隔写入一次时间。因为我创建的mydaemon程序的工作目录已经切换到了根目录,所以普通用户没有在根目录下创建文件的权限。如果这里将文件创建在当前目录的话就不用切换到root权限。

5:函数daemon()

  其实在linux下已经有函数daemon函数用于创建一个后台程序了,所以上面的工作已经被加入了函数库,直接使用轮子。

  5.1 daemon函数原型及描述

#include <unistd.h>
int daemon(int nochdir, int noclose); DESCRIPTION
The daemon() function is for programs wishing to detach themselves from the controlling terminal and run in the background as system daemons.
If nochdir is zero, daemon() changes the process's current working directory to the root directory ("/"); otherwise,the current working directory is left unchanged.
If noclose is zero, daemon() redirects standard input, standard output and standard error to /dev/null; otherwise, no changes are mad to these file descriptors.

  通过man手册的描述可知,函数daemon接收两个参数:

  nochdir:如果是0,将当前工作目录切换到根目录"/",否则工作目录不改变。

  noclose:如果是0,将0,1,2重定向到/dev/null,否则不变。

  5.2 mydaemon和daemon

  其实可以将mydeamon函数稍加修改符合daemon函数

 void mydaemon(int nochdir, int noclose)
{
pid_t pid;
int fd, i, nfiles;
struct rlimit rl; pid = fork();
if(pid < )
ERROR_EXIT("First fork failed!"); if(pid > )
exit(EXIT_SUCCESS);// father exit if(setsid() == -)
ERROR_EXIT("setsid failed!"); pid = fork();
if(pid < )
ERROR_EXIT("Second fork failed!"); if(pid > )// father exit
exit(EXIT_SUCCESS);
   #ifdef RLIMIT_NOFILE
/* 关闭从父进程继承来的文件描述符 */
if (getrlimit(RLIMIT_NOFILE, &rl) == -)
ERROR_EXIT("getrlimit failed!");
nfiles = rl.rlim_cur = rl.rlim_max;
setrlimit(RLIMIT_NOFILE, &rl);
for(i=; i<nfiles; i++)
close(i);
#endif
/* 重定向标准的3个文件描述符 */
if(!noclose)
{
if(fd = open("/dev/null", O_RDWR) < )
ERROR_EXIT("open /dev/null failed!");
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
if(fd > )
close(fd);
} /* 改变工作目录和文件掩码常量 */
if(!nochdir)
chdir("/");
umask();
}

  问题:这样普通用户调用mydaemon(0,0)函数时还是会在console提示open mydaemon.log failed!: Permission denied,但是在37~39行已经将它们重定向到了/dev/null了,很疑惑??当你close(0,1,2)之后普通用户才不会提示错误信息。

参考文章1

参考文章2

参考文章3

Linux 守护进程创建原理及简易方法的更多相关文章

  1. Linux 守护进程创建

    1. 守护进程: 是Linux中的后台服务进程.它是一个生存期较长的进程,通常独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件.守护进程常常在系统启动时开始运行,在系统关闭时终止 2. ...

  2. linux 守护进程创建流程

    #include <sys/stat.h> #include <fcntl.h> /* Bit-mask values for 'flags' argument of beco ...

  3. [转]❲阮一峰❳Linux 守护进程的启动方法

    ❲阮一峰❳Linux 守护进程的启动方法 "守护进程"(daemon)就是一直在后台运行的进程(daemon). 本文介绍如何将一个 Web 应用,启动为守护进程. 一.问题的由来 ...

  4. Linux 命令详解(六)Linux 守护进程的启动方法

    Linux 守护进程的启动方法 http://www.ruanyifeng.com/blog/2016/02/linux-daemon.html

  5. Linux 守护进程原理及实例(Redis、Nginx)

    1. 什么是守护进程 守护进程daemon,是指没有控制终端,运行在后台的进程,通常伴随着系统启动产生,系统关机结束.可以使用命令ps -axj查看系统的守护进程,输出如下所示: 父ID PID 组I ...

  6. .NET跨平台实践:用C#开发Linux守护进程

    Linux守护进程(Daemon)是Linux的后台服务进程,它脱离了与控制终端的关联,直接由Linux init进程管理其生命周期,即使你关闭了控制台,daemon也能在后台正常工作. 一句话,为L ...

  7. .NET跨平台实践:用C#开发Linux守护进程(转)

    Linux守护进程(Daemon)是Linux的后台服务进程,它脱离了与控制终端的关联,直接由Linux init进程管理其生命周期,即使你关闭了控制台,daemon也能在后台正常工作. 一句话,为L ...

  8. Linux守护进程详解(init.d和xinetd) [转]

    一 Linux守护进程 Linux 服务器在启动时需要启动很多系统服务,它们向本地和网络用户提供了Linux的系统功能接口,直接面向应用程序和用户.提供这些服务的程序是由运行在后台 的守护进程来执行的 ...

  9. Linux守护进程的编程实现

    Linux 守护进程的编程方法 守护进程(Daemon)是执行在后台的一种特殊进程.它独立于控制终端而且周期性地执行某种任务或等待处理某些发生的事件.守护进程是一种非常实用的进程.Linux的大多数s ...

随机推荐

  1. Linux的基础预备知识

       Linux下一切皆文件 1.root@mk-virtual-machine:/home/mk#   root:该位置表示当前终端登录的用户名 mk-virtual-machine:/home/m ...

  2. Median Weight Bead(最短路—floyed传递闭包)

    Description There are N beads which of the same shape and size, but with different weights. N is an ...

  3. js 拼接字符串时,本来想要’#1′ ,返回的却是’#01′

    今天在操作一个元素时,id值是拼接的. var index = $(this).attr(‘index’);    //0var id = ‘#’ + (index+1);    //#01$(id) ...

  4. lintcode-172-删除元素

    172-删除元素 给定一个数组和一个值,在原地删除与值相同的数字,返回新数组的长度. 元素的顺序可以改变,并且对新的数组不会有影响. 样例 给出一个数组 [0,4,4,0,0,2,4,4],和值 4 ...

  5. lintcode-151-买卖股票的最佳时机 III

    151-买卖股票的最佳时机 III 假设你有一个数组,它的第i个元素是一支给定的股票在第i天的价格.设计一个算法来找到最大的利润.你最多可以完成两笔交易. 注意事项 你不可以同时参与多笔交易(你必须在 ...

  6. C++并行编程1

    what is concurrency 我们可以一边看电视,一边唱歌.人并行非常容易理解,但是计算机呢?是不是我们一边编辑着word文档,一边听着歌,这样计算机就是在并行吗?不一定欧,如果你计算机是单 ...

  7. 【Linux】- vi/vim

    所有的 Unix Like 系统都会内建 vi 文书编辑器,其他的文书编辑器则不一定会存在. 但是目前我们使用比较多的是 vim 编辑器. vim 具有程序编辑的能力,可以主动的以字体颜色辨别语法的正 ...

  8. 【PHP】- PHPStorm+XDebug进行调试图文教程

    转载:https://www.cnblogs.com/LWMLWM/p/8251905.html   这篇文章主要为大家详细介绍了PHPStorm+XDebug进行调试图文教程,内容很丰富,具有一定的 ...

  9. C# 知识回顾 - 匿名方法

    C# 基础回顾 - 匿名方法 目录 简介 匿名方法的参数使用范围 委托示例 简介 在 C# 2.0 之前的版本中,我们创建委托的唯一形式 -- 命名方法. 而 C# 2.0 -- 引进了匿名方法,在 ...

  10. name(实例化类名).hbm.xml文件案例

    [html] view plain copy print? <span xmlns="http://www.w3.org/1999/xhtml"><?xml ve ...