Daemon(守护进程)是运行在后台的一种特殊线程。它独立于终端并在后台周期性地执行任务或等待处理某些发生的事件,它不需要用户输入就能运行并且提供某种服务,不是对整个系统就是对某个用户程序提供服务。Linux系统的大多数服务器都是通过守护进程实现的,如系统日志进程syslogd,web服务器httpd,邮件服务器sendmail。
Daemon独立于终端是为了避免进程在执行过程中的信息在终端上显示或者因收到终端上所产生的终端信息而中断。在Linux中从该控制终端开始运行的进程会依附于该控制终端,当控制终端被关闭时,相应的进程都会自动关闭,所以守护进程必须脱离控制终端。
一,创建子进程,退出父进程。
为了脱离终端的控制需要退出父进程,使得子进程可以在后台执行。在Linux中父进程先于子进程退出会导致子进程变为孤儿进程,而每当系统发现一个孤儿进程,就由有1号进程(init)收养它,这样,原先的子进程就会变成了init进程的子进程。
二,在子进程中创建新的会话(session)
在Linux中,进程组:是一个或多个进程的集合。进程组由进程组ID来唯一标识。除了进程号PID之外,进程组ID也是一个进程的必备属性。每个进程组都有一个组长进程,其组长进程的进程号等于进程组ID,且该进程组ID不会因组长进程的退出而受到影响。
每个进程属于一个进程组,进程组号(GID)就是进程组长的进程号(PID)。登录会话可以包含多个进程组。这些进程组共享一个控制终端。由于在调用fork函数时,子进程拷贝了父进程的会话期、进程组、控制终端等,虽然父进程退出了,但会话期、进程组、控制终端等并没有改变,为了使子进程不再受到它们的影响,需要调用setsid()来创建一个新的会话。
setsid() :用于创建一个新的会话,并且让调用该函数的进程成为该会话组的组长。对于子进程来说其主要有三个作用:
1. 让子进程摆脱原会话的控制。
2. 让子进程摆脱原进程组的控制。
3. 让子进程摆脱原控制终端的控制。
三,改变当前目录为根目录
使用fork创建的子进程继承了父进程的当前的工作目录。由于在进程运行中,当前目录所在的文件系统(如“/mnt/usb”)是不能卸载的,这对以后的使用会造成诸多的麻烦(比如系统由于某种原因要进入单用户模式)。因此,通常的做法是让根目录”/”作为守护进程的当前工作目录。这样就可以避免上述的问题。如有特殊的需求,也可以把当前工作目录换成其他的路径。改变工作目录的方法是使用chdir函数。
四,重设文件权限掩码
文件权限掩码:是指屏蔽掉文件权限中的对应位。例如,有个文件权限掩码是050,它就屏蔽了文件组拥有者的可读与可执行权限(对应二进制为,rwx, 101)。由于fork函数创建的子进程继承了父进程的文件权限掩码,这就给子进程使用文件带来了诸多的麻烦。因此,把文件权限掩码设置为0(即,不屏蔽任何权限),可以增强该守护进程的灵活性。设置文件权限掩码的函数是umask。通常的使用方法为umask(0)。
五,再次 fork() 一个子进程并终止父进程
现在,进程已经成为无终端的会话组长,但它可以重新申请打开一个控制终端,,所以需要再次fork()一个子进程,并终止父进程。打开一个控制终端的前提条件是该进程必须为会话组组长,而我们通过第二次fork,确保了第二次fork出来的子进程不会是会话组组长。
六,关闭文件描述符
用fork创建的子进程也会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读写,但它们一样消耗系统资源,而且可能导致所在的文件系统无法卸载。在使用setsid调用之后,守护进程已经与所属的控制终端失去了联系,因此从终端输入的字符不可能达到守护进程,守护进程中用常规方法(如printf)输出的字符也不可能在终端上显示出来。所以,文件描述符为0、1、2(即,标准输入、标准输出、标准错误输出)的三个文件已经失去了存在的价值,也应该关闭。
七,守护进程退出处理
当用户需要外部停止守护进程时,通常使用kill命令停止该守护进程。所以,守护进程中需要编码来实现kill发出的signal信号处理,达到进程正常退出。
void mydaemon(void)
{
umask(0);//调用umask将文件模式创建屏蔽字设置为0.
pid_t id=fork();//调用fork,创建子进程
if(id<0){
perror("fork()");
}else if(id>0){//father
exit(0);
}
setsid();//set new session//调用setsid函数创建一个会话
signal(SIGCHLD,SIG_IGN);//忽略SIGCHLD信号,子进程退出时不再给父进程发信号。
pid_t id1=fork();
if(id1<0){
perror("fork()");
}else if(id1>0){//father
exit(0);
}
if(chdir("/")<0){//将当前工作目录更改为根目录:
printf("child dir error\n");
return;
}
//关闭不需要的文件描述符,或者重定向到/dev/null中
close(0);
int fd0;
fd0=open("dev/null",O_RDWR);
dup2(fd0,1);
dup2(fd0,2);
}
在JAVA中有两类进程:用户线程(User Thread),守护线程(Daemon Thread)。守护线程--也称“服务线程”, 他是后台线程, 它有一个特性,即为用户线程提供公共服务, 在没有用户线程可服务时会自动离开。
只要当前JVM实例中存在任何一个非守护线程,守护线程就不会终止;只有当所有的非守护进程都终止时,守护进程就会随着JVM一起结束。
User和Daemon的区别:两种唯一的不同之处就在于虚拟机的离开,如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。 因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。同时在Daemon线程中产生的新线程也是Daemon线程。
优先级:守护线程的优先级比较低,用于为系统中的其它对象和线程提供服务。
创建守护线程:
Thread daemonTread = new Thread();
// 设定 daemonThread 为 守护线程,default false(非守护线程)
daemonThread.setDaemon(true);
// 验证当前线程是否为守护线程,返回 true 则为守护线程
daemonThread.isDaemon();
thread.setDaemon(true)必须在thread.start()之前设置,否则会抛出一个IllegalThreadStateException异常。不能把正在运行的常规线程设置为守护线程。
使用守护线程需要注意的事项:
不要认为所有的应用都可以分配给Daemon来进行服务,比如读写操作或者计算逻辑。因为你不可能知道在所有的User完成之前,Daemon是否已经完成了预期的服务任务。一旦User退出了,可能大量数据还没有来得及读入或写出,计算任务也可能多次运行结果不一样。这对程序是毁灭性的。造成这个结果理由已经说过了:一旦所有User Thread离开了,虚拟机也就退出运行了。JRE判断程序是否执行结束的标准是所有的前台执线程行完毕了,而不管后台线程的状态,因此,在使用后台县城时候一定要注意这个问题。