Linux系统编程41 进程控制 - 守护进程

僵死进程:在UNIX术语中,一个已经终止,但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息,释放它仍然占用的资源)的进程成为 僵尸进程。

孤儿进程:如果父进程先退出,子进程还没有完成,那么此时子进程就会被托付给 init进程,此时该子进程的父进程就是 init进程(1号进程)。

守护进程:一般是指 Linux中的后台服务进程,它是一个生存周期较长的进程,如其父进程为init进程,通常独立于控制终端,即 不接受电脑用户的直接操作。守护进程是一个特殊的孤儿进程。

会话(session): 会话是一个或多个进程组的集合。通常一个会话开始于 用户登录,终止于用户退出,在此期间用户运行的所有进程都属于会话

进程组:每个进程除了有一个进程ID之外,还属于一个进程组。进程组是一个或多个进程的集合。每个进程组有一个组长进程,组长进程的进程ID等于进程组ID。

NAME
       setsid - creates a session and sets the process group ID
创建会话,并设置进程组的ID

SYNOPSIS
       #include <unistd.h>

       pid_t setsid(void);

如果调用setsid()的进程不是一个进程组的组长进程,则 创建一个会话。即父进程不能调用setsid()创建会话,只能由子进程调用。调用setsid()的进程会成为 会话组长,以及 会成为当前新的进程组的组长。并且脱离控制终端。

setsid()用于创建一个新的会话,并有以下三个作用:
1 让进程摆脱原会话的控制
2 让进程摆脱原进程组的控制
3 让进程摆脱原控制终端的控制

守护进程:
1 后台服务程序
2 脱离控制终端,用户无法通过终端与守护进程交互,TTY == ?
3 长期,周期性的执行某任务,即父进程为init进程,即1号进程
4 不受用户登录,注销的影响 即不受当前会话控制

那么,在创建守护进程时为什么要调用setsid函数呢?由于创建守护进程的第一步调用了fork函数来创建子进程,再将父进程退出。由于在调用了fork函数时,子进程全盘拷贝了父进程的会话期、进程组、控制终端等,虽然父进程退出了,但会话期、进程组、控制终端等并没有改变,因此,这还不是真正意义上的独立开来,而setsid函数能够使进程完全独立出来,从而摆脱其他进程的控制。

一个进程一定会父进程,但是守护进程有点不一样,一般子进程调用之后,按照之前的逻辑,子进程运行的时候,父进程会一直等待子进程运行完成 并释放资源,即 wait()收尸,但是守护进程就没有必要 wait()了,因为它运行的时间会很长,父进程以及父进程的父进程等等就没必要一直等待了,所以直接托付给init进程就好。

mhr@ubuntu:~/Desktop/xitongbiancheng/test$ ps axj
  PPID    PID   PGID    SID TTY       TPGID STAT   UID   TIME COMMAND
父进程ID  : PPID
进程ID : PID
组进程ID : PGID
会话ID : SID

实验:守护进程的创建

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
#include <fcntl.h>

#define FILENAME "/tmp/out"

static int craetdeamon(void)
{
	pid_t pid;
	int fd;	
	
	pid = fork();
	if(pid < 0)
	{
		perror("fork()");
		return -1;
	}
	
	if(pid > 0) //父进程结束
	{
		printf("%d\n",getpid());
		exit(0);
	}
		

	fd = open("/dev/null",O_RDWR);
	if(fd < 0)
	{	
		perror("open()");
		return -1;
	}

//重定向 0 1  2 文件描述符
	dup2(fd,0);
	dup2(fd,1);
	dup2(fd,2);	
	if(fd > 2)
	{
		close(fd);
	}

//创建守护进程
	setsid();
	
	chdir("/");

	return 0;
}

int main(int argc,char* argv[])
{

	FILE* fp;
	int i;


	if(craetdeamon())
	{
		exit(1);
	}
	
	fp = fopen(FILENAME,"w");
	if(fp == NULL)
	{
		perror("fopen()");
		exit(1);
	}

	for(i = 0; ;i++)
	{
		fprintf(fp,"%d\n",i);
		fflush(fp);
		sleep(1);
	}
	
	
	exit(0);
}

mhr@ubuntu:~/Desktop/xitongbiancheng/test$ ps axj
   ...
  1662   3141   3141   3141 ?            -1 Ss    1000   0:00 ./a.out
  2959   3188   3188   2959 pts/2      3188 R+    1000   0:00 ps axj

父进程是 1662 并不是init进程

mhr@ubuntu:~/Desktop/xitongbiancheng/test$ ps 1662
   PID TTY      STAT   TIME COMMAND
  1662 ?        Ss     0:00 /sbin/upstart --user
mhr@ubuntu:~/Desktop/xitongbiancheng/test$ 
mhr@ubuntu:~/Desktop/xitongbiancheng/test$ tail -f /tmp/out
1693
1694
1695
1696
1697
1698
1699
1700
1701

mhr@ubuntu:~/Desktop/xitongbiancheng/test$ kill 3141
最后记得杀死 守护进程

发现此时守护进程的父进程 并不是init进程,后面得知 原来较新 ubuntu 使用 upstart进程 来替代的 init进程 收养孤儿进程。

之所以来重定向 0 1 2 三个文件描述符:
用fork函数新建的子进程会从父进程那里继承一些已经打开了的文件。守护进程已经与所属的控制终端失去了联系。因此从终端输入的字符不可能达到守护进程,守护进程中用常规方法(如printf)输出的字符也不可能在终端上显示出来。所以,文件描述符为0、1和2 的3个文件(常说的输入、输出和报错)已经失去了存在的价值,也应被关闭或重定向。

使用fork创建的子进程会继承父进程的工作目录,如果守护进程踩在某个设备上运行,而当设备卸载时,会出现异常,device is busy。通常做法是让让 根目录(“/”)作为守护进程的当前工作目录。可以避免上述问题。
chdir("/");

猜你喜欢

转载自blog.csdn.net/LinuxArmbiggod/article/details/113962786