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

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(0)int main(int argc, char **argv)
{time_t t;int fd, i;mydaemon();fd = open("./mydaemon.log", O_RDWR|O_CREAT, 0644);if(fd < 0)ERROR_EXIT("open /mydaemon.log failed!");for(i=0; i<3; i++){t = time(0);char *buf = asctime(localtime(&t));write(fd, buf, strlen(buf));sleep(10);}close(fd);return 0;
}

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

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

5:函数daemon()

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

  5.1 daemon函数原型及描述

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

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

 linux编程-守护进程编写守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程是一种很有用的进程。 Linux的大多数服务器就是用守护进程实现的。比如,Internet服务器inetd,Web服务器httpd等。同时,守护进程完成许多系统任务。比如,作业规划进程crond,打印进程lpd等。守护进程的编程本身并不复杂,复杂的是各种版本的Unix的实现机制不尽相同,造成不同 Unix环境下守护进程的编程规则并不一致。需要注意,照搬某些书上的规则(特别是BSD4.3和低版本的System V)到Linux会出现错误的。下面结合一些前辈的文档和自己的例子说说守护进程的编程。.基本概念
.进程.每个进程都有一个父进程.当子进程终止时,父进程会得到通知并能取得子进程的退出状态。
.进程组.每个进程也属于一个进程组.每个进程主都有一个进程组号,该号等于该进程组组长的PID号.一个进程只能为它自己或子进程设置进程组ID号
.会话期.对话期(session)是一个或多个进程组的集合。.setsid()函数可以建立一个对话期:如果,调用setsid的进程不是一个进程组的组长,此函数创建一个新的会话期。(1)此进程变成该对话期的首进程(2)此进程变成一个新进程组的组长进程。(3)此进程没有控制终端,如果在调用setsid前,该进程有控制终端,那么与该终端的联系被解除。如果该进程是一个进程组的组长,此函数返回错误。(4)为了保证这一点,我们先调用fork()然后exit(),此时只有子进程在运行,子进程继承了父进程的进程组ID,但是进程PID却是新分配的,所以不可能是新会话的进程组的PID。从而保证了这一点。if((pid=fork())>0)  //parentexit(0);else if(pid==0){        //th1 childsetsid();           //th1是成为会话期组长if(fork() ==0){     //th2不会是会话期组长(变成孤儿进程组)...}}一. 守护进程及其特性(1)守护进程最重要的特性是后台运行。在这一点上DOS下的常驻内存程序TSR与之相似。(2)其次,守护进程必须与其运行前的环境隔离开来。这些环境包括未关闭的文件描述符,控制终端,会话和进程组,工作目录以及文件创建掩模等。这些环境通常是守护进程从执行它的父进程(特别是shell)中继承下来的。(3)最后,守护进程的启动方式有其特殊之处。它可以在Linux系统启动时从启动脚本/etc/rc.d中启动,可以由作业规划进程crond启动,还可以由用户终端(通常是 shell)执行。总之,除开这些特殊性以外,守护进程与普通进程基本上没有什么区别。因此,编写守护进程实际上是把一个普通进程按照上述的守护进程的特性改造成为守护进程。二. 守护进程的编程要点  (来自UEAP)前面讲过,不同Unix环境下守护进程的编程规则并不一致。所幸的是守护进程的编程原则其实都一样,区别在于具体的实现细节不同。这个原则就是要满足守护进程的特性。同时,Linux是基于Syetem V的SVR4并遵循Posix标准,实现起来与BSD4相比更方便。编程要点如下;1. 在后台运行。为避免挂起控制终端将Daemon放入后台执行。方法是在进程中调用fork使父进程终止,让Daemon在子进程中后台执行。if(pid=fork())exit(0); //是父进程,结束父进程,子进程继续2. 脱离控制终端,登录会话和进程组进程属于一个进程组,进程组号(GID)就是进程组长的进程号(PID)。登录会话可以包含多个进程组。这些进程组共享一个控制终端。这个控制终端通常是创建进程的登录终端。控制终端,登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们,使之不受它们的影响。方法是在第1点的基础上,调用setsid()使进程成为会话组长:setsid();说明:当进程是会话组长时setsid()调用失败。但第一点已经保证进程不是会话组长。setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。由于会话过程对控制终端的独占性,进程同时与控制终端脱离。3. 禁止进程重新打开控制终端现在,进程已经成为无终端的会话组长。但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端:if(pid=fork())exit(0); //结束第一子进程,第二子进程继续(第二子进程不再是会话组长)4. 关闭打开的文件描述符进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误。按如下方法关闭它们:for(i=0;i 关闭打开的文件描述符close(i);>5. 改变当前工作目录进程活动时,其工作目录所在的文件系统不能卸下。一般需要将工作目录改变到根目录。对于需要转储核心,写运行日志的进程将工作目录改变到特定目录如 /tmpchdir("/")6. 重设文件创建掩模进程从创建它的父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩模清除:umask(0);7. 处理SIGCHLD信号处理SIGCHLD信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在Linux下可以简单地将 SIGCHLD信号的操作设为SIG_IGN。signal(SIGCHLD,SIG_IGN);这样,内核在子进程结束时不会产生僵尸进程。这一点与BSD4不同,BSD4下必须显式等待子进程结束才能释放僵尸进程。三. 守护进程实例守护进程实例包括两部分:主程序test.c和初始化程序init.c。主程序每隔一分钟向/tmp目录中的日志test.log报告运行状态。初始化程序中的init_daemon函数负责生成守护进程。读者可以利用init_daemon函数生成自己的守护进程。
#include<unistd.h>
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/param.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<time.h>void init_daemon()
{
int pid;
int i;
pid=fork();
if(pid<0)    exit(1);  //创建错误,退出
else if(pid>0) //父进程退出exit(0);setsid(); //使子进程成为组长
pid=fork();
if(pid>0)exit(0); //再次退出,使进程不是组长,这样进程就不会打开控制终端
else if(pid<0)    exit(1);//关闭进程打开的文件句柄
for(i=0;i<NOFILE;i++)close(i);
chdir("/root/test");  //改变目录
umask(0);//重设文件创建的掩码
return;
}void main()
{FILE *fp;time_t t;init_daemon();while(1){sleep(60); //等待一分钟再写入fp=fopen("testfork2.log","a");if(fp>=0){time(&t);fprintf(fp,"current time is:%s\n",asctime(localtime(&t)));  //转换为本地时间输出fclose(fp);}}return;
}

运行下面的命令:

cc testfork2.c -o testfork2

./testfork2

ps -ef|grep testfork2 可以查找到对应的进程

kill -9 1231杀死进程

Linux 守护进程创建原理及简易方法相关推荐

  1. linux c 守护进程创建原理及简易方法

    1:什么是Linux下的守护进程 Linux daemon是运行于后台常驻内存的一种特殊进程,周期性的执行或者等待trigger执行某个任务,与用户交互断开,独立于控制终端.一个守护进程的父进程是in ...

  2. Linux 守护进程的原理与实现

    一.守护进程概述 在linux或者unix操作系统中在系统的引导的时候会开启很多服务,这些服务就叫做守护进程.为了增加灵活性,root可以选择系统开启的模式,这些模式叫做运行级别,每一种运行级别以一定 ...

  3. linux守护进程原理及创建详解

    在linux或者unix操作系统中在系统的引导的时候会开启很多服务,这些服务就叫做守护进程.为了增加灵活性,root可以选择系统开启的模式,这些模式叫做运行级别,每一种运行级别以一定的方式配置系统. ...

  4. linux+守护进程+php,【转载】Linux 守护进程的编程方法

    [转载]Linux 守护进程的编程方法 原文见: http://www.linuxdevelop.org/tingxx/show.php?table=c&id=3 Linux 守护进程的编程方 ...

  5. linux守护进程写法_搞懂进程组、会话、控制终端关系,才能明白守护进程如何创建...

    守护进程 概念: 守护进程,也就是通常所说的Daemon进程,是Linux中的后台服务进程.周期性的执行某种任务或等待处理某些发生的事件. Linux系统有很多守护进程,大多数服务都是用守护进程实现的 ...

  6. linux守护进程的创建

    下面的完成了这样一个功能,创建一个守护进程,每个一秒在/tmp目录下的文件peng.txt中记录当前系统时间. 一.守护进程 守护进程是linux中的后台服务进程,在系统启动时开始运行,在系统关闭时终 ...

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

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

  8. Linux守护进程的创建(结合nginx框架)

    Linux守护进程的创建(结合nginx框架) 先介绍几个相关函数: int dup2(arg1,arg2):参数一指向的内容赋给参数二,shi的参数二也能访问参数一所指向的内容,并返回新的描述符 i ...

  9. 【Linux】Linux 守护进程的启动方法

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

最新文章

  1. 校园网安全问题分析与对策
  2. 由“递归遍历二叉树”引发的思考
  3. 28 多进程之数据交换Pipe
  4. vue 输入框限制3位小数_vue+element 中 el-input框 限制 只能输入数字及几位小数(自定义)和输入框之键盘...
  5. 数据包过滤 系统 linux,Ubuntu16.04安装libpcap开发库对pcap文件中的数据包进行过滤...
  6. docker 查看虚拟网卡_最简单的免费虚拟化方案:Hyper-V Server + Windows Admin Center
  7. Requests库基本使用
  8. 按月拆分数据库表--oracle
  9. Linux关机运行的脚本,Linux 关机 脚本
  10. Javascript中的null、undefined、NaN
  11. Python中导包问题
  12. 3.Python data types
  13. (第24讲)java小程序——Applet
  14. groovy脚本一键360加固多渠道打包
  15. 年仅30岁!腾讯游戏程序员毛星云意外身故。。。
  16. 银行股的分红是不是比利率要高,投十万银行股一年分红有多少啊?
  17. matlab学习增强学习,使用 MATLAB 和 Simulink 进行强化学习
  18. html中 主标题与副标题,主标题和副标题的范例(论文正副标题格式样板)
  19. 基于JavaScript的幸运大转盘
  20. hdu 2189 悼念512汶川大地震遇难同胞——来生一起走

热门文章

  1. Nature子刊:中科院动物所合作揭示灵长类血管衰老的驱动力
  2. python匿名函数调用_python中引用局部变量的匿名函数
  3. python小游戏之三
  4. js实现文字从右滚动到左边代码循环滚动实例
  5. cad在布局怎么调比例_大神们都在用的9个CAD制图技巧,你会用几个?
  6. CANopen | 对象字典OD 02 - 修改CANopen节点的心跳报文发送间隔
  7. java dib文件 加位图文件头_位图(bmp)文件格式分析
  8. python3比较文本差异_脑科学方向 | Python3的安装与环境搭建
  9. origin tangent插件_如何用origin绘制切线
  10. Arduino笔记-人体感应灯项目