守护进程
守护进程
Daemon(精灵)进程,是Linux中的后台服务进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。一般采用以d结尾的名字。
Linux后台的一些系统服务进程,没有控制终端,不能直接和用户交互。不受用户登录、注销的影响,一直在运行着,他们都是守护进程。如:预读入缓输出机制的实现;ftp服务器;nfs服务器等。
创建守护进程,最关键的一步是调用setsid函数创建一个新的Session,并成为Session Leader。
创建守护进程模型
- 创建子进程,父进程退出
所有工作在子进程中进行形式上脱离了控制终端
- 在子进程中创建新会话
setsid()函数
使子进程完全独立出来,脱离控制
- 改变当前目录为根目录
chdir()函数
防止占用可卸载的文件系统
也可以换成其它路径
- 重设文件权限掩码
umask()函数
防止继承的文件创建屏蔽字拒绝某些权限
增加守护进程灵活性
- 关闭文件描述符
继承的打开文件不会用到,浪费系统资源,无法卸载
- 开始执行守护进程核心工作
- 守护进程退
创建会话
创建一个会话需要注意以下6点注意事项:
- 调用进程不能是进程组组长,该进程变成新会话首进程(session header);父进程不能调用
- 该进程成为一个新进程组的组长进程。
- 需有root权限(ubuntu不需要)
- 新会话丢弃原有的控制终端,该会话没有控制终端
- 该调用进程是组长进程,则出错返回
- 建立新会话时,先调用fork, 父进程终止,子进程调用setsid
getsid函数
获取进程所属的会话ID
pid_t getsid(pid_t pid); 成功:返回调用进程的会话ID;失败:-1,设置errno
pid为0表示察看当前进程session ID
ps ajx命令查看系统中的进程。参数a表示不仅列当前用户的进程,也列出所有其他用户的进程,参数x表示不仅列有控制终端的进程,也列出所有无控制终端的进程,参数j表示列出与作业控制相关的信息。
组长进程不能成为新会话首进程,新会话首进程必定会成为组长进程。
setsid函数
创建一个会话,并以自己的ID设置进程组ID,同时也是新会话的ID。
pid_t setsid(void); 成功:返回调用进程的会话ID;失败:-1,设置errno
调用了setsid函数的进程,既是新的会长,也是新的组长。
创建守护进程由上述说明,来实现一个守护进程
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
void my_daemond(void)
{
pid_t pid, sid;
int fd;
pid = fork();
if (pid)//父进程
{
exit(1);//结束父进程
}
//创建会话
sid = setsid();
//在这里之后子进程就脱离控制终端了
//改变当前目录为家目录下的lovedan目录
if ((chdir(" / home / lovedan"))<0)
{
perror("chdir error");
exit(1);
}
//重设文件权限掩码
umask(0022);//第一个0代表八进制,这是默认的文件掩码,在Linux中2
//关闭文件描述符(尤其是012这三个文件描述符)
close(STDIN_FILENO);
open(" / dev / null", O_RDWR);
dup2(0, STDOUT_FILENO);
dup2(0, STDERR_FILENO);
//解释一下这里之后为什么012三个文件描述符都是指向null这个文件,主要是我们最开始关闭了STDIN_FILENO,后面打开的文件,系统会自动占用前面没有占用的文件描述符,恰好,0号描述符没有被占用,所以。。。懂了吧。
}
int main(void)
{
my_daemond();
while (1)//守护进程周期性的运作
{
//若是考虑带退出,则注册一个信号捕捉函数一个信号,执行自杀函数。
}
return 0;
}
大体上就是这样,很简单的。运行结果如图:这里补充一下知识:ps aux之后的STAT列代表的含义:
D 不可中断 Uninterruptible(usually IO)
R 正在运行,或在队列中的进程
S 处于休眠状态
T 停止或被追踪
Z 僵尸进程
W 进入内存交换(从内核2.6开始无效)
X 死掉的进程
< 高优先级
n 低优先级
s 包含子进程
+ 位于后台的进程组
至于结果,为何运行之后无阻塞无显示,因为它是守护进程,只在后台运行。
现在来拓展一下:创建一个守护进程,让它在后台每隔一段时间不断打印系统时间到一个文件中。
因为很简单,所以我也直接上代码:
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
void my_daemond(void)
{
pid_t pid, sid;
int fd;
pid = fork();
if (pid)//父进程
{
exit(1);//结束父进程
}
//创建会话
sid = setsid();
//在这里之后子进程就脱离控制终端了
//改变当前目录为家目录下的lovedan目录
if ((chdir(" / home / lovedan"))<0)
{
perror("chdir error");
exit(1);
}
//重设文件权限掩码
umask(0022);//第一个0代表八进制,这是默认的文件掩码,在Linux中2
//关闭文件描述符(尤其是012这三个文件描述符)
close(STDIN_FILENO);
open(" / dev / null", O_RDWR);
dup2(0, STDOUT_FILENO);
dup2(0, STDERR_FILENO);
//解释一下这里之后为什么012三个文件描述符都是指向null这个文件,主要是我们最开始关闭了STDIN_FILENO,后面打开的文件,系统会自动占用前面没有占用的文件描述符,恰好,0号描述符没有被占用,所以。。。懂了吧。
}
void puts_date(void)
{
int fd;
if (!fork())//子进程
{
fd = open("date.txt", O_RDWR | O_CREAT | O_APPEND, 0644);
dup2(fd, STDOUT_FILENO);
execlp("date", "date", NULL);
}
else
{
close(fd);
wait(NULL);//回收子进程.
}
}
int main(void)
{
my_daemond();
while (1)//守护进程周期性的运作
{
sleep(2);//休眠2s
puts_date();
//若是考虑带退出,则注册一个信号捕捉函数一个信号,执行自杀函数。
}
return 0;
}
但是为了达到更好的综合知识的角度,我们要自己实现sleep,检查所有的错误。下次更新吧。