守护进程
守护进程(daemon)是一类在后台运行的特殊进程,守护进程特点是,不受任何终端控制、不受用户登录注销影响。通常在系统启动的时候启动,仅在系统运行结束后才终止。
可以通过“ps ajx”命令查看系统的守护进程,其中TPGID为-1的就是守护进程。
守护进程作用
守护进程在后台中周期性地执行某种特殊任务;或者等待某些发生地事件而唤醒该进程进程处理事件,处理完则进入休眠。
【1】系统守护进程提供用户和应用程序必要的功能
【2】守护进程可以监测某一事件,如某进程异常崩溃,守护进程监测到则重新启动该进程
常用守护进程
Linux通常针对一个领域都有专门的守护进程,日志服务、网络服务、时间、数据库等,只列举常见易理解的守护进程,其他的待增加。
【1】amd:自动安装NFS(网络文件系统)守护进程
【2】apmd:高级电源管理
【3】Arpwatch:记录日志并构建一个在LAN接口上看到的以太网地址和IP地址对数据库
【4】Autofs:自动安装管理进程automount,与NFS相关,依赖于NIS
【5】Bootparamd:引导参数服务器,为LAN上的无盘工作站提供引导所需的相关信息
【5】crond:Linux下的计划任务
【7】Dhcpd:启动一个DHCP(动态IP地址分配)服务器
【8】Gated:网关路由守候进程,使用动态的OSPF路由选择协议
【9】Httpd:WEB服务器
【10】Inetd:支持多种网络服务的核心守候程序
【11】Innd:Usenet新闻服务器
【12】Linuxconf:允许使用本地WEB服务器作为用户接口来配置机器
【13】Lpd:打印服务器
【14】Mars-nwe:mars-nwe文件和用于Novell的打印服务器
【15】Mcserv:Midnight命令文件服务器
【16】named:DNS服务器
【17】netfs:安装NFS、Samba和NetWare网络文件系统
【18】network:激活已配置网络接口的脚本程序
【19】nfs:打开NFS服务
【20】nscd:nscd(Name Switch Cache daemon)服务器,用于NIS的一个支持服务,它高速缓存用户口令和组成成员关系
【21】portmap:RPC portmap管理器,与inetd类似,它管理基于RPC服务的连接
【22】postgresql:一种SQL数据库服务器
【23】routed:路由守候进程,使用动态RIP路由选择协议
【24】rstatd:一个为LAN上的其它机器收集和提供系统信息的守候程序
【25】ruserd:远程用户定位服务,这是一个基于RPC的服务,它提供关于当前记录到LAN上一个机器日志中的用户信息
【26】rwalld:激活rpc.rwall服务进程,这是一项基于RPC的服务,允许用户给每个注册到【27】LAN机器上的其他终端写消息
【28】rwhod:激活rwhod服务进程,它支持LAN的rwho和ruptime服务
【29】sendmail:邮件服务器sendmail
【30】smb:Samba文件共享/打印服务
【31】snmpd:本地简单网络管理候进程
【32】squid:激活代理服务器squid
【33】syslog:一个让系统引导时起动syslog和klogd系统日志守候进程的脚本
【34】xfs:X Window字型服务器,为本地和远程X服务器提供字型集
【35】xntpd:网络时间服务器
【36】ypbind:为NIS(网络信息系统)客户机激活ypbind服务进程
【37】yppasswdd:NIS口令服务器
【38】ypserv:NIS主服务器
【39】gpm:管理鼠标
【40】identd:AUTH服务,在提供用户信息方面与finger类似
【41】 time :以TCP协议从远程主机获取时间和日期守护进程
【42】 time-udp :以udp协议从远程主机获取时间和日期守护进程
【43】 mysqld:一个快速高效可靠的轻型SQL数据库引擎守护进程
【44】yum:RPM操作系统自动升级和软件包管理守护进程
守护进程创建步骤
1. fork子进程,父进程exit退出
根据守护进程“脱离终端”特点,fork子进程后,父进程应该退出,后续所有任务和事件由子进程处理,子进程成为“孤儿进程”,达到脱离终端目的。另一方面,需要创建新的会话,而创建会话的进程不能是组长进程,让父进程创建一个子进程,那么子进程一定不是组长进程。
2. 子进程创建会话
我们知道,fork()子进程复制父进程的会话期、进程组、控制终端等信息,即使父进程退出了。子进程调用setsid函数创建新会话,子进程成为新会话的首进程,并成为新进程组的组长进程,到这一步,如果setsid执行成功,已经实现进程脱离终端的目标。
3. 再次 fork() 一个子进程,父进程退出
子进程虽然已经成为无终端的会话组长,但它仍可以重新申请打开一个控制终端,所以再 fork() 一个子进程,该子进程不是会话首进程;然后退出父进程(第一个子进程)。
4. 更改工作目录
子进程复制了父进程的信息,与父进程处于同一工作目录下。如果父进程处于挂载目录下(如U盘、光驱),由于子进程持续运行导致文件会不能被卸载。因此,需要调用chdir函数使得子进程工作于根目录(“/”)下。
5. 处理SIGCHLD信号
子进程终止时,会向父进程发送一个SIGCHLD信号,父进没有wait/waitpid会导致子进程变成一个僵尸进程。父进程可以重写信号handler,忽略SIGCHLD信号。
6. 关闭文件描述符
子进程复制了父进程的信息,子进程与父进程拥有相同的文件描述符表,如果父进程打开的文件,子进程也能操作,通常把从父进程继承过来的文件描述符关闭。
7. 更改文件操作权限
子进程继承父进程的文件操作权限(读、写、执行),为增强守护进程的 灵活性,将文件操作权限设为无任何权限(0),即通过文件掩码函数umask设置文件权限掩码(umask(0))。
8. 重定向输出流
避免父进程向终端输出信息,需将标准输入、标准输出、标准错误等重定向至“/dev/null”。
8. 守护进程退出
一般情况下守护进程是随系统关闭而终止。如果守护进程支持用户主动终止,守护进程内部需实现 kill 发出的signal信号handler,用户通过kill终止守护进程。
守护进程创建
实现一个周期记录时间日志的守护进程。
【1】根据守护进程创建步骤fork进程实现
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include <syslog.h>
#include <sys/stat.h>
static bool flag = true;
void create_daemon()
{
int i= 0;
pid_t pid;
pid = fork();
if(pid == -1)
{
perror("fork first");
exit(1);
}
else if(pid > 0)
{
exit(0); /* 创建子进程,父进程退出 */
}
if(setsid() == -1) /* 创建新会话 */
{
perror("setsid");
exit(1);
}
pid = fork(); /* 创建子子进程,子进程退出 */
if(pid == -1)
{
perror("fork second");
exit(1);
}
else if(pid > 0)
{
exit(0);
}
if(chdir("/") < 0) /* 更改工作目录为根目录 */
{
perror("chdir");
exit(0);
}
signal(SIGCHLD,SIG_IGN); /* 忽略SIGCHLD信号 */
//close(fd); /* 关闭文件 */
umask(0); /* 更改文件权限 */
int fd;
fd=open("dev/null",O_RDWR);
if(fd < 0)
{
perror("open 'dev/null'");
exit(0);
}
dup2(fd,STDIN_FILENO); /* 重定向标准输入、输出、错误输出 */
dup2(fd,STDOUT_FILENO);
dup2(fd,STDERR_FILENO);
close(fd);
return;
}
void handler(int sig)
{
printf("Recv exit signal %d\n", sig);
flag = false;
}
int main(int argc, char** argv)
{
time_t now;
int fd;
char *p;
struct sigaction act;
create_daemon();
act.sa_handler = handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if(sigaction(SIGQUIT, &act, NULL) < 0)
{
perror("sigaction");
exit(0);
}
syslog(LOG_USER|LOG_INFO,"Daemon LOG!\n");
while(flag)
{
time(&now);
syslog(LOG_USER|LOG_INFO,"%s\t\n",ctime(&now));
sleep(30);
}
closelog();
return 0;
}
使用syslog函数前需要配置。在“/etc/rsyslog.conf”配置文件末尾加上关于自定义日志。
user.* /var/log/daemon.log
配置完成重启syslog服务。
/etc/init.d/rsyslog restart
编译运行,可以查看守护进程(ps ajx),及周期日志记录(cat /var/log/daemon.log)。
【守护进程】
【log】
【2】调用系统函数
#include <unistd.h>
int daemon(int nochdir,int noclose);
Linux系统已经提供守护进程创建函(daemon)。
参数 | 含义 |
---|---|
nochdir | 指定工作目录,0表示使用根目录(“/”);非0使用当前工作目录 |
noclose | 指定设备,0表示STDIN、STDOUT、STDERR重定向至“/dev/null”;非0使用原来设备 |
函数执行成功返回0,失败返回-1,失败原因存于errno中。
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include <syslog.h>
#include <sys/stat.h>
static bool flag = true;
void handler(int sig)
{
printf("I got a signal %d\nI'm quitting.\n", sig);
flag = false;
}
int main(int argc, char** argv)
{
time_t now;
int fd;
struct sigaction act;
if(daemon(0, 0) == -1)
{
perror("daemon\n");
exit(1);
}
act.sa_handler = handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if(sigaction(SIGQUIT, &act, NULL) < 0)
{
perror("sigaction");
exit(0);
}
syslog(LOG_USER|LOG_INFO,"Daemon LOG!\n");
while(flag)
{
time(&now);
syslog(LOG_USER|LOG_INFO,"%s\t\n",asctime(&now));
sleep(30);
}
closelog();
return 0;
}