UNIX編程常見問題--1

 1. Process Control 進程控制

 1.1 Creating new processes: fork() 創建新進程:fork函數
   1.1.1 What does fork() do? fork函數幹什麼?
   1.1.2 What's the difference between fork() and vfork()? fork函數 與 vfork函數的區別在哪裏?
   1.1.3 Why use _exit rather than exit in the child branch of a fork? 爲何在一個fork的子進程分支中使用_exit函數而不使用exit函數?
 1.2 Environment variables 環境變量
   1.2.1 How can I get/set an environment variable from a program? 我怎樣在程序中獲得/設置環境變量?
   1.2.2 How can I read the whole environment? 我怎樣讀取整個環境變量表?
 1.3 How can I sleep for less than a second? 我怎樣睡眠小於一秒?
 1.4 How can I get a finer-grained version of alarm()? 我怎樣得到一個更細分時間單位的alarm函數版本(譯者注:希望alarm的時間小於一秒)?
 1.5 How can a parent and child process communicate? 父子進程如何通信?
 1.6 How do I get rid of zombie processes? 我怎樣去除僵死進程?
   1.6.1 What is a zombie? 何爲僵死進程?
   1.6.2 How do I prevent them from occuring? 我怎樣避免它們的出現?
 1.7 How do I get my program to act like a daemon? 我怎樣使我的程序作爲守護程序運行?
 1.8 How can I look at process in the system like ps does? 我怎樣象ps程序一樣審視系統的進程?
 1.9 Given a pid, how can I tell if it's a running program? 給定一個進程號(譯者注:pid: process ID),我怎樣知道它是個正在運行的程序?
 1.10 What's the return value of system/pclose/waitpid? system函數,pclose函數,waitpid函數的返回值是什麼?
 1.11 How do I find out about a process' memory usage? 我怎樣找出一個進程的存儲器使用情況?
 1.12 Why do processes never decrease in size? 爲什麼進程的大小不縮減?
 1.13 How do I change the name of my program (as seen by `ps')? 我怎樣改變我程序的名字(即“ps”看到的名字)?
 1.14 How can I find a process' executable file? 我怎樣找到進程的相應可執行文件?
   1.14.1 So where do I put my configuration files then? 那麼,我把配置文件放在哪裏呢?
 1.15 Why doesn't my process get SIGHUP when its parent dies? 爲何父進程死時,我的進程未得到SIGHUP信號?
 1.16 How can I kill all descendents of a process? 我怎樣殺死一個進程的所有派生進程?
2. General File handling (including pipes and sockets) 一般文件操作(包括管道和套接字)
 2.1 How to manage multiple connections? 怎樣管理多個連接?
   2.1.1 How do I use select()? 我怎樣使用select()?
   2.1.2 How do I use poll()? 我怎樣使用poll() ?
   2.1.3 Can I use SysV IPC at the same time as select or poll? 我是否可以將SysV 進程間通信 (譯者注:IPC: Interprocess Communications) 與select或poll同
時使用?
 2.2 How can I tell when the other end of a connection shuts down? 我怎麼知道連接的另一端已關閉?
 2.3 Best way to read directories? 讀目錄的最好方法?
 2.4 How can I find out if someone else has a file open? 我怎麼知道其他人已經打開一個文件?
 2.5 How do I `lock' a file? 我怎樣鎖定一個文件?
 2.6 How do I find out if a file has been updated by another process? 我怎麼知道一個文件是否已被其他進程更新?
 2.7 How does the `du' utility work? “du”工具程序是怎麼工作的?
 2.8 How do I find the size of a file? 我怎麼知道一個文件的大小?
 2.9 How do I expand `~' in a filename like the shell does? 我怎樣象shell程序一樣將一個文件名中含有的“~”展開?
 2.10 What can I do with named pipes (FIFOs)? 我能用有名管道(FIFOs)(譯者注:FIFO: First In First Oout)幹什麼?
   2.10.1 What is a named pipe? 什麼是有名管道?
   2.10.2 How do I create a named pipe? 我怎樣創建一個有名管道?
   2.10.3 How do I use a named pipe? 我怎樣使用一個有名管道?
   2.10.4 Can I use a named pipe across NFS? 我能基於網絡文件系統(譯者注:NFS:Network File System)使用有名管道嗎?
   2.10.5 Can multiple processes write to the pipe simultaneously? 多個進程能否同時向這個管道寫執行寫操作?
   2.10.6 Using named pipes in applications 在應用程序中使用有名管道。
3. Terminal I/O 終端輸入/輸出(I/O:input/output)
 3.1 How can I make my program not echo input? 我怎樣使我的程序不回射輸入?
 3.2 How can I read single characters from the terminal? 我怎樣從終端讀取單個字符?
 3.3 How can I check and see if a key was pressed? 我怎樣檢查是否一個鍵被摁下?
 3.4 How can I move the cursor around the screen? 我怎樣將光標在屏幕裏移動?
 3.5 What are pttys? pttys(pttys:Pseudo-teletypes)是什麼?
 3.6 How to handle a serial port or modem? 怎樣控制一個串行口和調制解調器(譯者注:modem: modulate-demodulate)
   3.6.1 Serial device names and types 串行設備和類型
   3.6.2 Setting up termios flags 設置termios的標誌位
     3.6.2.1 c_iflag
     3.6.2.2 c_oflag
     3.6.2.3 c_cflag
     3.6.2.4 c_lflag
     3.6.2.5 c_cc
4. System Information 系統信息
 4.1 How can I tell how much memory my system has? 我怎樣知道我的系統有多少存儲器容量?
 4.2 How do I check a user's password? 我怎樣檢查一個用戶的口令?
   4.2.1 How do I get a user's password? 我怎樣得到一個用戶的口令?
   4.2.2 How do I get shadow passwords by uid? 我怎樣通過用戶號(譯者注:uid: User ID)得到陰影口令文件中的口令?
   4.2.3 How do I verify a user's password? 我怎樣覈對一個用戶的口令?
5. Miscellaneous programming 編程雜技
 5.1 How do I compare strings using wildcards? 我怎樣使用通配字符比較字符串?
   5.1.1 How do I compare strings using filename patterns? 我怎樣使用文件名通配模式比較字符串?
   5.1.2 How do I compare strings using regular expressions? 我怎樣使用正則表達式比較字符串?
 5.2 What's the best way to send mail from a program? 什麼是在程序中發送電子郵件的最好方法?
   5.2.1 The simple method: /bin/mail 簡單方法:/bin/mail
   5.2.2 Invoking the MTA directly: /usr/lib/sendmail 直接啓動郵件傳輸代理(譯者注:MTA: mail transfer agent):/usr/bin/sendmail
     5.2.2.1 Supplying the envelope explicitly 顯式提供收件人信息
     5.2.2.2 Allowing sendmail to deduce the recipients 允許sendmail程序根據郵件內容分析出收件人
6. Use of tools 工具的使用
 6.1 How can I debug the children after a fork? 我怎樣調試fork函數產生的子進程?
 6.2 How to build library from other libraries? 怎樣通過其他庫文件建立新的庫文件?
 6.3 How to create shared libraries / dlls? 怎樣創建動態連接庫/dlls?
 6.4 Can I replace objects in a shared library? 我能更改一個動態連接庫裏的目標嗎?
 6.5 How can I generate a stack dump from within a running program? 我能在一個運行着的程序中生成堆棧映象嗎?

1. 進程控制
***********
1.1 創建新進程:fork函數
========================
1.1.1 fork函數幹什麼?
----------------------
    #include <sys/types.h>
    #include <unistd.h>
    pid_t fork(void);
'fork()’函數用於從已存在進程中創建一個新進程。新進程稱爲子進程,而原進程稱爲
父進程。你可以通過檢查'fork()’函數的返回值知道哪個是父進程,哪個是子進程。父
進程得到的返回值是子進程的進程號,而子進程則返回0。以下這個範例程序說明它的基本
功能:
    pid_t pid;
    switch (pid = fork())
    {
    case -1:
        /* 這裏pid爲-1,fork函數失敗 */
        /* 一些可能的原因是 */
        /* 進程數或虛擬內存用盡 */
        perror("The fork failed!");
        break;
    case 0:
        /* pid爲0,子進程 */
        /* 這裏,我們是孩子,要做什麼? */
        /* ... */
        /* 但是做完後, 我們需要做類似下面: */
        _exit(0);
    default:
        /* pid大於0,爲父進程得到的子進程號 */
        printf("Child's pid is %d\n",pid);
    }
當然,有人可以用'if() ... else ...’語句取代'switch()’語句,但是上面的形式是
一個有用的慣用方法。
知道子進程自父進程繼承什麼或未繼承什麼將有助於我們。下面這個名單會因爲
不同Unix的實現而發生變化,所以或許準確性有了水份。請注意子進程得到的是
這些東西的 *拷貝*,不是它們本身。
由子進程自父進程繼承到:
 * 進程的資格(真實(real)/有效(effective)/已保存(saved) 用戶號(UIDs)和組號(GIDs))
  * 環境(environment)
  * 堆棧
  * 內存
  * 打開文件的描述符(注意對應的文件的位置由父子進程共享,這會引起含糊情況)
  * 執行時關閉(close-on-exec) 標誌 (譯者注:close-on-exec標誌可通過fnctl()對文件描
    述符設置,POSIX.1要求所有目錄流都必須在exec函數調用時關閉。更詳細說明,

    參見<<UNIX環境高級編程>> W. R. Stevens, 1993, 尤晉元等譯(以下簡稱<<高級編
    程>>), 3.13節和8.9節)
  * 信號(signal)控制設定
  * nice值 (譯者注:nice值由nice函數設定,該值表示進程的優先級,數值越小,優
    先級越高)
  * 進程調度類別(scheduler class) (譯者注:進程調度類別指進程在系統中被調度時所
    屬的類別,不同類別有不同優先級,根據進程調度類別和nice值,進程調度程序可計
    算出每個進程的全局優先級(Global process prority),優先級高的進程優先執行)
 * 進程組號
  * 對話期ID(Session ID) (譯者注:譯文取自<<高級編程>>,指:進程所屬的對話期
    (session)ID, 一個對話期包括一個或多個進程組, 更詳細說明參見<<高級編程
>>
    9.5節)
  * 當前工作目錄
  * 根目錄 (譯者注:根目錄不一定是“/”,它可由chroot函數改變)
  * 文件方式創建屏蔽字(file mode creation mask (umask)) (譯者注:譯文取自<<高級編
    程>>,指:創建新文件的缺省屏蔽字)
 * 資源限制
  * 控制終端
子進程所獨有:
  * 進程號
  * 不同的父進程號(譯者注:即子進程的父進程號與父進程的父進程號不同,父進
    程號可由getppid函數得到)
  * 自己的文件描述符和目錄流的拷貝(譯者注:目錄流由opendir函數創建,因其爲
    順序讀取,顧稱“目錄流”)
  * 子進程不繼承父進程的進程,正文(text),數據和其它鎖定內存(memory locks)
    (譯者注:鎖定內存指被鎖定的虛擬內存頁,鎖定後,不允許內核將其在必要時
    換出(page out),詳細說明參見<<The GNU C Library Reference Manual>> 2.2版,
    1999, 3.4.2節)
 * 在tms結構中的系統時間(譯者注:tms結構可由times函數獲得,它保存四個數據
    用於記錄進程使用中央處理器(CPU:Central Processing Unit)的時間,包括:用戶時
    間,系統時間,用戶各子進程合計時間,系統各子進程合計時間)
 * 資源使用(resource utilizations)設定爲0
  * 阻塞信號集初始化爲空集(譯者注:原文此處不明確,譯文根據fork函數手冊頁
    稍做修改)
 * 不繼承由timer_create函數創建的計時器
  * 不繼承異步輸入和輸出
1.1.2 fork函數 與 vfork函數的區別在哪裏裏?
-------------------------------------------
有些系統有一個系統調用'vfork()’,它最初被設計成'fork()’的較少額外支出
(lower-overhead)版本。因爲'fork()’包括拷貝整個進程的地址空間,所以非常
“昂貴”,這個'vfork()’函數因此被引入。(在3.0BSD中)(譯者注:BSD:
Berkeley Software Distribution)
*但是*,自從'vfork()’被引入,'fork()’的實現方法得到了很大改善,最值得
注意的是“寫操作時拷貝”(copy-on-write)的引入,它是通過允許父子進程可訪問
相同物理內存從而僞裝(fake)了對進程地址空間的真實拷貝,直到有進程改變內
存中數據時才拷貝。這個提高很大程度上抹殺了需要'vfork()’的理由;事實上,

一大部份系統完全喪失了'vfork()’的原始功能。但爲了兼容,它們仍然提供
'vfork()’函數調用,但它只是簡單地調用'fork()’,而不試圖模擬所有'vfork()’
的語義(semantics, 譯文取自<<高級編程>>,指定義的內容和做法)。
結論是,試圖使用任何'fork()’和'vfork()’的不同點是*很*不明智的。事實上,
可能使用'vfork()’根本就是不明智的,除非你確切知道你想*幹什麼*。
兩者的基本區別在於當使用'vfork()’創建新進程時,父進程將被暫時阻塞,而
子進程則可以借用父進程的地址空間。這個奇特狀態將持續直到子進程要麼退
出,要麼調用'execve()’,至此父進程才繼續執行。
這意味着一個由'vfork()’創建的子進程必須小心以免出乎意料地改變父進程的
變量。特別的,子進程必須不從包含'vfork()’調用的函數返回,而且必須不調
用'exit()’(如果它需要退出,它需要使用'_exit()’;事實上,對於使用正常
'fork()’創建的子進程這也是正確的)(譯者注:參見1.1.3)
1.1.3 爲何在一個fork的子進程分支中使用_exit函數而不使用exit函數?
-----------------------------------------------------------------
'exit()’與'_exit()’有不少區別在使用'fork()’,特別是'vfork()’時變得很
突出。
'exit()’與'_exit()’的基本區別在於前一個調用實施與調用庫裏用戶狀態結構
(user-mode constructs)有關的清除工作(clean-up),而且調用用戶自定義的清除程序
(譯者注:自定義清除程序由atexit函數定義,可定義多次,並以倒序執行),相對
應,後一個函數只爲進程實施內核清除工作。
在由'fork()’創建的子進程分支裏,正常情況下使用'exit()’是不正確的,這是
因爲使用它會導致標準輸入輸出(譯者注:stdio: Standard Input Output)的緩衝區被
清空兩次,而且臨時文件被出乎意料的刪除(譯者注:臨時文件由tmpfile函數創建
在系統臨時目錄下,文件名由系統隨機生成)。在C++程序中情況會更糟,因爲靜
態目標(static objects)的析構函數(destructors)可以被錯誤地執行。(還有一些特殊情
況,比如守護程序,它們的*父進程*需要調用'_exit()’而不是子進程;適用於絕
大多數情況的基本規則是,'exit()’在每一次進入'main’函數後只調用一次。)
在由'vfork()’創建的子進程分支裏,'exit()’的使用將更加危險,因爲它將影響
*父*進程的狀態。
1.2 環境變量
============
1.2.1  如何從程序中獲得/設置環境變量?
--------------------------------------
獲得一個環境變量可以通過調用'getenv()’函數完成。
    #include <stdlib.h>
    char *getenv(const char *name);
設置一個環境變量可以通過調用'putenv()’函數完成。
    #include <stdlib.h>
    int putenv(char *string);
變量string應該遵守"name=value"的格式。已經傳遞給putenv函數的字符串*不*能夠被
釋放或變成無效,因爲一個指向它的指針將由'putenv()’保存。
這意味着它必須是
在靜態數據區中或是從堆(heap)分配的。如果這個環境變量被另一個'putenv()’的
調用重新定義或刪除,上述字符串可以被釋放。
/* 譯者增加:
因爲putenv()有這樣的侷限,在使用中經常會導致一些錯
誤,GNU libc 中還包括了兩個BSD風格的函數:
#include <stdlib.h>
int setenv(const char *name, const char *value, int replace);
void unsetenv(const char *name);
setenv()/unsetenv()函數可以完成所有putenv()能做的事。setenv() 可以不受指針
限制地向環境變量中添加新值,但傳入參數不能爲空(NULL)。當replace爲0時,如

果環境變量中已經有了name項,函數什麼也不做(保留原項),否則原項被覆蓋。
unsetenv()是用來把name項從環境變量中刪除。注意:這兩個函數只存在在BSD和
GNU
庫中,其他如SunOS系統中不包括它們,因此將會帶來一些兼容問題。我們可以用

getenv()/putenv()來實現:
int setenv(const char *name,  const char *value, int replace)
{
  char *envstr;
  if (name == NULL || value == NULL)
     return 1;
  if (getenv(name) !=NULL)
    {
       envstr = (char *) malloc(strlen(name) + strlen(value) + 2);
       sprintf (envstr, "%s=%s", name, value);
       if (putenv(envstr));
          return 1;
    }
  return 0;
}
*/
記住環境變量是被繼承的;每一個進程有一個不同的環境變量表拷貝(譯者注:
從core文件中我們可以看出這一點)。結果是,你不能從一個其他進程改變當前
進程的環境變量,比如shell進程。
假設你想得到環境變量'TERM’的值,你需要使用下面的程序:
    char *envvar;
    envvar=getenv("TERM");
    printf("The value for the environment variable TERM is ");
    if(envvar)
    {
        printf("%s\n",envvar);
    }
    else
    {
        printf("not set.\n");
    }
現在假設你想創建一個新的環境變量,變量名爲'MYVAR’,值爲'MYVAL’。
以下是你將怎樣做:
    static char envbuf[256];
    sprintf(envbuf,"MYVAR=%s","MYVAL");
    if(putenv(envbuf))
    {
        printf("Sorry, putenv() couldn't find the memory for %s\n",envbuf);
        /* Might exit() or something here if you can't live without it */
    }
1.2.2 我怎樣讀取整個環境變量表?
--------------------------------
如果你不知道確切你想要的環境變量的名字,那麼'getenv()’函數不是很有用。
在這種情況下,你必須更深入瞭解環境變量表的存儲方式。
全局變量,'char **envrion’,包含指向環境字符串指針數組的指針,每一個字
符串的形式爲'“NAME=value”’(譯者注:和putenv()中的“string”的格式相同)。
這個數組以一個'空’(NULL)指針標記結束。這裏是一個打印當前環境變量列表
的小程序(類似'printenv’)。
    #include <stdio.h>
    extern char **environ;
    int main()
    {
        char **ep = environ;
        char *p;
        while ((p = *ep++))
            printf("%s\n", p);
        return 0;
    }
一般情況下,'envrion’變量作爲可選的第三個參數傳遞給'main()’;就是說,
上面的程序可以寫成:
    #include <stdio.h>
    int main(int argc, char **argv, char **envp)
    {
        char *p;
        while ((p = *envp++))
            printf("%s\n", p);
        return 0;
    }
雖然這種方法被廣泛的操縱系統所支持(譯者注:包括DOS),這種方法事實上並
沒有被POSIX(譯者注:POSIX: Portable Operating System Interace)標準所定義。
(一
般的,它也比較沒用)
1.3 我怎樣睡眠小於一秒?
========================
在所有Unix中都有的'sleep()’函數只允許以秒計算的時間間隔。如果你想要更
細化,那麼你需要尋找替換方法:
  * 許多系統有一個'usleep()’函數
  * 你可以使用'select()’或'poll()’,並設置成無文件描述符並試驗;一個普
    遍技巧是基於其中一個函數寫一個'usleep()’函數。(參見comp.unix.questions
    FAQ 的一些例子)
  * 如果你的系統有itimers(很多是有的)(譯者注:setitimer和getitimer是兩個操作
    itimers的函數,使用“man setitimer”確認你的系統支持),你可以用它們自己攛一
    個'usleep()’。(參見BSD源程序的'usleep()’以便知道怎樣做)
  * 如果你有POSIX實時(realtime)支持,那會有一個'nanosleep()’函數。
衆觀以上方法,'select()’可能是移植性最好的(直截了當說,它經常比
'usleep()’或基於itimer的方法更有效)。
但是,在睡眠中捕獲信號的做法會有
所不同;基於不同應用,這可以成爲或不成爲一個問題。
無論你選擇哪條路,意識到你將受到系統計時器分辨率的限制是很重要的(一
些系統允許設置非常短的時間間隔,而其他的系統有一個分辨率,比如說10毫
秒,而且總是將所有設置時間取整到那個值)。而且,關於'sleep()’,你設置
的延遲只是最小值(譯者注:實際延遲的最小值);經過這段時間的延遲,會有
一箇中間時間間隔直到你的進程重新被調度到。
1.4  我怎樣得到一個更細分時間單位的alarm函數版本?
==================================================
當今Unix系統傾向於使用'setitimer()’函數實現鬧鐘,它比簡單的'alarm()’函
數具有更高的分辨率和更多的選擇項。一個使用者一般需要首先假設'alarm()’
和'setitimer(ITIMER_REAL)’可能是相同的底層計時器,而且假設同時使用兩
種方法會造成混亂。
Itimers可被用於實現一次性或重複信號;而且一般有3種不同的計時器可以用:
`ITIMER_REAL'
     計數真實(掛鐘)時間,然後發送'SIGALRM’信號
`ITIMER_VIRTUAL'
     計數進程虛擬(用戶中央處理器)時間,然後發送'SIGVTALRM’信號
`ITIMER_PROF'
    計數用戶和系統中央處理器時間,然後發送'SIGPROF’信號;它供解釋器
    用來進行梗概處理(profiling)
然而itimers不是許多標準的一部份,儘管它自從4.2BSD就被提供。POSIX實時標
準的擴充定義了類似但不同的函數。
1.5 父子進程如何通信?
======================
一對父子進程可以通過正常的進程間通信的辦法(管道,套接字,消息隊列,共
享內存)進行通信,但也可以通過利用它們作爲父子進程的相互關係而具有的一
些特殊方法。
一個最顯然的方法是父進程可以得到子進程的退出狀態。
因爲子進程從它的父進程繼承文件描述符,所以父進程可以打開一個管道的兩端,
然後fork,然後父進程關閉管道這一端,子進程關閉管道另一端。這正是你從你的
進程調用'popen()’函數運行另一個程序所發生的情況,也就是說你可以向
'popen()’返回的文件描述符進行寫操作而子進程將其當作自己的標準輸入,或
者你可以讀取這個文件描述符來看子進程向標準輸出寫了什麼。('popen()’函數
的mode參數定義你的意圖(譯者注:mode=“r”爲讀,mode=“w”爲寫);如果你
想讀寫都做,那麼你可以並不困難地用管道自己做到)
而且,子進程繼承由父進程用mmap函數映射的匿名共享內存段(或者通過映射特
殊文件'/dev/zero’);這些共享內存段不能從無關的進程訪問。
1.6 我怎樣去除僵死進程?
========================
1.6.1 何爲僵死進程?
--------------------
當一個程序創建的子進程比父進程提前結束,內核仍然保存一些它的信息以便父
進程會需要它 - 比如,父進程可能需要檢查子進程的退出狀態。爲了得到這些信
息,父進程調用'wait()’;當這個調用發生,內核可以丟棄這些信息。
在子進程終止後到父進程調用'wait()’前的時間裏,子進程被稱爲'僵死進程’
('zombie’)。(如果你用'ps’,這個子進程會有一個'Z’出現在它的狀態區
裏指出這點。)即使它沒有在執行,它仍然佔據進程表裏一個位置。(它不消耗其
它資源,但是有些工具程序會顯示錯誤的數字,比如中央處理器的使用;這是
因爲爲節約空間進程表的某些部份與會計數據(accounting info)是共用(overlaid)的。)
這並不好,因爲進程表對於進程數有固定的上限,系統會用光它們。即使系統沒
有用光 ,每一個用戶可以同時執行的進程數有限制,它總是小於系統的限制。
順便說一下,這也正是你需要總是 檢查'fork()’是否失敗的一個原因。
如果父進程未調用wait函數而終止,子進程將被'init’進程收管,它將控制子進
程退出後必須的清除工作。('init’是一個特殊的系統程序,進程號爲1 - 它實際
上是系統啓動後運行的第一個程序),
1.6.2 我怎樣避免它們的出現?
----------------------------
你需要卻認父進程爲每個子進程的終止調用'wait()’(或者'waitpid()’,
'wait3()’,等等); 或者,在某些系統上,你可以指令系統你對子進程的退出狀
態沒有興趣。(譯者注:在SysV系統上,可以調用signal函數,設置SIGCLD信號爲
SIG_IGN,系統將不產生僵死進程, 詳細說明參見<<高級編程>>10.7節)
另一種方法是*兩次*'fork()’,而且使緊跟的子進程直接退出,這樣造成孫子進
程變成孤兒進程(orphaned),從而init進程將負責清除它。欲獲得做這個的程序,參
看範例章節的函數'fork2()’。
爲了忽略子進程狀態,你需要做下面的步驟(查詢你的系統手冊頁以知道這是否正
常工作):
        struct sigaction sa;
        sa.sa_handler = SIG_IGN;
    #ifdef SA_NOCLDWAIT
        sa.sa_flags = SA_NOCLDWAIT;
    #else
        sa.sa_flags = 0;
    #endif
        sigemptyset(&sa.sa_mask);
        sigaction(SIGCHLD, &sa, NULL);
如果這是成功的,那麼'wait()’函數集將不再正常工作;如果它們中任何一個被
調用,它們將等待直到*所有*子進程已經退出,然後返回失敗,並且
'errno==ECHILD’。
另一個技巧是捕獲SIGCHLD信號,然後使信號處理程序調用'waitpid()’或
'wait3()’。參見範例章節的完整程序。
1.7 我怎樣使我的程序作爲守護程序運行?
======================================
一個“守護程序”進程通常被定義爲一個後臺進程,而且它不屬於任何一個終端
會話,(terminal session)。許多系統服務由守護程序實施;如網絡服務,打印等。
簡單地在後臺啓動一個程序並非足夠是這些長時間運行的程序;那種方法沒有正
確地將進程從啓動它的終端脫離(detach)。而且,啓動守護程序的普遍接受的的方
法是簡單地手工執行或從rc腳本程序執行(譯者注:rc:runcom);並希望這個守護
程序將其*自身*安置到後臺。
這裏是成爲守護程序的步驟:
 1. 調用'fork()’以便父進程可以退出,這樣就將控制權歸還給運行你程序的
    命令行或shell程序。需要這一步以便保證新進程不是一個進程組頭領進程
(process
    group leader)。下一步,'setsid()’,會因爲你是進程組頭領進程而失敗。
 2. 調用'setsid()’ 以便成爲一個進程組和會話組的頭領進程。由於一個控制終端
    與一個會話相關聯,而且這個新會話還沒有獲得一個控制終端,我們的進程沒
    有控制終端,這對於守護程序來說是一件好事。
 3. 再次調用'fork()’所以父進程(會話組頭領進程)可以退出。這意味着我們,一
    個非會話組頭領進程永遠不能重新獲得控制終端。
 4. 調用'chdir("/")’確認我們的進程不保持任何目錄於使用狀態。不做這個會導
    致系統管理員不能卸裝(umount)一個文件系統,因爲它是我們的當前工作目錄。
    [類似的,我們可以改變當前目錄至對於守護程序運行重要的文件所在目錄]
 5. 調用'umask(0)’以便我們擁有對於我們寫的任何東西的完全控制。我們不知
    道我們繼承了什麼樣的umask。
    [這一步是可選的](譯者注:這裏指步驟5,因爲守護程序不一定需要寫文件)
 6. 調用'close()’關閉文件描述符0,1和2。這樣我們釋放了從父進程繼承的標
    準輸入,標準輸出,和標準錯誤輸出。我們沒辦法知道這些文描述符符可能
    已經被重定向去哪裏。注意到許多守護程序使用'sysconf()’來確認
    '_SC_OPEN_MAX’的限制。'_SC_OPEN_MAX’告訴你每個進程能夠打
    開的最多文件數。然後使用一個循環,守護程序可以關閉所有可能的文件描
    述符。你必須決定你需要做這個或不做。如果你認爲有可能有打開的文件描
    述符,你需要關閉它們,因爲系統有一個同時打開文件數的限制。
 7. 爲標準輸入,標準輸出和標準錯誤輸出建立新的文件描述符。即使你不打算
    使用它們,打開着它們不失爲一個好主意。準確操作這些描述符是基於各自
    愛好;比如說,如果你有一個日誌文件,你可能希望把它作爲標準輸出和標
    準錯誤輸出打開,而把'/dev/null’作爲標準輸入打開;作爲替代方法,你可
    以將'/dev/console’作爲標準錯誤輸出和/或標準輸出打開,而'/dev/null’作
    爲標準輸入,或者任何其它對你的守護程序有意義的結合方法。(譯者注:一
    般使用dup2函數原子化關閉和複製文件描述符,參見<<高級編程>>3.12節)
如果你的守護程序是被'inetd’啓動的,幾乎所有這些步驟都不需要(或不建議
採用)。在那種情況下,標準輸入,標準輸出和標準錯誤輸出都爲你指定爲網絡
連接,而且'fork()’的調用和會話的操縱不應做(以免使'inetd’造成混亂)。只
有'chdir()’和'umask()’這兩步保持有用。
1.8  我怎樣象ps程序一樣審視系統的進程?
=======================================
你真的不該想做這個。
到目前爲止,移植性最好的是調用'popen(pscmd,"r")’並處理它的輸出。(pscmd
應當是類似SysV系統上的'“ps -ef”’,BSD系統有很多可能的顯示選項:選

擇一個。)
在範例章節有這個問題的兩個完整解決方法;一個適用於SunOS 4,它需要root權
限執行並使用'kvm_*’例程從內核數據結果讀取信息;另一種適用於SVR4系統
(包括Sun OS 5),它使用'/proc’文件系統。
在具有SVR4.2風格'/proc’的系統上更簡單;只要對於每一個感興趣的進程號從
文件'/proc/進程號/psinfo’讀取一個psinfo_t結構。但是,這種可能是最清晰的方
法也許又是最不得到很好支持的方法。(在FreeBSD的'/proc’上,你從
'/proc/進程號/status’讀取一個半未提供文檔說明(semi-undocumented)的可打印字
符串;Linux有一些與其類似的東西)
1.9  給定一個進程號,我怎樣知道它是個正在運行的程序?
=====================================================
使用'kill()’函數,而已0作爲信號代碼(signal number)。
從這個函數返回有四種可能的結果:
  * 'kill()’返回0
      - 這意味着一個給定此進程號的進程退出,系統允許你向它發送信號。該進
        程是否可以是僵死進程與不同系統有關。
 * 'kill()’返回-1,'errno == ESRCH’
      - 要麼不存在給定進程號的進程,要麼增強的安全機制導致系統否認它的存
        在。(在一些系統上,這個進程有可能是僵死進程。)
  * 'kill()’返回-1,'errno == EPERM’
      - 系統不允許你殺死(kill)這個特定進程。這意味着要麼進程存在(它又可能是
        僵死進程),要麼嚴格的增強安全機制起作用(比如你的進程不允許發送信號
        給*任何人*)。
   * 'kill()’返回-1,伴以其它'errno’值
      - 你有麻煩了!
用的最多的技巧是認爲調用“成功”或伴以'EPERM’的“失敗”意味着進程存
在,而其它錯誤意味着它不存在。
如果你特別爲提供'/proc’文件系統的系統(或所有類似系統)寫程序,一個替換
方法存在:檢查'proc/進程號’是否存在是可行的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章