守护进程详解
一、普通进程运行观察代码示例:#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <signal.h>int main(int argc, char *const *argv){printf("进程开始执行!\n");while (1){sleep(1);//休眠1秒//p
一、普通进程运行观察
代码示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
int main(int argc, char *const *argv)
{
printf("进程开始执行!\n");
while (1)
{
sleep(1); //休眠1秒
//printf("休息1秒,进程ID=%d\n", getpid());
}
printf("再见了!\n");
return 0;
}
查看进程id,进程状态的命令
ps -eo pid,ppid,sid,tty,pgrp,comm | grep -E 'bash|PID|nginx'
- 进程有对应的终端,如果终端退出,那么对应的进程也就消失了
- 终端被占住了,输入各种命令没有反应。
二、守护进程基本概念
守护进程:一种长期运行的进程,这种进程在后台运行,并且不跟任何的控制终端关联。
基本特点:
- 生存周期长[非必须],一般操作系统启动的时候就启动,关闭的时候关闭。
- 守护进程和终端无关联,也就是他们没有控制终端,所以当控制终端退出,也不会导致守护进程退出。
- 守护进程是在后台运行,不会占着终端,终端可以执行其他命令
linux操作系统本身是有很多的守护进程在默默执行,维持着系统的日常活动。大概30-50个。
查看后台守护进程命令:
ps -efj
- ppid = 0:内核进程,跟随系统启动而启动,生命周期贯穿整个系统。
- cmd列名带[]这种,叫内核守护进程
- 老祖init:也是系统守护进程,它负责启动各运行层次特定的系统服务;所以很多进程的PPID是init,也负责收养孤儿进程。
- cmd列中名字不带[]的普通守护进程(用户集守护进程)
共同点总结:
- 大多数守护进程都是以超级用户特权运行的。
- 守护进程没有控制终端;内核守护进程以无控制终端方式启动,普通守护进程可能是守护进程调用了setsid的结果(无控制终端)
三、守护进程编写规则
- 调用umask(0),umask是一个函数,用来限制(屏蔽)一些文件权限。
- fork()一个子进程出来,然后父进程退出(固定套路,把终端空出来,不让终端卡住);fork()的目的是想成功调用setsid()来建立新会话,子进程有单独的sid,并且成为新进程组的组长,不关联任何终端。
1. 文件描述符
文件描述符:用来标识一个文件。当你打开一个存在的文件或者创建一个新文件,操作系统都会返回这个文件描述符。后续对这个文件的操作的一些函数,都会用到这个文件描述符作为参数。
Linux中三个特殊的文件描述符,数字分别为0,1,2:
0:标准输入[键盘],对应的符号常量叫 STDIN_FILENO
1:标准输出[屏幕],对应的符号常量叫 STDOUT_FILENO
2:标准错误[屏幕],对应的符号常量叫STDERR_FILENO
文件描述符虽然是数字,但是,我们把文件描述符直接理解成指针(指针里面保存的是地址-其实也是一个数字)
2. 输入输出重定向
输出重定向:标准输出文件描述符不在指向屏幕,可以重新指向一个文件。
重定向,在命令行中用 > 即可;例如:ls -la > file,可看到内容保存到file文件里面
输入重定向:在命令行中用 < 即可,例如 cat < file ,从文件里面读取内容
3. 空设备
/dev/null : 是一个特殊的设备文件,它丢弃一切写入其中的数据(像黑洞一些)例如:cat file > /dev/null 输出重定向文件到 空洞(无任何输出)。
我们一般把守护进程的标准输入、标准输出重定向到空设备(黑洞),从而确保守护进程不从键盘接收任何东西,也不把输出结果打印到屏幕。
4. 实现范例
#include <stdio.h>
#include <stdlib.h> //malloc
#include <unistd.h>
#include <signal.h>
#include <sys/stat.h>
#include <fcntl.h>
//创建守护进程
//创建成功则返回1,否则返回-1
int ngx_daemon()
{
int fd;
switch (fork()) //fork()子进程
{
case -1:
//创建子进程失败,这里可以写日志......
return -1;
case 0:
//子进程,走到这里,直接break;
break;
default:
//父进程,直接退出
exit(0);
}
//只有子进程流程才能走到这里
if (setsid() == -1) //脱离终端,终端关闭,将跟此子进程无关
{
//记录错误日志......
return -1;
}
umask(0); //设置为0,不要让它来限制文件权限,以免引起混乱
fd = open("/dev/null", O_RDWR); //打开黑洞设备,以读写方式打开
if (fd == -1)
{
//记录错误日志......
return -1;
}
if (dup2(fd, STDIN_FILENO) == -1) //先关闭STDIN_FILENO[这是规矩,已经打开的描述符,动他之前,先close],类似于指针指向null,让/dev/null成为标准输入;
{
//记录错误日志......
return -1;
}
if (dup2(fd, STDOUT_FILENO) == -1) //先关闭STDIN_FILENO,类似于指针指向null,让/dev/null成为标准输出;
{
//记录错误日志......
return -1;
}
if (fd > STDERR_FILENO) //fd应该是3,这个应该成立
{
if (close(fd) == -1) //释放资源这样这个文件描述符就可以被复用;不然这个数字【文件描述符】会被一直占着;
{
//记录错误日志......
return -1;
}
}
return 1;
}
int main(int argc, char *const *argv)
{
if(ngx_daemon() != 1)
{
//创建守护进程失败,可以做失败后的处理比如写日志等等
return 1;
}
else
{
//创建守护进程成功,执行守护进程中要干的活
for(;;)
{
sleep(1); //休息1秒
printf("休息1秒,进程id=%d!\n",getpid()); //你就算打印也没用,现在标准输出指向黑洞(/dev/null),打印不出任何结果【不显示任何结果】
}
}
return 0;
}
守护进程可以用命令启动,如果想开机启动,则需要借助系统初始化脚本来启动。
四、守护进程不会收到的信号
1. SIGUP信号
守护进程不会收到来自内核的 SIGHUP 信号; 潜台词就是 如果守护进程收到了 SIGHUP信号,那么肯定是另外的进程发给你的;
很多守护进程把这个信号作为通知信号,表示配置文件已经发生改动,守护进程应该重新读入其配置文件;
2. SIGINT、SIGWINCH信号
守护进程不会收到来自内核的 SIGINT(ctrl+C),SIGWINCH(终端窗口大小改变) 信号;
五、守护进程和后台进程的区别
- 守护进程和终端不挂钩;后台进程能往终端上输出东西(和终端挂钩);
- 守护进程关闭终端时不受影响,守护进程不会随着终端的退出而退出;
更多推荐
所有评论(0)