Linux守护进程编写

Linux守护进程编写

环境:VMware 15 + ubuntu 16 内核:4.19

护进程的特点

  • 后台服务程序,随系统启动而启动。

  • 很多系统服务通过守护进程完成。

    • 守护进程的名字往往以字母‘d’结尾
  • 生存周期长。系统装入时启动,系统关闭时终止。

  • 周期性的执行某种任务或等待处理某些特别的事件。

  • 在Linux中,每一个与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端。但守护进程能突破这种限制,它从被执行开始,直到整个系统关闭时才退出.

依附:当控制终端被关闭时,依附于这个终端的进程都会自动的关闭。

守护进程编程一般步骤

  • 第一步、创建子进程、父进程退出

    编写守护进程的第一步就是使其独立与父进程,可以将父进程退出,子进程变成孤儿进程,并由init进程收养。为避免终端挂起,将父进程退出,造成程序已经退出的假象,所有后面的工作都在子进程完成,这样控制终端也可以继续执行其他命令,从而在形式上脱离控制终端的控制。

    • pid = fork(); //创建子进程

    • if (pid > 0)

      exit(0); //父进程退出

  • 第二步、在子进程中创建新的会话

    经过第一步,子进程已经有后台运行,而在fork()的时候,子进程复制了大量父进程的PCB,包括会话、进程组、控制终端等信息。尽管父进程已经退出,但子进程的会话、进程组、控制终端的信息没有改变。为使子进程完全摆脱父进程的环境,需要调用 setsid 函数。

    • setsid( ) //让进程脱离控制终端。

      通过调用 setsid 函数可以创建一个新会话,调用进程担任新会话的首进程,其作用有:

      • 使当前进程脱离原会话的控制
      • 使当前进程脱离原进程组的控制
      • 使当前进程脱离原控制终端的控制
  • 第三步、改变当前目录为根目录

    • chdir(“/”); //避免原工作目录不能被卸载

      直接调用 chdir 函数将切换到根目录下。
      由于进程运行过程中,当前目录所在的文件系统(如:“/mnt/usb”)是不能卸载的,为避免对以后的使用造成麻烦,改变工作目录为根目录是必要的。如有特殊需要,也可以改变到特定目录,如“/tmp”。

  • 第四步、重设文件权限掩码

    • umask(0); // 文件权限掩码设置成为0

      fork 函数创建的子进程,继承了父进程的文件操作权限,为防止对以后使用文件带来问题,需要重设文件权限掩码。调用 umask 设置文件权限掩码,通常是重设为 0,清除掩码,这样可以大大增强守护进程的灵活性。

      权限掩码:设定了文件权限中要屏蔽掉的对应位。这个跟文件权限的八进制数字模式表示差不多,将现有存取权限减去权限掩码(或做异或运算),就可产生新建文件时的预设权限。

  • 第五步、关闭文件描述符

    • for(i = 0;i < MAXFILE;i++)

      close(i); //关闭继承自父进程的文件

      同文件权限掩码一样,子进程可能继承了父进程打开的文件,而这些文件可能永远不会被用到,但它们一样消耗系统资源,而且可能导致所在的文件系统无法卸下,因此需要一一关闭它们。由于守护进程脱离了终端运行,因此标准输入、标准输出、标准错误输出这3个文件描述符也要关闭。通过调用函数 getdtablesize 返回进程文件描述符表中的项数(即打开的文件数目):

      for (i=0; i < getablesize(); i++)
          close(i);

测试守护进程是否正常运行

/*
	向文件/home/emmmm/Desktop/program.log中隔一秒钟写入时间
*/
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<time.h>
#include<string.h>
#include<stdlib.h>

int main(void){
    pid_t child;
    time_t rawTime;
	struct tm *timeInfor;
    FILE *pf;
    child = fork();
    if(child){
        exit(0);
    }
    setsid();//让进程脱离控制终端
    chdir("/");//避免原工作目录不被卸载
    umask(0);//文件权限掩码设置为0
    for(int i = 0; i<getdtablesize();i++){//关闭继承自父进程的文件
        close(i);
    }
    
    for(int i = 0;i<60;i++){
        pf = fopen("/home/emmmm/Desktop/program.log", "a");
        if(pf==NULL){
            printf("can't open the file!");
            return 0;
        }
        time(&rawTime);
	    timeInfor = localtime(&rawTime);
        fprintf(pf, "pid = %d,and the time is ", getpid());
        fprintf(pf, "%d%d%d%d%d%d%d%d%d%d%d\n", timeInfor->tm_year + 1900, 							(timeInfor->tm_mon + 1)/10,
                (timeInfor->tm_mon + 1)%10, 
                (timeInfor->tm_mday)/10, 
                (timeInfor->tm_mday) % 10, 
                (timeInfor->tm_hour)/10, 
                (timeInfor->tm_hour)%10, 
                (timeInfor->tm_min)/10, 
                (timeInfor->tm_min)%10, (timeInfor->tm_sec)/10, 
                (timeInfor->tm_sec)%10);
        fclose(pf);
        sleep(1);
    }
    return 0;

}

测试结果:
在这里插入图片描述

如何获得正在运行的进程

方法一:

在目录/proc/下有许多以数字命名的文件夹,这个目录下储存着系统所有进程的task_struct结构,研究了一下发现,那些数字命名的文件夹是正在运行进程的pid,进入这些文件夹找到state文件,这里面记录了正在运行pid的名称、状态等等信息。可以搜索/proc/目录来查看。

/*程序的状态*/
static const char * const task_state_array[] = {
	"R (running)",		/* 0x00 */
	"S (sleeping)",		/* 0x01 */
	"D (disk sleep)",	/* 0x02 */
	"T (stopped)",		/* 0x04 */
	"t (tracing stop)",	/* 0x08 */
	"X (dead)",		/* 0x10 */
	"Z (zombie)",		/* 0x20 */
	"P (parked)",		/* 0x40 */

	/* states beyond TASK_REPORT: */
	"I (idle)",		/* 0x80 */
}

1.搜索该目录下的子文件名称:获取文件夹中文件夹的名称

2.读取/proc/xxx/state文件中关于进程的信息:一定要注意名字的长度管够,不然可能出现乱码

/*已知PID在文件中查找进程的状态*/
char* getNamebyPID(char *pid){
    char *name = (char*)malloc(sizeof(char*)*PRONUM);
    char path[BUFNUM];
    char buf[BUFNUM];

    sprintf(path, "/proc/%s/status", pid);
    FILE *pf = fopen(path, "r");
    if(pf == NULL){
        return name;
    }
    else{
        if( fgets(buf, BUFNUM-1, pf)== NULL ){
             fclose(pf);
             return name;
         }
        fclose(pf);
        sscanf(buf, "%*s %s", name);
    }

    return name;
}

方法二:

建立和命令行之间的管道,将输入命令行的结果重定向给文件流,通过文件流可以读取结果

参考:popen介绍

popen 建立管道

#include<stdio.h>//测试popen()

int main(void){
    FILE *fp;
    char buf[1024];
    fp = popen("ps -A -o pid,user", "r");
    if(fp==NULL){
        printf("The commad doesn't work!");
        return 0;
    }
    else {
        while(fgets(buf, 1023, fp)!=NULL){
            printf("%s", buf);
        }
        pclose(fp);
    }
    return 0;
}

测试结果:
在这里插入图片描述

如何获得运行程序的起止时间

我选用的方法是每隔1秒钟判断查看一次正在运行的程序,如果程序在文件中出现,更新其终止时间,如果进程在原来的文件中不存在,则将不存在的进程相关信息写到文件的结尾。

但是还有一种方法,用上面的popen()方法,查看PS -A -o lstart,etime(etime表示进程持续运行的时间)

将该程序加入开机自启

我的内核版本是4.19的,因此用这个方法可以成功,但是在5.0.1版本以上就不能使用

开机自启for4.19

开机自启for5.0.1

检验

将文件删除后关机重启,看到有文件生成。点开文件后,不停有提示该文件内容发生改变,这样基本上就成功了。

在这里插入图片描述

参考:

Linux——ps指令介绍

通过pid 获取运行程序的名称

已知进程名获得pid

Linux下守护进程的实现

linux下是时间转字符串和字符串转时间

sscanf()函数介绍(真的超好用!!!!)

多种文件操作和字符串操作:fseek(), strcmp(), strcat()


版权声明:本文为AnnaZhan原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。