守护进程的三种实现方式

Posted Shemesz

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了守护进程的三种实现方式相关的知识,希望对你有一定的参考价值。

实现守护进程


   Linux Daemon(守护进程)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。它不需要用户输入就能运行而且提供某种服务,不是对整个系统就是对某个用户程序提供服务。Linux系统的大多数服务器就是通过守护进程实现的。常见的守护进程包括系统日志进程syslogd、 web服务器httpd、邮件服务器sendmail和数据库服务器mysqld等。

  守护进程一般在系统启动时开始运行,除非强行终止,否则直到系统关机都保持运行。守护进程经常以超级用户(root)权限运行,因为它们要使用特殊的端口(1-1024)或访问某些特殊的资源。

  一个守护进程的父进程是init进程,因为它真正的父进程在fork出子进程后就先于子进程exit退出了,所以它是一个由init继承的孤儿进程。守护进程是非交互式程序,没有控制终端,所以任何输出,无论是向标准输出设备stdout还是标准出错设备stderr的输出都需要特殊处理。

守护进程的名称通常以d结尾,比如sshd、xinetd、crond等,下面我们就来介绍三种守护进程的创建方式。

(1)nohup

  • nohup ——英文全称 no hang up(不挂起),用于在系统后台不挂断地运行命令,退出终端不会影响程序的运行。
  • nohup 命令,在默认情况下(非重定向时),会输出一个名叫 nohup.out 的文件到当前目录下,如果当前目录的 nohup.out 文件不可写,输出重定向到 $HOME/nohup.out 文件中。

使用权限

  • 所有使用者

语法格式

 nohup Command [ Arg … ] [ & ]

参数说明:

  • Command:要执行的命令。
  • Arg:一些参数,可以指定输出文件。
  • &:让命令在后台执行,终端退出后命令仍旧执行。

实例
以下命令在后台执行 root 目录下的 runoob.sh 脚本:

nohup /root/runoob.sh &

在终端如果看到以下输出说明运行成功:

appending output to nohup.out

这时我们打开 root 目录 可以看到生成了 nohup.out 文件。

如果要停止运行,你需要使用以下命令查找到 nohup 运行脚本到 PID,然后使用 kill 命令来删除:

ps -aux | grep "runoob.sh" 

参数说明

  • a : 显示所有程序
  • u : 以用户为主的格式来显示
  • x : 显示所有程序,不区分终端机

找到 PID 后,就可以使用 kill PID 来删除。

kill -9  进程号PID

以下命令在后台执行 root 目录下的 runoob.sh 脚本,并重定向输入到 runoob.log 文件:

nohup /root/runoob.sh > runoob.log 2>&1 &

2>&1 解释:

将标准错误 2 重定向到标准输出 &1 ,标准输出 &1 再被重定向输入到 runoob.log 文件中。

  • 0 – stdin (standard input,标准输入)
  • 1 – stdout (standard output,标准输出)
  • 2 – stderr (standard error,标准错误输出)

(2)fork()按步骤创建

步骤如下:

  1. 将程序进入后台执行。由于守护进程最终脱离控制终端,到后台去运行。方法是在进程中调用 fork 使父进程终止,让 Daemon 在子进程中后台执行。这就是常说的“脱壳”。子进程继续函数 fork()的定义如下:pid_t fork(void);
  2. 脱离控制终端、登录会话和进程组。开发人员如果要摆脱它们,不受它们的影响,一般使用 setsid() 设置新会话的领头进程,并与原来的登录会话和进程组脱离。
    有必要先介绍一下Linux中的进程与控制终端,登录会话和进程组之间的关系:进程属于一个进程组,进程组号(GID)就是进程组长的进程号(PID)。登录会话可以包含多个进程组。这些进程组共享一个控制终端。这个控制终端通常是创建进程的登录终端。
    控制终端,登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们,使之不受它们的影响。方法是在第1点的基础上,调用setsid()使进程成为会话组长:
    setsid();
    说明:当进程是会话组长时setsid()调用失败。但第一点已经保证进程不是会话组长。setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。由于会话过程对控制终端的独占性,进程同时与控制终端脱离。
  3. 禁止进程重新打开控制终端。现在,进程已经成为无终端的会话组长。但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端。
    if(pid=fork())
    exit(0);//结束第一子进程,第二子进程继续(第二子进程不再是会话组长)
  4. 重设文件权限掩码
  5. 关闭打开的文件描述符,并重定向标准输入、标准输出和标准错误输出的文件描述符。进程从创建它的父进程那里继承了打开的文件描述符。如果不关闭,将会浪费系统资源,引起无法预料的错误。
  6. 改变工作目录到根目录或特定目录进程活动时,其工作目录所在的文件系统不能卸下守护进程实例包括两部分:主程序 test.c 和初始化程序 init.c。主程序每隔一分钟向/tmp 目录中的日志test.log 报告运行状态。初始化程序中的 init_daemon 函数负责生成守护进程。读者可以利用 init_daemon函数生成自己的守护进程。

首先我们看一下要用到的函数

1setsid();
  • 说明:当进程是会话的领头进程时setsid()调用失败并返回(-1)。setsid()调用成功后,返回新的会话的ID,调用setsid函数的进程成为新的会话的领头进程,并与其父进程的会话组和 进程组脱离。由于会话对控制终端的独占性,进程同时与控制终端脱离。
  • 之前parent和child运行在同一个session里,parent是会话(session)的领头进程,
    parent进程作为会话的领头进程,如果exit结束执行的话,那么子进程会成为孤儿进程,并被init收养
  • 执行setsid()之后,child将重新获得一个新的会话(session)id。
  • 这时parent退出之后,将不会影响到child了。
2)  umask
  • 每个文件都有权限,每新创建一个文件就要指定文件权限(mode &umask)
  • 新创建的文件pathname 的 文件权限 = (umask & mode)
    例:
    #include <fcntl.h>
    int main()

    int fd;
    umask(0026);
    fd = open(“test.txt”,O_RDWR | O_CREAT,0666);
    if(fd < 0)
    perror(“open”);
    return 0;

    //文件权限结果为 666 - 26 = 640 = rw-r-----
3getdtablesize ()
  • getdtablesize ()是返回所在进程的文件描述附表的项数,即该进程打开的文件数目

代码如下:

#include < unistd.h >
#include < sys/types.h >
#include < sys/stat.h >
#include <stdlib.h>
void init_daemon(void)

	int pid;
	int i;
	if (pid = fork()) 
	
	 	exit(0); 
	 //是父进程,结束父进程
	else if (pid < 0) 
	
	 	exit( -1 ); 
	 //fork 失败,退出
	setsid(); //第一子进程成为新的会话组长和进程组长
	//并与控制终端分离
	if (pid = fork()) 
	
		 exit(0); 
	 //是第一子进程,结束第一子进程
	else if (pid < 0) 
	 
	exit(1); 
	 //fork 失败,退出
	//第二子进程不再是会话组长
	for(i=0; i< getdtablesize(); ++i) //关闭打开的文件描述符
	close(i);
	
	chdir("/tmp"); //改变工作目录到 /tmp
	umask(0); //重设文件创建掩模
	return;


(3)deamon函数创建

daemon进程
  Unix/Linux中的守护进程(Daemon)类似于Windows中的后台服务进程,一直在后台长时间运行的进程。它通常在系统启动后就运行,没有控制终端,也无法和前台的用户交互,在系统关闭时才结束。Daemon程序一般都作为服务程序使用,等待客户端程序与它通信。我们也把运行的Daemon程序称作守护进程。守护进程不占用终端,在后台运行。UNIX的守护进程一般都命名为 *d 的形式,如httpd,vsftpd,sshd等。守护进程一旦脱离了终端,退出就成了问题,这时需要使用 ps 命令查出进程ID然后再使用kill命令停止。

  daemon进程和普通进程不一样吗?为什么要单独提出如何编写daemon进程呢?有时在Linux上面打开一个命令行终端,输入编译命令进行编译,编译的时间可能比较长,这时候你不小心关闭了这个terminal,编译就中断了。因为编译脚本是作为当前
终端的一个子进程来执行的,当终端退出后子进程也就退出了。在学完Linux基本命令使用之后我们知道,在命令后面加上 后台运行符(&) 可以让该命令在后台运行。如:

make &

  该命令会让编译命令make到后台执行,这样只是造成了make在后台一直运行的假象,它依然没有脱离和terminal之间的父子关系。当terminal退出后,make依然会退出。而作为daemon进程,我们希望一旦启动就能在后台一直运行,不会随着terminal的退出而结束,所以针对daemon进程就要用特殊的编程来处理。

Linux系统专门提供了一个用来创建daemon进程的库函数,该函数的原型是

#include <unistd.h>
int daemon(int nochdir, int noclose);
  • nochdir 指定是否要切换当前工作路径到"/"根目录
  • noclose 指定是否要关闭标准输入、标准输出和标准出错(即重定向到/dev/null)
  • 在创建守护进程的时候,往往需要将进程的工作目录修改为"/"根目录,并将标准输入、标准输出和标
    准出错关闭。所以这两个参数我们一般都是传0。

以上是关于守护进程的三种实现方式的主要内容,如果未能解决你的问题,请参考以下文章

JAVA笔记(19)--- 线程概述;如何实现多线程并发;线程生命周期;Thread常用方法;终止线程的三种方式;线程安全问题;synchronized 实现同步线程模型;

进程间实现数据共享的三种方式

守护进程

守护进程

27多线程(多线程的三种实现方式Thread线程类的常见方法线程安全问题)

27多线程(多线程的三种实现方式Thread线程类的常见方法线程安全问题)