如何写一个Linux精灵进程

如何写一个Linux精灵进程,第1张

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

}

#include <time.h>

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <sys/types.h>

#include <fcntl.h>

#define MUSIC "/home/brisk/Music/QQ爱.mp3"

typedef struct

{

int s

int m

int h

int date

}TIME

void child_run(TIME *alarm)

{

setsid()

chdir("/")

int fd

long sleep_time

if((fd=open("/dev/null",O_RDWR))==-1)

exit(1)

dup2(fd,STDOUT_FILENO)

dup2(fd,STDIN_FILENO)

dup2(fd,STDERR_FILENO)

close(fd)

//close(STDOUT_FILENO)

//close(STDIN_FILENO)

//close(STDERR_FILENO)

time_t t

long sec

struct tm *ti

//sec=alarm->h*3600+alarm->m*60+alarm->s

t=time(NULL)

ti=localtime(&t)

alarm->h+=(alarm->date-ti->tm_mday)*24

sec=alarm->h*3600+alarm->m*60+alarm->s

sleep_time=sec-(ti->tm_hour*3600+ti->tm_min*60+ti->tm_sec)

if(sleep_time>0)

sleep(sleep_time)

else

exit(1)

//sleep(sec-(ti->tm_hour*3600+ti->tm_min*60+ti->tm_sec))

if(fork()==0)

execl("/usr/bin/mplayer","mplayer",MUSIC,NULL)

exit(0)

}

void again(void)

{

printf("输入有误,请重新输入:")

while(getchar()!='\n')

continue

}

int main(void)

{

TIME alarm

printf("输入日期:")

while(scanf("%d",&alarm.date)==0||alarm.date>31)

again()

printf("请输入小时:")

while(scanf("%d",&alarm.h)==0||alarm.h>=24)

again()

printf("请输入分钟:")

while(scanf("%d",&alarm.m)==0||alarm.m>=60)

again()

printf("请输入秒数:")

while(scanf("%d",&alarm.s)==0||alarm.s>=60)

again()

if(fork()==0)

child_run(&alarm)

return 0

}


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/yw/8784955.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-21
下一篇 2023-04-21

发表评论

登录后才能评论

评论列表(0条)

保存