如何写一个Linux精灵进程

发布网友 发布时间:2022-04-24 10:43

我来回答

1个回答

热心网友 时间:2023-10-10 11:11

1.引言:什么是一个精灵进程
一个精灵进程(或服务)是一个后台进程,它被设计用来自己运行,并且很少或没有用户的干预。Apache服务器http精灵进程(httpd)就是精灵进程的一个例子,它在后台中等待,监听特定的端口,根据请求的类型提供页面或处理脚本。
在Linux中创建一个精灵进程使用了一个有序的规则集。知道它们如何工作,将会帮助你理解精灵进程不但可以在Linux 用户态中的工作,也会和内核调用一起运行。事实上,一些精灵进程和内核模块的接口,会和硬件设备一起工作,例如全局控制板,打印机以及PDA。它们是Linux的基本构建组件之一,这使得Linux有着难以置信的灵活性和力量。
通过这篇文档,将会使用C语言构建一个非常简单的精灵进程。我们将会一步一步的向里面添加代码,展示了设置和运行一个daemon的合适的顺序。
2.开始
首先,你需要使用下面的两个软件安装在你的Linux机器上,来开发精灵进程:
(1)GCC 3.2.2 或者更高的版本
(2)Linux 开发头文件和库
如果你的系统还没有安装这两个软件,那么你将会需要安装它们来开发这个文档中的例子。检测你的GCC的版本,可以使用命令:
[plain] view plain copy
gcc --version

3.计划你的精灵进程

3.1 精灵进程都干了写什么
一个精灵进程应该做一件事情,并且把它做好。这个事情可能会像管理多个域名上的成百个发件箱一样复杂,或者像写一个报告并且调用发送邮件程序将报告发送出去一样简单。
无论如何,关于这个精灵进程做什么,你都应该有一个好的计划。如果它要和其他进程协作(这些进程可能写了或者还没有写),那么也需要考虑其他的进程。
3.2 如何交流

精灵进程绝不应该通过一个终端和用户交流。事实上,一个精灵进程完全不应该和一个用户直接交流。所有的交流都应该通过一些接口(你可能写了或者没有写),它可能会像GTK+GUI一样复杂,也可能像信号集一样简单。
4.基本的精灵进程结构
当一个精灵进程启动之后,它必须做一些底层次的工作,让它自己准备好来做它真正的工作。这包括了下面几个步骤:
[cpp] view plain copy
(1)创建子进程,退出父进程
(2)改变文件的掩码
(3)打开日志文件,以便向里面写入执行信息
(4)创建唯一的会话ID(SID)
(5)改变当前的工作路径到一个安全的地方
(6)关闭标准文件描述符
(7)编写实际的精灵进程代码

4.1 创建子进程
一个精灵进程可以通过由系统自己启动,或者由用户通过终端或者脚本启动。当它启动后,这个进程就像系统中的其它可执行文件一样。为了让它真正的有自主权,必须创建一个子进程,这个子进程用来执行实际的代码。我们使用fork来创建子进程:
[cpp] view plain copy
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()函数来完成这些:

[cpp] view plain copy
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来进行运作。否则,子进程在系统中,将会成为一个孤儿进程。
[cpp] view plain copy
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函数:
[cpp] view plain copy
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函数可以处理这些:
[cpp] view plain copy
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 大循环
一个精灵进程的主要代码实在一个无限循环之中,从技术上说,它不是一个无限循环,但是它的结构如下:
[cpp] view plain copy
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来写你自己的日志文件。
[cpp] view plain copy
#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;
}

声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com