今天继续学习系统编程,学习的主题还是进程,今天主要讨论的是守护进程相关的概念,开始进入正题:
什么是守护进程:
![](https://i-blog.csdnimg.cn/blog_migrate/a988ac15a6c80a366df97d72b25cc90f.png)
![](https://i-blog.csdnimg.cn/blog_migrate/3dd650875390fbc4a9ae050bf787481e.png)
守护进程的创建步骤:
在描述它之前,首先得先了解两个概念:进程组、会话期:
![](https://i-blog.csdnimg.cn/blog_migrate/4a6e2c9189d1577d1fef2cdfe90c1cfa.png)
而它里面有bash shell进程组,里面只有bash进程:
![](https://i-blog.csdnimg.cn/blog_migrate/cc739ca617d451d38bf82cb06b97f8eb.png)
这时,当我们在shell命令行中敲入如下命令:
![](https://i-blog.csdnimg.cn/blog_migrate/075982994441c36b59afa34950694460.png)
这时,会话期中又会多出一个进程组,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/d34be1f7ae43815abb5c3dbd1af8a824.png)
而一个会话期,实际上就对应一个终端,当我们打开多个虚拟终端时,可以用tty来查看终端数:
![](https://i-blog.csdnimg.cn/blog_migrate/6dbdf82421d0121f4ac645d7b82e8597.png)
而
守护进程是跟控制终端无关的,并且是在后台执行的,如果想让我们在shell中启动的进程变成守护进程,则应该将它放到会话期当中:
![](https://i-blog.csdnimg.cn/blog_migrate/74860c6c8a0e74adcf24c4bcbe56e4cd.png)
那这时,我们需要一个创建新的会话期的函数,实际上是系统函数,它为setsid(),通过man来查看一下它的说明:
![](https://i-blog.csdnimg.cn/blog_migrate/6b7da3bbf906caa7b41ba11cfa6cc1f2.png)
这就意味着,我们在
创建一个新的会话期之前,需要准备一个进程,保证该进程不是一个进程组组长,那如何保证呢?由于我们运行的shell命令的父进程可能是进程组组长,所以需要让父进程退出,这样就可以保证fork出来的子进程不是进程组组长,从而可以创建一个新的会话期了,总结一下上面说的流程:
![](https://i-blog.csdnimg.cn/blog_migrate/47fd645765e4431f665a068b83ea8a5b.png)
按照上面的步骤下面以具体代码来实现一个守护进程:
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int setup_daemon(void);
int main(int argc, char *argv[])
{
return 0;
}
int setup_daemon(void)
{
pid_t pid;
pid = fork();
if (pid == -1)
ERR_EXIT("fork error");
if (pid > 0)//将父进程退出,保证子进程不是进程组组长
exit(EXIT_SUCCESS);
setsid();//如果走到这,代表是子进程,由于它不是一个进程组组长,所以可以创建一个新的会话期
return 0;
}
当我们用setsid()创建一个新的会话期之后,会有一个什么样的影响呢,还是接着看它的说明介绍:
![](https://i-blog.csdnimg.cn/blog_migrate/056ac8b95739a6a8517024f648c9afe3.png)
![](https://i-blog.csdnimg.cn/blog_migrate/57503908c01078ca8bc640ea54fc10a9.png)
也就是如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/66939126668784b22cbeb97b5831da8b.png)
![](https://i-blog.csdnimg.cn/blog_migrate/63acea9af4d9059b1bcd1c22ed6c582a.png)
其实上面的程序就已经实现了一个守护进程,我们调用一下运行看下:
![](https://i-blog.csdnimg.cn/blog_migrate/d8d53aa19c33799c1fe70dfae0a56d8f.png)
编译运行:
![](https://i-blog.csdnimg.cn/blog_migrate/f1cd48471ce80451874dda33980b96dc.png)
我们来查看下进程:
![](https://i-blog.csdnimg.cn/blog_migrate/43101096bf31a638e2b3864e35ee9861.png)
![](https://i-blog.csdnimg.cn/blog_migrate/b7660b0fae09e5bd66863079d1f79682.png)
![](https://i-blog.csdnimg.cn/blog_migrate/31a858cbbe9441bda40629695cf71486.png)
守护进程通常是在系统运行而运行的,通常将当前目录改为根目录,因为有可能守护进程是在某个shell提示符下运行的, 那么当前目录就是shell提示符所在的目的, 就拿我们创建的这个守护进程而言,它的当前目录为:
![](https://i-blog.csdnimg.cn/blog_migrate/70b0c0d12225c7d3b07b6cfe77ffa206.png)
这样,系统管理员就无法umount这个目录,因为守护进程是学期在后期运行的,这个目录不应该作为它的环境,所以这就产生了创建守护进程的第四个步骤:
![](https://i-blog.csdnimg.cn/blog_migrate/3a5fb295babed898d480a6dc594e5197.png)
修改代码:
![](https://i-blog.csdnimg.cn/blog_migrate/c4ddca436b2c3cda81a9f2640cd496f7.png)
最后还有一个步骤:
![](https://i-blog.csdnimg.cn/blog_migrate/c54445fb4d9786e32b66b094db4f4260.png)
【说明:/dev/null表示空设备,这里就是把日志记录到空设备里,就是不记录日志。】
怎么做呢?先看代码:
![](https://i-blog.csdnimg.cn/blog_migrate/3795704ed1a5596e71569c9f077852c4.png)
这时再运行,如果我们往屏幕输出内容,这时是看不到内容的,因为已经将标准输出重定向了空设备:
![](https://i-blog.csdnimg.cn/blog_migrate/8b113c5ff573e26ecdb6f43af9c8b4bd.png)
【说明:关于dup的知识,可参考博文:http://www.cnblogs.com/webor2006/p/3498443.html】
daemon:
实际上linux上已经有现成的方法可以创建一个守护进程了,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/8d7ba940e85e14681eec1e5178de6021.png)
我们使用一下它:
![](https://i-blog.csdnimg.cn/blog_migrate/5f331ce448e0ce72b12d108bb4612de4.png)
在运行它之前,我们来看下现在应该有几个守护进程了:
![](https://i-blog.csdnimg.cn/blog_migrate/ccee65db27bd241a2f74ad932f2a1557.png)
![](https://i-blog.csdnimg.cn/blog_migrate/31a26f35fc69d6766aeb58231a5333a6.png)
先将其都杀掉,以便来观察调用系统的创建守护进程是否成功:
![](https://i-blog.csdnimg.cn/blog_migrate/edde20f99c3db967a0f4ef955a6c1f88.png)
这时,再运行:
![](https://i-blog.csdnimg.cn/blog_migrate/5addd7af14a2516b46ef88b4bee9904f.png)
对于系统的这个函数,都是传递的0,如果传递1会怎样呢?
![](https://i-blog.csdnimg.cn/blog_migrate/a92f02c3c675f131b284edb1fc8391f4.png)
编译运行:
![](https://i-blog.csdnimg.cn/blog_migrate/8dc3a6b775c7a7f504c473d6543bffd9.png)
实际上,对于我们写的守护进程,也可以模拟成跟系统调用方式一样,修改程序如下:
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int setup_daemon(int nochdir, int noclose);//模拟系统创建守护进程的函数声明
int main(int argc, char *argv[])
{
setup_daemon(1, 1);//这时改用跟调用系统创建守护进程的自己实现的函数
printf("test ...\n");
for (;;) ;
return 0;
}
int setup_daemon(int nochdir, int noclose)
{
pid_t pid;
pid = fork();
if (pid == -1)
ERR_EXIT("fork error");
if (pid > 0)
exit(EXIT_SUCCESS);
setsid();
if (nochdir == 0)//实现很简单,做下参数判断既可
chdir("/");
if (noclose == 0)
{
int i;
for (i=0; i<3; ++i)
close(i);
open("/dev/null", O_RDWR);
dup(0);
dup(0);
}
return 0;
}
编译运行:
![](https://i-blog.csdnimg.cn/blog_migrate/cd3328da48005719c36b4a58e0df6ebc.png)
【提示:在创建守护进程时,不重定向至空设备其实对于开发期间便于调试,如果等程序发布了之后,就得重定向了!】
好了,进程相关的东西就告一段落了,下节会继续探寻系统编程的其它东东,下节见!