如何写一个Linux精灵进程

                                                                                                                                                                    --Devin Watson

     这个文档演示了如何使用gcc在linux中写一个精灵进程,使用这篇文档的前提是有Linux的知识和熟悉C语言。这篇文档的版权者是Devin Watson,使用BSD协议。

     1.引言:什么是一个精灵进程

     一个精灵进程(或服务)是一个后台进程,它被设计用来自己运行,并且很少或没有用户的干预。Apache服务器http精灵进程(httpd)就是精灵进程的一个例子,它在后台中等待,监听特定的端口,根据请求的类型提供页面或处理脚本。

     在Linux中创建一个精灵进程使用了一个有序的规则集。知道它们如何工作,将会帮助你理解精灵进程不但可以在Linux 用户态中的工作,也会和内核调用一起运行。事实上,一些精灵进程和内核模块的接口,会和硬件设备一起工作,例如全局控制板,打印机以及PDA。它们是Linux的基本构建组件之一,这使得Linux有着难以置信的灵活性和力量。

     通过这篇文档,将会使用C语言构建一个非常简单的精灵进程。我们将会一步一步的向里面添加代码,展示了设置和运行一个daemon的合适的顺序。

     2.开始

     首先,你需要使用下面的两个软件安装在你的Linux机器上,来开发精灵进程:

     (1)GCC 3.2.2 或者更高的版本

     (2)Linux 开发头文件和库

     如果你的系统还没有安装这两个软件,那么你将会需要安装它们来开发这个文档中的例子。检测你的GCC的版本,可以使用命令:

gcc --version

      3.计划你的精灵进程

          3.1 精灵进程都干了写什么

          一个精灵进程应该做一件事情,并且把它做好。这个事情可能会像管理多个域名上的成百个发件箱一样复杂,或者像写一个报告并且调用发送邮件程序将报告发送出去一样简单。

          无论如何,关于这个精灵进程做什么,你都应该有一个好的计划。如果它要和其他进程协作(这些进程可能写了或者还没有写),那么也需要考虑其他的进程。

          3.2 如何交流

          精灵进程绝不应该通过一个终端和用户交流。事实上,一个精灵进程完全不应该和一个用户直接交流。所有的交流都应该通过一些接口(你可能写了或者没有写),它可能会像GTK+GUI一样复杂,也可能像信号集一样简单。

     4.基本的精灵进程结构

     当一个精灵进程启动之后,它必须做一些底层次的工作,让它自己准备好来做它真正的工作。这包括了下面几个步骤:

(1)创建子进程,退出父进程
(2)改变文件的掩码
(3)打开日志文件,以便向里面写入执行信息
(4)创建唯一的会话ID(SID)
(5)改变当前的工作路径到一个安全的地方
(6)关闭标准文件描述符
(7)编写实际的精灵进程代码
    

          4.1 创建子进程

          一个精灵进程可以通过由系统自己启动,或者由用户通过终端或者脚本启动。当它启动后,这个进程就像系统中的其它可执行文件一样。为了让它真正的有自主权,必须创建一个子进程,这个子进程用来执行实际的代码。我们使用fork来创建子进程:

pid_t pid;
/* Fork off the parent process */
pid = fork();
if (pid < 0) {
exit(EXIT_FAILURE);
}
/* If we got a good PID, then
we can exit the parent process. */
if (pid > 0) {
exit(EXIT_SUCCESS);
}

          注意在调用fork函数之后要立即进行出错检查,当写一个精灵进程的时候,你需要使你的代码尽可能的健壮。事实上,一个精灵进程的代码中相当多的部分就是出错检查。fork函数或者返回子进程的ID(大于0),或者出错返回-1。如果进程不能fork一个子进程,那么精灵进程应该就在这里结束。

          如果fork成功的话,父进程必须结束。这对那些没有见过的人来说可能有些奇怪,但是通过fork,子进程从这里继续执行。

          4.2 改变文件掩码

          为了写那些被精灵进程创建的文件(包括日志文件),文件掩码必须改变来保证它们能够被正确的写或者读。这和在命令行运行umask命令有些相似。但是我们在这里使用编程的方式修改。我们可以使用umask()函数来完成这些:

pid_t pid, sid;

/* Fork off the parent process */
pid = fork();
if (pid < 0) {
/* Log failure (use syslog if possible) */
exit(EXIT_FAILURE);
}
/* If we got a good PID, then
we can exit the parent process. */
if (pid > 0) {
exit(EXIT_SUCCESS);
}

/* Change the file mode mask */
umask(0);

           通过设置umask为0,我们将会对精灵进程产生的文件有足够的权限。尽管你可能不需要使用任何文件,在这里设置umask是一个不错的注意,就是为了防止你可能会访问文件系统中的文件。

          4.3 打开日志文件

          这部分是可选的,但是推荐你打开一个系统中的日志文件来写日志信息。这可能是你可以查看你的精灵进程调试信息的唯一的一个地方。

          4.4 创建一个唯一的会话期ID

          从这里开始,子进程必须从内核得到一个唯一的SID来进行运作。否则,子进程在系统中,将会成为一个孤儿进程。

pid_t pid, sid;

 /* Fork off the parent process */
pid = fork();
if (pid < 0) {
exit(EXIT_FAILURE);
}
/* If we got a good PID, then
we can exit the parent process. */
if (pid > 0) {
exit(EXIT_SUCCESS);
}

 /* Change the file mode mask */
umask(0);

 /* Open any logs here */

 /* Create a new SID for the child process */
sid = setsid();
if (sid < 0) {
/* Log any failure */
exit(EXIT_FAILURE);
}


          setsid函数和fork函数的返回类型相同,我们可以使用同样的出错检查。

          4.5 改变工作路径

          当前的工作路径应该改变到一个总是存在的地方。因为许多Linux发行版本并没有完全遵守Linux文件系统结构标准,唯一的一个确定目录就是根目录,我们可以使用chdir函数:

pid_t pid, sid;

 /* Fork off the parent process */
pid = fork();
if (pid < 0) {
exit(EXIT_FAILURE);
}
/* If we got a good PID, then
we can exit the parent process. */
if (pid > 0) {
exit(EXIT_SUCCESS);
}

 /* Change the file mode mask */
umask(0);

 /* Open any logs here */

 /* Create a new SID for the child process */
sid = setsid();
if (sid < 0) {/* Log any failure here */
exit(EXIT_FAILURE);
}

/* Change the current working directory */
if ((chdir("/")) < 0) {
/* Log any failure here */
exit(EXIT_FAILURE);
}

          再一次的,你可以看到检测出错的代码。chdir函数在失败时会返回-1,所以一定要在改变目录后进行检查。

          4.6 关闭文件描述符

          设置精灵进程的最后一步是关闭标准的文件描述符(STDOUT,STDIN,STDERR),因为一个金陵进程必须不能使用终端,这些文件描述符就是多余的,并且是一个潜在的危险。close函数可以处理这些:

pid_t pid, sid;

/* Fork off the parent process */
pid = fork();
if (pid < 0) {
exit(EXIT_FAILURE);
}
/* If we got a good PID, then
we can exit the parent process. */
if (pid > 0) {
exit(EXIT_SUCCESS);
}

/* Change the file mode mask */
umask(0);

/* Open any logs here */

/* Create a new SID for the child process */
sid = setsid();
if (sid < 0) {
/* Log any failure here */
exit(EXIT_FAILURE);
}

/* Change the current working directory */
if ((chdir("/")) < 0) {
/* Log any failure here */
exit(EXIT_FAILURE);
}

/* Close out the standard file descriptors */
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO); 

          把常数文件描述符和定义的文件描述符结合起来是一个好主意,这就使得程序能够在不同的系统版本之间具有良好的可移植性。

     5.编写精灵进程代码

          5.1 初始化

          到了这一步,你一经向Linux系统说明这个程序就是一个精灵进程,所以现在就是开始编写Linux精灵进程的代码了。初始化是第一步,因为这里可能会调用大量不同的函数,来设置你的精灵进程的任务,我将不会太过深入:

          5.2 大循环

          一个精灵进程的主要代码实在一个无限循环之中,从技术上说,它不是一个无限循环,但是它的结构如下:

pid_t pid, sid;

/* Fork off the parent process */
pid = fork();
if (pid < 0) {
exit(EXIT_FAILURE);}
/* If we got a good PID, then
we can exit the parent process. */
if (pid > 0) {
exit(EXIT_SUCCESS);
}

/* Change the file mode mask */
umask(0);

/* Open any logs here */

/* Create a new SID for the child process */
sid = setsid();
if (sid < 0) {
/* Log any failures here */
exit(EXIT_FAILURE);
}

/* Change the current working directory */
if ((chdir("/")) < 0) {
/* Log any failures here */
exit(EXIT_FAILURE);
}

/* Close out the standard file descriptors */
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);

/* Daemon-specific initialization goes here */

/* The Big Loop */
while (1) {
/* Do some task here ... */
sleep(30); /* wait 30 seconds */
} 

          这个典型的循环使用了while语句,带有一个无限循环的条件,在它的内部调用sleep函数使得它以一定的时间间隔来运行。

          可以把它想象成为心跳一样:当你的心在跳动时,它就执行了一些工作,然后就等待下一次跳动。许多的精灵进程都遵从了相同的方法。

     6.一个完整的程序

     下面列出了一个完整的精灵程序示例代码,它展示了设置和执行必须的所有步骤。可以使用gcc编译,在命令行终端模式下运行。要终止的话,可以在找到它的pid之后使用kill命令。

     我也包括了操作系统日志文件的头文件,推荐(至少)应该记录程序开始,停止,暂停,死亡等日志记录,此外使用fopen,fwrite,fclose来写你自己的日志文件。

#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <syslog.h>
#include <string.h>

int main(void)
{
	pid_t pid,sid;
	
	/*fork*/
	pid = fork();
	if(pid<0)
	{
		exit(EXIT_FAILURE);
	}
	else if(pid>0)
	{
		exit(EXIT_SUCCESS);
	}

	/*change the file mode mask*/
	umask(0);

	/*open logs here*/
	
	/*create new SID for the child process*/
	sid = setsid();
	if(sid<0)
	{
		exit(EXIT_FAILURE);
	}

	/*change the current working directory*/
	if(chdir("/")<0)
	{
		exit(EXIT_FAILURE);
	}

	/*close the file standard file descriptors*/
	close(STDIN_FILENO);
	close(STDOUT_FILENO);
	close(STDERR_FILENO);

	/*deamon-specific initialization here*/
	
	/*the big loop*/
	while(1)
	{
		/*do some task here*/
		sleep(30);	//wait 30 seconds
	}

	exit(EXIT_SUCCESS);

	return 0;
}


  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值