13.1 引言
守护进程没有控制终端,所以说是后台运行的。
13.2 守护进程的特征
ps -axj
-a 显示由其他用户所拥有的进程状态
-x 显示没有控制终端的进程状态
-j 显示与作业有关的信息
- kthreadd表现为其他内核进程的父进程。
zion6135@zion6135-VirtualBox:~$ ps -axj | grep -Ei "kthread" 0 2 0 0 ? -1 S 0 0:00 [kthreadd] 6913 11078 11077 6913 pts/0 11077 S+ 1000 0:00 grep --color=auto -Ei kthread
- 进程1通常是init进程,它是一个系统守护进程
zion6135@zion6135-VirtualBox:~$ ps -axj | grep -Ei "init" 0 1 1 1 ? -1 Ss 0 0:08 /sbin/init splash 6913 11084 11083 6913 pts/0 11083 S+ 1000 0:00 grep --color=auto -Ei init
13.3 编程规则
1.调用umask将守护进程设定到希望的文件权限,确保文件权限是自己期望的
2.调用fork(),然后父进程exit(),让守护进程不关联终端。父进程exit()让shell认为程序正常终止。另外子进程继承父进程的进程组ID,但是子进程不等于进程组ID。
3.子进程调用setsid()创建新的会话;然后(1)子进程成为新会话的首进程。(2)子进程成为一个新进程组的组长进程。(3)没有控制终端
4.为了子进程一直存在,需要chdir("/"); 让子进程的工作目录在一个一直存在的目录,因为我们希望子进程一直运行。
5.关闭不需要的file descripter; 这让子进程不在继承来自父进程的文件描述符,可通过getrlimit()来关闭已经打开的文件描述符。
6.某些守护进程打开/dev/null,让文件描述符0 1 2对应的标准输入,标准输出,标准错误都不会产生任何效果。
13.4 出错记录
- 系统日志服务syslogd
sysog()函数的调用:
level级别:一般系统日志会通过配置文件,决定代码里面高于什么level的日志会被写如系统日志文件。
- 简单的例子,Ubuntu生成的日志在/var/log/syslog文件里面
#include <errno.h> #include <string.h> #include <syslog.h> #define FNAME "/tmp/out" int main() { openlog("lbwwww", LOG_PID, LOG_DAEMON); syslog(LOG_INFO, "daemon() sucess"); printf("1111\n"); syslog(LOG_ERR, "fopen:%s", strerror(errno)); syslog(LOG_INFO, "%s was opend.", FNAME); syslog(LOG_DEBUG, "%d is printed", 2); sleep(1); exit(0); } //打印结果 lbw@HP-ZHAN-66-Pro-15-G3:~$ cat /var/log/syslog | grep -Ei "lbwww" Feb 23 20:10:32 tstxb-HP-ZHAN-66-Pro-15-G3 lbwwww[462105]: daemon() sucess Feb 23 20:10:32 tstxb-HP-ZHAN-66-Pro-15-G3 lbwwww[462105]: fopen:Success Feb 23 20:10:32 tstxb-HP-ZHAN-66-Pro-15-G3 lbwwww[462105]: /tmp/out was opend. Feb 23 20:10:32 tstxb-HP-ZHAN-66-Pro-15-G3 lbwwww[462105]: 2 is printed
- 有三种产生日志消息的方法:
1.内核例程可调用log()函数,任何一个进程可open()然后read("/dev/kog" ..)来读取日志。
2.大多数守护进程调用syslog(3)函数来产生日志消息,从而让消息被发送到套接字/dev/log
3.可将日志消息发送到UDP的514端口
- 通常syslogd守护进程读取上述的3种格式的日志消息。通常这个守护进程启动时读取配置文件/etc/syslog.conf文件,该文件决定了不同种类的消息被送到何处,。
13.5 单实例的守护进程
- 守护进程实例
// 守护进程 #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> static int daemonize() { pid_t pid = fork(); if (pid < 0) { perror("fork()"); return -1; } if (pid > 0) { // parent exit(0); } int fd = open("/dev/null", O_RDWR); if (fd < 0) { perror("open()"); return -1; } dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); if (fd > 2) { close(fd); } setsid(); // child // 让守护进程在一定存在的目录 chdir("/"); // umask(0); return 0; } #define FNAME "/tmp/out" int main() { if (daemonize()) { exit(1); } FILE *fp = fopen(FNAME, "w"); if (fp == NULL) { perror("fopen()"); exit(1); } for (int i = 0;; i++) { fflush(fp); fprintf(fp, "%d\n", i); sleep(1); } exit(0); } // 父进程执行结束,子进程后台运行 // lbw@lbw:~/lbw/gitNote/chap9$ g++ main.c -o mydaemon // lbw@lbw:~/lbw/gitNote/chap9$ ./mydaemon // lbw@lbw:~/lbw/gitNote/chap9$ // 查看子进程(守护进程) // lbw@lbw:~/lbw/gitNote/chap9$ // lbw@lbw:~/lbw/gitNote/chap9$ps axj // 96186 4160831 4160831 4160831 ? -1 Ss 1000 0 : 00. / mydaemon //查看输出的日志 // lbw@lbw:~/lbw/gitNote/chap9$:tail -f /tmp/out // 158 // 159 // 160 // 161 // 162 // 163 // 164 // 165 //杀死守护进程 // lbw@lbw:~/lbw/gitNote/chap9$kill -9 4160831
- 守护进程需要是有且唯一的。通过锁文件/var/run/name.pid来控制进程的单实例的目的
比如下面的ssh.pid可以看出文件内容是ssh的pid,通过ssh.pid文件可以达到单实例的目的。
lbw@HP-ZHAN-66-Pro-15-G3:/var/run$ ls acpid.pid crond.pid docker.pid libvirt openvpn spice-vdagentd thermald utmp acpid.socket crond.reboot docker.sock libvirtd.pid openvpn-client sshd tmpfiles.d uuidd alsa cups gdm3 lock openvpn-server sshd.pid udev vmblock-fuse avahi-daemon dbus gdm3.pid log plymouth sudo udisks2 vmware blkid dmeventd-client initctl lvm sendsigs.omit.d systemd usbmuxd wpa_supplicant console-setup dmeventd-server initramfs mount shm teamviewerd.ipc usbmuxd.pid xtables.lock containerd docker irqbalance NetworkManager speech-dispatcher teamviewerd.pid user lbw@HP-ZHAN-66-Pro-15-G3:/var/run$ cat sshd.pid 1232 lbw@HP-ZHAN-66-Pro-15-G3:/var/run$ ps axf | grep sshd.pid 1507627 pts/0 S+ 0:00 | | \_ grep --color=auto sshd.pid lbw@HP-ZHAN-66-Pro-15-G3:/var/run$ ps axf | grep sshd 1232 ? Ss 0:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups 1520618 pts/0 S+ 0:00 | | \_ grep --color=auto sshd lbw@HP-ZHAN-66-Pro-15-G3:/var/run$
13.6 守护进程的惯例
- 若守护进程用锁文件,锁文件通常存在/var/run目录下,名字一般叫name.pid(name是守护进程的名字),比如cron守护进程锁文件的名字是/var/run/cron.pid
- 若守护进程的配置选项,该配置文件在/etc目录。比如syslogd守护进程的配置文件/etc/syslog.conf
- 开机自启:守护进程可用命令行启动,但一般是由系统初始化脚本之一(/etc/rc*或/etc/init.d/*)启动的。
- 如果希望守护进程终止的时候自动重启,我们就可在/etc/inittab中为该守护进程包括respawn记录项。