参考http://zxuhong.blog.51cto.com/368977/99283/
注: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
)
;
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 模式也称 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 守护进程列表
守护进程 | 含义 |
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_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); // 关闭日志