红联Linux门户
Linux帮助

Linux下Daemon的实现

发布时间:2014-12-03 10:42:46来源:linux网站作者:linux人

注:Linux下可以直接调用damon函数来实现daemon进程,没必要重新实现,理解其原理即可。

在Linux中专门提供了一个函数来完成这个daemon化的过程,这个函数的原型如下

int daemon (int __nochdir, int __noclose);

如果__nochdir的值为0,则将切换工作目录为根目录;如果__noclose为0,则将标准输入,输出和标准错误都重定向到/dev/null。

经过这个函数调用后的程序将运行在后台,成为一个daemon程序,而linux下大多的服务都是以此方式运行的。


我们来看一个简单的例子。例如编写例子程序test.c

#include <unistd.h> #include <stdio.h>   int do_sth() {  //Add what u want return 0;  } int main() {  daemon(0,0); //or Daemon(1,1);  while ( 1 ) {  do_sth();  sleep(1);  }  }

编译并运行

[leconte@localhost daemon]$ gcc -o test test.c [leconte@localhost daemon]$ ./test

程序进入了后台,通过ps查看进程情况,可以看到进程的父进程id为1,即init进程

用lsof查看test进程所打开的文件,可以看到文件描述符0,1,2都被重定向到/dev/null

并且能够看到,进程的当前工作目录(cwd)为根目录/,daemon函数已经帮我们完成了daemon化的过程,接下来我们只需要关注于程序功能的实现了。

 
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模式也称 InternetSuper-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 守护进程列表
 

守护进程
含义
alsasound
Alsa声卡驱动的守护程序
acpid
替代传统APM电源管理标准的新型电源管理标准(Advanced Configuration and Power Interface Daemon)的守护进程
atalk
AppleTalk网络的守护进程
amd
自动安装NFS的守护进程
anacron
自动运行任务的守护进程
apmd
高级电源管理(Advanced Power Management)的守护进程
arpwatch
记录日志并构建一个在LAN接口上看到的以太网地址和IP地址的进程
atd
at和batch命令的守护进程
autofs
自动安装管理进程的守护进程,与NFS相关,依赖于NIS服务器
bootparamd
引导参数服务器,为LAN上的无盘工作站提供引导所需的相关信息
bluetooch
蓝牙服务的守护进程
crond
Cron服务的守护进程,该程序周期地运行用户调度的任务
chargen
使用tcp协议的chargen server,(Character Generator Protocol)是一种网络服务,主要功能是提供类似远程打字的功能
chargen-udp
使用udp协议的chargen server的守护进程
cpuspeed
监测系统空闲百分比,降低或加快CPU时钟速度和电压的守护进程
dhcpd
动态主机控制协议(Dynamic Host Control Protocol)服务的守护进程
cups
提供第三代打印功能的守护进程(Common UNIX Printing System)
daytime
使用tcp 协议日期时间的守护进程,该协议为客户机实现从远程服务器获取日期和时间的功能
daytime-udp
使用udp协议日期时间的守护进程
dc_server
使用SSL安全套接字代理服务器的守护进程
dc_client
使用SSL安全套接字客户端的守护进程
diskdump
服务器磁盘备份的守护进程
echo
服务器回显客户数据服务的守护进程
echo-udp
使用udp协议的服务器回显客户数据服务守护进程
gated
网关路由的守护进程
gpm
为文本模式下的Linux程序如mc(Midnight Commander)提供鼠标的支持的守护进程(General Purpose Mouse Daemon)
httpd
Web服务器Apache的守护进程
inetd
网络管理的守护进程
innd
Usenet新闻服务器的守护进程
iiim
中文输入法服务器的守护进程
iptables
防火墙功能的守护进程
irda
红外功能的守护进程
isdn
isdn启动和中止服务的守护进程
krb5-telnet
使用kerberos 5认证的telnet守护进程
klogin
远程登陆的守护进程
irqbalance
对多个系统处理器环境下的系统中断请求进行负载平衡的守护进程
kshell
Kshell服务器的守护进程
kudzu
硬件自动检测的守护进程,自动检测硬件的添加、删除等
ldap
目录访问协议服务器的守护进程(Lightweight Directory Access Protocol)
lm_seroems
检测主板工作情况的守护进程
lpd
老式打印服务的守护进程
mdmonitor
RAID相关设备的守护程序
mysqld
Mysql数据库的守护进程
named
名称服务器的守护进程
netplugd
网络接口管理的守护进程(Network Cable Hot Plug Management Daemon)
netdump
远程网络备份服务器的守护进程
netfs
安装和卸载NFS、SAMBA和NCP网络文件系统的守护进程(Network Filesystem Mounter)
ntpd
使系统和一个精确的时间源保持时间同步的协议守护进程(Network Time Protocol Daemon)
network
激活、关闭网络接口的守护进程
pcmcia
支持笔记本PCMCIA服务的守护进程
portmap
支持RPC连接的守护进程,RPC被用于NFS以及NIS 等服务
postgresql
PostgreSQL数据库的守护进程
random
随机数生成器的守护进程
rawdevices
使用集群文件系统时用于加载raw设备的守护进程
routed
支持RIP协议的自动IP路由表维护的守护进程
rsync
远程数据备份(Remote Sync)的守护进程
rsh
Shell服务器的守护进程
sendmail
邮件服务器的守护进程
smb
Samba文件共享/打印服务的守护进程
snmpd
本地简单网络管理的守护进程
squid
代理服务器squid守护进程
sshd
OpenSSH(Secure Shell Protocol)服务器的守护进程
smartd
监控硬盘的守护进程(Self Monitor Analysis and Reporting Technology System)
syslog
系统引导时启动syslog和klogd系统日志守护进程的程序
time
使用tcp协议从远程主机获取时间和日期的守护进程
time-udp
使用udp协议从远程主机获取时间和日期的守护进程
tux
在Linux内核中运行apache服务器的守护进程
vsftpd
vsftp服务器的守护进程
vncserver
VNC服务(Virtual Network Computing虚拟网络计算)的守护进程
xfs
X Window字型服务器的守护进程,为本地和远程X服务器提供字型集
xinetd
支持多种网络服务的核心守护进程
ypbind
使客户进程能绑定或连接到网络信息系统(Network Information System)服务器的守护进程
yppasswdd
网络信息系统NIS口令服务器的守护进程
ypserv
网络信息系统NIS主服务器的守护进程
yum
自动升级和软件包管理的守护进程

运行不必要或有漏洞的守护进程会给操作系统带来安全和性能上的影响。如果操作系统中的任何一个漏洞,都可能使整个系统受到攻击。所以,增加系统安全的最佳办法就是尽量监视系统的功能。对于一些重要的守护进程,比如crond、syslog、keytable、xinetd、kudzu、iptables等是需要运行的;echo、echo-udp、daytime、daytime-udp、chargen、chargen-udp主要是做调试用,普通用户基本用不到,可以关闭。
 
r字开头的守护进程:rsh、rstatd、rsync、rusersd、rwalld这些命令都是Berkley远程命令,因为都以字母r开头,故称r*命令。主要使用来使一台计算机上的某个用户以相同的帐户远程执行另一台计算机的一个程序。但是,r命令已经被证实存在安全风险。对于确实需要的守护进程,应该尽量选用最新的版本程序,并增加其安全防范。 另外我们还要合理选择守护进程例如innd是运行新闻组服务的进程,如果用户不做Usenet服务器,应该关掉。

 
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_tsetsid(void);
返回值:成功返回新会话的ID;失败返回-1
 
setsid函数创建一个新会话和新进程组,setsid调用保证新会话没有控制终端。
守护进程调用该函数将成为新会话的会话领导和新进程组的进程组领导。
 
chdir函数
#include<unistd.h>
intchdir(const char *path); 
 
chdir函数改变当前的工作目录为path所包含的新目录
 
umask函数
#include
voidumask(int mask);   // r-4; w-2; x-1
 
umask函数改变目录和文件的创建模式。

 
第一个守护进程
 
$ vimydaemon1.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>
 
intmain(void)
{
pid_t pid, sid;
time_t timebuf;
int   fd, len;
char  *buf;
// 创建子进程,然后父进程退出
pid = fork();
if(pid < 0){
perror("fork");
exit(EXIT_FAILURE);
}elseif(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 写入当前的时间。
 
编译、运行:
# gccmydaemon1.c -o mydaemon1
#./mydaemon1
# cat/var/log/mydaemon.log
Thu Sep11 19:01:34 2008
Thu Sep11 19:01:44 2008
Thu Sep11 19:01:54 2008
Thu Sep11 19:02:04 2008
 
# ps -elf| grep mydaemon1               
1 S root4732  1 0  75   0 - 408 -  19:01 ?    00:00:00 ./mydaemon1
# kill -94732
 
运行时需要超级用户权限;
运行后查看生成的日志文件信息,该守护进程每10秒钟向日志文件写入当前的时间;
查看该守护进程,进程的父进程已变成init进程(PID=1);
向该进程PID号发送信号强行终止该进程。
 
守护进程出错处理
守护进程调用了setsid函数就不再拥有控制终端,所以就无法正常向标准输出stdout或标准出错stderr(比如出错信息)输出信息。幸运的是系统日志守护进程syslogd可以提供这一服务。
 
Syslog 函数
#include<syslog.h>
voidopenlog(char *ident, int option, int facility);         //打开日志
voidsyslog(int priority, char *format, …);                 //写入消息
voidcloselog(void);                                                 //关闭日志