Linux守護進程的編程實現(轉)

http://blog.csdn.net/zg_hover/article/details/2553321shell

http://blog.csdn.net/kongdefei5000/article/details/8808147編程

 

守護進程(Daemon)是運行在後臺的一種特殊進程。它獨立於控制終端而且週期性地執行某種任務或等待處理某些發生的事件。守護進程是一種頗有用的進程。Linux的大多數服務器就是用守護進程實現的。好比,Internet服務器inetd,Web服務器httpd等。同時,守護進程完成許多系統任務。好比,做業規劃進程crond,打印進程lpd等。
守護進程的編程自己並不複雜,複雜的是各類版本的Unix的實現機制不盡相同,形成不一樣Unix環境下守護進程的編程規則並不一致。這須要讀者注意,照搬某些書上的規則(特別是BSD4.3和低版本的System V)到Linux會出現錯誤的。下面將全面介紹Linux下守護進程的編程要點並給出詳細實例。
一. 守護進程及其特性
守護進程最重要的特性是後臺運行。在這一點上DOS下的常駐內存程序TSR與之類似。其次,守護進程必須與其運行前的環境隔離開來。這些環境包括未關閉的文件描述符,控制終端,會話和進程組,工做目錄以及文件建立掩模等。這些環境一般是守護進程從執行它的父進程(特別是shell)中繼承下來的。最後,守護進程的啓動方式有其特殊之處。它能夠在Linux系統啓動時從啓動腳本/etc/rc.d中啓動,能夠由做業規劃進程crond啓動,還能夠由用戶終端(一般是shell)執行。
總之,除開這些特殊性之外,守護進程與普通進程基本上沒有什麼區別。所以,編寫守護進程其實是把一個普通進程按照上述的守護進程的特性改形成爲守護進程。若是讀者對進程有比較深刻的認識就更容易理解和編程了。
二. 守護進程的編程要點
前面講過,不一樣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下必須顯式等待子進程結束才能釋放殭屍進程。
三. 守護進程實例
守護進程實例包括兩部分:主程序test.c和初始化程序init.c。主程序每隔一分鐘向/tmp目錄中的日誌test.log報告運行狀態。初始化程序中的init_daemon函數負責生成守護進程。讀者能夠利用init_daemon函數生成本身的守護進程。
1. init.c清單 服務器

#include < unistd.h > 
#include < signal.h > 
#include < sys/param.h > 
#include < sys/types.h > 
#include < sys/stat.h > 
void init_daemon(void) 
{ 
int pid; 
int i; 
if(pid=fork()) 
exit(0);//是父進程,結束父進程 
else if(pid< 0) 
exit(1);//fork失敗,退出 
//是第一子進程,後臺繼續執行 
setsid();//第一子進程成爲新的會話組長和進程組長 
//並與控制終端分離 
if(pid=fork()) 
exit(0);//是第一子進程,結束第一子進程 
else if(pid< 0) 
exit(1);//fork失敗,退出 
//是第二子進程,繼續 
//第二子進程再也不是會話組長 

for(i=0;i< NOFILE;++i)//關閉打開的文件描述符 
close(i); 
chdir("/tmp");//改變工做目錄到/tmp 
umask(0);//重設文件建立掩模 
return; 
} 

2. test.c清單併發

#include < stdio.h > 
#include < time.h > 

void init_daemon(void);//守護進程初始化函數 

main() 
{ 
FILE *fp; 
time_t t; 
init_daemon();//初始化爲Daemon 

while(1)//每隔一分鐘向test.log報告運行狀態 
{ 
sleep(60);//睡眠一分鐘 
if((fp=fopen("test.log","a")) >=0) 
{ 
t=time(0); 
fprintf(fp,"Im here at %s/n",asctime(localtime(&t)) ); 
fclose(fp); 
} 
} 
} 

以上程序在RedHat Linux6.0下編譯經過。步驟以下:
編譯:gcc -g -o test init.c test.c
執行:./test
查看進程:ps -ef
從輸出能夠發現test守護進程的各類特性知足上面的要求。函數

說明:在系統調用庫中有一個庫函數能夠直接使一個進程變成守護進程,
       #include <unistd.h>
       int daemon(int nochdir, int noclose);性能

若是nochdir的值爲0,則將切換工做目錄爲根目錄;若是noclose爲0,則將標準輸入,輸出和標準錯誤都重定向到/dev /nullspa

例子:.net

  

/*
*功能:建立一個守護進程,監視系統全部運行的進程
*/
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/resource.h>

 #include <unistd.h>

int main()
{
    FILE *fp;
    FILE *fstream;
    signal(SIGCHLD, SIG_IGN); // 忽略子進程結束信號,防止出現殭屍進程          
      daemon(0,0);//初始化守護進程,就是建立一個守護進程
    while(1)
    {    
        /*PID 進程ID ,user:進程開闢用戶,comm:進程名,lstart:進程開始時間,etime:進程持續時間*/
        fstream=popen("ps -eo pid,user,comm,lstart,etime>test.txt","r");  
        //若是執行命令失敗,則寫入錯誤報告
        if(fstream==NULL)
        {
            //在打開或者建立error.log成功的狀況下,寫入錯誤(使用errno時失敗)
            if((fp = fopen("error.log", "a+")) != NULL)
            {
                fprintf(fp, "%s\n", "執行命令失敗");
                fclose(fp);
            }
            else
               exit(1);  //寫入錯誤失敗,則終止程序推出並關閉全部進程
        }
        else
          pclose(fstream); //關閉popen打開的I/O流
        sleep(120);   //設置成5分鐘獲取一次系統進程狀況
    }
    return 0;
}

命令:gcc -o testdaemon testdaemon.c日誌

編譯程序code

命令:./testdaemon

運行程序

命令:ps -ef| egrep 'testdaemon'

經過ps查看進程狀況,能夠看到進程的父進程id爲1,即init進程

命令:lsof -c testdaemon

用lsof查看test進程所打開的文件,能夠看到文件描述符0,1,2都被重定向到/dev/null

相關文章
相關標籤/搜索