第十三章 守护进程
来源:互联网 发布:java socket select 编辑:程序博客网 时间:2024/06/14 16:18
在看第十二章之前,这里要先提一个十一章遗留下来的一个问题.
在使用条件变量的时候,下面哪个步骤是正确的?
1、对互斥量加锁
2、改变互斥量保护的条件
3、给等待条件的线程发信号
4、对互斥量解锁
或者
1、对互斥量加锁
2、改变互斥量保护的条件
3、对互斥量解锁
4、给等待信号的线程发信号
总的来说,这两种情况可能都是正确的,但每一种方法都有不足之处。
在第一种情况下,等待线程可能会被安排在调用 pthread_cond_broadcast之后运行。如果程序运行在多处理机上,由于还持有互斥锁,一些线程就会运行而马上阻塞。
在第二种情况下,运行线程可以在第三步和第四步之间获取互斥锁,然后使条件失效,最后释放互斥锁。接着,当调用 pthread_cond_broadcast时,条件不再为真,线程无需运行,这就使为什么唤醒线程必须重新检查条件,不能仅仅因为 pthread_cond_wait返回就假定条件就为真。
大多数首进程都是以超级用户特权运行。所有的守护进程都没有控制终端,其终端名设置为问号。
用户层守护进程的父进程是init进程
守护进程编程规则:
这个规则之前提到过,这里我们详细的看一看
(一)、首先要做的是调用 umask 将文件创建模式创建屏蔽字设置为一个已知值
因为这个是会被继承的,这里要为将来使用作准备
(二)、调用fork,然后使父进程 exit。
1、如果该守护进程是作为一条简单的shell命令启动的,那么父进程终止会让shell认为这条命令已经执行完毕
2、让子进程不是进程组组长,作为下面要调用的setsid的先决条件。
(三)、调用setsid创建一个新会话
1、成为新会话的会话首进程
2、成为一个新进程组的组长进程
3、没有控制终端
(三. 五) 在这里,有人建议此时再使用一个fork, 终止父进程,这样就保证了该进程不是会话首进程,可以防止它获取控制终端。
另一种方式是 无论何时打开一个终端设备,都指定 O_NOCTTY
(四)、将当前工作目录改为根目录
从父进程继承来的当前工作目录可能是一个挂在的文件系统,因为守护进程必须是一直存在的,那么该文件系统就不能被卸载
当然,可能是其他目录
(五)、关闭不再需要的文件描述符
(六)、某些守护进程打开 /dev/null 使其具有文件描述父 0,1,2,这样任何一个试图读标准输入、写标准输出或标准错误的库例程都不会产生任何效果
以上程序的运行结果为
大部分是理解了,就是不知到为什么需要处理SIGHUP信号呢? 哪里说会话首进程终止会发送信号SIGHUP了? 我自己试了下,接收到该信号进程终止,进程还是照旧运行啊。。。
出错记录
守护进程是如何处理出错消息的呢?
因为其本身不具备控制终端,所以不能只是简单的写道标准错误。
大多数守护进程都使用了 syslog 函数。
有3种产生日志消息的方法:
1、内核例程调用 log函数。
2、大多数守护进程调用 syslog 函数老产生日志信息。
3、无论是本机进程还是通过 TCP/IP 链接到此主机的其他主机上,都可以将日志消息发向 UDP端口 514.
此系列函数:
openlog、 syslog、 closelog 、setlogmask
单实例守护进程
为了正常运作,某些守护进程会实现为, 在同一时刻只运行该守护进程的一个副本。
例如,对cron 守护进程而言, 如果同时有多个实例运行,那么每个副本都可能试图开始某个预定的操作,于是造成该操作的重复执行,从而导致出错。
这里我们使用文件和记录锁解决这个问题。如果每一个守护进程创建一个固定名字的文件,并在该文件的整体上加一把写锁,那么只允许创建一把这样的写锁。在此之后的创建写锁的尝试都会失败,这就向守护进程指明已经有一个副本正在运行。
在守护进程终止时,这把锁被自动删除。
以下函数就通过使用文件锁说明如何使守护进程只存在一个副本。文件中存当前守护进程的进程ID。
守护进程的惯例
1、若守护进程使用锁文件,那么通常存储在 /var/run 目录中,名字常常是 name.pid
2、若守护进程支持配置选项,那么配置文件常常在 /etc中, 名字常常是 name.conf
3、守护进程可以用命令行启动,但通常它们是由系统初始化脚本(/etc/rc* 或 /etc/init.d/*)之一启动的。如果守护进程终止时,应当自动的重新启动它,则我们可以在 /etc/inittab中为该守护进程包括 respawn项,这样,init 就将重新启动该守护进程
4、守护进程启动时会读取 配置文件,之后就不会再读取。 若我们修改了配置文件,可以发送 SIGHUP信号给守护进程,它就会重新读取配置文件。
以下我们就看一下如何通过 SIGHUP 信号来重读,这里会用到上面提到的 mdaemonize()函数和 already_daemon()函数(此程序使用 sigwait函数 )(这里依旧不进行守护进程的出错处理)
虽然SIGHUP和SIGTERM的默认动作是终止进程。因为我们阻塞了这些信号,所以当SIGHUP和SIGTERM的其中一个发送到守护进程的时候,守护进程不会消亡,作为代替,调用sigwait的线程在返回时候将指示已经接收到该信号。
当然,我们也可以不用线程而是简单的将处理放在信号处理函数中。
在学习过程中,我发现对 SIGHUP的理解一直存在疑惑。这里先贴一个地址,虽然看了依旧不是非常清楚,但至少能学到点什么
http://bbs.chinaunix.net/thread-766356-1-1.html
在使用条件变量的时候,下面哪个步骤是正确的?
1、对互斥量加锁
2、改变互斥量保护的条件
3、给等待条件的线程发信号
4、对互斥量解锁
或者
1、对互斥量加锁
2、改变互斥量保护的条件
3、对互斥量解锁
4、给等待信号的线程发信号
总的来说,这两种情况可能都是正确的,但每一种方法都有不足之处。
在第一种情况下,等待线程可能会被安排在调用 pthread_cond_broadcast之后运行。如果程序运行在多处理机上,由于还持有互斥锁,一些线程就会运行而马上阻塞。
在第二种情况下,运行线程可以在第三步和第四步之间获取互斥锁,然后使条件失效,最后释放互斥锁。接着,当调用 pthread_cond_broadcast时,条件不再为真,线程无需运行,这就使为什么唤醒线程必须重新检查条件,不能仅仅因为 pthread_cond_wait返回就假定条件就为真。
看过书上之后,发现看的迷迷糊糊,就上网提问了,得到了以下回答:
http://bbs.csdn.net/topics/390980156
1、对互斥量加锁
2、改变互斥量保护的条件
3、给等待条件的线程发信号
4、对互斥量解锁
/ /等待线程收到在 第3步发送的信号 而运行,但又立刻阻塞在互斥锁上,因为此时持有锁的线程 第4步 还未运行
或者
1、对互斥量加锁
2、改变互斥量保护的条件
3、对互斥量解锁
4 、给等待信号的线程发信号
//第3步后,可能另一线程运行并使条件为假,这时在第 4 步后,线程就不应该运行,如果不判断条件、仅仅因为 pthread_cond_wait返回就运行则错了,强调的是要同时判断两者
守护进程特征
守护进程常常在系统引导装入时启动,仅在系统关闭时才终止。是在后台运行的。大多数首进程都是以超级用户特权运行。所有的守护进程都没有控制终端,其终端名设置为问号。
用户层守护进程的父进程是init进程
守护进程编程规则:
这个规则之前提到过,这里我们详细的看一看
(一)、首先要做的是调用 umask 将文件创建模式创建屏蔽字设置为一个已知值
因为这个是会被继承的,这里要为将来使用作准备
(二)、调用fork,然后使父进程 exit。
1、如果该守护进程是作为一条简单的shell命令启动的,那么父进程终止会让shell认为这条命令已经执行完毕
2、让子进程不是进程组组长,作为下面要调用的setsid的先决条件。
(三)、调用setsid创建一个新会话
1、成为新会话的会话首进程
2、成为一个新进程组的组长进程
3、没有控制终端
(三. 五) 在这里,有人建议此时再使用一个fork, 终止父进程,这样就保证了该进程不是会话首进程,可以防止它获取控制终端。
另一种方式是 无论何时打开一个终端设备,都指定 O_NOCTTY
(四)、将当前工作目录改为根目录
从父进程继承来的当前工作目录可能是一个挂在的文件系统,因为守护进程必须是一直存在的,那么该文件系统就不能被卸载
当然,可能是其他目录
(五)、关闭不再需要的文件描述符
(六)、某些守护进程打开 /dev/null 使其具有文件描述父 0,1,2,这样任何一个试图读标准输入、写标准输出或标准错误的库例程都不会产生任何效果
#include <stdio.h>#include <fcntl.h>#include <unistd.h>#include <stdlib.h>#include <signal.h>void mdaemonize(){ int i,n,fd0,fd1,fd2; pid_t pid; struct sigaction sa; umask(0); if((pid=fork()) < 0) exit(1); else if(pid != 0) exit(0); setsid(); sa.sa_handler = SIG_IGN; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); // why do we have to set SIGHUP? 我查了一下,可能是因为避免session leader退出的时候发送过来的SIGHUP,但因为我们不是前台进程,所以不会收到这个信号 if(sigaction(SIGHUP,&sa,NULL) < 0) exit(1); //<strong>在UNP中,书上说这里必须要设置SIGHUP,因为当会话头进程(首个fork产生的子进程)终止时,其会话中的所有进程(即再次fork产生的子进程)都收到SIGHUP信号</strong> if((pid=fork()) < 0) exit(1); if(pid != 0) exit(0); if(chdir("/") < 0) exit(1); // close all the descriptors n = getdtablesize(); for(i=0; i<n; i++) close(i); // Attach file descriptor 0,1 and 2 to /dev/null fd0 = open("/dev/null",O_RDWR); fd1 = dup(0); fd2 = dup(0); if(fd0!=0 || fd1!=1 || fd2 !=2) { printf("wrong , %d %d %d\n",fd0,fd1,fd2); exit(1); } sleep(10);}int main(){ mdaemonize(); return 0;}
以上程序的运行结果为
uid pid ppid pgid sid 500 5046 1 5045 5045 0 15:27 ? 00:00:00 ./DZ
大部分是理解了,就是不知到为什么需要处理SIGHUP信号呢? 哪里说会话首进程终止会发送信号SIGHUP了? 我自己试了下,接收到该信号进程终止,进程还是照旧运行啊。。。
出错记录
守护进程是如何处理出错消息的呢?
因为其本身不具备控制终端,所以不能只是简单的写道标准错误。
大多数守护进程都使用了 syslog 函数。
有3种产生日志消息的方法:
1、内核例程调用 log函数。
2、大多数守护进程调用 syslog 函数老产生日志信息。
3、无论是本机进程还是通过 TCP/IP 链接到此主机的其他主机上,都可以将日志消息发向 UDP端口 514.
此系列函数:
openlog、 syslog、 closelog 、setlogmask
单实例守护进程
为了正常运作,某些守护进程会实现为, 在同一时刻只运行该守护进程的一个副本。
例如,对cron 守护进程而言, 如果同时有多个实例运行,那么每个副本都可能试图开始某个预定的操作,于是造成该操作的重复执行,从而导致出错。
这里我们使用文件和记录锁解决这个问题。如果每一个守护进程创建一个固定名字的文件,并在该文件的整体上加一把写锁,那么只允许创建一把这样的写锁。在此之后的创建写锁的尝试都会失败,这就向守护进程指明已经有一个副本正在运行。
在守护进程终止时,这把锁被自动删除。
以下函数就通过使用文件锁说明如何使守护进程只存在一个副本。文件中存当前守护进程的进程ID。
#include <stdio.h>#include <unistd.h>#include <fcntl.h>#define LOCKFILE "/var/run/daemon.pid"#define LOCKMODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)int lockfile(int fd) //详情见下一章关于记录锁部分{ struct flock fl; fl.l_type = F_WRLCK; fl.l_start = 0; fl.l_whence = SEEK_SET; fl.l_len = 0; return (fcntl(fd,F_SETLK, &fl));}int already_daemon(){ int fd; char buf[16]; if(fd = open(LOCKFILE, O_RDWR | O_CREAT, LOCKMODE) < 0) exit(1); if(lockfile(fd) < 0) { if(errno == EACCESS || errno == EAGAIN) //如果当前已经有守护进程副本在运行了,就设置 errno 为 EACCESS或EAGAIN { close(fd); return 1; } exit(1); } ftruncate(fd,0); //因为我们要文件存的是当前守护进程的进程ID,这个函数用于将文件长度截断为0。另一方面,如果不截断,虽然会覆盖,但若是之前的长度超过当前要插入的字符串,那么之前的字符串会遗留一部分在文档中。 sprintf(buf,"ld",(long)getpid()); write(fd,buf,strlen(buf)+1); return 0;}
守护进程的惯例
1、若守护进程使用锁文件,那么通常存储在 /var/run 目录中,名字常常是 name.pid
2、若守护进程支持配置选项,那么配置文件常常在 /etc中, 名字常常是 name.conf
3、守护进程可以用命令行启动,但通常它们是由系统初始化脚本(/etc/rc* 或 /etc/init.d/*)之一启动的。如果守护进程终止时,应当自动的重新启动它,则我们可以在 /etc/inittab中为该守护进程包括 respawn项,这样,init 就将重新启动该守护进程
4、守护进程启动时会读取 配置文件,之后就不会再读取。 若我们修改了配置文件,可以发送 SIGHUP信号给守护进程,它就会重新读取配置文件。
以下我们就看一下如何通过 SIGHUP 信号来重读,这里会用到上面提到的 mdaemonize()函数和 already_daemon()函数(此程序使用 sigwait函数 )(这里依旧不进行守护进程的出错处理)
#include <stdio.h>#include <pthread.h>#include <signal.h>sigset_t mask;void reread(){ //....}void *th_fun(void *arg){ int signo; while(1) { if(sigwait(&mask,&signo) != 0) //利用线程处理 SIGHUP和 SIGTERM信号,一个是重读配置文件,一个是终止 exit(1); switch(signo) { case SIGHUP: reread(); break; case SIGTERM: exit(0); default: //出错处理 } }}int main(int ac, char **av) //参数即为配置文件{ char *cmd; pthread_t tid; struct sigaction sa; if((cmd=strrchr(av[0],'/')) == NULL) //取出配置文件的名字 cmd = av[0]; else cmd ++; mdaemonize(cmd); if(already_daemon()) //发现已经有一个当前守护进程的副本存在,选择退出 exit(1); sa.sa.handler = SIG_DFL; //接着就是设置SIGHUP的处理为默认。因为在 mdaemonize函数中,我们将此信号设置为忽略,如果这里不改变,那么创建的线程也将会忽略,从而即使有sigwait函数在等他,这个信号也永远不会出现。 sigemptyset(&sa.sa_mask); sa.sa_flag = 0; if(sigaction(SIGHUP, &sa, NULL) < 0) exit(1); sigfillempty(&mask;) //我们处理信号的是用一个线程来处理的,所以首先在主线程里屏蔽所有信号 if(pthread_sigmask(SIG_BLOCK,&mask,NULL) != 0) exit(1); if(pthread_create(&tid,NULL,th_fun,0) != 0) exit(1); // other things to deal with return 0;}
虽然SIGHUP和SIGTERM的默认动作是终止进程。因为我们阻塞了这些信号,所以当SIGHUP和SIGTERM的其中一个发送到守护进程的时候,守护进程不会消亡,作为代替,调用sigwait的线程在返回时候将指示已经接收到该信号。
当然,我们也可以不用线程而是简单的将处理放在信号处理函数中。
#include <stdio.h>#include <pthread.h>#include <signal.h>sigset_t mask;void reread(){ //....}void sigterm(int s){ //守护进程消息处理syslog exit(1);}void sighup(int s){ reread();}int main(int ac, char **av) //参数即为配置文件{ char *cmd; pthread_t tid; struct sigaction sa; if((cmd=strrchr(av[0],'/')) == NULL) //取出配置文件的名字 cmd = av[0]; else cmd ++; mdaemonize(cmd); if(already_daemon()) //发现已经有一个当前守护进程的副本存在,选择退出 exit(1); sa.sa.handler = sighup; sigemptyset(&sa.sa_mask); sigaddset(&sa.sa_mask,SIGHUP); sa.sa_flag = 0; if(sigaction(SIGHUP, &sa, NULL) < 0) exit(1); sa.sa.handler = sigterm; sigemptyset(&sa.sa_mask); sigaddset(&sa.sa_mask,SIGTERM); sa.sa_flag = 0; if(sigaction(SIGTERM, &sa, NULL) < 0) exit(1); // other things to deal with return 0;}
在学习过程中,我发现对 SIGHUP的理解一直存在疑惑。这里先贴一个地址,虽然看了依旧不是非常清楚,但至少能学到点什么
http://bbs.chinaunix.net/thread-766356-1-1.html
0 0
- 第十三章 守护进程
- 第十三章 守护进程
- apue 第十三章 守护进程
- APUE第十三章(守护进程)
- 《APUE》笔记-第十三章-守护进程
- 《APUE》读书笔记—第十三章守护进程
- APUE读书笔记-第十三章-守护进程
- APUE学习笔记——第十三章 守护进程
- apue学习笔记(第十三章 守护进程)
- UNIX环境高级编程第十三章 守护进程 总结
- UNIX环境高级编程学习之第十三章守护进程 - 初始化一个守护进程
- UNIX环境高级编程学习之第十三章守护进程 - 单实例的守护进程
- unix高级环境编程 例子 代码实现练习 第十三章:守护进程
- UNP卷1:第十三章(守护进程和inetd超级服务器)
- apue学习第二十天——守护进程(第十三章)
- UNIX网络编程卷一:第十三章 守护进程和inetd超级服务器
- UNP学习笔记(第十三章 守护进程和inetd超级服务器)
- 第五章 守护进程
- Demo2 模拟简单登陆 客户端代码
- OpenCV学习笔记(一)——安装配置、第一个程序
- 无刷新提交或无刷新与服务器通讯,通用AJax类及使用方法
- Java 8新特性:全新的Stream API
- thinkphp底层调用PDO访问数据库时报错
- 第十三章 守护进程
- 关于cocos2d-x对etc1图片支持的分析
- 并查集 小希的迷宫
- bzoj 1053 题解
- Android Studio Tips Of the Day - Roundup #3
- Demo2 模拟简单登陆 服务器端代码
- Lua字符串模式和捕获 (转)
- 【线性同余方程】toj2297&poj2115 C Looooops
- 数学建模总结(一)