守护进程

在Linux或者Unix操作系统中在系统引导的时候会开启很多服务,这些服务就叫做守护进程。

守护进程,也就是通常说的Daemon进程,是Linux中的后台服务进程。

它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。

守护进程常常在系统引导装入时启动,在系统关闭时终止。

Linux系统有很多守护进程,大多数服务都是通过守护进程实现的,如Internet服务器inetd,Web服务器httpd等;

同时,守护进程还能完成许多系统任务,例如,作业规划进程crond、打印进程lqd等(这里的结尾字母d就是Daemon的意思)。

 

由于在Linux中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端。

当控制终端被关闭时,相应的进程都会自动关闭。但是守护进程却能够突破这种限制,它从被执行开始运转,直到整个系统关闭时才退出。

如果想让某个进程不因为用户或终端或其他地变化而受到影响,那么就必须把这个进程变成一个守护进程。

 

守护进程也可能从某个终端由用户在shell提示符下键入命令行启动,这样的守护进程必须亲自脱离与控制终端的关联,

从而避免与作业控制、终端会话管理、终端产生信号等发生任何不期望的交互,也可避免在后台运行的守护进程非预期地输出到终端。

 

Linux守护进程列表:

amd:自动安装NFS(网络文件系统)守侯进程
apmd:高级电源治理
Arpwatch:记录日志并构建一个在LAN接口上看到的以太网地址和ip地址对数据库
Autofs:自动安装治理进程automount,与NFS相关,依靠于NIS
Bootparamd:引导参数服务器,为LAN上的无盘工作站提供引导所需的相关信息
crond:linux下的计划任务
Dhcpd:启动一个DHCP(动态IP地址分配)服务器
Gated:网关路由守候进程,使用动态的OSPF路由选择协议
Httpd:WEB服务器
Inetd:支持多种网络服务的核心守候程序
Innd:Usenet新闻服务器
Linuxconf:答应使用本地WEB服务器作为用户接口来配置机器
Mars-nwe:mars-nwe文件和用于Novell的打印服务器
Mcserv:Midnight命令文件服务器
named:DNS服务器
netfs:安装NFS、Samba和NetWare网络文件系统
network:激活已配置网络接口的脚本程序
nfs:打开NFS服务
nscd:nscd(Name Switch Cache daemon)服务器,用于NIS的一个支持服务,它高速缓存用户口令和组成成员关系
portmap:RPC portmap治理器,与inetd类似,它治理基于RPC服务的连接
postgresql:一种SQL数据库服务器
routed:路由守候进程,使用动态RIP路由选择协议
rstatd:一个为LAN上的其它机器收集和提供系统信息的守候程序
ruserd:远程用户定位服务,这是一个基于RPC的服务,它提供关于当前记录到LAN上一个机器日志中的用户信息
rwalld:激活rpc.rwall服务进程,这是一项基于RPC的服务,答应用户给每个注册到LAN机器上的其他终端写消息
rwhod:激活rwhod服务进程,它支持LAN的rwho和ruptime服务
sendmail:邮件服务器sendmail
smb:Samba文件共享/打印服务
snmpd:本地简单网络治理候进程
squid:激活代理服务器squid
syslog:一个让系统引导时起动syslog和klogd系统日志守候进程的脚本 (514/udp syslog UNIX 系统日志服务)
xfs:X Window字型服务器,为本地和远程X服务器提供字型集
xntpd:网络时间服务器
ypbind:为NIS(网络信息系统)客户机激活ypbind服务进程
yppasswdd:NIS口令服务器
ypserv:NIS主服务器
gpm:管鼠标的
identd:AUTH服务,在提供用户信息方面与finger类似
 

#include <syslog.h>

void syslog(int priority, const char *message, ...);   // 记录至系统日志

第一个参数priority是级别(level)和设施(facility)两者的组合;

第二个参数message类似printf的格式串。

日志消息的level:

日志消息的facility:

例如rename函数调用意外失败时,守护进程可以执行以下调用:

syslog(LOG_INFO | LOG_LOCAL2, "rename(%s, %s): %m", file1, file2);

level和facility的目的在于,允许在/etc/syslog.conf文件中统一配置来自同一个给定设施的所有消息,

或者统一配置具有相同级别的所有消息。举例来说,该配置文件可能含有以下两行:

kern.*               /dev/console

local7.debug      /var/log/cisco.log

这两行指定所有内核消息登记到控制台,来自local7设施的所有debug消息添加到文件/var/log/cisco.log的末尾。

当syslog被应用进程首次调用时,它创建一个Unix域数据报套接字,然后调用connect连接到由syslogd守护进程创建的

Unix域数据报套接字的众所周知路径名(如/var/run/log)。这个套接字一直保持打开,指导进程终止为止。

作为替换,进程也可以调用openlog和closelog。

#include <syslog.h>

void openlog(const char *ident, int options, int facility);  // 可在首次调用syslog前调用

void closelog(void);  // 可在应用进程不再需要发送日志消息时调用

参数ident是一个由syslog冠于每个日志消息前的字符串,其值通常为程序名。

参数options由一个或多个常值的逻辑或构成。

openlog被调用时,通常并不立即创建Unix域套接字;相反,该套接字直到首次调用syslog时才打开。

LOG_NDELAY选项迫使该套接字在openlog被调用时就创建。

 

日志消息也可以由logger命令产生,可用在shell脚本中以向syslogd发送消息。

 

Linux创建守护进程的步骤:

1)创建子进程,父进程退出

2)在子进程中创建新会话

3)改变当前目录为根目录

4)重设文件权限掩码

5)关闭文件描述符

6)忽略SIGCHLD信号

7)用日志系统记录出错信息

8)守护进程退出处理

 

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#define MAXFILE 65535
void sigterm_handler(int arg);
volatile sig_atomic_t _running = 1;

int main()
{
    pid_t pc, pid;
    int i, fd, len, flag = 1;
    char *buf = "this is a Dameon\n";
    len = strlen(buf);
    pc = fork();                  //第一步
    if(pc < 0){
        printf("error fork\n");
        exit(1);
    }
    else if(pc > 0)
        exit(0);

    pid = setsid();               //第二步
    if (pid < 0)
        perror("setsid error");

    chdir("/");                   //第三步
    umask(0);                     //第四步
    for(i = 0; i < MAXFILE; i++)        //第五步
        close(i);

    signal(SIGTERM, sigterm_handler);
    while(_running){
        if( flag == 1 && (fd=open("/tmp/daemon.log", O_CREAT | O_WRONLY | O_APPEND, 0600)) < 0){
            perror("open");
            flag=0;
            exit(1);
        }

        write(fd, buf, len);
        close(fd);
        usleep(10 * 1000); //10毫秒
    }
}

void sigterm_handler(int arg)
{
    _running = 0;
}                

 

#include "unp.h"
#include <syslog.h>
#define MAXFD 64

extern int daemon_proc;

int daemon_init(const char *pname, int facility)
{
    int i;
    pid_t pid; 
    
    if ( (pid = fork()) < 0)
        return -1;
    else if (pid)
        exit(0);

    if (setsid() < 0)
        return -1;
    
    signal(SIGHUP, SIG_IGN);
    if ( (pid = fork()) < 0)
        return -1;
    else if (pid)
        exit(0);

    daemon_proc = 1;    // 把全局变量daemon_proc置为非0值,告知它们改为调用syslog,以取代fprintf到标准错误输出。
    
    chdir("/");    // 把工作目录改到根目录,守护进程可能是在某个任意的文件系统中启动,若仍在其中,该文件系统就无法拆卸,除非采用潜在破坏性的强制措施。

    for (i = 0; i < MAXFD; i++)
        close(i);    // 关闭本守护进程从执行它的进程(通常是一个shell)继承来的所有打开的描述符

    open("/dev/null", O_RDONLY);   // 将stdin、stdout和stderr重定向到/dev/null
    open("/dev/null", O_RDWR);
    open("/dev/null", O_RDWR);

    openlog(pname, LOG_PID, facility);   // 使用syslogd处理错误,第一个参数来自调用者,通常为程序名字(argv[0])。
// 第二个参数指定把进程ID加到每个日志消息中。
// 第三个参数由调用者指定。
return 0; }

首次调用fork终止父进程。本进程是从前台作为一个shell命令启动的,当父进程终止时shell认为该命令已执行完毕。这样子进程就自动在后台运行。

另外,子进程继承了父进程的进程组ID,不过它有自己的进程ID,这就保证子进程不是一个进程组的头进程,则该接着调用setsid。

 

setsid用于创建一个新的会话(session)。当前进程变为新会话的会话头进程以及新进程组的进程组头进程,从而不再有控制终端。

 

忽略SIGHUP信号并再次调用fork。再次调用fork目的是确保本守护进程将来即使打开一个终端设备,也不会自动获得控制终端。(确保新的子进程不再是一个会话头进程)

这里必须忽略SIGHUP信号,因为当会话头进程(即首次fork产生的子进程)终止时,其会话中的所有进程(即再次fork产生的子进程)都收到SIGHUP信号。 

 

一个session可能会有一个session首进程,而一个session首进程可能会有一个控制终端。
一个进程组可能会有一个进程组首进程。进程组首进程的进程ID与该进程组ID相等。
系统对SIGHUP信号(中止信号)的默认处理是终止收到该信号的进程。所以若程序中没有捕捉该信号,当收到该信号时,进程就会退出。
 
/dev/null(空设备)是一个特殊的设备文件,它丢弃一切写入其中的数据(但报告写入操作成功),read系统调用读取它则会立即得到一个EOF。
它可看作黑洞(black hole),空设备通常被用于丢弃不需要的输出流,或作为用于输入流的空文件。这些操作通常由重定向完成。
 
一个程序作为守护进程运行,就得避免调用诸如printf和fprintf之类函数,可改用err_msg()
 
int main(int argc, char **argv)
{
    int listenfd, connfd;
    socklen_t addrlen, len;
    struct sockaddr_in servaddr;
    char buff[MAXLINE];
    pid_t pid;
    time_t ticks;

    daemon_init(argv[0], 0);

    if ( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
        cout<<"Error No:"<<errno<<endl;
        cout<<"socket error!"<<endl;
        exit(0);
    }

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(30000);
    
    if (bind(listenfd, (SA*)&servaddr, sizeof(servaddr)) < 0){
        cout<<"bind error!"<<endl;
        exit(0);
    }

    if (listen(listenfd, LISTENQ) < 0){
        cout<<"listen error!"<<endl;
        exit(0);
    }

    for ( ; ; ){
        connfd = accept(listenfd, (SA*)NULL, NULL);
        if ( (pid = fork()) == 0){
            close(listenfd);
            
            ticks = time(NULL);
            bzero(buff, sizeof(buff));

            sprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
            write(connfd, buff, strlen(buff));
            close(connfd);
            //str_echo(connfd);
            exit(0);
        }
        close(connfd);
    }

    return 0;
}

 

posted @ 2016-05-09 20:47  LarryKnight  阅读(1042)  评论(0编辑  收藏  举报