Linux C++ 通信 - 守护进程
明镜止水,长话短说。
普通进程
写一段简单代码模拟持续运行的程序:
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
using namespace std;
int main(){
cout << "程序开始运行!" << endl;
while (1){
sleep(1);
cout << "休眠1s!" << endl;
}
return 0;
}
编译执行:
root@ubuntu:/home/qiye# g++ -o t143 /mnt/hgfs/c/t143.cpp
root@ubuntu:/home/qiye# ./t143
程序开始运行!
休眠1s!
休眠1s!
休眠1s!
休眠1s!
ll休眠1s! #在前台持续运行的程序的终端使用ll命令不会有结果
休眠1s!
进程查看:
root@ubuntu:/home/qiye# ps -eo pid,ppid,sid,tty,pgrp,comm,stat | grep -E 'bash|PID|t143'
PID PPID SID TT PGRP COMMAND STAT
2631 2626 2631 pts/0 2631 bash Ss
2700 2699 2631 pts/0 2700 bash S+
2988 2987 2988 pts/1 2988 bash Ss
3162 3161 2988 pts/1 3162 bash S
6974 3162 2988 pts/1 6974 t143 S+
观察发现普通进程的特点:
1.程序进程是有对于终端的,终端一旦退出,对于的进程也会跟着消失。
2.程序的前台运行导致终端无法使用其他命令。
守护进程
为了解决普通进程的弊端,我们需要使用守护进程。
守护进程是一种长期运行的进程,这种进程在后台运行,不与任务终端关联。守护进程特点如下:
- 生存期长,一般操作系统启动的时候它就启动,操作系统关闭时它就关闭。
- 守护进程与终端无关,哪个终端的退出都不影响守护进程。
- 守护进程是在系统后台运行的,不是在终端后台运行的。
#查看开头几个进程
root@ubuntu:/home/qiye# ps -efj
UID PID PPID PGID SID C STIME TTY TIME CMD
root 1 0 1 1 0 04:56 ? 00:00:02 /sbin/init auto noprompt
root 2 0 0 0 0 04:56 ? 00:00:00 [kthreadd]
root 3 2 0 0 0 04:56 ? 00:00:00 [rcu_gp]
root 4 2 0 0 0 04:56 ? 00:00:00 [rcu_par_gp]
- PPID为0的时内核进程,随操作系统的启动而启动,生命周期贯穿整个系统,他不与终端挂钩,属于超级用户特权进程。
- CMD字段里许多带中括号的叫内核守护进程。
- init进程是系统守护进程,负责启动各运行层次特定的系统服务,它会收留孤儿进程。
- 还有一些用户级的守护进程,如sshd、rsyslogd、cron等。
守护进程编写
调用umask(0)
umask既是命令,也是个函数,都是用来限制文件权限的,正常使用时,传进去的参数一般为0,目的是为了不让它限制文件权限。
fork出的子进程为守护进程
理由:
- 进程是通过shell启动的,那么父进程的终止会让shell以为这条命令执行完毕,会把终端空出来,终端就能做其他事。
- 父进程无法使用setsid函数建立新会话,只能是子进程来调用1setsid函数来成为一个新会话。
- 子进程调用setsid创建新会话,会拥有一个单独的SID,成为一个新进程组的组长,并且没有所属的终端。
代码编写:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<sys/stat.h>
#include<fcntl.h>
//守护进程编写
int ngx_daemon(){
int fd;
switch(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(这是规矩,已经打开的描述符,
//改动之前先关闭),类似于指针指向null,让/dev/null成为标准输入
//记录错误日志
return -1;
}
if ( dup2( fd, STDOUT_FILENO ) == -1 ){ //先关闭STDOUT_FILENO,类似于指针指向null,让/dev/null成为标准输出
//记录错误日志
return -1;
}
if ( fd > STDERR_FILENO ){
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);
printf("休息1s, 进程id=%d!\n", getpid()); // 即使打印也没用,现在标准输出指向黑洞,打印不出任何结果
}
}
return 0;
}
编译运行并查看该进程:
root@ubuntu:/home/qiye# g++ -o t_d /mnt/hgfs/c/nginx/d/d.cpp
root@ubuntu:/home/qiye# ./t_d
root@ubuntu:/home/qiye#
root@ubuntu:/home/qiye# ps -eo pid,ppid,sid,tty,pgrp,comm,stat | grep -E 'PID|t_d'
PID PPID SID TT PGRP COMMAND STAT
76169 1 76169 ? 76169 t_d Ss
可以发现,现在这个进程的父进程为init,没有所属终端,且没有输出内容,因为内容都输出到黑洞/dev/null中去了。