一个精灵进程(或服务)是一个后台进程,它被设计用来自己运行,并且很少或没有用户的干预。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
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)