UNIX环境高级编程-守护进程

目录

linux各种守护进程说明

守护进程的编写规则

守护进程的一个例子

syslog

syslog的例子

参考


 

 

linux各种守护进程说明

进程名 说明
kthreadd  
kswapd0 内存交换守护进程,支持虚拟内存子系统在经过一段时间后将脏页面慢慢的写回磁盘来回收这些页面
flush-8:0 在内用内存达到设置的最小阀值时将脏页写到磁盘,也定期将脏页面写回磁盘来减少在系统出现故障时发生的数据丢失
           多个flush守护进程可以同时存在,每个写回设备都有一个flush的守护进程,flush-8:0表示主设备号为8,副设备号为0
jbd2/sda1-8 守护进程帮助实现了ext4文件系统中的日志功能    
rpcbind 提供远程过程调用
sync_supers 定期将文件系统元数据写到磁盘
crypto/0           
kthrotld/0
aio/0
md/0
md_misc/0
linkwatch
khungtaskd
ata_sff/0
kblockd/0
kintegrityd/0
cgroupkhelper
events/0
migration/0
ksoftirqd/0
watchdog/0
 

 

 

守护进程的编写规则

在编写守护进程时需遵循一些基本规则,以便防止产生并不需要的交互作用。下面先说明这些规则,然后给出一个按照这些规则编写的函数daemonize。

  1. 首先要做的是调用umask将文件模式创建屏蔽字设置为0。由继承得来的文件模式创建屏蔽字可能会拒绝设置某些权限。例如,若守护进程要创建一个组可读、写的文件,而继承的文件模式创建屏蔽字可能屏蔽了这两种权限,于是所要求的组可读、写就不能起作用。
  2. 调用fork,然后使父进程退出(exit)。这样做实现了下面几点:第一,如果该守护进程是作为一条简单shell命令启动的,那么父进程终止使得shell认为这条命令已经执行完毕(也就没有了控制终端);第二,子进程继承了父进程的进程组ID,但具有一个新的进程ID,这就保证了子进程不是一个进程组的组长进程。这对于下面就要做的setsid调用是必要的前提条件。
  3. 调用setsid以创建一个新会话。于是执行http://www.cnblogs.com/nufangrensheng/p/3513400.html中列举的三个操作,使调用进程:(a)成为新会话的首进程,(b)成为一个新进程组的组长进程,(c)没有控制终端。
  4. 在基于系统V的系统中,有些人建议在此时再次调用fork,并使父进程终止。第二个子进程作为守护进程继续运行。这样就保证了该守护进程不是会话首进程,于是按照系统V规则(见http://www.cnblogs.com/nufangrensheng/p/3513443.html)可以防止它取得控制终端。避免取得控制终端的另一种方法是,无论何时打开一个终端设备都一定要指定O_NOCTTY。
  5. 将当前工作目录更改为根目录。从父进程出继承过来的当前工作目录可能在一个挂载的文件系统(a mounted file system)中。因为守护进程通常在系统再引导之前是一直存在的,所以如果守护进程的当前工作目录在一个挂载的文件系统中,那么该文件系统就不能被卸载。这与挂载文件系统的原意不符。
  6. 另外,某些守护进程可能会把当前工作目录更改到某个指定位置,在那里做它们的工作,例如,行式打印机假脱机守护进程常常将其工作目录更改到它们的spool目录上。
  7. 关闭不再需要的文件描述符。这使守护进程不再持有从其父进程继承来的某些文件描述符(父进程可能是shell进程,或某个其他进程)。可以使用http://www.cnblogs.com/nufangrensheng/p/3496323.html中程序清单2-4中的open_max函数或getrlimit函数(http://www.cnblogs.com/nufangrensheng/p/3509262.html)来判定最高文件描述符值,并关闭直到该值的所有描述符。
  8. 某些守护进程打开/dev/null使其具有文件描述符0、1和2,这样,任何一个试图读标准输入、写标准输出和标准出错的库例程都不会产生任何效果。因为守护进程并不与终端设备相关联,所以不能在终端设备上显示其输出,也无处从交互式用户那里接受输入。即使守护进程是从交互式会话启动的,但因为守护进程是在后台运行的,所以登录会话的终止并不影响守护进程。如果其他用户在同一终端设备上登录,我们也不会在该终端上见到守护进程的输出,用户也不可期望他们在终端上的输入会由守护进程读取。

 

 

守护进程的一个例子

#include <syslog.h>
#include <fcntl.h>
#include <sys/resource.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>


void err_quit(const char *fmt, ...) {
    printf(fmt);
    exit(1);
}


void daemonize(const char *cmd) {
    int                  i, fd0, fd1, fd2;
    pid_t                pid;
    struct rlimit        rl;
    struct sigaction     sa;
    
/*
 ** Clear file creation mask.
*/
    umask(0);

/*
** Get maximum number of file descriptors.
*/
    if(getrlimit(RLIMIT_NOFILE, &rl) < 0)
        err_quit("%s: can't get file limit", cmd);
    
/*
** Become a session leader to lose controlling TTY.
*/
    if((pid = fork()) < 0)
        err_quit("%s: can't fork", cmd);
    else if (pid != 0)    /* parent */
        exit(0);
    setsid();

/*
** Ensure future opens won't allocate controlling TTYs.
*/
    sa.sa_handler = SIG_IGN;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    if(sigaction(SIGHUP, &sa, NULL) < 0)
        err_quit("%s: can't ignore SIGHUP");
    if((pid = fork()) < 0)
        err_quit("%s: can't fork", cmd);
    else if( pid != 0 )    /* parent */
        exit(0);

/*
** Change the current working directory to the root so 
*** we won't prevent file system from being unmounted.
*/
    if(chdir("/") < 0)
        err_quit("%s: can't change directory to /");
    
/*
** Close all open file descriptors.
*/
    if(rl.rlim_max = RLIM_INFINITY)
        rl.rlim_max = 1024;
    for(i = 0; i < rl.rlim_max; i++)
        close(i);

/*
** Attach file descriptors 0, 1, and 2 to /dev/null.
*/    
    fd0 = open("/dev/null", O_RDWR);
    fd1 = dup(0);
    fd2 = dup(0);

/*
 ** Initialize the log file.
*/
    openlog(cmd, LOG_CONS, LOG_DAEMON);
    if(fd0 != 0 || fd1 != 1 || fd2 != 2)
    {
        syslog(LOG_ERR, "unexpected file descriptors %d %d %d", fd0, fd1, fd2);
        exit(1);
    }
    sleep(3600);
}


int main() {
    daemonize("xx");
    return 0;
}

 

syslog

1.内核程序可以调用log函数记日志. 任何用户进程可以打开并读取/dev/klog设备读取这些消息
2.大多数用户进程,调用syslog函数产生日志消息. 这使得消息发送到UNIX域数据报套接字/dev/log
3.用户进程发送日志消息到UDP端口514

通常syslogd守护进程会读取一个配置文件(/etc/syslog.conf)来决定不同类型的消息应该送到何处.

//相关函数
#include <syslog.h>

void openlog(const char* ident,int option,int facility);

void syslog(int priority,const char* format,...);

void vsyslog(int priority,const char* format,va_list arg);

void closelog();

/* 返回旧的日志优先级屏蔽值 */
int setlogmask(int maskpriority);

openlog函数

  • 调用openlog是可选择的,若不调用openlog则第一次调用syslog时自动调用syslog
  • 参数ident一般为程序名称.可以用来表示是哪个进程发送的消息日志,openlog会将它加到每则日志消息中
  • option参数是指定许多选项的位屏蔽

    options 说明
    LOG\_CONS 若无法将信息发送到syslogd,则直接输出到控制台
    LOG\_NDELAY 立即打开与syslogd的连接,默认为第一次写日志时才建立连接
    LOG\_ODELAY 在就第一条消息时才建立与syslogd的连接
    LOG\_PERROR 输出到stderr中
    LOG\_NOWAIT syslog记录日志时可能会fork子进程,该选项表示不wait子进程退出
    LOG\_PID 每条消息都记录下进程ID
  • 参数facility说明

    参数facility指明了默认的消息种类. 消息种类会根据syslogd的配置文件来分发到不同位置

    facility 说明
    LOG\_AUTH 认证相关的信息,入login,su,getty产生的信息
    LOG\_CRON 由cron或at产生的信息
    LOG\_DAEMON 系统守护进程产生的消息
    LOG\_FTP FTP守护进程产生的消息
    LOG\_KERN 内核产生的消息
    LOG\_LOCAL0 – LOG\_LOCAL7 保留本地使用
    LOG\_LPR 行打印系统产生的消息
    LOG\_MAIL 邮件系统产生的消息
    LOG\_NEWS Usenet网络新闻系统产生的消息
    LOG\_SYSLOG syslogd本身产生的消息
    LOG\_USER 来自其他用户进程的消息(默认)
    LOG\_UUCP UUCP系统产生的消息

syslog函数

参数priority可以用来指明消息的 种类等级

即priority的可选择同时包含了openlog中facility参数的可选择和level可选值

facility 说明
LOG\_AUTH 认证相关的信息,入login,su,getty产生的信息
LOG\_CRON 由cron或at产生的信息
LOG\_DAEMON 系统守护进程产生的消息
LOG\_FTP FTP守护进程产生的消息
LOG\_KERN 内核产生的消息
LOG\_LOCAL0 – LOG\_LOCAL7 保留本地使用
LOG\_LPR 行打印系统产生的消息
LOG\_MAIL 邮件系统产生的消息
LOG\_NEWS Usenet网络新闻系统产生的消息
LOG\_SYSLOG syslogd本身产生的消息
LOG\_USER 来自其他用户进程的消息(默认)
LOG\_UUCP UUCP系统产生的消息
   
LOG\_EMERG 紧急状态(系统不可用)
LOG\_ALTERT 必须立即修复的状态
LOG\_CRIT 严重状态(例如硬件故障)
LOG\_ERR 出错信息
LOG\_WARNING 警告信息
LOG\_NOTICE 正常,但重要的消息
LOG\_INFO 消息性质的信息
LOG\_DEBUG 调试信息

closelog函数

这个函数也是可选的,只有在调用openlog后才需要调用. 它用于关闭曾被用于与syslogd守护进程通信的描述符

 

 

syslog的例子

#include <syslog.h>
int main(int argc, char **argv) {
    openlog("MyMsgMARK", LOG_CONS | LOG_PID, 0);
    syslog(LOG_DEBUG,"hello'%s'\n",argv[0]);
    closelog();
    return 0;
}

strace这个程序

//通过strace可以看出,是通过socket来实现syslog的
...
socket(AF_LOCAL, SOCK_DGRAM|SOCK_CLOEXEC, 0) = 3
connect(3, {sa_family=AF_LOCAL, sun_path="/dev/log"}, 110) = 0
sendto(3, "<15>Oct  8 16:38:01 MyMsgMARK[27"..., 54, MSG_NOSIGNAL, NULL, 0) = 54
close(3)                                = 0
....

 

 

 

 

参考

守护进程之编程规则

进程关系之会话

linux下C语言编程打印syslog日志

UNIX环境高级编程之syslog

 

 

 

 

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页