Linux--守护进程

一、认识守护进程

1、守护进程的引入 

在现实生活中, 许多大型的软件或服务器必须保证7*24小时(一周7天,一天24小时)无障碍的运行,例如淘宝网、百度搜索引擎、支付宝等等,那么像这样一种要一直运行的程序怎么实现呢?究其本质其实就是我们的守护进程。

2.守护进程的定义

守护进程也称为精灵进程(Daemon),是运行在后台的一种特殊进程。它独立于控制终端并且周期性的执行某种发生的事件。守护进程是一种很有用的进程。Linux的大多数服务器就是用守护进程实现的。比如,ftp服务器,Web服务器http等。同时,守护进程完成许多系统任务。比如,作业规划进程crond等。

Linux系统启动时会启动很多系统服务进程,这些系统服务进程没有控制终端,不能直接和用户交互。其他进程都是在用户登录或运行程序时创建,在运行结束或用户注销时终止,但系统服务进程(守护进程)不受用户登录注销的影响,它们一直在运行着。这种进程有一个名称叫守护进程(Daemon)

下面,我们用ps axj命令查看系统中的进程。参数a 表示不仅列出当前用户的进程,也列出所有其他用户的进程;

                                                                   参数x 表示不仅列出有控制终端的进程,也列出所有无控制终端的进程;

                                                                   参数j 表示列出与作业控制相关的信息。


(1)凡是TPGID一栏写着-1的都是没有控制终端的进程,也就是守护进程;

(2)在COMMAND一列用[ ]括起来的名字表示内核线程,这些线程在内核里创建,没有用户空间代码,因此没有程序文件名和命令行,通常采用以k开头的名字,表示Kernel;

(3)init进程我们已经很熟悉了,udevd负责维护/dev目录下的设备文件,acpid负责电源管理,syslogd负责维护/var/log下的日志文件;

(4)可以看出,守护进程通常采用以d结尾的名字,表示Daemon。

总结一下上面我们所说的守护进程中一些容易忽略的小点。

(1)守护进程的本职就是孤儿进程,该进程自成会话,自成进程组,一般守护进程与终端无关;(即:pid=sid=gid)

(2)守护进程和后台进程的区别:后台进程受用户登录注销的影响,而守护进程不受用户登录和注销的影响。但是它们都受关机的影响。

3.守护进程的特点

(1)在Linux中,每个系统与用户进行交流的界面成为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端被称为这些进程的控制终端;

(2)当控制终端被关闭的时候,相应的进程都会自动关闭。但是守护进程却能突破这种限制,它脱离于终端并且在后台运行,(脱离终端的目的是为了避免进程在运行的过程中的信息在任何终端中显示并且进程也不会被任何终端所产生的终端信息所打断),它从被执行的时候开始运转,直到整个系统关闭才退出(当然可以认为是杀死相应的守护进程);

(3)如果想让某个进程不因为用户或中断或其他变化而影响,那么就必须把这个进程变成一个守护进程。

二、创建守护进程

创建守护进程最关键的一步是调用setsid函数创建一个新的Session Leader。

#include<unistd.h>
pid_t setid(void);
//该函数调用成功时返回新创建的Session的id(其实也就是当前进程的id),出错返回-1。

注意,调用这个函数之前,当前进程不允许是进程组的Leader,否则该函数返回-1。要保证当前进程不是进程组的Leader也很容易,只要先fork再调用setsid就行了。fork创建的子进程和父进程在同一个进程组中,进程组的Leader必然是该组的第一个进程,所以子进程不可能是该组的第一个进程,在子进程中调用setsid就不会有问题了。

成功调用该函数的结果是:

(1)创建一个新的Session,当前进程为Session Leader,当前进程的id就是Session的id;

(2)创建一个新的进程组,当前进程为进程组的Leader,当前进程的id就是进程组的id;

(3)如果当前进程原本有一个控制终端,则它失去这个终端,成为一个没有控制终端的进程。(所谓失去控制终端指的是,原来的控制终端仍然是打开的,仍然可以读写,但只是一个普通的打开文件,而不是控制终端了)。

三、守护进程代码

1.调用umask将文件模式创建屏蔽字设置为0

umask(0);//umask必须清0,否则创建的新文件受系统默认权限的影响

文件权限掩码是屏蔽掉文件权限中的对应位。

由于使用fork出来的子进程继承了父进程的文件权限掩码,这就给该子进程使用文件带了很多的麻烦(比如父进程中的文件没有执行文件的权限,然而在子进程中希望执行相应的文件这个时候就会出问题)。因此在子进程中要把文件的权限掩码设置成为0(也就是说子进程有最大的权限),这样做能增强该守护进程的灵活性。

2.调用fork函数,父进程退出(exit)

这样的做的目的:

(1)保证子进程不是一个进程组的Leader;

(2)如果该守护进程作为一条简单的shell命令启动的,那么父进程终止使得shell认为该命令已经执行完毕。

3.调用setsid创建一个新的会话

调用该函数的原因:

由于调用了fork()函数之后,子进程拷贝了父进程的会话期、进程组、控制终端的资源(虽然父进程退出了,这些资源都并没有改变),因此需要调用setsid()函数来让该子进程完全独立出来,从而摆脱其他进程的控制。

4.忽略SIGCHLD信号

signal(SIGCHLD,SIG_IGN);

5.将当前工作目录更改为根目录

这样做的目的是:

防止当前目录有一个目录被删除,导致守护进程无效。(因为调用fork后的子进程继承了父进程的当前工作目录,由于在进程运行中,当前目录所在的文件系统是不能被卸载的,这对以后使用会造成很多麻烦,因此,通常会让"/"作为守护进程的当前目录,当然也可以指定别的目录来作为守护进程的当前工作目录)

6.关闭不再需要的文件描述符

这样做的目的:

文件权限码一样,用fork出来的子进程会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读写,如果不关闭将会浪费系统的资源,造成进程所在的文件系统无法卸载或者引起一些不可预料的错误。

有了上面六个步骤的铺垫,我们来一起写一下代码吧!

#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/stat.h>
void mydaemon(void)
{
	int i;
	int fd0;
	pid_t pid;
	struct sigaction sa;

	//1.调用umask将文件模式创建屏蔽字设置为0
	umask(0);

	//2.调用fork,父进程退出(exit)
	if ((pid = fork()) < 0)
	{
		perror("fork");
	}
	else if (pid>0)
	{
		exit(0);
	}

	//3.调用setsid创建一个新的会话
	setsid();

	//4.忽略SIGCHLLD信号
	sa.sa_handler = SIG_IGN;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
	//注册子进程退出,忽略信号
	if (sigaction(SIGCHLD, &sa, NULL) < 0)
	{
		return;
	}
	//再次fork,终止父进程,保持子进程不是会话首进程,从而保证后续不在和其他终端关联
	//这部分不是必须的
	if ((pid = fork()) < 0)
	{
		printf("fork error!\n");
		return;
	}
	else if (pid != 0)
	{
		exit(0);
	}
	
	//5.将当前工作目录更改为根目录
	if (chdir("/") < 0)
	{
		printf("child dir error\n");
		return;
	}

	//6.关闭不在需要的文件描述符,或者重定向到 /dev/null
	close(0);
	fd0 = open("/dev/null", O_RDWR);
	dup2(fd0, 1);
	dup2(fd0, 2);
}

int main()
{
	mydaemon();
	while (1)
	{
		sleep(1);
	}
}

四、daemon函数

#include<stdio.h>
#include<unistd.h>
{
    daemon(nochdir,noclose);
    while(1);
}
(1)daemon函数主要用于希望脱离控制台,以守护进程的形式在后台运行的程序。 
(2)当nochdir为0时,daemon将更改当前进程的目录为root(“/”)目录。 
(3)当noclose为0时,daemon将进程的STDIN,STDOUT,STDERR都重定向到/dev/null。 

        /dev/null:linux下的黑洞,写入的所有数据会直接丢弃。

五、如何杀死守护进程

1.首先ps axj | grep 守护进程名字,找到相应的守护进程,然后使用kill -9 守护进程名杀掉;

2.利用ps -ef命令查找相应的守护进程,再用kill -9命令将其杀死;

3.创建shell脚本对进程的启动、关闭、重启进行自动管理

猜你喜欢

转载自blog.csdn.net/cherrydreamsover/article/details/80000024