Linux Daemon
时间:2009-03-23 来源:sjhf
<link rel="File-List" href="file:///C:%5CDOCUME%7E1%5Czxuhong%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_filelist.xml"><link rel="Edit-Time-Data" href="file:///C:%5CDOCUME%7E1%5Czxuhong%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_editdata.mso"><link rel="OLE-Object-Data" href="file:///C:%5CDOCUME%7E1%5Czxuhong%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_oledata.mso">
Linux 守护进程Linux 守护进程概述Linux Daemon(守护进程)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。它不需要用户输入就能运行而且提供某种服务,不是对整个系统就是对某个用户程序提供服务。Linux系统的大多数服务器就是通过守护进程实现的。常见的守护进程包括系统日志进程syslogd、 web服务器httpd、邮件服务器sendmail和数据库服务器mysqld等。 守护进程一般在系统启动时开始运行,除非强行终止,否则直到系统关机都保持运行。守护进程经常以超级用户(root)权限运行,因为它们要使用特殊的端口(1-1024)或访问某些特殊的资源。 一个守护进程的父进程是init进程,因为它真正的父进程在fork出子进程后就先于子进程exit退出了,所以它是一个由init继承的孤儿进程。守护进程是非交互式程序,没有控制终端,所以任何输出,无论是向标准输出设备stdout还是标准出错设备stderr的输出都需要特殊处理。 工作原理 Linux 守护进程的工作模式是服务器/客户机(Server/Client),服务器在一个特定的端口上监听(Listen)等待客户连接,连接成功后服务器和客户端通过端口进行数据通信。守护进程的工作就是打开一个端口,并且监听(Listen)等待客户连接。如果客户端产生一个连接请求,守护进程就创建(Fork)一个子服务器响应这个连接,而主服务器继续监听其他的服务请求。 工作模式 Linux 守护进程有两种工作模式:stand-alone模式和xinetd模式。 (1)stand-alone模式 独立运行的守护进程由init负责管理,所有独立运行守护进程的脚本在/etc/rc.d/init.d/目录下。独立运行的守护进程工作方式称作stand-alone,是Unix传统的C/S模式的访问模式。服务器监听(Listen)在一个特点的端口上等待客户端的联机。如果客户端产生一个连接请求,守护进程就创建(Fork)一个子服务器响应这个连接,而主服务器继续监听。工作在stand-alone模式下的网络服务有route、gated、web服务器等。在Linux系统中通过stand-alone工作模式启动的服务由/etc/rc.d/下面对应的运行级别当中的符号链接启动。
(2)xinetd模式
从守护进程的概念可以看出,对于系统所要求的每一种服务,都必须运行一个监听某个端口连接所发生的守护进程,这意味着资源浪费。为了解决这个问题,Linux引进了"网络守护进程服务程序"的概念。Redhat Linux使用的网络守护进程是xinted(eXtended InterNET Daemon)。和stand-alone模式相比xinetd模式也称 Internet Super-Server(超级服务器)。xinetd能够同时监听多个指定的端口,在接受用户请求时,他能够根据用户请求的端口不同,启动不同的网络服务进程来处理这些用户请求。可以把xinetd看做一个管理启动服务的管理服务器,它决定把一个客户请求交给那个程序处理,然后启动相应的守护进程。
和stand-alone工作模式相比,系统不必为每一个网络服务进程监听其服务端口,运行xinetd守护进程就可以同时监听所有服务端口,这样就降低了系统开销,保护系统资源。但是对于访问量大、经常出现并发访问时,xinetd想要频繁启动对应的网络服务进程,反而会导致系统性能下降。一般来说系统一些负载高的服务,比如Apache、sendmail等服务是单独启动的。而其他服务类型都可以使用xinetd超级服务器管理。
查看系统为Linux服务提供那种模式方法在Linux命令行可以使用pstree命令可以看到两种不同方式启动的网络服务。
Linux 守护进程管理Linux提供了几种不同的守护进程管理工具: ntsysv、chkconfig等,可以根据具体需要灵活运用。 (1)ntsysv ntsysv 工具为启动或关闭由xinetd管理的服务提供了简单的界面,也可以使用 ntsysv 来配置运行级别。按照默认设置,只有当前运行级别会被配置。要配置不同的运行级别,使用 --level 选项来指定一个或多个运行级别。比如,命令 ntsysv --level 345 配置运行级别3、4、和5。使用上下箭头来上下查看列表。使用空格键来选择或取消选择服务,或用来"按"「确定」和「取消」按钮。要在服务列表和「确定」、「取消」按钮中切换,使用 [Tab]键。* 标明某服务被设为启动。[F1] 键会弹出每项服务的简短描述。 (3)chkconfig Chkconfig工具可以用来启动或停止服务。 chkconfig --list 命令显示系统服务列表,以及这些服务在运行级别0到6中已被启动(on)还是停止(off),还显示xinetd管理的系统服务。 chkconfig 还能用来设置某一服务在某一指定的运行级别内被启动还是被停运。比如,要在运行级别3、4、5中停运 nfs 服务,使用下面的命令: chkconfig --level 345 nfs off 查看系统为Linux服务提供那种模式方法在Linux命令行可以使用pstree命令可以看到两种不同方式启动的网络服务。Linux 守护进程列表
Linux 守护进程编程守护进程最重要的特性是后台运行;其次守护进程必须与其运行前的环境隔离开来,这些环境包括未关闭的文件描述符,控制终端,会话和进程组,工作目录以及文件创建模式等。这些环境通常是守护进程从执行它的父进程(特别是shell)中继承下来的;最后守护进程的启动方式有其特殊之处。它可以在Linux系统启动时从启动脚本/etc/rc.d中启动,可以由作业规划进程crond启动,还可以由用户终端(通常是shell)执行。 总之,除开这些特殊性以外,守护进程与普通进程基本上没有什么区别。因此,编写守护进程实际上是把一个普通进程按照上述的守护进程的特性改造成为守护进程。 编写守护进程的步骤: (1)在父进程中执行fork并exit推出; (2)在子进程中调用setsid函数创建新的会话; (3)在子进程中调用chdir函数,让根目录 ”/” 成为子进程的工作目录; (4)在子进程中调用umask函数,设置进程的umask为0; (5)在子进程中关闭任何不需要的文件描述符 Setsid函数
#include <unistd.h>
pid_t setsid(void);
返回值:成功返回新会话的ID;失败返回-1
setsid函数创建一个新会话和新进程组,setsid调用保证新会话没有控制终端。
守护进程调用该函数将成为新会话的会话领导和新进程组的进程组领导。
chdir函数
#include <unistd.h>
int chdir(const char *path);
chdir函数改变当前的工作目录为path所包含的新目录
umask函数
#include
void umask(int mask); // r-4; w-2; x-1
umask函数改变目录和文件的创建模式。
第一个守护进程
$ vi mydaemon1.c
/* mydaemon1.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <syslog.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
int main(void)
{
pid_t pid, sid;
time_t timebuf;
int fd, len;
char *buf;
// 创建子进程,然后父进程退出
pid = fork();
if(pid < 0){
perror("fork");
exit(EXIT_FAILURE);
}else if(pid > 0){ // 父进程
exit(EXIT_SUCCESS);
}
// 子进程
// 创建新会话和新进程组
if((sid = setsid()) < 0){
perror("setsid");
exit(EXIT_FAILURE);
}
// 改变当前的工作目录到根目录
if((chdir("/")) < 0){
perror("chdir");
exit(EXIT_FAILURE);
}
// 改变目录文件的创建模式
umask(0);
// 关闭不必要的文件描述符
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
// malloc buf
len = strlen(ctime(&timebuf));
buf = malloc(len+1);
// 子进程主要工作,每10秒钟向日志文件写入当前的时间
while(1)
{
if((fd = open("/var/log/mydaemon.log", \
O_CREAT | O_WRONLY | O_APPEND, 0600)) < 0){
perror("open file");
exit(EXIT_FAILURE);
}
time(&timebuf);
bzero(buf, len+1);
strncpy(buf, ctime(&timebuf),len+1);
write(fd, buf, len+1);
close(fd);
sleep(10);
}
free(buf);
exit(EXIT_SUCCESS);
}
该守护进程的主要任务是每10秒钟向日志文件/var/log/mydaemon.log 写入当前的时间。
编译、运行:
# gcc mydaemon1.c -o mydaemon1
# ./mydaemon1
# cat /var/log/mydaemon.log
Thu Sep 11 19:01:34 2008
Thu Sep 11 19:01:44 2008
Thu Sep 11 19:01:54 2008
Thu Sep 11 19:02:04 2008
# ps -elf | grep mydaemon1
1 S root 4732 1 0 75 0 - 408 - 19:01 ? 00:00:00 ./mydaemon1
# kill -9 4732
运行时需要超级用户权限;
运行后查看生成的日志文件信息,该守护进程每10秒钟向日志文件写入当前的时间;
查看该守护进程,进程的父进程已变成init进程(PID=1);
向该进程PID号发送信号强行终止该进程。
守护进程出错处理守护进程调用了setsid函数就不再拥有控制终端,所以就无法正常向标准输出stdout或标准出错stderr(比如出错信息)输出信息。幸运的是系统日志守护进程syslogd可以提供这一服务。 Syslog 函数
#include <syslog.h>
void openlog(char *ident, int option, int facility); // 打开日志
void syslog(int priority, char *format, …); // 写入消息
void closelog(void); // 关闭日志
syslog 函数向系统日志写入信息。
$ vi mydaemon_syslog.c
/* mydaemon_syslog.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <syslog.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
int main(void)
{
pid_t pid, sid;
time_t timebuf;
int fd, len;
char *buf;
pid = fork();
if(pid < 0){
syslog(LOG_ERR, "Error: fork %s\n", strerror(errno));
exit(EXIT_FAILURE);
}else if(pid > 0){ // Parent
exit(EXIT_SUCCESS);
}
// Child
// open log
openlog("mydaemon", LOG_PID, LOG_DAEMON);
// setsid
if((sid = setsid()) < 0){
syslog(LOG_ERR, "Error: setsid %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
// change work directory
if((chdir("/")) < 0){
syslog(LOG_ERR, "Error: chdir %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
// change umask
umask(0);
// close unnessary file descriptions
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
// malloc buf
len = strlen(ctime(&timebuf));
buf = malloc(len+1);
// main work, write current datetime to file 10 second per
while(1)
{
if((fd = open("/var/log/mydaemon.log", \
O_CREAT | O_WRONLY | O_APPEND, 0600)) < 0){
syslog(LOG_ERR, "Error: open %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
time(&timebuf);
bzero(buf, len+1);
strncpy(buf, ctime(&timebuf),len+1);
write(fd, buf, len+1);
close(fd);
sleep(10);
}
free(buf);
// close log
closelog();
exit(EXIT_SUCCESS);
}
该守护进程出错时向syslogd写入出错信息
编译、运行:
$ gcc mydaemon_syslog.c -o mydaemon_syslog
$ ./mydaemon_syslog
# cat /var/log/messages | grep mydaemon
Sep 11 19:12:03 cn_emb_501 mydaemon[7165]: Error: open Permission denied
使用非超级用户root运行该守护进程,然后查询系统日志信息,可看到该守护进程打开文件时出错,没有权限。Mydaemon是打开日志时的名称,后面的数字7165是该守护进程的进程PID。即使时间信息仍然被写入到/etc/log/mydaemon.log文件,但该进程产生的所有出错信息都被记录到系统日志/var/log/messages文件。
控制守护进程
/* mydaemon_control.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <syslog.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <signal.h>
#define NORMAL 1
#define ROT13 2
int style;
int read_config(int etcfd, int *style)
{
int len;
char etcbuf[256];
if((len = read(etcfd, etcbuf, 256)) < 0){
syslog(LOG_ERR, "Error: read %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
etcbuf[len]='\0';
// Determine style
if( strncmp(etcbuf, "normal", 6) == 0){
*style = NORMAL;
syslog(LOG_INFO, "output style is normal\n");
}else if( strncmp(etcbuf, "rot13", 5) == 0){
*style = ROT13;
syslog(LOG_INFO, "output style is rot13\n");
}else{
syslog(LOG_ERR, "invalid output style\n");
return(2);
}
return(0);
}
|