Dameon进程又被称做守护进程,一般来说他有以下2个特点:
1.生命周期非常长,一旦启动,一般不会终止,直到系统推出,不过dameon进程可以通过stop或者发送信号将其杀死
2.在后台执行,不跟任何控制终端关联,终端信号比如:SIGINT,SIGQUIT,SIGTSTP,以及关闭终端都不会影响deamon

如何编写Daemon进程,需要遵循以下规则:
(1)执行fork()函数,父进程退出,子进程继续
执行这一步,原因有二:
·父进程有可能是进程组的组长(在命令行启动的情况下),从而不能够执行后面要执行的setsid函数,子进程继承了父进程的进程组ID,并且拥有自己的进程ID,一定不会是进程组的组长,所以子进程一定可以执行后面要执行的setsid函数。
·如果daemon是从终端命令行启动的,那么父进程退出会被shell检测到,shell会显示shell提示符,让子进程在后台执行。
(2)子进程执行如下三个步骤,以摆脱与环境的关系
1)修改进程的当前目录为根目录(/)。
这样做是有原因的,因为daemon一直在运行,如果当前工作路径上包含有根文件系统以外的其他文件系统,那么这些文件系统将无法卸载。因此,常规是将当前工作目录切换成根目录,当然也可以是其他目录,只要确保该目录所在的文件系统不会被卸载即可。
chdir("/") 
2)调用setsid函数。这个函数的目的是切断与控制终端的所有关系,并且创建一个新的会话。
这一步比较关键,因为这一步确保了子进程不再归属于控制终端所关联的会话。因此无论终端是否发送SIGINT、SIGQUIT或SIGTSTP信号,也无论终端是否断开,都与要创建的daemon进程无关,不会影响到daemon进程的继续执行。
3)设置文件模式创建掩码为0。
  1. umask(0)
这是为了让daemon进程创建的文件权限属性跟shell脱离关系,因为默认情况下,进程的umask来源于父进程shell的umask.如果不执行umask(0),那么父进程shell的umask就会影响daemon进程的umask.如果用户改变了shell的umask,那么也就改变了dameon的umask,就会使得daemon进程每次执行的umask信息可能不一致
(3)再次执行fork,父进程退出,子进程继续
执行完前面两步之后,可以说已经比较圆满了:新建会话,进程是会话的首进程,也是进程组的首进程。进程ID、进程组ID和会话ID,三者的值相同,进程和终端无关联。那么这里为何还要再执行一次fork函数呢?
原因是,daemon进程有可能会打开一个终端设备,即daemon进程可能会根据需要,执行类似如下的代码:
  1. int fd = open("/dev/console", O_RDWR);
这个打开的终端设备是否会成为daemon进程的控制终端,取决于两点:
·daemon进程是不是会话的首进程。
·系统实现。(BSD风格的实现不会成为daemon进程的控制终端,但是POSIX标准说这由具体实现来决定)。
既然如此,为了确保万无一失,只有确保daemon进程不是会话的首进程,才能保证打开的终端设备不会自动成为控制终端。因此,不得不执行第二次fork,fork之后,父进程退出,子进程继续。这时,子进程不再是会话的首进程,也不是进程组的首进程了。
(4)关闭标准输入(stdin)、标准输出(stdout)和标准错误(stderr)
因为文件描述符0、1和2指向的就是控制终端。daemon进程已经不再与任意控制终端相关联,因此这三者都没有意义。一般来讲,关闭了之后,会打开/dev/null,并执行dup2函数,将0、1和2重定向到/dev/null。这个重定向是有意义的,防止了后面的程序在文件描述符0、1和2上执行I/O库函数而导致报错。
至此,即完成了daemon进程的创建,进程可以开始自己真正的工作了。
上述步骤比较繁琐,对于C语言而言,glibc提供了daemon函数,从而帮我们将程序转化成daemon进程。
  1. #include <unistd.h>
  2. int daemon(int nochdir, int noclose);
该函数有两个入参,分别控制一种行为,具体如下。
其中的nochdir,用来控制是否将当前工作目录切换到根目录。
·0:将当前工作目录切换到/。
·1:保持当前工作目录不变。
noclose,用来控制是否将标准输入、标准输出和标准错误重定向到/dev/null。
·0:将标准输入、标准输出和标准错误重定向到/dev/null。
·1:保持标准输入、标准输出和标准错误不变。
一般情况下,这两个入参都要为0。
  1. ret = daemon(0,0)
成功时,daemon函数返回0;失败时,返回-1,并置errno。因为daemon函数内部会调用fork函数和setsid函数,所以出错时errno可以查看fork函数和setsid函数的出错情形。
glibc的daemon函数做的事情,和前面讨论的大体一致,但是做得并不彻底,没有执行第二次的fork。


进程的终止
在不考虑线程的情况下,进程的退出有以下5种方式。
正常退出有3种:
·从main函数return返回
·调用exit
·调用_exit
异常退出有两种:
·调用abort
·接收到信号,由信号终止


_exit函数的接口定义如下:
  1. #include <unistd.h>
  2. void _exit(int status);
用户调用_exit函数,本质上是调用exit_group系统调用。这点在前面已经详细介绍过,在此就不再赘述了。
exit函数
exit函数更常见一些,其接口定义如下:
  1. #include <stdlib.h>
  2. void exit(int status);
exit()函数的最后也会调用_exit()函数,但是exit在调用_exit之前,还做了其他工作:
1)执行用户通过调用atexit函数或on_exit定义的清理函数。
2)关闭所有打开的流(stream),所有缓冲的数据均被写入(flush),通过tmpfile创建的临时文件都会被删除。
3)调用_exit。
图4-11给出了exit函数和_exit函数的差异。
 

下面介绍exit函数和_exit函数的不同之处。
首先是exit函数会执行用户注册的清理函数。用户可以通过调用atexit()函数或on_exit()函数来定义清理函数。这些清理函数在调用return或调用exit时会被执行。执行顺序与函数注册的顺序相反。当进程收到致命信号而退出时,注册的清理函数不会被执行;当进程调用_exit退出时,注册的清理函数不会被执行;当执行到某个清理函数时,若收到致命信号或清理函数调用了_exit()函数,那么该清理函数不会返回,从而导致排在后面的需要执行的清理函数都会被丢弃。
其次是exit函数会冲刷(flush)标准I/O库的缓冲并关闭流。glibc提供的很多与I/O相关的函数都提供了缓冲区,用于缓存大块数据。
缓冲有三种方式:无缓冲(_IONBF)、行缓冲(_IOLBF)和全缓冲(_IOFBF)。
·无缓冲:就是没有缓冲区,每次调用stdio库函数都会立刻调用read/write系统调用。
·行缓冲:对于输出流,收到换行符之前,一律缓冲数据,除非缓冲区满了。对于输入流,每次读取一行数据。
·全缓冲:就是缓冲区满之前,不会调用read/write系统调用来进行读写操作。
对于后两种缓冲,可能会出现这种情况:进程退出时,缓冲区里面可能还有未冲刷的数据。如果不冲刷缓冲区,缓冲区的数据就会丢失。比如行缓冲迟迟没有等到换行符,又或者全缓冲没有等到缓冲区满。尤其是后者,很容易出现,因为glibc的缓冲区默认是8192字节。exit函数在关闭流之前,会冲刷缓冲区的数据,确保缓冲区里的数据不会丢失。
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. void foo()
  5. {
  6. fprintf(stderr,"foo says bye.\n");
  7. }
  8. void bar()
  9. {
  10. fprintf(stderr,"bar says bye.\n");
  11. }
  12. int main(int argc, char **argv)
  13. {
  14. atexit(foo);
  15. atexit(bar);
  16. fprintf(stdout,"Oops ... forgot a newline!");
  17. sleep(2);
  18. if (argc > 1 && strcmp(argv[1],"exit") == 0)
  19. exit(0);
  20. if (argc > 1 && strcmp(argv[1],"_exit") == 0)
  21. _exit(0);

  22.     return 0;
  23. }
注意上面的示例代码,fprintf打印的字符串是没有换行符的,对于标准输出流stdout,采用的是行缓冲,收到换行符之前是不会有输出的。输出情况如下:
  1. manu@manu-hacks:exit$ ./test exit //调用exit结束,输出了缓冲区的字符
  2. bar says bye.
  3. foo says bye.
  4. Oops ... forgot a newline!manu@manu-hacks:exit$ //调用return 输出了缓冲区字符
  5. manu@manu-hacks:exit$
  6. manu@manu-hacks:exit$ ./test
  7. bar says bye.
  8. foo says bye.
  9. Oops ... forgot a newline!manu@manu-hacks:exit$ //直接调用_exit没有输出缓冲区的字符
  10. manu@manu-hacks:exit$
  11. manu@manu-hacks:exit$ ./test _exit
  12. manu@manu-hacks:~/code/self/c/exit$
尽管缓冲区里的数据没有等到换行符,但是无论是调用return返回还是调用exit返回,缓冲区里的数据都会被冲刷,“Oops...forgot a newline!”都会被输出。因为exit()函数会负责此事。从测试代码的输出也可以看出,exit()函数首先执行的是用户注册的清理函数,然后才执行了缓冲区的冲刷。
第三,存在临时文件,exit函数会负责将临时文件删除.
exit函数的最后调用了_exit()函数,最终殊途同归,走向内核清理。

return退出
return是一种更常见的终止进程的方法。执行return(n)等同于执行exit(n),因为调用main()的运行时函数会将main的返回值当作exit的参数。




 



linux之Deamon进程创建及其进程exit,_exit,return之间的区别的更多相关文章

  1. ASP.NET Core Linux下为 dotnet 创建守护进程(必备知识)

    前言 在上篇文章中介绍了如何在 Docker 容器中部署我们的 asp.net core 应用程序,本篇主要是怎么样为我们在 Linux 或者 macOs 中部署的 dotnet 程序创建一个守护进程 ...

  2. ASP.ENT Core Linux 下 为 donet创建守护进程(转载)

    原文地址:http://www.cnblogs.com/savorboard/p/dotnetcore-supervisor.html 前言 在上篇文章中介绍了如何在 Docker 容器中部署我们的 ...

  3. 进程基本-进程创建,僵尸进程,exec系列函数

    Linux系统中,进程的执行模式划分为用户模式和内核模式,当进程运行于用户空间时属于用户模式,如果在用户程序运行过程中出现系统调用或者发生中断事件,就要运行操作系统(即核心)程序,进程的运行模式就变为 ...

  4. Win32进程创建、进程快照、进程终止用例

    进程创建: 1 #include <windows.h> #include <stdio.h> int main() { // 创建打开系统自带记事本进程 STARTUPINF ...

  5. linux fork函数与vfork函数,exit,_exit区别

    man vfork: NAME vfork - create a child process and block parent SYNOPSIS #include <sys/types.h> ...

  6. abort exit _exit return的区别

    exit()函数导致子进程的正常退出,并且参数status&这个值将被返回给父进程.exit()应该是库函数.exit()函数其实是对_exit()函数的一种封装(库函数就是对系统调用的一种封 ...

  7. Linux编程中的坑——C++中exit和return的区别

    今天遇到一个坑,折腾了一天才把这个坑填上,情况是这样的: 写了段代码,在main()函数中创建一个分离线程,结果这个线程什么都没干就直接挂掉了,代码长这样: int main() { 创建一个分离线程 ...

  8. exit()和return语句的区别

    (1)exit用于结束正在运行的程序,exit函数将参数是返回给OS.而return是返回函数值并退出函数. (2)return是语言级别的,它表示了调用堆栈的返回:而exit是系统调用级别的,它表示 ...

  9. Linux软件安装包中devel与非devel包之间的区别

    带devel(develop)的包,俗称开发包.功能上与普通包相同,但体积更大使用rpm -qi看看这两类包的区别: # rpm -qi glibc-devel-2.12-1.149.el6.x86_ ...

随机推荐

  1. 20180901 JavaScript闭包和匿名函数自动调用

    引用: 1. JavaScript闭包_by runoob 2. JS中(function(){xxx})这么写是什么意思? (一)闭包是可以访问上一层函数作用域里变量的函数,即便上一层函数已经关闭. ...

  2. ubuntu 16.04 + 中文输入法

    在桌面右上角设置图标中找到"System Setting",双击打开. 在打开的窗口里找到"Language Support",双击打开. 可能打开会说没有安装 ...

  3. NOIP模拟赛 虫洞

    [题目描述] John在他的农场中闲逛时发现了许多虫洞.虫洞可以看作一条十分奇特的有向边,并可以使你返回到过去的一个时刻(相对你进入虫洞之前).John的每个农场有M条小路(无向边)连接着N (从1. ...

  4. 【差分约束】poj1275Cashier Employment

    比较经典的差分约束 Description A supermarket in Tehran is open 24 hours a day every day and needs a number of ...

  5. 【贪心 哈夫曼树】bzoj2923: [Poi1998]The lightest language

    失去了以前用STL乱搞的能力…… 题目描述 语言也是数学上经常研究的一种数据. 给出数学上关于语言的如下定义: 字母表:大小为 K 的字母表是一个由 K 不同的字符组成的集合. 单词:长度为 m 的单 ...

  6. salt常用模块

    salt 常用命令整理     转载:https://www.cnblogs.com/aslongas/p/6964890.html salt 常用命令整理 ***********模块******** ...

  7. 解决iPhone滑动不流畅问题

    前段时间在做一个手机端的页面时遇到了iOS上滑动不流畅的问题,后来才发现安卓上没有问题,才意识到这是兼容性问题引起的,所以遇到问题后快速定位到问题根源非常重要.在网上一搜就找到了解决方案.以后遇到类似 ...

  8. vue_music:封装scroll.vue组件

    在项目中经常用到滚动,结合Better-scroll封装了sroll.vue组件参考链接:https://ustbhuangyi.github.io...http://www.imooc.com/ar ...

  9. urlopen SSL证书验证

    错误描述: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:777) 解决方法 ...

  10. WPF使用异步+绑定的方式处理大数据量

    WPF的优势在于界面处理,即使是这样,在面对大数据量的时候也免不了界面假死,同一个线程里处理界面跟大数据量,这是不可避免的.解决办法还是有的,可以使用分页加载,虚拟加载,动态加载,增加条件限制... ...