經典C/S服務器模型之守護進程

linux編程-守護進程編寫html

 

    守護進程(Daemon)是運行在後臺的一種特殊進程。它獨立於控制終端而且週期性地執行某種任務或等待處理某些發生的事件。守護進程是一種頗有用的進程。 linux

Linux的大多數服務器就是用守護進程實現的。好比,Internet服務器inetd,Web服務器httpd等。同時,守護進程完成許多系統任務。好比,做業規劃進程crond,打印進程lpd等。shell

守護進程的編程自己並不複雜,複雜的是各類版本的Unix的實現機制不盡相同,形成不一樣 Unix環境下守護進程的編程規則並不一致。編程

    須要注意,照搬某些書上的規則(特別是BSD4.3和低版本的System V)到Linux會出現錯誤的。ubuntu

    守護進程及其特性 
  守護進程最重要的特性是後臺運行。在這一點上DOS下的常駐內存程序TSR與之類似。其次,守護進程必須與其運行前的環境隔離開來。這些環境包括未關閉的文件描述符,控制終端,會話和進程組,工做目錄以及文件建立掩模等。這些環境一般是守護進程從執行它的父進程(特別是shell)中繼承下來的。最後,守護進程的啓動方式有其特殊之處。它能夠在Linux系統啓動時從啓動腳本/etc/rc.d中啓動,能夠由做業規劃進程crond啓動,還能夠由用戶終端(一般是shell)執行。 
總之,除開這些特殊性之外,守護進程與普通進程基本上沒有什麼區別。所以,編寫守護進程其實是把一個普通進程按照上述的守護進程的特性改形成爲守護進程。若是讀者對進程有比較深刻的認識就更容易理解和編程了。服務器

 

基本概念及特性網絡

進程:session

系統進行資源分配和CPU調度的單位.函數getpid能夠獲得進程的進程ID:pid_t getpid(void);函數getppid能夠獲得進程的父進程ID:pid_t getppid(void);併發

① 每一個進程都有一個父進程函數

② 當子進程終止時,父進程會獲得通知並能取得子進程的退出狀態.

 

進程組:

進程組是一個或多個進程的集合。它們與同一做業相關聯,能夠接受來自同一終端的各類信號。每一個進程組都有惟一的進程組ID。函數getpgrp能夠獲得進程的進程組ID

pid_t getpgrp(void);

每一個進程組均可以有一個組長進程。組長進程的標識是,其進程組ID等於進程ID

① 每一個進程也屬於一個進程組。

② 每一個進程主都有一個進程組號,該號等於該進程組組長的PID

③ 一個進程只能爲它本身或子進程設置進程組ID

 

會話期:

對話期(session)是一個或多個進程組的集合。函數getsid返回會話首進程的進程組ID。此函數是Single UNIX SpecificationXSI擴展。pid_t getsid(pid_t pid);

若是pid0,返回調用進程的會話首進程的進程組ID。若是pid並不屬於調用者所在的會話,那麼調用者就不能獲得該會話首進程的進程組ID

① setsid()函數能夠創建一個對話期:

② 若是,調用setsid的進程不是一個進程組的組長,此函數建立一個新的會話期。

    (1)此進程變成該新的對話期的首進程

    (2)此進程變成一個新進程組的組長進程。

    (3)此進程沒有控制終端,若是在調用setsid前,該進程有控制終端,那麼與該終端的聯繫被解除。若是該進程是一個進程組的組長,此函數返回錯誤。

    (4)爲了保證這一點,咱們先調用fork()而後exit(),此時只有子進程在運行,子進程繼承了父進程的進程組ID,可是進程PID倒是新分配的,因此不多是新會話的進程組的PID

控制終端:
  linux是一個多用戶多任務的分時操做系統,必需要支持多個用戶同時登錄同一個操做系統,當一個用戶登錄一次終端時就會產生一個會話,
  每一個會話有一個會話首進程,即建立會話的進程,創建與終端鏈接的就是這個會話首進程,也被稱爲控制進程。

pid_t tcgetpgrp(int filedes);

函數tcgetpgrp返回前臺進程組的進程組ID,該前臺進程組與在filedes上打開的終端相關聯;若是進程有一個控制終端,則該進程能夠調用tcsetpgrp將前臺進程組ID設置爲pgrpidpgrpid的值應該是在同一會話中的一個進程組的IDfiledes必須引用該會話的控制終端。

下圖能夠表示以上四者的基本關係:

   會話和進程組有一些特性:

  1). 一個會話能夠有一個控制終端(controlling terminal)。

  2). 創建與控制終端鏈接的會話首進程被稱爲控制進程(controlling process)。

  3). 一個會話中的幾個進程組可被分紅一個前臺進程組(forkground process group)和幾個後臺進程組(background process group)。

  4). 若是一個會話有一個控制終端,則它有一個前臺進程組。

  5). 不管什麼時候鍵入終端的中斷鍵(DELETE或Ctrl+C),就會將中斷信號發送給前臺進程組的全部進程。

  6). 不管什麼時候鍵入終端的退出鍵(Ctrl+\),就會將退出信號發送給前臺進程組的全部進程。

  7). 若是終端檢測到調制解調器(或網絡)已經斷開鏈接,則將掛斷信號發送給控制進程(會話首進程)。

下邊就以守護進程的實際代碼運行,輔助理解。

 

    守護進程的編程要點

 
前面講過,不一樣Unix環境下守護進程的編程規則並不一致。所幸的是守護進程的編程原則其實都同樣,區別在於具體的實現細節不一樣。這個原則就是要知足守護進程的特性。同時,Linux是基於Syetem V的SVR4並遵循Posix標準,實現起來與BSD4相比更方便。編程要點以下; 
1. 在後臺運行。 
爲避免掛起控制終端將Daemon放入後臺執行。方法是在進程中調用fork使父進程終止,讓Daemon在子進程中後臺執行。 
if(pid=fork()) 
exit(0);//是父進程,結束父進程,子進程繼續 
2. 脫離控制終端,登陸會話和進程組 
有必要先介紹一下Linux中的進程與控制終端,登陸會話和進程組之間的關係:進程屬於一個進程組,進程組號(GID)就是進程組長的進程號(PID)。登陸會話能夠包含多個進程組。這些進程組共享一個控制終端。這個控制終端一般是建立進程的登陸終端。 
控制終端,登陸會話和進程組一般是從父進程繼承下來的。咱們的目的就是要擺脫它們,使之不受它們的影響。方法是在第1點的基礎上,調用setsid()使進程成爲會話組長: 
setsid(); 
說明:當進程是會話組長時setsid()調用失敗。但第一點已經保證進程不是會話組長。setsid()調用成功後,進程成爲新的會話組長和新的進程組長,並與原來的登陸會話和進程組脫離。因爲會話過程對控制終端的獨佔性,進程同時與控制終端脫離。 
3. 禁止進程從新打開控制終端 
如今,進程已經成爲無終端的會話組長。但它能夠從新申請打開一個控制終端。能夠經過使進程再也不成爲會話組長來禁止進程從新打開控制終端: 

if(pid=fork()) 
exit(0);//結束第一子進程,第二子進程繼續(第二子進程再也不是會話組長) 
4. 關閉打開的文件描述符 
進程從建立它的父進程那裏繼承了打開的文件描述符。如不關閉,將會浪費系統資源,形成進程所在的文件系統沒法卸下以及引發沒法預料的錯誤。按以下方法關閉它們: 
for(i=0;i 關閉打開的文件描述符close(i);> 
5. 改變當前工做目錄 
進程活動時,其工做目錄所在的文件系統不能卸下。通常須要將工做目錄改變到根目錄。對於須要轉儲核心,寫運行日誌的進程將工做目錄改變到特定目錄如/tmpchdir("/") 
6. 重設文件建立掩模 
進程從建立它的父進程那裏繼承了文件建立掩模。它可能修改守護進程所建立的文件的存取位。爲防止這一點,將文件建立掩模清除:umask(0); 
7. 處理SIGCHLD信號 
處理SIGCHLD信號並非必須的。但對於某些進程,特別是服務器進程每每在請求到來時生成子進程處理請求。若是父進程不等待子進程結束,子進程將成爲殭屍進程(zombie)從而佔用系統資源。若是父進程等待子進程結束,將增長父進程的負擔,影響服務器進程的併發性能。在Linux下能夠簡單地將SIGCHLD信號的操做設爲SIG_IGN。 
signal(SIGCHLD,SIG_IGN); 
這樣,內核在子進程結束時不會產生殭屍進程。這一點與BSD4不一樣,BSD4下必須顯式等待子進程結束才能釋放殭屍進程。 

  關於信號的處理此處作一些補充:參考此博客:http://www.cnblogs.com/hoys/archive/2012/08/19/2646377.html

void setupSignal(void)
{
        signal( SIGTERM, SIG_IGN );
        signal( SIGINT,  SIG_IGN );
        signal( SIGPIPE, SIG_IGN );
        signal( SIGHUP,  SIG_IGN );
        signal( SIGTTOU, SIG_IGN );
        signal( SIGTTIN, SIG_IGN );
        signal( SIGTSTP, SIG_IGN );
        signal( SIGCHLD, SIG_IGN );//設置忽略此信號,覺得着子進程退出的時候,不須要父進程處理子進程的殭屍狀態,而是由init進程(pid=1)負責收屍
}

 

#include<unistd.h>
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/param.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<time.h>

void init_daemon()
{
        int pid;
        int i;

//       for(i=0;i<NOFILE;i++)
  //              close(i);     

        printf("parent\n");
        printf("pid[%d]\n",getpid());
        printf("ppid[%d]\n",getppid());
        printf("gid[%d]\n",getpgrp());
        printf("sid[%d]\n",getsid(0));
        printf("tcid[%d]\n",tcgetpgrp(0));

        printf("\n\n");
        pid=fork();

        if(pid<0)
                exit(1);//使得子進程必定不是進程組組長,這樣才能調用setsid,創建新的進程組和會話組
        else if(pid>0)
                exit(0);

        printf("child 1\n");
        printf("pid[%d]\n",getpid());
        printf("ppid[%d]\n",getppid());
        printf("gid[%d]\n",getpgrp());
        printf("sid[%d]\n",getsid(0));
        printf("tcid[%d]\n",tcgetpgrp(0));
        printf("\n\n");
        else if(pid>0)
                exit(0);

        printf("child 1\n");
        printf("pid[%d]\n",getpid());
        printf("ppid[%d]\n",getppid());
        printf("gid[%d]\n",getpgrp());
        printf("sid[%d]\n",getsid(0));
        printf("tcid[%d]\n",tcgetpgrp(0));
        printf("\n\n");
        setsid(); //創建新的進程組和會話組,併成爲新的進程組的組長,和回話組的組長
        printf("setsid child 1\n");
        printf("pid[%d]\n",getpid());
        printf("ppid[%d]\n",getppid());
        printf("gid[%d]\n",getpgrp());
        printf("sid[%d]\n",getsid(0));
        printf("tcid[%d]\n",tcgetpgrp(0));
        printf("\n\n");
        pid=fork();
        if(pid<0)
                exit(1);
        else if(pid>0)
                exit(0);//使得孫子進程不在是進程組的組長,即沒有權限創建新的與回話組綁定的控制終端

        printf("child 2\n");
        printf("pid[%d]\n",getpid());
        printf("ppid[%d]\n",getppid());
        printf("gid[%d]\n",getpgrp());
        printf("sid[%d]\n",getsid(0));
        printf("tcid[%d]\n",tcgetpgrp(0));
        printf("\n\n");
        //關閉文件描述符,這樣進程不在與文件描述符傳遞數據,好比printf打印的數據不在如今是終端界面中
        for(i=0;i<NOFILE;i++)
                close(i);

        printf("close fd\n");
        chdir("/home/cz/Desktop/mcs/");  //切換工做目錄
        printf("cd\n");
        umask(0);//清除文件掩膜
        printf("umask\n");
}

void main()
{
        FILE *fp;
        time_t t;

        printf("%s\n","start");
        init_daemon();
     setupSignal();
while(1) { printf("%s\n","run"); sleep(1); printf("hello\n"); fp=fopen("test.log","a"); //if(fp>=0) //{ time(&t); printf("current time is:%s\n",asctime(localtime(&t))); //} } return 0; }

 

cz@ubuntu:~/Desktop/mcs$ ./demaontest 
start
parent
pid[2724]
ppid[1821]
gid[2724]
sid[1821]
tcid[2724]


cz@ubuntu:~/Desktop/mcs$ child 1
pid[2725]
ppid[1]
gid[2724]
sid[1821]
tcid[1821]


setsid child 1
pid[2725]
ppid[1]
gid[2725]
sid[2725]
tcid[-1]


child 2
pid[2726]
ppid[1]
gid[2725]
sid[2725]
tcid[-1]

 

 由以上實驗結果就能夠清晰的明白,守護進程化過程當中,每一步的做用。

查看/tmp下的test.log的,能夠看到守護進程在不斷運行。

相關文章
相關標籤/搜索