Linux守護進程詳解(init.d和xinetd)
一 Linux守護進程
Linux 服務器在啟動時需要啟動很多系統服務,它們向本地和網絡用戶提供了Linux的系統功能接口,直接面向應用程序和用戶。提供這些服務的程序是由運行在後臺的守護進程來執行的。守護進程是生存期長的一種進程。它們獨立於控制終端並且周期性的執行某種任務或等待處理某些發生的事件。他們常常在系統引導裝入時啟動,在系統關閉時終止。linux系統有很多守護進程,大多數服務器都是用守護進程實現的。同時,守護進程完成許多系統任務,比如,作 業規劃進程crond、打印進程lqd等。有些書籍和資料也把守護進程稱作:“服務”。
守護進程,也就是指daemon和service。
二 Linux守護進程的分類
根據守護進程的啟動和管理方式,可以分為獨立啟動守護進程和超級守護進程兩類
獨立啟動(stand_alone):該類進程啟動後就常駐內存,所以會一直占用系統資源。其最大的優點就是它會一直啟動,當外界有要求時相應速度較快,像httpd等進程;
超級守護進程:系統啟動時由一個統一的守護進程xinet來負責管理一些進程,當相應請求到來時需要通過xinet的轉接才可以喚醒被xinet管理的進程。這種進程的優點時最初只有xinet這一守護進程占有系統資源,其他的內部服務並不一直占有系統資源,只有數據包到來時才會被xinet管理員來喚醒。並且我們還可以通過xinet來對它所管理的進程設置一些訪問權限,相當於多了一層管理機制。
如果用兩個比喻來形容兩類守護進程的話一般會用銀行的業務處理窗口來類比:
獨立啟動:銀行裏有一種單服務的窗口,像取錢,存錢等窗口,這些窗口邊上始終會坐著一個人,如果有人來取錢或存錢,可以直接到相應的窗口去辦理,這個處理單一服務的始終存在的人就是獨立啟動的守護進程;
超級守護進程:銀行裏還有一種窗口,提供綜合服務,像匯款,轉賬,提款等業務;這種窗口附近也始終坐著一個人(xinet),她可能不提供具體的服務,提供具體服務的人在裏面閑著聊天啊,喝茶啊,但是當有人來匯款時他會大聲喊一句,小王,有人匯款啦,然後裏面管匯款的小王會立馬跑過來幫忙辦完匯款業務。其他的人繼續聊天,喝茶。這些負責具體業務的人我們就稱之為超級守護進程。當然可能匯款人會有一些規則,可能不能往北京匯款,他就會提早告訴xinet,所以如果有人來匯款想匯往北京的話,管理員就直接告訴他這個我們這裏辦不到的,於是就根本不會去喊匯款員了,相當於提供了一層管理機制。針對這種窗口還存在多線程和單線程的區別:多線程:將所有用戶的要求都提上來,裏面的人都別閑著了,都一起幹活吧;
單線程:大家都排好隊了,一個一個來,裏面的人同一時間只有一個人在工作。
這裏需要註意的是超級守護進程的管理員xinet也是一個守護進程,只不過它的任務就是傳話,其實這也是一個很具體很艱巨的任務哦。
當然每個守護進程都會監聽一個端口(銀行窗口),一些常用守護進程的監聽端口是固定的,像httpd監聽80端口, sshd監聽22端口等;我們可以將其理解為責任制,時候等待,有求必應。具體的端口信息可以通過cat /etc/services來查看。
三、Linux守護進程管理工具
Linux提供了三種不同的守護進程管理工具:redhat-config-services、ntsysv、chkconfig,可以根據具體需要靈活運用。
# service iptables status #查看相應服務的狀態,用service需要服務在/etc/init.d/目錄中存在
# netstat -tulp #會列出相應的服務及其監聽的端口號等,若加n參數會列出端口號
#chkconfig --list |grep 服務名 #會列出現在當前服務的各種狀態,包括在不同運行級別下的啟情況,分為上線兩部分,上部分是獨立啟動的服務,你會看到xinetd也在,下面部分是有inet管理的超級守護進程,沒有運行級別可分的。
四 Linux守護進程的運行方式
1.獨立運行(stand-alone)的守護進程
獨立運行的守護進程由init腳本負責管理,所有獨立運行的守護進程的腳本在/etc/rc.d/init.d/目錄下。系統服務都是獨立運行的守護進程,包括syslogd和cron等。獨立運行的守護進程的工作方式稱做stand-alone,它是UNIX傳統的C/S模式的訪問模式。stand-alone模式的工作原理如圖4-4所示。
工作在stand-alone模式下的網絡服務有xinetd、route、gated,另外還有Web服務器Apache和郵件服務器Sendmail、域名服務器Bind。在Linux系統中通過stand-alone模式啟動的服務由/etc/rc.d/下面對應的運行級別當中的符號鏈接啟動。
2.xinetd模式運行獨立的守護進程
從守護進程的概念可以看出,對於系統所要通過的每一種服務,都必須運行一個監聽某個端口連接所發生的守護進程,這通常意味著資源浪費。為了解決這個問題,Linux引進了"網絡守護進程服務程序"的概念。Red Hat Linux 9.0使用的網絡守護進程是xinted(eXtended InterNET daemon)。xinetd能夠同時監聽多個指定的端口,在接受用戶請求時,它能夠根據用戶請求的端口的不同,啟動不同的網絡服務進程來處理這些用戶請求。可以把xinetd看做一個管理啟動服務的管理服務器,它決定把一個客戶請求交給哪個程序處理,然後啟動相應的守護進程。xinetd無時不在運行並監聽它所管理的所有端口上的服務。當某個要連接它管理的某項服務的請求到達時,xinetd就會為該服務啟動合適的服務器。xinetd模式的工作原理如圖4-5所示。
3.
xinetd和stand-alone工作模式相比,系統不想要每一個網絡服務進程都監聽其服務端口,運行單個xinetd就可以同時監聽所有服務端口,這樣就降低了系統開銷,保護系統資源。但是對於訪問量大、經常出現並發訪問的情況,xinetd則要頻繁啟動相應的網絡服務進程,反而會導致系統性能下降。查看系統為Linux服務提供哪種工作模式,可以在Linux命令行中使用pstree命令,就能看到兩種不同模式啟動的網絡服務。一般來說系統中一些負載高的服務,Sendmail、Apache服務是單獨啟動的;而其他服務類型都可以使用xinetd超級服務器管理。
五 Xinetd
1.什麽是xinetd
xinetd即extended internet daemon,xinetd是新一代的網絡守護進程服務程序,又叫超級Internet服務器。經常用來管理多種輕量級Internet服務。xinetd提供類似於inetd+tcp_wrapper的功能,但是更加強大和安全。
2. xinetd的特色
1) 強大的存取控制功能
— 內置對惡意用戶和善意用戶的差別待遇設定。
— 使用libwrap支持,其效能更甚於tcpd。
— 可以限制連接的等級,基於主機的連接數和基於服務的連接數。
— 設置特定的連接時間。
— 將某個服務設置到特定的主機以提供服務。
2) 有效防止DoS攻擊
— 可以限制連接的等級。
— 可以限制一個主機的最大連接數,從而防止某個主機獨占某個服務。
— 可以限制日誌文件的大小,防止磁盤空間被填滿。
3) 強大的日誌功能
— 可以為每一個服務就syslog設定日誌等級。
— 如果不使用syslog,也可以為每個服務建立日誌文件。
— 可以記錄請求的起止時間以決定對方的訪問時間。
— 可以記錄試圖非法訪問的請求。
4) 轉向功能
可以將客戶端的請求轉發到另一臺主機去處理。
5) 支持IPv6
xinetd自xinetd 2.1.8.8pre*起的版本就支持IPv6,可以通過在./configure腳本中使用with-inet6 capability選項來完成。註意,要使這個生效,核心和網絡必須支持IPv6。當然IPv4仍然被支持。
6) 與客戶端的交互功能
無論客戶端請求是否成功,xinetd都會有提示告知連接狀態。
3. Xinetd的缺點
當前,它最大的缺點是對RPC支持的不穩定性,但是可以啟動protmap,使它與xinetd共存來解決這個問題。
4 使用xinetd啟動守護進程
原則上任何系統服務都可以使用xinetd,然而最適合的應該是那些常用的網絡服務,同時,這個服務的請求數目和頻繁程度不會太高。像DNS和Apache就不適合采用這種方式,而像FTP、Telnet、SSH等就適合使用xinetd模式,系統默認使用xinetd的服務可以分為如下幾類。
① 標準Internet服務:telnet、ftp。
② 信息服務:finger、netstat、systat。
③ 郵件服務:imap、imaps、pop2、pop3、pops。
④ RPC服務:rquotad、rstatd、rusersd、sprayd、walld。
⑤ BSD服務:comsat、exec、login、ntalk、shell、talk。
⑥ 內部服務:chargen、daytime、echo、servers、services、time。
⑦ 安全服務:irc。
⑧ 其他服務:name、tftp、uucp。
5. 解讀xinet的配置文件/etc/services, /etc/xinetd.conf和/etc/xinetd.d/*
0)/etc/services
在/etc/services 中設置了xinetd下的service對應的端口,例如:
$ cat /etc/services | grep rsync
rsync 873/tcp # rsync
rsync 873/udp # rsync
1) /etc/xinetd.conf
xinetd的配置文件是/etc/xinetd.conf,但是它只包括幾個默認值及/etc/xinetd.d目錄中的配置文件。如果要啟用或禁用某項xinetd服務,編輯位於/etc/xinetd.d目錄中的配置文件。例如,disable屬性被設為yes,表示該項服務已禁用;disable屬性被設為no,表示該項服務已啟用。/etc/xinetd.conf有許多選項,下面是RHEL
4.0的/etc/xinetd.conf。
# Simple configuration file for xinetd
# Some defaults, and include /etc/xinetd.d/
defaults
{
instances = 60
log_type = SYSLOG authpriv
log_on_success = HOST PID
log_on_failure = HOST
cps = 25 30
}
includedir /etc/xinetd.d
— instances = 60:表示最大連接進程數為60個。
— log_type = SYSLOG authpriv:表示使用syslog進行服務登記。
— log_on_success= HOST PID:表示設置成功後記錄客戶機的IP地址的進程ID。
— log_on_failure = HOST:表示設置失敗後記錄客戶機的IP地址。
— cps = 25 30:表示每秒25個入站連接,如果超過限制,則等待30秒。主要用於對付拒絕服務攻擊。
— includedir /etc/xinetd.d:表示告訴xinetd要包含的文件或目錄是/etc/xinetd.d。
2) /etc/xinetd.d/*
下面以/etc/xinetd.d/中的一個文件(rsync)為例。
service rsync
{
disable = yes
socket_type = stream
wait = no
user = root
server = /usr/bin/rsync
log_on_failure += USERID
}
下面說明每一行選項的含義。
— disable = yes:表示禁用這個服務。
— socket_type = stream:表示服務的數據包類型為stream。
— wait = no:表示不需等待,即服務將以多線程的方式運行。
— user = root:表示執行此服務進程的用戶是root。
— server = /usr/bin/rsync:啟動腳本的位置。
— log_on_failure += USERID:表示設置失敗時,UID添加到系統登記表。
5 配置xinetd
1) 格式
/etc/xinetd.conf中的每一項具有下列形式:
service service-name
{
……
}
其中service是必需的關鍵字,且屬性表必須用大括號括起來。每一項都定義了由service-name定義的服務。
service-name是任意的,但通常是標準網絡服務名,也可增加其他非標準的服務,只要它們能通過網絡請求激活,包括localhost自身發出的網絡請求。有很多可以使用的屬性,稍後將描述必需的屬性和屬性的使用規則。
操作符可以是=、+=或-=。所有屬性可以使用=,其作用是分配一個或多個值,某些屬性可以使用+=或-=,其作用分別是將其值增加到某個現存的值表中,或將其值從現存值表中刪除。
2) 配置文件
相關的配置文件如下:
/etc/xinetd.conf
/etc/xinetd.d/* //該目錄下的所有文件
/etc/hosts.allow
/etc/hosts.deny
3)/etc/xinetd.conf中的disabled與enabled
前者的參數是禁用的服務列表,後者的參數是啟用的服務列表。他們的共同點是格式相同(屬性名、服務名列表與服務中間用空格分開,例如disabled
= in.tftpd
in.rexecd),此外,它們都是作用於全局的。如果在disabled列表中被指定,那麽無論包含在列表中的服務是否有配置文件和如何設置,都將被禁用;如果enabled列表被指定,那麽只有列表中的服務才可啟動,如果enabled沒有被指定,那麽disabled指定的服務之外的所有服務都可以啟動。
4) 註意問題
① 在重新配置的時候,下列的屬性不能被改變:socket_type、wait、protocol、type;
② 如果only_from和no_access屬性沒有被指定(無論在服務項中直接指定還是通過默認項指定),那麽對該服務的訪問IP將沒有限制;
③ 地址校驗是針對IP地址而不是針對域名地址。
6 xinetd防止拒絕服務攻擊(Denial of Services)的原因
xinetd能有效地防止拒絕服務攻擊(Denial of Services)的原因如下。
1) 限制同時運行的進程數
通過設置instances選項設定同時運行的並發進程數:
instances=20
當服務器被請求連接的進程數達到20個時,xinetd將停止接受多出部分的連接請求。直到請求連接數低於設定值為止。
2) 限制一個IP地址的最大連接數
通過限制一個主機的最大連接數,從而防止某個主機獨占某個服務。
per_source=5
這裏每個IP地址的連接數是5個。
3) 限制日誌文件大小,防止磁盤空間被填滿
許多攻擊者知道大多數服務需要寫入日誌。入侵者可以構造大量的錯誤信息並發送出來,服務器記錄這些錯誤,可能就造成日誌文件非常龐大,甚至會塞滿硬盤。同時會讓管理員面對大量的日誌,而不能發現入侵者真正的入侵途徑。因此,限制日誌文件大小是防範拒絕服務攻擊的一個方法。
log_type FILE.1 /var/log/myservice.log 8388608 15728640
這裏設置的日誌文件FILE.1臨界值為8MB,到達此值時,syslog文件會出現告警,到達15MB,系統會停止所有使用這個日誌系統的服務。
4) 限制負載
xinetd還可以使用限制負載的方法防範拒絕服務攻擊。用一個浮點數作為負載系數,當負載達到這個數目的時候,該服務將暫停處理後續的連接。
max_load = 2.8
上面的設定表示當一項系統負載達到2.8時,所有服務將暫時中止,直到系統負載下降到設定值以下。
說明 要使用這個選項,編譯時應加入“--with-loadavg”,xinetd將處理max-load配置選項,從而在系統負載過重時關閉某些服務進程,來實現防範某些拒絕服務攻擊。
5) 限制所有服務器數目(連接速率)
xinetd可以使用cps選項設定連接速率,下面的例子:
cps = 25 60
上面的設定表示服務器最多啟動25個連接,如果達到這個數目將停止啟動新服務60秒。在此期間不接受任何請求。
6) 限制對硬件資源的利用
通過rlimit_as和rlimit_cpu兩個選項可以有效地限制一種服務對內存、中央處理器的資源占用:
rlimit_as = 8M
rlimit_cpu=20
上面的設定表示對服務器硬件資源占用的限制,最多可用內存為8MB,CPU每秒處理20個進程。
xinetd的一個重要功能是它能夠控制從屬服務可以利用的資源量,通過它的以上設置可以達到這個目的,有助於防止某個xinetd服務占用大量資源,從而導致“拒絕服務”情況的出現。
六 Service命令
Linux的service命令就是查看和控制所有的獨立啟動的守護進程。 這個命令不是在所有的linux發行版本中都有。主要是在redhat系linux中。service此命令位於/sbin/service,用file命令查看此命令會發現它是一個腳本命令。分析腳本可知此命令的作用是去/etc/init.d目錄下尋找相應的服務,進行開啟和關閉等操作。例如service mysqld stop等價於/etc/init.d/mysqld stop。
七 xinetd本身也是一個獨立的守護進程,在/etc/init.d/xinetd。
參考:
http://book.51cto.com/art/200906/127264.htm
http://wordpress.facesoho.com/server/what-is-xinetd.html
http://wbwk2005.blog.51cto.com/2215231/400260
http://hi.baidu.com/neubuffalo/blog/item/3d48148291be90b76d8119e1.html
http://www.cyberciti.biz/faq/linux-how-do-i-configure-xinetd-service1. 概述
守護進程是在後臺運行且不與任何控制終端關聯的進程。unix系統通常有很多守護進程在後臺運行,執行不同的管理任務。
守護進程沒有控制終端通常源於它們由系統初始化腳本啟動。然而守護進程也可能從某個終端由用戶在shell提示符下鍵入命令行啟動,這樣的守護進程必須親自脫離與控制終端的關聯,從而避免與作業控制,終端會話管理,終端產生信號等發生任何不期望的交互,也可以避免在後臺運行的守護進程非預期的輸出到終端。
守護進程有多種啟動方法:
1.在系統啟動階段,許多守護進程由系統初始化腳本啟動。這些腳本通常位於/etc目錄或以/etc/rc開頭的某個目錄中,它們的具體位置和內容卻是實現相關的。由這些腳本啟動的守護進程一開始擁有超級用戶權限。
有若幹個網絡服務器通常從這些腳本啟動:inetd超級服務器,web服務器,郵件服務器(經常是sendmail)。
2. 許多網絡服務器由inetd超級服務器啟動。inetd自身由上一條中的某個腳本啟動。inetd監聽網絡請求,每當有一個請求到達時,啟動相應的實際服務器(telnet服務器,FTP服務器等)
3. cron守護進程按照規則定期執行一些程序,而由它啟動執行的程序同樣作為守護進程運行。cron自身由第一條啟動方法中的某個腳本啟動
4. at命令用於指定將來某個時刻的程序執行。這些程序的執行時刻到來時,通常由cron守護進程啟動執行它們,因此這些程序同樣作為守護進程運行。
5.守護進程還可以從用戶終端或在前臺或在後臺啟動。這麽做往往是為了測試守護進程或重啟因某種原因而終止了的某個守護進程。
因為守護進程沒有控制終端,所以當有事發生時它們得有輸出消息的某種方法可用,而這些消息既可能是普通的通告性消息,也可能是需由系統管理員處理的緊急事件消息。syslog函數是輸出這些消息的標準方法,它把這些消息發送給syslogd守護進程。
2. syslog函數,openlog函數和closelog函數
備註:遇到類似的函數,具體說明請查看APUE
[cpp] view plaincopy- #include <syslog.h>
- void syslog(int priority, const char *message,...);
- void openlog(const char *ident, int options, int facility);
- void closelog(void);
1) 作為守護進程運行的協議無關時間獲取服務器程序
服務器程序daytimetcpsrv.c:
[cpp] view plaincopy
- #include <stdio.h>
- #include <netdb.h>
- #include <sys/socket.h>
- #include <time.h>
- #include <syslog.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)
- return -1;
- signal(SIGHUP, SIG_IGN);
- if ((pid = fork()) < 0)
- return -1;
- else if (pid)
- _exit(0);
- daemon_proc = 1;
- chdir("/");
- for (i = 0; i < MAXFD; i++)
- close(i);
- open("/dev/null", O_RDONLY);
- open("/dev/null", O_RDWR);
- 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:
[cpp] view plaincopy
- #include <stdio.h>
- #include <netdb.h>
- #include <sys/socket.h>
- #define MAXLINE 1024
- int tcp_connect(const char *host, const char *serv);
- int main(int argc, char **argv)
- {
- int sockfd, n;
- char recvline[MAXLINE + 1];
- socklen_t len;
- struct sockaddr_in cliaddr;
- if (argc != 3){
- printf("argument should be 3\n");
- exit(1);
- }
- 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)
- {
- int sockfd, n;
- struct addrinfo hints, *res, *ressave;
- struct sockaddr_in *cliaddr;
- bzero(&hints, sizeof(struct addrinfo));
- hints.ai_family = AF_UNSPEC;
- hints.ai_socktype = SOCK_STREAM;
- if ((n = getaddrinfo(host, serv, &hints, &res)) != 0){
- printf("tcp_connect error for %s,%s:%s\n", host, serv, gai_strerror(n));
- exit(1);
- }
- ressave = res;
- do{
- sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
- if (sockfd < 0)
- continue;
- if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)
- break;
- cliaddr = (struct sockaddr_in *)res->ai_addr;
- close(sockfd);
- } while ((res = res->ai_next) != NULL);
- if (res == NULL)
- printf("tcp_connect error for %s,%s\n", host, serv);
- freeaddrinfo(ressave);
- return sockfd;
- }
程序運行如下:
服務端:
[cpp] view plaincopy
- leichaojian@ThinkPad-T430i:~$ ./daytimetcpsrv ThinkPad-T430i 9878
客戶端:
[cpp] view plaincopy
- 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”,發現如下的語句:
[cpp] view plaincopy
- Oct 8 21:07:35 ThinkPad-T430i ./daytimetcpsrv[10528]: 127.0.0.1.this is a test
2) 對daemon_init函數的分析
(1)fork
用於產生子進程
(2)setsid
setsid用於創建一個新的回話。當前進程變為新會話的會話頭進程以及新進程組的進程組頭進程,從而不再有控制終端。
(3)忽略SIGHUP信號並再次fork
忽略SIGHUP信號並再次調用fork。該函數返回時,父進程實際上是上一次調用fork產生的子進程,它被終止掉,留下新的子進程繼續運行。再次fork的目的是確保本守護進程將來即使打開了一個終端設備,也不會自動獲得控制終端。當沒有控制終端的一個會話頭進程打開一個終端設備時(該終端不會是當前某個其他會話的控制終端),該終端自動成為這個會話頭進程的控制終端。然而再次調用fork之後,我們確保新的子進程不再是一個會話頭進程,從而不能自動獲得一個控制終端。這裏必須霍略SIGHUP信號,因為當會話頭進程(即首次fork產生的子進程)終止時,其會話中的所有進程(即再次fork產生的子進程)都收到SIGHUP信號。
(4)將stdin,stdout和stderr重定向到/dev/null
因為之前關閉了所有的描述符,所以要打開這三個基本描述符並且重定向,讓read返回0,write系統調用丟棄所寫的數據(書上說如果調用了syslog函數,則不要調用類似printf之類的函數,因為會被簡單的忽略掉)。因為如果繼續關閉,則萬一有新的進程打開一個描述符,卻占用了0,1,2這三個描述符,則可能導致將錯誤的數據發送給客戶端。
3. inetd守護進程
舊的服務器只是等待客戶請求的到達,如FTP,Telnet,TFTP等。這些進程都是在系統自舉階段從/etc/rc文件中啟動,而且每個進程執行幾乎相同的啟動任務:創建一個套接字,把本服務器的眾所周知端口捆綁到該套接字,等待一個連接或一個數據報,然後派生子進程。子進程為客戶提供服務,父進程則繼續等待下一個客戶請求。這個模型存在兩個問題:
(1)所有這些守護進程含有幾乎相同的啟動代碼,既表現在創建套接字上,也表現在演變成守護進程上(類似我們的daemon_init函數)
(2)每個守護進程在進程表中占據一個表項,然而它們大部分時間處於睡眠狀態。
而新版本的系統通過提供inetd守護進程(因特網超級服務器)來簡化問題:
(1)通過inetd處理普通守護進程的大部分啟動細節來簡化守護進程的編寫。這麽一來每個服務器不再有調用daemon_init函數的必要。
(2)單個進程就能為多個服務等待外來的客戶請求,以此取代每個服務一個進程的做法。這麽做減少了系統中的進程總數。
1) inetd守護進程的工作流程
(0)對xinetd.conf文件的說明
字段 | 說明 |
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 | stream | tcp | nowait | root | /usr/bin/telnetd | telnetd |
(1)socket()
在啟動階段,讀入/etc/xinetd.conf文件並給該文件中指定的每個服務創建一個適當類型(字節流或數據報)的套接字。inetd能夠處理的服務器的最大數目取決於inetd能夠創建的描述符的最大數目。新創建的每個套接字都被加入到將由某個select調用使用的一個描述符集中。
(2)bind()
為每個套接字調用bind,指定捆綁相應服務器的眾所周知端口和通配地址。這個TCP或UDP端口號通過調用getservbyname獲得,作為函數參數的是相應服務器在配置文件中的service-name字段和protocol字段。
(3)listen()
對於每個TCP套接字,調用listen以接收外來的連接請求。對於數據報套接字則不執行本步驟
(4)select()等待可讀條件
創建完畢所有套接字之後,調用select等待其中任何一個套接字變為可讀。TCP監聽套接字將在有一個新連接準備好可被接受時變為可讀,UDP套接字將在有一個數據報到達時變為可讀。inetd的不部分時間花在阻塞於select調用內部,等待某個套接字變為可讀。
(5)accept()
當select返回指出某個套接字已可讀之後,如果該套接字是一個TCP套接字,而且其服務器的wait-flag值為nowait,那就調用accept接受這個新連接。
(6)fork()
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 <stdio.h>
- #include <stdlib.h>
- #include <sys/socket.h>
- #include <string.h>
- #include <signal.h>
- #include <fcntl.h>
- #include <unistd.h>
- #include <time.h>
- #include <netinet/in.h>
- #define MAXLINE 1024
- int main(int argc, char **argv)
- {
- socklen_t len;
- struct sockaddr_in cliaddr;
- char buff[MAXLINE];
- time_t ticks;
- openlog(argv[0], 0);
- len = sizeof(cliaddr);
- 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);
- exit(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
Linux守護進程詳解(init.d和xinetd)