最近在搞android的进程保活,目前发下在5.0以上机器上,force kill 会把同组进程组的全部进程kill,就要研究下如何在JNI中实现守护进程
什么是守护进程
Linux Daemon(守护进程)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。它不需要用户输入就能运行而且提供某种服务,不是对整个系统就是对某个用户程序提供服务。Linux系统的大多数服务器就是通过守护进程实现的。常见的守护进程包括系统日志进程syslogd、 web服务器httpd、邮件服务器sendmail和数据库服务器mysqld等。
守护进程一般在系统启动时开始运行,除非强行终止,否则直到系统关机都保持运行。守护进程经常以超级用户(root)权限运行,因为它们要使用特殊的端口(1-1024)或访问某些特殊的资源。
一个守护进程的父进程是init进程,因为它真正的父进程在fork出子进程后就先于子进程exit退出了,所以它是一个由init继承的孤儿进程。
守护进程是非交互式程序,没有控制终端,所以任何输出,无论是向标准输出设备stdout还是标准出错设备stderr的输出都需要特殊处理。
守护进程的名称通常以d结尾,比如sshd、xinetd、crond等
基础知识
1. 进程组
- 每个进程都属于一个进程组
- 每个进程组都有一个进程组号,该号等于该进程组组长的PID号 .
- 一个进程只能为它自己或子进程设置进程组ID号
1.1 获取进程组id
#include<unistd.h>
//getpgrp()用来取得目前进程所属的组识别码。
//此函数相当于调用getpgid(0);返回目前进程所属的组识别码。 getpgid(0)=getpgrp()
pid_t getpgrp(void);
//如果参数pid为0,则会取得目前进程的组识别码。执行成功则返回组识别码,如果有错误则返回-1,错误原因存于errno中
pid_t getpgid( pid_t pid);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
pid_t pid;
if ((pid=fork())<0) {
printf("fork error!");
}else if (pid==0) {
printf("The child process PID is %d.\n",getpid());
printf("The Group ID is %d.\n",getpgrp());
printf("The Group ID is %d.\n",getpgid(0));
printf("The Group ID is %d.\n",getpgid(getpid()));
exit(0);
}
sleep(3);
printf("The parent process PID is %d.\n",getpid());
printf("The Group ID is %d.\n",getpgrp());
return 0;
}
进程组id = 父进程id,即父进程为组长进程
1.2 组长进程与设置进程组id
- 组长进程标识: 其进程组ID==其进程ID
- 组长进程可以创建一个进程组,创建该进程组中的进程,然后终止
- 只要进程组中有一个进程存在,进程组就存在,与组长进程是否终止无关
- 进程组生存期: 进程组创建到最后一个进程离开(终止或转移到另一个进程组)
setpgid()加入一个现有的进程组或创建一个新进程组
#include<unistd.h>
//将参数pid指定进程所属的组id设为参数pgid指定的组id
//如果参数pid 为0,则会用来设置目前进程的组识别码,如果参数pgid为0,则由pid指定的进程ID将用作进程组ID
//一个进程可以为自己或子进程设置进程组ID:
int setpgid(pid_t pid,pid_t pgid);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
pid_t pid;
if ((pid=fork())<0) {
printf("fork error!");
exit(1);
}else if (pid==0) {
printf("The child process PID is %d.\n",getpid());
printf("The Group ID of child is %d.\n",getpgid(0)); // 返回组id
sleep(5);
printf("The Group ID of child is changed to %d.\n",getpgid(0));
exit(0);
}
sleep(1);
setpgid(pid,pid); // 改变子进程的组id为子进程本身
sleep(5);
printf("The parent process PID is %d.\n",getpid());
printf("The parent of parent process PID is %d.\n",getppid());
printf("The Group ID of parent is %d.\n",getpgid(0));
setpgid(getpid(),getppid()); // 改变父进程的组id为父进程的父进程
printf("The Group ID of parent is changed to %d.\n",getpgid(0));
return 0;
}
2. 会话
会话: 一个或多个进程组的集合
- 开始于用户登录
- 终止与用户退出
- 此期间所有进程都属于这个会话期
2.1 获取会话id
会话ID:会话首进程的进程组ID
#include <unistd.h>
//getsid(0) 返回当前进程的会话ID.
//getsid(p) 返回p的会话ID.
//On error, (pid_t) -1 will be returned, and errno is set appropriately.
pid_t getsid(pid_t pid);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
pid_t pid;
if ((pid=fork())<0) {
printf("fork error!");
exit(1);
}else if (pid==0) {
printf("The child process PID is %d.\n",getpid());
printf("The Group ID of child is %d.\n",getpgid(0));
printf("The Session ID of child is %d.\n",getsid(0));
sleep(10);
setsid(); // 子进程非组长进程,故其成为新会话首进程,且成为组长进程。该进程组id即为会话进程
printf("Changed:\n");
printf("The child process PID is %d.\n",getpid());
printf("The Group ID of child is %d.\n",getpgid(0));
printf("The Session ID of child is %d.\n",getsid(0));
sleep(20);
exit(0);
}
return 0;
}
在子进程中调用setsid()后,子进程成为新会话首进程,且成为一个组长进程,其进程组id等于会话id
2.2 设置新的会话id
pid setsid(void)函数:成功返回 会话id 失败返回-1
说明:
当进程是会话组长时setsid()调用失败。setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。由于会话过程对控制终端的独占性,进程同时与控制终端脱离。
- 该调用进程是组长进程,则出错返回
- 先调用fork, 父进程终止,子进程调用
- 该调用进程不是组长进程,则创建一个新会话
- 该进程变成新会话首进程(session header)
- 该进程成为一个新进程组的组长进程。
- 此进程没有控制终端,如果在调用setsid前,该进程有控制终端,那么与该终端的联系被解除。 如果该进程是一个进程组的组长,此函数返回错误。
- 为了保证这一点,我们先调用fork()然后exit(),此时只有子进程在运行
组长进程不能成为新会话首进程,新会话首进程必定会成为组长进程…
用途:
- 之前parent和child运行在同一个session里,parent是会话(session)的领头进程,
- parent进程作为会话的领头进程,如果exit结束执行的话,那么子进程会成为孤儿进程,并被init收养。
- 执行setsid()之后,child将重新获得一个新的会话(session)id。
- 这时parent退出之后,将不会影响到child了
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
pid_t pid;
if ((pid=fork())<0) {
printf("fork error!");
exit(1);
}else if (pid==0) {
printf("The child process PID is %d.\n",getpid());
printf("The Group ID of child is %d.\n",getpgid(0));
printf("The Session ID of child is %d.\n",getsid(0));
sleep(10);
setsid(); // 子进程非组长进程,故其成为新会话首进程,且成为组长进程。该进程组id即为会话进程
printf("Changed:\n");
printf("The child process PID is %d.\n",getpid());
printf("The Group ID of child is %d.\n",getpgid(0));
printf("The Session ID of child is %d.\n",getsid(0));
sleep(20);
exit(0);
}
return 0;
}
控制终端
会话可以包含多个进程组。这些进程组共享一个控制终端。这个控制终端通常是创建进程的登录终端。 控制终端,登录会话和进程组通常是从父进程继承下来的。
如果要实现守护进程,我们的目的就是要摆脱它们,使之不受它们的影响。
创建守护进程的步骤
- 创建fork子进程,父进程exit退出(第一次fork)
- 【目的】:所有工作在子进程中进行
- 形式上脱离了控制终端:
(为避免挂起控制终端将Daemon放入后台执行。方法是在进程中调用fork使父进程终止,让Daemon在子进程中后台执行。 )
在子进程中创建新会话setsid
- 【目的】:使子进程完全独立出来,脱离控制终端
- 【目的】:setsid后,进程已经成为无终端的会话组长。但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端:再fork一次(第二次fork)
改变当前目录为根目录 chdir()
- 防止占用可卸载的文件系统
- 也可以换成其它路径
- 【目的】:进程活动时,其工作目录所在的文件系统不能卸下。一般需要将工作目录改变到根目录。对于需要转储核心,写运行日志的进程将工作目录改变到特定目录如/tmpchdir(“/”)
- 重设文件权限掩码 umask()
- 防止继承的文件创建屏蔽字拒绝某些权限
- 增加守护进程灵活性
- 【目的】:进程从创建它的父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩模清除
- 关闭文件描述符
- 继承的打开文件不会用到,浪费系统资源,无法卸载
- getdtablesize()
- 返回所在进程的文件描述符表的项数,即该进程打开的文件数目
示例
调用创建守护进程函数,每隔一秒向/tmp目录下的print_time文件打印系统时间
/*name: init_deamon.c
*function:创建一个守护进程
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
void init_deamon(void)
{
int pid;
int i;
/* 处理SIGCHLD信号。处理SIGCHLD信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。*/
if(signal(SIGCHLD,SIG_IGN) == SIG_ERR){
printf("Cant signal in init_daemon.");
exit(1);
}
if(pid=fork()>0)
exit(0);//【1】是父进程,结束父进程
else if(pid< 0){
perror("fail to fork1");
exit(1);//fork失败,退出
}
//是第一子进程,后台继续执行
setsid();//【2】第一子进程成为新的会话组长和进程组长
//并与控制终端分离
if(pid=fork()>0)
exit(0);//【2.1】是第一子进程,结束第一子进程
else if(pid< 0)
exit(1);//fork失败,退出
//是第二子进程,继续
//第二子进程不再是会话组长
chdir("/tmp");//【3】改变工作目录到/tmp
umask(0);//【4】重设文件创建掩模
for(i=0;i< getdtablesize();++i)//【5】关闭打开的文件描述符
close(i);
return;
}
/* name :test.c
* function :调用init_deamon函数使进程变成守护进程,然后每个一秒向/tmp目录下的print_time文件打印当前时间
* */
#include <stdio.h>
#include <time.h>
void init_deamon(void);//守护进程初始化函数
void main()
{
FILE *fp;
time_t t;
init_deamon();//初始化为Daemon
while(1)//每隔一分钟向test.log报告运行状态
{
sleep(1);//睡眠一秒钟
if((fp=fopen("print_time","a")) >=0)
{
t=time(0);
fprintf(fp,"The time right now is : %s",asctime(localtime(&t)));
fclose(fp);
}
}
return;
}
手动去查看文档: