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介绍
#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版本以上就不能使用
检验
将文件删除后关机重启,看到有文件生成。点开文件后,不停有提示该文件内容发生改变,这样基本上就成功了。

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