先给工具
源码和工具都在这里自取
https://github.com/tangbu/myblob/tree/master/c/deamon
这是一个c编译而成的可运行程序,名字叫deamon,比如原本执行 /bin/test -Darg1=value1, 现在执行deamon /bin/test -Darg1=value1就可以
原理浅显易懂,java程序员不影响阅读
发生了什么事情
笔者的电脑操作系统为ubuntu, 数年以前在兴致勃勃的打开postman的时候调试,突然想整理多余的bash命令界面,突然正在工作的postman退出了工作,留下我默默发呆。经过测试,使用postman的时候必须保持bash界面打开状态,也就是我要一直面对这个黑黑的窗口无法关掉
寻找解决方法
解决办法1 nohup,
网上搜索nohup可以让进程在后台运行,会在当前目录下生成nohup.out,输出会重定向添加到nohup.out里面去,但是并不成功
解决办法2 &符号和-d
命令后面加&符号和-d ,postman -d &, 也不成功页面卡死
解决办法3 disown
使用 disown命令,执行postman的时候界面不给输入命令的机会,导致无法执行disown命令
postman退出原因
执行man bash命令看一下为什么会导致postman退出
shell退出的时候会向shell启动的jobs发送SIGHUP信号,进程在接收到SIGHUP的时候,如果进程没有提前向操作系统注册信号处理函数的时候,操作系统就会关闭该进程
守护进程
守护进程 在linux操作系统中,守护进程(deamon)是作为后台进程运行的进程,对于在bash启动的任务(job), 如果直接退出job同样会丢失。为了解决这个问题,需要使用守护进程的方式启动任务。
接下来我们看一下如何把命令行运行的程序编程守护进程
守护进程的创建
仍然以命令行直接运行postman为例,执行命令查看进程树结构
ps -ajx | egrep 'postman|bash|PPID'
可以看到,终端进程和postman命令进程在同一会话ID中,postman的父进程为终端。
从终端创建一个进程A,A进程再fork一个子进程B执行postman命令, 然后A进程退出
这样就可以创建运行postman命令的进程,本质上和直接在终端运行postman命令进程效果一样,但是我们可以通过c代码对B进程属性更改让最终的进程继承更改后的特性
在子进程中创建新会话
从终端创建一个进程A,A进程再fork一个子进程B,这时候,子进程的SID会话id和终端仍然是一个,当终端退出的时候,仍然会发送SIGHUP信号,子进程默认仍然会退出,所以子进程B仍然需要新建一个会话,就用到了下面的函数
setsid()函数
使子进程完全独立出来,脱离终端控制,可以man 3 setsid看一下使用
改变当前目录为根目录
chidr()函数
防止工作在可卸载的文件系统,文件系统的卸载后,进程也随之消亡, 所以一般更改到根目录下
重设文件权限掩码
umask()函数
本机的umask的是0002 第一个0不考虑
所以在代码中创建777,002代表写,这个umask创建的文件默认去掉写权限(位运算)775,避免影响到守护进程
umask(0);
关闭文件描述符
重定向dup2()
标准输入输出、错误都给丢掉。然后把继承下来的打开的文件描述符关掉,避免占用资源
实际运行的源码
下面是附注释的C代码
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char* argv[])
{
//pid_t int类型, a代表子进程号
pid_t a;
//创建子进程, 父进程中a是子进程号,子进程中a=0
a = fork();
if(a > 0)
{
// 父进程退出,子进程继承父进程代码块继续运行,a就是子进程号
exit(0);
}
// 子进程忽略终端退出SIGHUP信号
signal(SIGHUP, SIG_IGN);
// 创建新会话,脱离原会话,脱离控制终端。
setsid();
//脱离原进程组,创建并进入只包含自身的进程组
setpgrp();
//关闭父辈继承下来的所有文件
int max_fd = sysconf(_SC_OPEN_MAX);
for (int i = 0; i < max_fd && i > 2; i++)
{
close(i);
}
//设置掩码位
umask(0);
int drop_fd=open( "/dev/null", O_RDWR );
dup2(0, drop_fd);
dup2(1, drop_fd);
dup2(2, drop_fd);
//切换工作路径
chdir("/");
// deamon postman arg1 args 变成了postman arg1 arg2
if (argc > 2) {
char *params[argc - 2];
memcpy(params, argv + 1, sizeof params);
params[argc - 3] = NULL;
// execvp支持在环境变量PATH中寻找postman命令并替换代码段,运行新进程的代码段,
execvp(*params, params);
} else {
execvp(argv[1], NULL);
}
return 0;
}
接下来执行 deamon postman 命令查看进程关系
可以看到postman的父进程已经变成了systemd, 比较新的系统守护进程已经由systemd接管,我们的守护进程创建成功
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。