Linux进程守护

守护进程概述
Linux Daemon(守护进程)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。它不需要用户输入就能运行而且提供某种服务,不是对整个系统就是对某个用户程序提供服务。Linux系统的大多数服务器就是通过 守护进程实现的。常见的守护进程包括系统日志进程sysslogd,web服务器httpd,邮件服务器sendmail和数据库服务器mysqld等。
守护进程一般在系统启动时开始运行,除非强行终止,否则直到系统关机都保持运行。守护进程经常以超级用户(root)权限运行,因为它们要使用特殊的端口(1-1024)或访问某些特殊的资源。
一个守护进程的父进程的init进程,因为它真正的父进程在fork出子进程后就先子进程exit退出了,所以它是一个由init继承的孤儿进程。守护进程是非交互式程序,没有控制终端,所以任何输出,无论是向标准输出设备stdout还是标准出错设备stderr的输出都需要经过特殊处理。
守护进程的名称通常以d结尾,比如,sshd,xinetd,crond等

守护进程的特点

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

2.生命周期:守护进程会长时间运行,常常在系统启动时就开始运行,直到系统关闭时才终止。

3.守护进程不依赖于终端:显而易见,从终端开始运行的进程都会依附于这个终端,这个终端称为这些进程的控制终端。当控制终端被关闭时,相应的进程都会被自动关闭。咱们平常写进程时,一个死循环程序关闭终端的同时也关闭了我们的程序,但是对于守护进程来说其生命周期守护需要突破这种限制,它从开始运行,直到整个系统关闭才会退出,所以守护进程不能依赖与终端。

如何识别一个守护进程

1.一般所有的守护进程都是以超级用户启动的.(UID为0);

2.没有控制终端(TTY为?);

3.终端进程组ID为-1(TPGID表示终端进程组ID,该值表示与控制终端相关的前台进程组,如果未和任何终端相关,其值为-1);

4.所有的守护进程的父进程都为init进程(PID为1的进程)

在这里插入图片描述
进程,进程组,会话,控制终端之间的关

因为守护进程的创建需要改变这些环境参数,所以了解他们之间的关系很重要。
在这里插入图片描述

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

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

控制终端:一个会话一般会拥有一个控制终端用于执行io操作。用户登录的终端就成为该会话的控制终端。与控制终端建立连接的会话领头进程也被称为控制进程。一个会话只能有一个控制终端。

前台进程组:该进程组中的进程能够向终端设备进行读,写操作的进程组。

后台进程组:该进程组中的进程只能够向终端设备写。

每个会话有且只有一个前台进程组,但会有0个或多个后台进程组。

终端进程组ID:每个进程还有一个属性,终端进程组ID(TPGID),用来标识一个进程是否处于一个和终端相关的进程组中。前台进程组中的进程的TPGID=PGID,后台进程组的PGID!=TPGID。若该进程和任何终端无关,其值为-1,。通过比较他们来判断一个进程是属于前台进程组,还是后台进程组。
在这里插入图片描述

Linux守护进程编写步骤

Step1:创建子进程,父进程退出

实质是 让init进程成为新产生进程的父进程。调用fork函数创建子进程后,使父进程立即退出。从而使产生的子进程将变成孤儿进程,并被init进程接管,同时,所产生的新进程将变为在后台运行;利用前面介绍的父进程先于子进程退出后内核会自动托付子进程给init的原理。

pid = fork();

if (pid > 0) /父进程退出/
{
exit(0);

}

Step2:脱离控制终端,创建新的会话

这个步骤是创建守护进程最重要的一步,虽然实现非常简单,但意义却非常重大。

#include <unistd.h>

pid_t setsid(void);

返回值:若成功,返回新的会话期ID;若出错,返回-1。

setsid函数作用

首先内核会创建一个新的会话,并让该进程成为该会话的leader进程,
同时伴随该session的建立,一个新的进程组也会被创建,同时该进程成为该进程组的组长。
该进程此时还没有和任何控制终端关联。如果调用setsid之前该进程有一个控制终端,那么这种联系也被切断。
Ps:如果该调用进程已经是一个进程组组长,则此函数返回出错。回想一下我们为了保证不处于这种情况我们是如何处理的?第一步,先调用fork,然后父进程终止,而子进程继续。因为子进程继承了父进程的进程组ID,而其进程ID是新分配的,两者不可能相等,这就保证了子进程不是一个进程组组长。

Step 3: 改变当前工作目录

这一步也是必要的步骤。使用fork()创建的子进程继承了父进程的当前工作目录。由于在进程运行过程中,当前目录所在的文件系统(如“/mnt/usb”等)是不能卸载的,这对以后的使用会造成诸多的麻烦。因此,通常的做法是让“/”作为守护进程的当前工作目录,这样就可以避免上述问题。当然,如有特殊需要,也可以把当前工作目录换成其他的路径,如/tmp。改变工作目录的常见函数是chdir()。

Step4:重设文件权限掩码

进程从创建它的父进程那里继承了文件创建掩码。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩模清除:

umask(0);

Step5:关闭文件描述符

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

for(i=0;i<=getdtablesize();i++)

close(i);

#include <unistd.h>
int getdtablesize(void);

返回:进程最多打开文件描述符的个数(1024)

PS:
禁止进程重新打开控制终端
现在,进程已经成为无终端的会话组长。但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端:

if(pid=fork())
exit(0);//结束第一子进程,第二子进程继续(第二子进程不再是会话组长)

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>

void creat_daemon(void);
int main(void)
{
	time_t t;
	int fd;
	if (daemon(0, 0) == -1)
		perror("daemon error");
	while (1){
		fd = open("daemon.log", O_WRONLY | O_CREAT | O_APPEND, 0644);
		if (fd == -1)
			ERR_EXIT("open error");
		t = time(0);
		char *buf = asctime(localtime(&t));
		write(fd, buf, strlen(buf));
		close(fd);
		sleep(60);

	}
	return 0;
}

void creat_daemon(void)
{
	pid_t pid;
	pid = fork();
	if (pid == -1)
		perror("fork error");
	if (pid > 0)
		exit(EXIT_SUCCESS);
	if (setsid() == -1)
		ERR_EXIT("SETSID ERROR");
	chdir("/");//将目录更改到/下


	int fd = open("/dev/null", O_RDWR);
	dup2(fd, 0);
	dup2(fd, 1);
	dup2(fd, 2);
	close(fd);

	umask(0);
	return;
}
  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值