《APUE》读书笔记—第十三章守护进程
守護進程也稱為精靈進程是一種生存期較長的一種進程。它們獨立于控制終端并且周期性的執行某種任務或等待處理某些發生的事件。他們常常在系統引導裝入時啟動,在系統關閉時終止。unix系統有很多守護進程,大多數服務器都是用守護進程實現的,例如inetd守護進程。
1、守護進程的特征
用ps命令察看一些常用的系統守護進程,看一下他們和幾個概念:進程組、控制終端和會話有什么聯系。執行: ps?–axj ,結果如下所示:
從結果可以看出守護進程沒有控制終端,其終端名設置為?,終端前臺進程組ID設置為-1,init進程ID為1。系統進程依賴于操作系統實現,父進程ID為0的各進程通常是內核進程,它們作為系統自舉的一部分而啟動。內核進程以超級用戶特權運行,無控制終端,無命令行。大多數守護進程的父進程是init進程。
?守護進程與后臺進程的區別:(1)?后臺運行程序,即加&啟動的程序,(2)后臺運行的程序擁有控制終端,守護進程沒有。
2、守護進程編程規則
(1)調用umask將文件模式創建屏蔽字設置為0。因為進程從創建它的父進程那里繼承了文件創建掩模。它可能修改守護進程所創建的文件的存取位。為防止這一點,將文件創建掩模清除:調用umask(0)。
(2)調用fork,然后使父進程退出。這樣可避免掛起控制終端將Daemon放入后臺執行。
(3)調用setsid以創建一個新會話。這樣可以使得調用進程成為新會話的首進程,成為一個新進程組的組長進程,沒有控制終端。
(4)將當前工作目錄更改為根目錄。進程活動時,其工作目錄所在的文件系統不能卸下。一般需要將工作目錄改變到根目錄。對于需要轉儲核心,寫運行日志的進程將工作目錄改變到特定目錄如/tmpchdir("/") 。
(5)關閉不再需要的文件描述符。進程從父進程那里繼承了打開的文件描述符。如不關閉,將會浪費系統資源,造成進程所在的文件系統無法卸下以及引起無法預料的錯誤。
(6)某些守護進程打開/dev/null使其具有文件描述符0、1和2。使得任何一個試圖讀標準輸入、寫標準輸出或者標準出錯的歷程都不會產生任何效果。
(7)處理SIGCHLD信號 。處理SIGCHLD信號并不是必須的。但對于某些進程,特別是服務器進程往往在請求到來時生成子進程處理請求。如果父進程不等待子進程結束,子進程將成為僵尸進程(zombie)從而占用系統資源。如果父進程等待子進程結束,將增加父進程的負擔,影響服務器進程的并發性能。在Linux下可以簡單地將SIGCHLD信號的操作設為SIG_IGN。
初始化一個守護進程的程序如下:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <pthread.h> #include <signal.h> #include <fcntl.h> #include <syslog.h> #include <sys/resource.h>void daemonize(const char *cmd) {int i,fd0,fd1,fd2;pid_t pid;struct rlimit r1;struct sigaction sa;umask(0);//獲取文件描述符最大值getrlimit(RLIMIT_NOFILE,&r1);//創建子進程if((pid = fork()) < 0){perror("fork() error");exit(0);}else if(pid > 0) //使父進程退出exit(0);setsid(); //創建會話//創建子進程避免獲取終端sa.sa_handler = SIG_IGN;sigemptyset(&sa.sa_mask);sa.sa_flags = 0;sigaction(SIGHUP,&sa,NULL);if((pid = fork()) < 0){perror("fork() error");exit(0);}else if(pid > 0)exit(0);//修改目錄chdir("/");//關閉不需要的文件描述符if(r1.rlim_max == RLIM_INFINITY)r1.rlim_max = 1024;for(i=0;i<r1.rlim_max;++i)close(i);//打開文件描述符fd0 = open("/dev/null",O_RDWR);fd1 = dup(0);fd2 = dup(0);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);} }int main() {daemonize("ls");sleep(30); //主進程休眠,以便查看守護進程狀態exit(0); }?第一次調用fork的目的是保證調用setsid的調用進程不是進程組長。(而setsid函數是實現與控制終端脫離的唯一方法);setsid函數使進程成為新會話的會話頭和進程組長,并與控制終端斷開連接;第二次調用fork的目的是:即使守護進程將來打開一個終端設備,也不會自動獲得控制終端。(因為在SVR4中,當沒有控制終端的會話頭進程打開終端設備時,如果這個終端不是其他會話的控制終端,該終端將自動成為這個會話的控制終端),這樣可以保證這次生成的進程不再是一個會話頭。忽略SIGHUP信號的原因是,當第一次生成的子進程(會話頭)終止時,該會話中的所有進程(第二次生成的子進程)都會收到該信號。
程序執行結果,輸入ps -axj命令查看守護進程的信息:3、出錯記錄
守護進程沒有控制終端,不能將錯誤寫到標準輸錯上。大多數進程使用集中的守護進程出錯syslog設施,該設施的接口是syslog函數,原型如下:
#include <syslog.h>
void openlog(const char *ident, int option, int facility);
void syslog(int priority, const char *format, ...);
void closelog(void);
int setlogmask(int mask);
#include <stdarg.h>
void vsyslog(int priority, const char *format, va_list ap);
大多數syslog實現將使消息多時間處于隊列中,如果在此時間中到達了重復消息,那么syslog守護進程將不把它寫到日志記錄中,而是打印輸出重復消息。
4、單實例守護進程
為了正常運作,某些守護進程實現為單實例,即在任一時刻只運行該守護進程的一個副本。采用文件鎖和記錄鎖機制可以實現單實例守護進程,如果每一個守護進程創建一個文件,并且在整個文件上加上一把鎖,那就只允許創建一把這樣的寫鎖,之后試圖再創建這樣的一把寫鎖將會失敗。這樣就保證守護進程只有一個副本在運行。使用文件和記錄鎖保證只運行某守護進程的一個副本,守護進程的每個副本都試圖創建一個文件,并將其進程ID寫到該文件中。程序如下:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <signal.h> #include <fcntl.h> #include <syslog.h> #include <sys/stat.h>#define LOCKFILE "/var/run/daemon.pid" #define LOCKMODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH ) extern int lockfile(int);int already_running(void) {int fd;char buf[16];//打開文件,不存在則創建fd = open(LOCKFILE,O-RDWR|O_CREAT,LOCKMODE);if(fd < 0){syslog(LOG_ERR,"can't open %s : %s",LOCKFILE,strerror(errno));exit(1);}//對文件加鎖if(lockfile(fd)<0){if(errno == EACCES | errno == EAGAIN){close(fd);return 1;}syslog(LOG_ERR,"can,t lock %s : %s",LOCKFILE,strerror(errno));exit(1);}ftruncate(fd,0); //將文件長度截短為0sprintf(buf,"%ld",(long)getpid());write(fd,buf,strlen(buf)+1);return 0; }?5、守護進程的慣例
(1)若守護進程使用鎖文件,那么該文件通常存放在/var/run目錄中。
(2)若守護進程支持配置選項,那么配置文件通常存放在/etc中目錄中。
(3)守護進程可以用命令行啟動,通常是系統初始化腳本。
(4)若一守護進程有一配置文件,那么當該守護進程啟動時,讀取該文件,此后一把不會在查看它。
使用sigwait及多線程實現守護進程重讀配置文件程序如下:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <signal.h> #include <fcntl.h> #include <syslog.h> #include <sys/stat.h> #include <sys/resource.h>#define LOCKFILE "/var/run/daemon.pid" #define LOCKMODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH )sigset_t mask; int lockfile(int fd) {struct flock f1;f1.l_type = F_WRLCK;f1.l_start = 0;f1.l_whence = SEEK_SET;f1.l_len = 0;return fcntl(fd,F_SETLK,&f1); } int already_running(void) {int fd;char buf[16];fd = open(LOCKFILE,O_RDWR|O_CREAT,LOCKMODE);if(fd < 0){syslog(LOG_ERR,"can't open %s : %s",LOCKFILE,strerror(errno));exit(1);}if(lockfile(fd)<0){if(errno == EACCES | errno == EAGAIN){close(fd);return 1;}syslog(LOG_ERR,"can,t lock %s : %s",LOCKFILE,strerror(errno));exit(1);}ftruncate(fd,0);sprintf(buf,"%ld",(long)getpid());write(fd,buf,strlen(buf)+1);return 0; }void daemonize(const char *cmd) {int i,fd0,fd1,fd2;pid_t pid;struct rlimit r1;struct sigaction sa;umask(0);getrlimit(RLIMIT_NOFILE,&r1);if((pid = fork()) < 0){perror("fork() error");exit(0);}else if(pid > 0)exit(0);setsid();sa.sa_handler = SIG_IGN;sigemptyset(&sa.sa_mask);sa.sa_flags = 0;sigaction(SIGHUP,&sa,NULL);if((pid = fork()) < 0){perror("fork() error");exit(0);}else if(pid > 0)exit(0);chdir("/");if(r1.rlim_max == RLIM_INFINITY)r1.rlim_max = 1024;for(i=0;i<r1.rlim_max;++i)close(i);fd0 = open("/dev/null",O_RDWR);fd1 = dup(0);fd2 = dup(0);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);} }void reread() {printf("read daemon config file again.\n"); } void * thread_func(void *arg) {int err,signo;while(1){sigwait(&mask,&signo);switch(signo){case SIGHUP:syslog(LOG_INFO,"Re-reading configuration file.\n");reread();break;case SIGTERM:syslog(LOG_INFO,"got SIGTERM;exiting.\n");exit(0);default:syslog(LOG_INFO,"unexpected signal %d.\n",signo);}}return NULL; } int main(int argc,char *argv[]) {pthread_t tid;char *cmd;struct sigaction sa;if((cmd = strrchr(argv[0],'/')) == NULL)cmd = argv[0];elsecmd++;daemonize(cmd);if(already_running()){syslog(LOG_ERR,"daemon already running.\n");exit(1);}sa.sa_handler =SIG_DFL;sigemptyset(&sa.sa_mask);sa.sa_flags = 0;sigaction(SIGHUP,&sa,NULL);sigfillset(&mask);pthread_sigmask(SIG_BLOCK,&mask,NULL);pthread_create(&tid,NULL,thread_func,0);sleep(90);exit(0); }?關于本章介紹的守護進程,很多地方不是很懂,具體怎么應用還不清楚,先了解個大概,知道什么是守護進程及其作用。具體怎么用日后再來補充,有待加強。
轉載于:https://www.cnblogs.com/Anker/archive/2012/12/20/2825929.html
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的《APUE》读书笔记—第十三章守护进程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MAX Script 脚本语言
- 下一篇: vsftp 添加虚拟帐号