热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

嵌入式linux系统下简单守护进程(daemon)的编写

最近公司项目需要,需要在我们的嵌入式linux设备中创建一个守护进程,用于保护系统中的主进程,防止某些不可预期的意外导致主进程异常结束后,系统完

最近公司项目需要,需要在我们的嵌入式linux设备中创建一个守护进程,用于保护系统中的主进程,防止某些不可预期的意外导致主进程异常结束后,系统完全宕机没有任何反应,破坏用户体验感。但是,查阅诸多资料之后发现,大部分人都只讲述了如何在x86平台上创建和实现守护进程,而并没有人介绍过如何在嵌入式平台上创建和实现守护进程。于是,经过一番摸索之后,从原理到代码,都进行了一些大致的了解,我自己提出了一些想法。下面就进行一下简单的总结和整理。

1、技术原理

下面是网上摘抄的,关于x86的linux系统中对于守护进程的介绍和描述。

守护进程(Daemon)是一种运行在后台的一种特殊的进程,它独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。

守护进程是个特殊的孤儿进程,这种进程脱离终端,为什么要脱离终端呢?之所以脱离于终端是为了避免进程被任何终端所产生的信息所打断,其在执行过程中的信息也不在任何终端上显示。由于在 Linux 中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端,当控制终端被关闭时,相应的进程都会自动关闭。但是守护进程却能突破这种限制,它脱离于终端并且在后台运行,并且它脱离终端的目的是为了避免进程在运行的过程中的信息在任何终端中显示并且进程也不会被任何终端所产生的终端信息所打断。它从被执行的时候开始运转,知道整个系统关闭才退出(当然可以认为的杀死相应的守护进程)。如果想让某个进程不因为用户或中断或其他变化而影响,那么就必须把这个进程变成一个守护进程。

2、设计步骤

对于x86平台的linux系统,理论上来说,要想实现上述的效果,守护进程具有一套严格的实现步骤。也就是说,守护进程必须在启动伊始,就去除掉一些系统相关的限制,这样才能稳定的在后台运行,而不至于被其他任务所干扰和影响。

下面是在x86平台编写守护进程的基本过程:

  1. 屏蔽一些控制终端操作的信号。这是为了防止守护进行在没有运行起来前,控制终端受到干扰退出或挂起。关于信号的更详细用法,请看《信号中断处理》。
  2. 在后台运行。这是为避免挂起控制终端将守护进程放入后台执行。方法是在进程中调用 fork() 使父进程终止, 让守护进行在子进程中后台执行。
  3. 脱离控制终端、登录会话和进程组。有必要先介绍一下 Linux 中的进程与控制终端,登录会话和进程组之间的关系:进程属于一个进程组,进程组号(GID)就是进程组长的进程号(PID)。登录会话可以包含多个进程组。这些进程组共享一个控制终端。这个控制终端通常是创建进程的 shell 登录终端。 控制终端、登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们 ,使之不受它们的影响。因此需要调用 setsid() 使子进程成为新的会话组长。setsid() 调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。由于会话过程对控制终端的独占性,进程同时与控制终端脱离。
  4. 禁止进程重新打开控制终端。现在,进程已经成为无终端的会话组长,但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端,采用的方法是再次创建一个子进程。
  5. 关闭打开的文件描述符。进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误。
  6. 改变当前工作目录。进程活动时,其工作目录所在的文件系统不能卸下。一般需要将工作目录改变到根目录。对于需要转储核心,写运行日志的进程将工作目录改变到特定目录如 /tmp。
  7. 重设文件创建掩模。进程从创建它的父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存取权限。为防止这一点,必须将文件创建掩模清除。
  8. 处理 SIGCHLD 信号。对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源(关于僵尸进程的更多详情,请看《僵尸进程》)。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在 Linux 下可以简单地将 SIGCHLD 信号的操作设为 SIG_IGN 。这样,内核在子进程结束时才不会产生僵尸进程。

-
下面就是摘自某前辈的博客上的全套源码:

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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&#xff09;脱离控制终端、登录会话和进程组
setsid();
// 4&#xff09;禁止进程重新打开控制终端
if( pid&#61;fork() ){
// 父进程
exit(0);// 结束第一子进程&#xff0c;第二子进程继续&#xff08;第二子进程不再是会话组长&#xff09;
}else if(pid<0){
// 出错
perror("fork");
exit(EXIT_FAILURE);
}
// 5&#xff09;关闭打开的文件描述符
// NOFILE 为 的宏定义
// NOFILE 为文件描述符最大个数&#xff0c;不同系统有不同限制
for(i&#61;0; i close(i);
}
// 6&#xff09;改变当前工作目录
chdir("/tmp");
// 7&#xff09;重设文件创建掩模
umask(0);
// 8&#xff09;处理 SIGCHLD 信号
signal(SIGCHLD,SIG_IGN);
return 0;
}
int main(int argc, char *argv[])
{
init_daemon();
while(1);
return 0;
}

3、实际情况

从上面的流程逻辑和实际代码可以看出&#xff0c;x86平台的守护进程&#xff0c;其实还是比较复杂的&#xff0c;需要进行一堆比较繁琐的初始化过程。然而&#xff0c;对于嵌入式平台而言&#xff0c;流程似乎可以简化一些&#xff0c;不用这么复杂的处理。因为&#xff0c;在本次嵌入式系统中启用守护进程。其目的只是简单的利用这个守护进程来启动另一个被守护的进程&#xff0c;然后定时监控该进程是否仍在正常运行&#xff0c;一旦发现其运行异常&#xff0c;则立即重启该进程就好。

所以&#xff0c;我对上述的流程进行了简化&#xff0c;得到如下的流程&#xff1a;

  1. 在守护进程中启动需要被监视的进程。
  2. 在守护进程中创建一个线程&#xff0c;用来定时监测被守护的进程的运行状态
  3. 守护进程判断被守护的进程是否仍在正常运行&#xff0c;一旦发现其运行异常&#xff0c;则立即重启该进程。

-
4、实际源码

以下就是在本嵌入式系统项目中所设计的守护进程模块的全套代码。

/************************************************************************************************** ** 函数名称: lockfile ** 功能描述: 对文件加锁/解锁 ** 输入参数: lock: 1表示进行加锁处理&#xff0c;0表示进行解锁处理 ** 输出参数: 无 ** 返回参数: 无 **************************************************************************************************/
int tryto_lockfile(int fd, int lock)
{
struct flock fl;
fl.l_type&#61; (lock &#61;&#61; 1) ? F_WRLCK : F_UNLCK;
fl.l_start &#61; 0;
fl.l_whence &#61; SEEK_SET;
fl.l_len &#61; 0;
return (fcntl(fd, F_SETLK, &fl));
}
/************************************************************************************************** ** 函数名称: get_proc_running_state ** 功能描述: 获取进程运行状态 ** 输入参数: 无 ** 输出参数: 无 ** 返回参数: 返回-1表示路径错误 ** 返回参数: 返回0表示进程从未运行过&#xff0c;返回1表示进程曾经运行过但是现在停止运行了&#xff0c;返回2表示进程正在运行中 **************************************************************************************************/
static int get_proc_running_state(const char* filename)
{
int fd;
if (filename &#61;&#61; NULL) { /* 文件名为空 */
return -1;
}
fd &#61; open(filename, O_RDWR, (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH));
if (fd <0) { /* 文件不存在&#xff0c;表示进程从未运行过 */
return 0;
}
if (tryto_lockfile(fd, 1) &#61;&#61; -1) { /* 文件加锁失败&#xff0c;表示进程在运行中 */
close(fd);
return 2;
} else { /* 文件加锁成功&#xff0c;表示进程已经消失 */
tryto_lockfile(fd, 0); /* 此处要注意记得解锁和关闭文件 */
close(fd);
return 1;
}
}
/************************************************************************************************** ** 函数名称: proc_watch ** 功能描述: 检测进程是否有在运行&#xff0c;没有运行则重新启动之 ** 输入参数: procname: 进程名 ** 输出参数: 无 ** 返回参数: 返回-1表示进程从未运行过&#xff1b;返回0表示进程当前运行正常&#xff1b; ** 返回参数: 返回其他非零值表示进程不存在且已被重新启动&#xff0c;返回的值是新的pid值 **************************************************************************************************/
int proc_watch(const char *procname)
{
int result, state;
char filename[100];
result &#61; 0;
sprintf(filename, "/var/run/%s.pid", procname);
state &#61; get_proc_running_state(filename);
switch (state)
{
case 0:
result &#61; -1;
break;
case 1:
result &#61; start_proc_by_name(procname);
break;
case 2:
result &#61; 0;
break;
default:
break;
}
return result;
}
/************************************************************************************************** ** 函数名称: start_proc ** 功能描述: 启动进程开始运行 ** 输入参数: 无 ** 输出参数: 无 ** 返回参数: 进程的ID号&#xff0c;若启动失败则返回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 &#61; 0;
if (access(filename, X_OK | F_OK) !&#61; 0) { /* 如果文件存在&#xff0c;并且可执行 */
return 0;
}
pid &#61; fork(); /* 首先要fork一个进程出来 */
if (pid <0) { /* 创建进程失败 */
return 0;
} else if (pid &#61;&#61; 0) { /* 创建进程成功&#xff0c;此处是子进程的代码 */
if (execl(filename, procname, (char *)NULL) !&#61; -1) {
return 1;
} else {
return 0;
}
} else { /* 创建进程成功&#xff0c;此处是父进程代码 */
child_pid &#61; pid;
}
return (int)child_pid;
}
/************************************************************************************************** ** 函数名称: thread_client_hdl ** 功能描述: client进程监视线程 ** 输入参数: 无 ** 输出参数: 无 ** 返回参数: 无 **************************************************************************************************/
static void *thread_client_hdl(void *pdata)
{
int result;
pdata &#61; pdata;
sleep(10);/* 第一次要进行延时 */
for (;;) {
printf("time to check thread_client...\n");
result &#61; proc_watch(PROC_NAME_CLIENT);
if (result &#61;&#61; -1) {
printf("thread_client never exist...\n");
} else if (result &#61;&#61; 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 &#61; argv[0]; /* 获取进程名称 */
p &#61; process_name &#43; strlen(process_name);
while (*p !&#61; &#39;/&#39; && p !&#61; process_name) {
p--;
}
if (*p &#61;&#61; &#39;/&#39;) {
process_name &#61; p &#43; 1;
}
printf("\"%s\" starting...\n", process_name);
client_para &#61; 0x01;
if (pthread_create(&thread_client, NULL, thread_client_hdl, &client_para) !&#61; 0) {
printf("create thread_client failed!\n");
return 1;
}
if (start_proc_by_name(PROC_NAME_CLIENT) &#61;&#61; 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中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • Nginx使用AWStats日志分析的步骤及注意事项
    本文介绍了在Centos7操作系统上使用Nginx和AWStats进行日志分析的步骤和注意事项。通过AWStats可以统计网站的访问量、IP地址、操作系统、浏览器等信息,并提供精确到每月、每日、每小时的数据。在部署AWStats之前需要确认服务器上已经安装了Perl环境,并进行DNS解析。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了一个Java猜拳小游戏的代码,通过使用Scanner类获取用户输入的拳的数字,并随机生成计算机的拳,然后判断胜负。该游戏可以选择剪刀、石头、布三种拳,通过比较两者的拳来决定胜负。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • Linux磁盘的分区、格式化的观察和操作步骤
    本文介绍了如何观察Linux磁盘的分区状态,使用lsblk命令列出系统上的所有磁盘列表,并解释了列表中各个字段的含义。同时,还介绍了使用parted命令列出磁盘的分区表类型和分区信息的方法。在进行磁盘分区操作时,根据分区表类型选择使用fdisk或gdisk命令,并提供了具体的分区步骤。通过本文,读者可以了解到Linux磁盘分区和格式化的基本知识和操作步骤。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • 本文讨论了如何使用IF函数从基于有限输入列表的有限输出列表中获取输出,并提出了是否有更快/更有效的执行代码的方法。作者希望了解是否有办法缩短代码,并从自我开发的角度来看是否有更好的方法。提供的代码可以按原样工作,但作者想知道是否有更好的方法来执行这样的任务。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
author-avatar
峰吹云飞_974
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有