工作中因为业务需要,被要求实现守护进程的功能。
对守护进程简单进行理解,从网络获取到一些有用的demo代码,以及自己进行测试,这里做笔记汇总。
1:守护进程理解
守护进程是一个在后台运行并且不受任何终端控制的进程。
个人广义理解:守护进程需要做到,脱离当前启动的终端,重定位输入输出相关信息。
代码实现一个守护进程的流程:(最终参考百科)
1:脱离终端:使用创建子进程,终止父进程,僵尸进程的形式实现。
2:在子进程中创建新会话:setsid函数能够使进程完全独立出来,从而脱离fork函数父进程的一些控制。
调用setsid的三个作用:让进程摆脱原会话的控制、让进程摆脱原进程组的控制,让进程摆脱原控制终端的控制。
3:改变工作目录:继承了父进程的工作目录,使用chdir定义该进程的工作目录(进程运行期间,目录无法删除)
4:重设文件创建掩码:文件创建掩码是指屏蔽掉文件创建时的对应位,使用umask(0)把文件创建掩码设置为0。
5:关闭文件描述符:关闭从父进程那里继承一些已经打开了的文件。
2:守护进程demo
按照上述的流程,大概知道守护进行的实现流程,整理到感觉比较好的两个demo:
demo1:设置守护进程,监控子进程如果终止做日志记录(注意代码中的目录):
#include <unistd.h> #include <signal.h> #include <sys/param.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/time.h> #include <time.h> #include <fcntl.h> #include <wait.h> #include <stdio.h> #include <stdlib.h> //提升控制 设置守护进程目录 以及输出重定向 int updateSTD() { int fd; umask(0); chdir("/tmp"); long maxfd; if ((maxfd = sysconf(_SC_OPEN_MAX)) != -1) { for (fd = 0; fd < maxfd; fd++) { close(fd); } } fd = open("/dev/null", O_RDWR); if (fd == -1) { return -1; } if (dup2(fd, STDIN_FILENO) == -1) { return -1; } if (dup2(fd, STDOUT_FILENO) == -1) { return -1; } if (dup2(fd, STDERR_FILENO) == -1) { return -1; } if (fd > STDERR_FILENO) { if (close(fd) == -1) { return -1; } } return 0; } int start_daemon(int monitorChild) { switch (fork()) { case -1: //fork失败 退出 return -1; case 0: //创建了一个子进程 继续运行 break; default: //是父进程 退出 exit(0); } //是第一子进程,后台继续执行 if (setsid() == -1) //第一子进程成为新的会话组长和进程组长 { return -1; } //Step2: 父进程监控子进程运行 pid_t pid; int status, i; FILE *fp; time_t t; while(1) { switch (fork()) { case -1: //fork失败 退出 return -1; case 0: //创建了一个子进程 提升权限 作为监控 updateSTD(); return 0; default: //父进程 监控子进程的运行 if(monitorChild == 0) //只是记录日志? { pid = wait(&status); i = WEXITSTATUS(status); if((fp=fopen("/home/hlp/0922/test.log","a")) >=0) { t=time(0); fprintf(fp,"[%s] child's pid = %d . exit status=%d \n",asctime(localtime(&t)),pid, i); fclose(fp); } } else { exit(0); } } } return 0; } //那么如何实现一个 应用进程挂了 使用守护进程监控启动 int main() { time_t t; start_daemon(0); FILE *fp1; while(1)//每隔一分钟向test.log报告运行状态 { sleep(10);//睡眠一分钟 if((fp1=fopen("/home/hlp/0922/test_exec.log","a")) >=0){ t=time(0); fprintf(fp1,"Im here at %s \n",asctime(localtime(&t)) ); fclose(fp1); } } return 0; }
demo2: 守护进程可以实现一些监控功能:
看到一个实例,这里做整理:
监控进程可以实现监控我们的运行进程情况,做记录或者重启。
这里可以衍生,实现达到系统监控的一些功能。
thisisatest.c:守护进程,脱离终端,在后台运行。 实现我们的业务功能:
#include<unistd.h> #include<signal.h> #include<stdio.h> #include<stdlib.h> #include<sys/param.h> #include<sys/types.h> #include<sys/stat.h> #include<time.h> void init_daemon() { int pid; int i; pid=fork(); if(pid<0) exit(1); //创建错误,退出 else if(pid>0) //父进程退出 exit(0); setsid(); //使子进程成为组长 pid=fork(); if(pid>0) exit(0); //再次退出,使进程不是组长,这样进程就不会打开控制终端 else if(pid<0) exit(1); //关闭进程打开的文件句柄 for(i=0;i<NOFILE;i++) close(i); chdir("/root/test"); //改变目录 umask(0);//重设文件创建的掩码 return; } void main() { FILE *fp; time_t t; init_daemon(); while(1) { sleep(60); //等待一分钟再写入 fp=fopen("testfork2.log","a"); if(fp>=0) { time(&t); fprintf(fp,"current time is:%s\n",asctime(localtime(&t))); //转换为本地时间输出 fclose(fp); } } return; }
监控进程monitor.c: 同样是守护进程,但是代码功能实现了监控的功能:
#include<unistd.h> #include<signal.h> #include<stdio.h> #include<stdlib.h> #include<sys/param.h> #include<sys/types.h> #include<sys/stat.h> #include<time.h> #include<sys/wait.h> #include<fcntl.h> #include<limits.h> #define BUFSZ 150 void init_daemon() { int pid; int i; pid=fork(); if(pid<0) exit(1); //创建错误,退出 else if(pid>0) //父进程退出 exit(0); setsid(); //使子进程成为组长 pid=fork(); if(pid>0) exit(0); //再次退出,使进程不是组长,这样进程就不会打开控制终端 else if(pid<0) exit(1); //关闭进程打开的文件句柄 for(i=0;i<NOFILE;i++) close(i); chdir("/root/test"); //改变目录 umask(0);//重设文件创建的掩码 return; } void err_quit(char *msg) { perror(msg); exit(EXIT_FAILURE); } // 判断程序是否在运行 int does_service_work() { FILE* fp; int count; char buf[BUFSZ]; char command[150]; sprintf(command, "ps -ef | grep thisisatest | grep -v grep | wc -l" ); if((fp = popen(command,"r")) == NULL) err_quit("popen"); if( (fgets(buf,BUFSZ,fp))!= NULL ) { count = atoi(buf); } pclose(fp); return count; // exit(EXIT_SUCCESS); } void main() { FILE *fp; time_t t; int count; init_daemon(); while(1) { sleep(10); //等待一分钟再写入 fp=fopen("testfork3.log","a"); if(fp>=0) { count = does_service_work(); time(&t); if(count>0) fprintf(fp,"current time is:%s and the process exists, the count is %d\n",asctime(localtime(&t)), count); //转换为本地时间输出 else { fprintf(fp,"current time is:%s and the process does not exist, restart it!\n",asctime(localtime(&t))); //转换为本地时间输出 system("/home/user/daemon/thisisatest"); //启动服务 注意自己可执行程序的目录 } fclose(fp); } } return; }
该测试程序,如果我终止thisisatest可执行程序,监控程序就会自动重启。
注意:内部涉及一些工作目录,以及日志目录,在测试的时候要关注。