嵌入式linux系统下简单守护进程(daemon)的编写
最近公司项目需要,需要在我们的嵌入式linux设备中创建一个守护进程,用于保护系统中的主进程,防止某些不可预期的意外导致主进程异常结束后,系统完全宕机没有任何反应,破坏用户体验感。但是,查阅诸多资料之后发现,大部分人都只讲述了如何在x86平台上创建和实现守护进程,而并没有人介绍过如何在嵌入式平台上创建和实现守护进程。于是,经过一番摸索之后,从原理到代码,都进行了一些大致的了解,我自己提出了一些想法。下面就进行一下简单的总结和整理。
1、技术原理
下面是网上摘抄的,关于x86的linux系统中对于守护进程的介绍和描述。
守护进程(Daemon)是一种运行在后台的一种特殊的进程,它独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。
守护进程是个特殊的孤儿进程,这种进程脱离终端,为什么要脱离终端呢?之所以脱离于终端是为了避免进程被任何终端所产生的信息所打断,其在执行过程中的信息也不在任何终端上显示。由于在 Linux 中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端,当控制终端被关闭时,相应的进程都会自动关闭。但是守护进程却能突破这种限制,它脱离于终端并且在后台运行,并且它脱离终端的目的是为了避免进程在运行的过程中的信息在任何终端中显示并且进程也不会被任何终端所产生的终端信息所打断。它从被执行的时候开始运转,知道整个系统关闭才退出(当然可以认为的杀死相应的守护进程)。如果想让某个进程不因为用户或中断或其他变化而影响,那么就必须把这个进程变成一个守护进程。
2、设计步骤
对于x86平台的linux系统,理论上来说,要想实现上述的效果,守护进程具有一套严格的实现步骤。也就是说,守护进程必须在启动伊始,就去除掉一些系统相关的限制,这样才能稳定的在后台运行,而不至于被其他任务所干扰和影响。
下面是在x86平台编写守护进程的基本过程:
- 屏蔽一些控制终端操作的信号。这是为了防止守护进行在没有运行起来前,控制终端受到干扰退出或挂起。关于信号的更详细用法,请看《信号中断处理》。
- 在后台运行。这是为避免挂起控制终端将守护进程放入后台执行。方法是在进程中调用 fork() 使父进程终止, 让守护进行在子进程中后台执行。
- 脱离控制终端、登录会话和进程组。有必要先介绍一下 Linux 中的进程与控制终端,登录会话和进程组之间的关系:进程属于一个进程组,进程组号(GID)就是进程组长的进程号(PID)。登录会话可以包含多个进程组。这些进程组共享一个控制终端。这个控制终端通常是创建进程的 shell 登录终端。 控制终端、登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们 ,使之不受它们的影响。因此需要调用 setsid() 使子进程成为新的会话组长。setsid() 调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。由于会话过程对控制终端的独占性,进程同时与控制终端脱离。
- 禁止进程重新打开控制终端。现在,进程已经成为无终端的会话组长,但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端,采用的方法是再次创建一个子进程。
- 关闭打开的文件描述符。进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误。
- 改变当前工作目录。进程活动时,其工作目录所在的文件系统不能卸下。一般需要将工作目录改变到根目录。对于需要转储核心,写运行日志的进程将工作目录改变到特定目录如 /tmp。
- 重设文件创建掩模。进程从创建它的父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存取权限。为防止这一点,必须将文件创建掩模清除。
- 处理 SIGCHLD 信号。对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源(关于僵尸进程的更多详情,请看《僵尸进程》)。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在 Linux 下可以简单地将 SIGCHLD 信号的操作设为 SIG_IGN 。这样,内核在子进程结束时才不会产生僵尸进程。
-
下面就是摘自某前辈的博客上的全套源码:
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/syslog.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h> int init_daemon(void)
{ int pid; int i; // 1)屏蔽一些控制终端操作的信号 signal(SIGTTOU,SIG_IGN); signal(SIGTTIN,SIG_IGN); signal(SIGTSTP,SIG_IGN); signal(SIGHUP ,SIG_IGN); // 2)在后台运行 if( pid=fork() ){// 父进程 exit(0);//结束父进程,子进程继续 }else if(pid< 0){// 出错 perror("fork"); exit(EXIT_FAILURE); } // 3)脱离控制终端、登录会话和进程组 setsid(); // 4)禁止进程重新打开控制终端 if( pid=fork() ){// 父进程 exit(0);// 结束第一子进程,第二子进程继续(第二子进程不再是会话组长) }else if(pid< 0){// 出错 perror("fork"); exit(EXIT_FAILURE); } // 5)关闭打开的文件描述符 // NOFILE 为 <sys/param.h> 的宏定义 // NOFILE 为文件描述符最大个数,不同系统有不同限制 for(i=0; i< NOFILE; ++i){ close(i); } // 6)改变当前工作目录 chdir("/tmp"); // 7)重设文件创建掩模 umask(0); // 8)处理 SIGCHLD 信号 signal(SIGCHLD,SIG_IGN); return 0;
} int main(int argc, char *argv[])
{ init_daemon(); while(1); return 0;
}
3、实际情况
从上面的流程逻辑和实际代码可以看出,x86平台的守护进程,其实还是比较复杂的,需要进行一堆比较繁琐的初始化过程。然而,对于嵌入式平台而言,流程似乎可以简化一些,不用这么复杂的处理。因为,在本次嵌入式系统中启用守护进程。其目的只是简单的利用这个守护进程来启动另一个被守护的进程,然后定时监控该进程是否仍在正常运行,一旦发现其运行异常,则立即重启该进程就好。
所以,我对上述的流程进行了简化,得到如下的流程:
- 在守护进程中启动需要被监视的进程。
- 在守护进程中创建一个线程,用来定时监测被守护的进程的运行状态
- 守护进程判断被守护的进程是否仍在正常运行,一旦发现其运行异常,则立即重启该进程。
-
4、实际源码
以下就是在本嵌入式系统项目中所设计的守护进程模块的全套代码。
/**************************************************************************************************
** 函数名称: lockfile
** 功能描述: 对文件加锁/解锁
** 输入参数: lock: 1表示进行加锁处理,0表示进行解锁处理
** 输出参数: 无
** 返回参数: 无
**************************************************************************************************/
int tryto_lockfile(int fd, int lock)
{struct flock fl;fl.l_type = (lock == 1) ? F_WRLCK : F_UNLCK;fl.l_start = 0;fl.l_whence = SEEK_SET;fl.l_len = 0;return (fcntl(fd, F_SETLK, &fl));
}/**************************************************************************************************
** 函数名称: get_proc_running_state
** 功能描述: 获取进程运行状态
** 输入参数: 无
** 输出参数: 无
** 返回参数: 返回-1表示路径错误
** 返回参数: 返回0表示进程从未运行过,返回1表示进程曾经运行过但是现在停止运行了,返回2表示进程正在运行中
**************************************************************************************************/
static int get_proc_running_state(const char* filename)
{int fd;if (filename == NULL) { /* 文件名为空 */return -1;}fd = open(filename, O_RDWR, (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH));if (fd < 0) { /* 文件不存在,表示进程从未运行过 */return 0;}if (tryto_lockfile(fd, 1) == -1) { /* 文件加锁失败,表示进程在运行中 */close(fd);return 2;} else { /* 文件加锁成功,表示进程已经消失 */tryto_lockfile(fd, 0); /* 此处要注意记得解锁和关闭文件 */close(fd);return 1;}
}/**************************************************************************************************
** 函数名称: proc_watch
** 功能描述: 检测进程是否有在运行,没有运行则重新启动之
** 输入参数: procname: 进程名
** 输出参数: 无
** 返回参数: 返回-1表示进程从未运行过;返回0表示进程当前运行正常;
** 返回参数: 返回其他非零值表示进程不存在且已被重新启动,返回的值是新的pid值
**************************************************************************************************/
int proc_watch(const char *procname)
{int result, state;char filename[100];result = 0;sprintf(filename, "/var/run/%s.pid", procname);state = get_proc_running_state(filename);switch (state){case 0:result = -1;break;case 1:result = start_proc_by_name(procname);break;case 2:result = 0;break;default:break;}return result;
}/**************************************************************************************************
** 函数名称: start_proc
** 功能描述: 启动进程开始运行
** 输入参数: 无
** 输出参数: 无
** 返回参数: 进程的ID号,若启动失败则返回0
**************************************************************************************************/
int start_proc_by_name(const char* procname)
{pid_t pid, child_pid;char filename[100];sprintf(filename, "%s%s", PROC_FILE_PATH, procname);child_pid = 0;if (access(filename, X_OK | F_OK) != 0) { /* 如果文件存在,并且可执行 */return 0;}pid = fork(); /* 首先要fork一个进程出来 */if (pid < 0) { /* 创建进程失败 */return 0;} else if (pid == 0) { /* 创建进程成功,此处是子进程的代码 */if (execl(filename, procname, (char *)NULL) != -1) {return 1;} else {return 0;}} else { /* 创建进程成功,此处是父进程代码 */child_pid = pid;}return (int)child_pid;
}/**************************************************************************************************
** 函数名称: thread_client_hdl
** 功能描述: client进程监视线程
** 输入参数: 无
** 输出参数: 无
** 返回参数: 无
**************************************************************************************************/
static void *thread_client_hdl(void *pdata)
{int result;pdata = pdata;sleep(10); /* 第一次要进行延时 */for (;;) {printf("time to check thread_client...\n");result = proc_watch(PROC_NAME_CLIENT);if (result == -1) {printf("thread_client never exist...\n");} else if (result == 0) {printf("thread_client running ok...\n");} else {printf("thread_client has gone! but restarted...\n");}sleep(10);}return NULL;
}/**************************************************************************************************
** 函数名称: main
** 功能描述: 入口主函数
** 输入参数: 无
** 输出参数: 无
** 返回参数: 无
**************************************************************************************************/
int main(int argc, char *argv[])
{int client_para;char *p, *process_name;pthread_t thread_client;process_name = argv[0]; /* 获取进程名称 */p = process_name + strlen(process_name);while (*p != '/' && p != process_name) {p--;}if (*p == '/') {process_name = p + 1;}printf("\"%s\" starting...\n", process_name);client_para = 0x01;if (pthread_create(&thread_client, NULL, thread_client_hdl, &client_para) != 0) {printf("create thread_client failed!\n");return 1;}if (start_proc_by_name(PROC_NAME_CLIENT) == 0) {printf("start thread_client failed!\n");return 1;}for (;;) {sleep(60);printf("i am still alive...\n");}return 0;
}
5、其他说明
待补充
6、参考文献
1、http://blog.csdn.net/lianghe_work/article/details/47659889
2、http://blog.csdn.net/liangxanhai/article/details/7752898
嵌入式linux系统下简单守护进程(daemon)的编写相关推荐
- Linux系统编程之--守护进程的创建和详解【转】
本文转载自:http://www.cnblogs.com/mickole/p/3188321.html 一,守护进程概述 Linux Daemon(守护进程)是运行在后台的一种特殊进程.它独立于控制终 ...
- 【Linux系统编程】守护进程、线程
------------->[Linux系统编程/网络编程](学习目录汇总) <-------------- 目录 1.守护进程 1.1 进程组 1.2 会话 1.3 setsid()函数 ...
- 通过python实现linux切换用户_Python实现在Linux系统下更改当前进程运行用户
在上一篇文章中,我们讲了如何在linux上用python写一个守护进程.主要原理是利用linux的fork函数来创建一个进程,然后退出父进程运行,生成的子进程就会成为一个守护进程.细心观察的可能会发现 ...
- python做Linux进程运行,Python实现在Linux系统下更改当前进程运行用户
在上一篇文章中,我们讲了如何在linux上用python写一个守护进程.主要原理是利用linux的fork函数来创建一个进程,然后退出父进程运行,生成的子进程就会成为一个守护进程.细心观察的可能会发现 ...
- Linux系统的护花使者-----守护进程
**************************************************************************************************** ...
- 怎么查看linux系统下数据库的进程数,如何查看sybase数据库运行情况
sybase数据库是不少企业管理系统的常用数据库,可运行在windows和linux等操作系统环境之下,适用性广. 1.检查数据库是否运行.检查步骤 :操作系统命令Sun solaris 和unix ...
- ndis拨号软件 linux,嵌入式linux系统下NDIS拨号
一.USB端口信息 U8300C模块USB会枚举出6个逻辑端口,他们的枚举顺序.端口功能.端口名称如下表所示: 二.驱动集成 1.内核配置: make menuconfig 到内核时: Device ...
- linux java进程消失_Linux系统下的Java进程无故消失怎么办?
Linux系统步骤的一些Java项目总是无故的消失,原来是Java进程被关闭掉了.为什么会出现这种情况呢?有可能是被系统自动清除多余进程,或是其他程序关掉了Java项目,这个时候该怎么办呢? 解决方法 ...
- 构建嵌入式linux系统_用于构建嵌入式Linux系统的4种工具
构建嵌入式linux系统 Linux正在被部署到比Linus Torvalds在他的宿舍里工作的设备更多的设备中. 受支持的各种芯片架构令人震惊,并导致各种大小的设备都使用Linux. 从庞大的IBM ...
最新文章
- 多视图立体匹配论文分享:P-MVSNet
- Tomcat unable to start within 45 seconds.
- python内存管理错误的是_关于Python内存管理,下列说法错误的是
- 画面逐渐放大_日本80后画“人体妖女”,画面诡异,放大10倍越看越可怕
- VS cmake 远程开发 opencv报错:CMake was unable to find a build program corresponding to “Ninja“.(换个构建方式)
- 结构体中vector自动为0_面试题:你是如何选择顺序存储数据结构的?
- 合并K个有序数组(链表)【字节跳动面试算法题】
- ubuntu 下可以尝试还不错的屏幕截图工具: flameshot
- JAVA自定义变量_Java 系统自定义变量-D
- Windows 8.1 轻量接触接触方式部署(二)
- 字符、字符串和文本的处理之String类型
- MySQL计算两个日期相差的天数、月数、年数
- SpringSecurity3.0.4的An AuthenticationManager is...
- Win10 桌面图标出现空文件夹的删除及桌面图标排列问题
- 2022-2027年中国电饭煲自动检测线行业市场全景评估及发展战略规划报告
- 【C语言】exit(0)与exit(1)有什么区别
- python给excel排序_Python实现EXCEL表格的排序功能示例
- CSS3伪类选择器:nth-child(n)及:nth-of-type(n)使用区别探究总结
- MBT测试实例:做个“机器人”,使其随机、持续的对“web页面”做交互性测试--补充篇1一些思考
- 【19调剂】齐鲁工业大学(山东省科学院)2019年硕士研究生预调剂信息
热门文章
- [系统底层] x86和x64下ssdt的差异
- spring-boot 自定义启动图标彩蛋
- “指定的文件名无效或太长” 无法拷贝,咋办
- CCF NOI1035 数根
- windows2008安装
- web.xml配置(转)
- 北邮王立新教授提到的“5年后运营商排名论”
- 全球计算机科学硕士申请,2019爱尔兰留学都柏林大学计算机科学硕士申请
- android落下动画,Android应用开发android 购物车小球掉落动画
- c语言开发 .c .h,求助C语言大佬 , 只会写到一个.c文件里 ,不会用.h头文件