一、认识守护进程
- 守护进程:其也称为精灵进程,是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理发生的事件。
守护进程不受用户登录与注销的影响,它一直在运行着。Linux下的大多数服务器都是利用守护进程实现的。 - 利用ps -axj | more 查看所有用户的作业。
ps:表示对进程监测和控制。
参数a:表示不仅列出当前用户的进程,也列出所有其它用户的进程。
参数x:表示不仅列出控制终端的进程,也列出所有无控制终端的进程。
参数j:表示列出与作业控制相关的信息。
(UID 用户进程;PID 进程ID;PPID 父进程) - PPID为1的进程:表示该进程为孤儿进程,即被init进程收养的进程(init进程pid为1),守护进程为孤儿进程。
- 其中凡是TPGID为-1的都是没有控制终端的进程,也就是守护进程。(如上图中绿色的框)
- 在COMMAND一列用[]括起来的名字表示内核线程,这些线程在内核中创建,没有用户空间代码,通常采用以k为开头的名字,表示Kernel。
- 守护进程通常采用以d结尾的名字,表示Daemon。
后台进程与守护进程的区别:
后台进程受用户登录、注销的影响,依旧是该会话内部的进程。
二、守护进程的相关函数
daemon函数
#include <unistd.h>
int daemon(int nochdir, int noclose);
nochdir:若其为0时,即可将工作目录修改为根目录。
noclose:若其为空时,输入、输出、以及错误输出重定向到/dev/null。
返回值:成功返回0,错误返回-1。
setsid函数
功能:使用其创建一个新的Session,并且成为Session Leader。
#include<unistd.h>
pid_t setsid(void);
返回值:调用成功时返回新创建的Session的pid,(也就是当前进程的pid),出错返回-1。
注意事项:
-
调用该函数之前,当前进程不允许是进程组的Leader,否则返回-1.
-
当要保证当前进程不是进程组的组长时,只需要将fork后的父进程退出即可。
函数调用成功的结果:
创建一个新的Session,当前进程成为Session的Leader,当前进程的pid即为会话的pid(符合守护进程的性质)。
创建一个新的进程组,当前进程为进程组的组长,当前进程的id即为进程组的id。
如果当前进程原本就有一个控制终端,则它失去这个控制终端,成为一个没有控制终端的进程,(所谓失去控制终端是指:原来的控制终端仍然可以打开、读写,但其不是控制终端了,只是一个普通的文件而已)。
三、创建守护进程 -
umask(0):调用umask将文件模式创建屏蔽字设置为0.
原因:用继承来的文件模式创建屏蔽字可能会拒绝设置某种权限,而守护进程需要创建的文件要具有读写权限。 -
调用fork,创建子进程,而调用setsid函数之前,当前进程不允许是进程组的Leader,则需要将父进程退出。
-
调用setsid函数创建一个会话,(具体的2中对setsid函数的描述)。
-
忽略SIGCHLD信号,子进程退出时不再给父进程发信号。
-
将当前工作目录更改为根目录:
原因:因为从当前父进程继承的当前目录可能在装配文件内,而守护进程要在计算机内长期存在,所以不能卸载,而要是装在装配文件中,则装配文件没办法卸载。 -
关闭不需要的文件描述符,或者重定向到/dev/null中
目的:可使得守护进程不再有父进程继承来的文件描述符。
代码实现:
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/stat.h>
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();//调用setsid函数创建一个会话
signal(SIGCHLD,SIG_IGN);//忽略SIGCHLD信号,子进程退出时不再给父进程发信号。
if(chdir("/")<0){//将当前工作目录更改为根目录:
printf("child dir error\n");
return;
}
close(0);
close(1);//关闭不需要的文件描述符
close(2);
}
int main()
{
mydaemon();
while(1){
sleep(1);
}
return 0;
}
可是基于安全性,一般会进行两次fork.下面来说说两次fork的意义:
第一次fork:是为了调用setsid函数,将子进程自成一个独立会话,成为一个进程组,且不受控制终端的控制。
第二次fork:由于当前的子进程为独立会话且为会话组长,独立的进程组,不受控制终端的控制且可打开控制终端,则需再进行一次fork,之后会话sid与pid不同,其便不可以打开控制终端。
代码实现:
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/stat.h>
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);
}
int main()
{
mydaemon();
while(1){
sleep(1);
}
return 0;
}
总结:
- 守护进程自成会话,自成进程组。
- 守护进程不受用户登录、注销的影响。
- 守护进程一般与控制终端无关。