一、基础知识
1)进程的类型,进程大体分为三类
(1)交互进程
(2)批处理进程
(3)守护进程
2)守护进程的特点
(1)运行方式: 守护进程,也就是通常所说的Daemon进程,是Linux中的后台服务进程。周期性的执行某种任务或等待处理某些发生的事件。Linux系统有很多守护进程,大多数服务都是用守护进程实现的。
比如:像我们的tftp,samba,nfs等相关服务。同时Linux的大多数服务器就是用守护进程实现的。
比如,Internet服务器inetd,Web服务器httpd等。
(2)生命周期: 守护进程会长时间运行,常常在系统启动时就开始运行,直到系统关闭时才终止
(3)守护进程不依赖于终端:显而异见,从终端开始运行的进程都会依附于这个终端,这个终端称为这些进程的控制终端。当控制终端被关闭时,相应的进程都会被自动关闭。咱们平常写进程时,一个死循环程序关闭终端的同时也关闭了我们的程序,但是对于守护进程来说,其生命周期守护需要突破这种限制,它从开始运行,直到整个系统关闭才会退出,所以守护进程不能依赖于终端。
二、查看系统中的守护进程
ps -ajx
-a: 显示所有
-x:显示没有控制终端的进程
-j:显示与作业有关的信息(显示的列):会话期ID(SID),进程组ID(PGID),控制终端(TTY),终端进程组ID(TRGID)
三、如何识别一个守护进程:
举个列子,作业规划进程cron
四、会话相关概念:进程组、前台进程组、后台进程组、会话、控制终端
1)进程组
进程组是一个或多个进程的集合。进程组由进程组ID(PGID)来唯一标识。每个进程组都有一个组长进程,进程组ID就是组长进程的进程号。
2)会话期
是一个或多个进程组的集合。一般一个用户登录后新建一个会话(打开终端),每个会话也有一个ID来标识(SID)。登录后的第一个进程叫做会话领头进程(session leader),通常是一个shell/bash。对于会话领头进程,其PID=SID。
3)控制终端
一个会话一般会拥有一个控制终端用于执行IO操作。用户登录的终端就成为该会话的控制终端。与控制终端建立连接的会话领头进程也称为控制进程 (controlling process) 。一个会话只能有一个控制终端。
4)前台进程组
该进程组中的进程能够向终端设备进行读、写操作的进程组。
5)后台进程组
该进程组中的进程只能够向终端设备写。
每个会话有且只有一个前台进程组,但会有0个或者多个后台进程组。
6)终端进程组ID
每个进程还有一个属性,终端进程组ID(TPGID),用来标识一个进程是否处于一个和终端相关的进程组中。前台进程组中的进程的TPGID=PGID,后台进程组的PGID≠TPGID。若该进程和任何终端无关,其值为-1。通过比较他们来判断一个进程是属于前台进程组,还是后台进程组。
举个例子
$ ping 127.0.0.1 | grep icmp > temp & 通过管道将两个指令连接起来并放置后台
$ ping 127.0.0.1 | grep icmp 放在前台运行
查看结果
结果我们发现
在终端中执行Ctrl+C后
打开新的终端时第一个创建的进程是shell,也就是会话的领头进程,该领头进程缺省处于一个前台进程组中并打开一个控制终端可以进行数据的读写。当在shell里运行一行命令后(不带&)创建一个新的进程组,命令行中如果有多个命令会创建多个进程,这些进程都处于该新建进程组中,shell将该新建的进程组设置为前台进程组并将自己暂时设置为后台进程组。
Step1:创建子进程,父进程退出
实质是 让init进程成为新产生进程的父进程。调用fork函数创建子进程后,使父进程立即退出。从而使产生的子进程将变成孤儿进程,并被init进程接管,同时,所产生的新进程将变为在后台运行;利用前面介绍的父进程先于子进程退出后内核会自动托付子进程给init的原理。
pid = fork();
if (pid > 0) /*父进程退出*/
{
exit(0);
}
Step2:脱离控制终端,创建新的会话
这个步骤是创建守护进程最重要的一步,虽然实现非常简单,但意义却非常重大。
#include
pid_t setsid(void);
返回值:若成功,返回新的会话期ID;若出错,返回-1。
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
int getdtablesize(void);
返回:进程最多打开文件描述符的个数(1024)
PS:
禁止进程重新打开控制终端
现在,进程已经成为无终端的会话组长。但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端:
if(pid=fork())
exit(0);//结束第一子进程,第二子进程继续(第二子进程不再是会话组长)