守護程序是在背景運作且不與任何控制終端關聯的程序。unix系統通常有很多守護程序在背景運作,執行不同的管理任務。
守護程序沒有控制終端通常源于它們由系統初始化腳本啟動。然而守護程序也可能從某個終端由使用者在shell提示符下鍵入指令行啟動,這樣的守護程序必須親自脫離與控制終端的關聯,進而避免與作業控制,終端會話管理,終端産生信号等發生任何不期望的互動,也可以避免在背景運作的守護程序非預期的輸出到終端。
守護程序有多種啟動方法:
1.在系統啟動階段,許多守護程序由系統初始化腳本啟動。這些腳本通常位于/etc目錄或以/etc/rc開頭的某個目錄中,它們的具體位置和内容卻是實作相關的。由這些腳本啟動的守護程序一開始擁有超級使用者權限。
有若幹個網絡伺服器通常從這些腳本啟動:inetd超級伺服器,web伺服器,郵件伺服器(經常是sendmail)。
2. 許多網絡伺服器由inetd超級伺服器啟動。inetd自身由上一條中的某個腳本啟動。inetd監聽網絡請求,每當有一個請求到達時,啟動相應的實際伺服器(telnet伺服器,FTP伺服器等)
3. cron守護程序按照規則定期執行一些程式,而由它啟動執行的程式同樣作為守護程序運作。cron自身由第一條啟動方法中的某個腳本啟動
4. at指令用于指定将來某個時刻的程式執行。這些程式的執行時刻到來時,通常由cron守護程序啟動執行它們,是以這些程式同樣作為守護程序運作。
5.守護程序還可以從使用者終端或在前台或在背景啟動。這麼做往往是為了測試守護程序或重新開機因某種原因而終止了的某個守護程序。
因為守護程序沒有控制終端,是以當有事發生時它們得有輸出消息的某種方法可用,而這些消息既可能是普通的通告性消息,也可能是需由系統管理者處理的緊急事件消息。syslog函數是輸出這些消息的标準方法,它把這些消息發送給syslogd守護程序。
備注:遇到類似的函數,具體說明請檢視APUE
#include <syslog.h>
void syslog(int priority, const char *message,...);
void openlog(const char *ident, int options, int facility);
void closelog(void);
伺服器程式daytimetcpsrv.c:
#include <stdio.h>
#include <netdb.h>
#include <sys/socket.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
extern int errno;
int daemon_proc;
#define MAXLINE 1024
#define MAXFD 64
int daemon_init(const char *pname, int facility);
int tcp_listen(const char *host, const char *serv, socklen_t *addrlenp);
int main(int argc, char **argv)
{
int listenfd, connfd;
socklen_t len;
char buff[MAXLINE];
time_t ticks;
struct sockaddr_in cliaddr;
daemon_init(argv[0], 0);
listenfd = tcp_listen(argv[1], argv[2], NULL);
for (; ;){
len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &len);
inet_ntop(AF_INET, &cliaddr.sin_addr, buff, sizeof(buff));
strcat(buff, ".this is a test\n");
syslog(LOG_INFO, buff);
ticks = time(NULL);
snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
write(connfd, buff, strlen(buff));
close(connfd);
}
}
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)
signal(SIGHUP, SIG_IGN);
daemon_proc = 1;
chdir("/");
for (i = 0; i < MAXFD; i++)
close(i);
open("/dev/null", O_RDONLY);
open("/dev/null", O_RDWR);
openlog(pname, LOG_PID, facility);
int tcp_listen(const char *host, const char *serv, socklen_t *addrlenp)
int listenfd, n;
const int on = 1;
struct addrinfo hints, *res, *ressave;
bzero(&hints, sizeof(struct addrinfo));
hints.ai_flags = AI_PASSIVE;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if ((n = getaddrinfo(host, serv, &hints, &res)) != 0){
printf("tcp_listen error for %s,%s:%s\n", host, serv, gai_strerror(n));
exit(1);
ressave = res;
do{
listenfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (listenfd < 0)
continue;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if (bind(listenfd, res->ai_addr, res->ai_addrlen) == 0)
break;
close(listenfd);
} while ((res = res->ai_next) != NULL);
if (res == NULL)
printf("tcp_listen error for %s,%s\n", host, serv);
listen(listenfd, 5);
if (addrlenp)
*addrlenp = res->ai_addrlen;
freeaddrinfo(ressave);
return listenfd;
用戶端程式daytimetcpcli.c:
#include <netdb.h>
int tcp_connect(const char *host, const char *serv);
int sockfd, n;
char recvline[MAXLINE + 1];
if (argc != 3){
printf("argument should be 3\n");
sockfd = tcp_connect(argv[1], argv[2]);
len = sizeof(cliaddr);
getpeername(sockfd, (struct sockaddr *)&cliaddr, len);
inet_ntop(AF_INET, &cliaddr.sin_addr, recvline, sizeof(recvline));
printf("connect to %s\n", recvline);
while ((n = read(sockfd, recvline, MAXLINE)) > 0){
recvline[n] = 0;
fputs(recvline, stdout);
exit(0);
int tcp_connect(const char *host, const char *serv)
struct sockaddr_in *cliaddr;
printf("tcp_connect error for %s,%s:%s\n", host, serv, gai_strerror(n));
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sockfd < 0)
if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)
cliaddr = (struct sockaddr_in *)res->ai_addr;
close(sockfd);
printf("tcp_connect error for %s,%s\n", host, serv);
return sockfd;
程式運作如下:
服務端:
leichaojian@ThinkPad-T430i:~$ ./daytimetcpsrv ThinkPad-T430i 9878
用戶端:
leichaojian@ThinkPad-T430i:~$ ./daytimetcpcli ThinkPad-T430i 9878
connect to 0.0.0.0
Wed Oct 8 21:07:35 2014
然後我們檢視/var/log/syslog這個檔案,通過查找字元串“this is a test”,發現如下的語句:
Oct 8 21:07:35 ThinkPad-T430i ./daytimetcpsrv[10528]: 127.0.0.1.this is a test
用于産生子程序
setsid用于建立一個新的回話。目前程序變為新會話的會話頭程序以及新程序組的程序組頭程序,進而不再有控制終端。
忽略SIGHUP信号并再次調用fork。該函數傳回時,父程序實際上是上一次調用fork産生的子程序,它被終止掉,留下新的子程序繼續運作。再次fork的目的是確定本守護程序将來即使打開了一個終端裝置,也不會自動獲得控制終端。當沒有控制終端的一個會話頭程序打開一個終端裝置時(該終端不會是目前某個其他會話的控制終端),該終端自動成為這個會話頭程序的控制終端。然而再次調用fork之後,我們確定新的子程序不再是一個會話頭程序,進而不能自動獲得一個控制終端。這裡必須霍略SIGHUP信号,因為當會話頭程序(即首次fork産生的子程序)終止時,其會話中的所有程序(即再次fork産生的子程序)都收到SIGHUP信号。
因為之前關閉了所有的描述符,是以要打開這三個基本描述符并且重定向,讓read傳回0,write系統調用丢棄所寫的資料(書上說如果調用了syslog函數,則不要調用類似printf之類的函數,因為會被簡單的忽略掉)。因為如果繼續關閉,則萬一有新的程序打開一個描述符,卻占用了0,1,2這三個描述符,則可能導緻将錯誤的資料發送給用戶端。
舊的伺服器隻是等待客戶請求的到達,如FTP,Telnet,TFTP等。這些程序都是在系統自舉階段從/etc/rc檔案中啟動,而且每個程序執行幾乎相同的啟動任務:建立一個套接字,把本伺服器的衆所周知端口捆綁到該套接字,等待一個連接配接或一個資料報,然後派生子程序。子程序為客戶提供服務,父程序則繼續等待下一個客戶請求。這個模型存在兩個問題:
(1)所有這些守護程序含有幾乎相同的啟動代碼,既表現在建立套接字上,也表現在演變成守護程序上(類似我們的daemon_init函數)
(2)每個守護程序在程序表中占據一個表項,然而它們大部分時間處于睡眠狀态。
而新版本的系統通過提供inetd守護程序(網際網路超級伺服器)來簡化問題:
(1)通過inetd處理普通守護程序的大部分啟動細節來簡化守護程序的編寫。這麼一來每個伺服器不再有調用daemon_init函數的必要。
(2)單個程序就能為多個服務等待外來的客戶請求,以此取代每個服務一個程序的做法。這麼做減少了系統中的程序總數。
字段
說明
service_name
必須在/etc/services檔案中定義
socket_type
stream(對于tcp)或dgram(對于udp)
protocol
必須在/etc/protocols檔案中定義:tcp或udp
wait-falg
對于TCP一半為nowait,對于UDP一般為wait
login-name
來自/etc/passwd的使用者名,一般為root
server-program
調用exec指定的完整路徑名
server-program-arguments
調用exec指定的指令行參數
下面是xinetd.conf檔案中的若幹行:
ftp
stream
tcp
nowait
root
/usr/bin/ftpd
ftpd -l
telnet
/usr/bin/telnetd
telnetd
在啟動階段,讀入/etc/xinetd.conf檔案并給該檔案中指定的每個服務建立一個适當類型(位元組流或資料報)的套接字。inetd能夠處理的伺服器的最大數目取決于inetd能夠建立的描述符的最大數目。新建立的每個套接字都被加入到将由某個select調用使用的一個描述符集中。
為每個套接字調用bind,指定捆綁相應伺服器的衆所周知端口和通配位址。這個TCP或UDP端口号通過調用getservbyname獲得,作為函數參數的是相應伺服器在配置檔案中的service-name字段和protocol字段。
對于每個TCP套接字,調用listen以接收外來的連接配接請求。對于資料報套接字則不執行本步驟
建立完畢所有套接字之後,調用select等待其中任何一個套接字變為可讀。TCP監聽套接字将在有一個新連接配接準備好可被接受時變為可讀,UDP套接字将在有一個資料報到達時變為可讀。inetd的不部分時間花在阻塞于select調用内部,等待某個套接字變為可讀。
當select傳回指出某個套接字已可讀之後,如果該套接字是一個TCP套接字,而且其伺服器的wait-flag值為nowait,那就調用accept接受這個新連接配接。
inetd守護程序調用fork派生程序,并由子程序處理服務請求。子程序關閉要處理的套接字描述符之外的所有描述符:對于TCP伺服器來說,這個套接字是由accept傳回的新的已連接配接套接字,對于UDP伺服器來說,這個套接字是父程序最初建立的UDP套接字。子程序dup2三次,把這個待處理套接字的描述符複制到描述符0,1和2,然後關閉原套接字描述符(由accept傳回的已連接配接的TCP套接字)。
子程序然後調用exec執行由相應的server-program字段指定的程式來具體處理請求,相應的server-program-arguments字段值則作為指令行參數傳遞給該程式。
如果第五步中的select傳回的是一個位元組流套接字,那麼父程序必須關閉已連接配接套接字(就像标準并發伺服器那樣)。父程序再次調用select,等待下一個變為可讀的套接字。(因為TCP設定的nowait,意味着inetd不必等待某個子程序終止就可以接收對于該子程序所提供之服務的另一個連接配接。如果對于某個子程序所提供之服務的另一個連接配接确實在該子程序終止之前到達:accept傳回,那麼父程序再次調用select:意味着要關閉已連接配接的套接字,繼續執行步驟4,5,6)
給一個資料報服務指定wait标志導緻父程序執行的步驟發生變化。這個标志要求inet必須在這個套接字再次稱為slect調用的候選套接字之前等待目前服務該套接字的子程序終止。發生的變化有以下幾點:
[1]fork傳回到父程序時,父程序儲存子程序的程序ID。這麼做使得父程序能夠通過檢視由waitpid傳回的值确定這個子程序的終止時間
[2]父程序通過使用FD_CLR宏關閉這個套接字在select所用描述符集中對應的位,達成在将來的select調用中禁止這個套接字的目的。這點意味着子程序将接管該套接字,直到自身終止為止。
[3]當子程序終止時,父程序被通知一個SIGCHLD信号,而父程序的信号處理函數将取得這個子程序的程序ID。父程序通過打開相應的套接字在select所用描述符集中對應的位,使得該套接字重新成為select的候選套接字。
2)inetd守護程序的伺服器程式
#include <netinet/in.h>
socklen_t len;
struct sockaddr_in cliaddr;
char buff[MAXLINE];
time_t ticks;
openlog(argv[0], 0);
getpeername(0, (struct sockaddr *)&cliaddr, &len);
inet_ntop(AF_INET, (struct sockaddr *)&cliaddr.sin_addr, buff, sizeof(buff));
printf("connect from %s\n", buff);
ticks = time(NULL);
snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
write(0, buff, strlen(buff));
close(0);
在/etc/service中增加:
mydaytime 9999/tcp
在/etc/xinetd.conf中增加:
mydaytime stream tcp nowait leichaojian /home/leichaojian/newdaytimetcpserv3 newdaytimetcpserv3
程式輸出:
leichaojian@ThinkPad-T430i:~$ ./newdaytimetcpserv3
connect from 0.0.0.0
Fri Oct 3 14:27:31 2014
要運作這個例子程式,
1.先要添加服務:做法,在/etc/services最後加上: mydaytime 9999/tcp
2.安裝xinetd: sudo yum install xinetd
3.編輯配置:在/etc/xinetd.d/目錄下建立一個mydaytime檔案,内容如下:
service mydaytime
{
socket_type = stream
protocol = tcp
wait = no
user =root
server =/home/xpmo/unp/server
}
其中server是例子程式的路徑
4.重新開機xinetdsudo killall -HUP xinetd
5.檢視啟動成功否:
sudo netstat -tlp
如果看到mydaytime就說明啟動成功了。
安裝yum install telnet