欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
Unix編程常見(jiàn)問(wèn)題解答
1. Process Control 進(jìn)程控制
 1.1 Creating new processes: fork() 創(chuàng )建新進(jìn)程: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? 為何在一個(gè)fork的子進(jìn)程分支中使用_exit函數而不使用exit函數?
 1.2 Environment variables 環(huán)境變量
   1.2.1 How can I get/set an environment variable from a program? 我怎樣在程序中獲得/設置環(huán)境變量?
   1.2.2 How can I read the whole environment? 我怎樣讀取整個(gè)環(huán)境變量表?
 1.3 How can I sleep for less than a second? 我怎樣睡眠小于一秒?
 1.4 How can I get a finer-grained version of alarm()? 我怎樣得到一個(gè)更細分時(shí)間單位的alarm函數版本(譯者注:希望alarm的時(shí)間小于一秒)?
 1.5 How can a parent and child process communicate? 父子進(jìn)程如何通信?
 1.6 How do I get rid of zombie processes? 我怎樣去除僵死進(jìn)程?
   1.6.1 What is a zombie? 何為僵死進(jìn)程?
   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程序一樣審視系統的進(jìn)程?
 1.9 Given a pid, how can I tell if it‘s a running program? 給定一個(gè)進(jìn)程號(譯者注:pid: process ID),我怎樣知道它是個(gè)正在運行的程序?
 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? 我怎樣找出一個(gè)進(jìn)程的存儲器使用情況?
 1.12 Why do processes never decrease in size? 為什么進(jìn)程的大小不縮減?
 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? 我怎樣找到進(jìn)程的相應可執行文件?
   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? 為何父進(jìn)程死時(shí),我的進(jìn)程未得到SIGHUP信號?
 1.16 How can I kill all descendents of a process? 我怎樣殺死一個(gè)進(jìn)程的所有派生進(jìn)程?
2. General File handling (including pipes and sockets) 一般文件操作(包括管道和套接字)
 2.1 How to manage multiple connections? 怎樣管理多個(gè)連接?
   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 進(jìn)程間通信 (譯者注:IPC: Interprocess Communications) 與select或poll同
時(shí)使用?
 2.2 How can I tell when the other end of a connection shuts down? 我怎么知道連接的另一端已關(guān)閉?
 2.3 Best way to read directories? 讀目錄的最好方法?
 2.4 How can I find out if someone else has a file open? 我怎么知道其他人已經(jīng)打開(kāi)一個(gè)文件?
 2.5 How do I `lock‘ a file? 我怎樣鎖定一個(gè)文件?
 2.6 How do I find out if a file has been updated by another process? 我怎么知道一個(gè)文件是否已被其他進(jìn)程更新?
 2.7 How does the `du‘ utility work? “du”工具程序是怎么工作的?
 2.8 How do I find the size of a file? 我怎么知道一個(gè)文件的大???
 2.9 How do I expand `~‘ in a filename like the shell does? 我怎樣象shell程序一樣將一個(gè)文件名中含有的“~”展開(kāi)?
 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? 我怎樣創(chuàng )建一個(gè)有名管道?
   2.10.3 How do I use a named pipe? 我怎樣使用一個(gè)有名管道?
   2.10.4 Can I use a named pipe across NFS? 我能基于網(wǎng)絡(luò )文件系統(譯者注:NFS:Network File System)使用有名管道嗎?
   2.10.5 Can multiple processes write to the pipe simultaneously? 多個(gè)進(jìn)程能否同時(shí)向這個(gè)管道寫(xiě)執行寫(xiě)操作?
   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? 我怎樣從終端讀取單個(gè)字符?
 3.3 How can I check and see if a key was pressed? 我怎樣檢查是否一個(gè)鍵被摁下?
 3.4 How can I move the cursor around the screen? 我怎樣將光標在屏幕里移動(dòng)?
 3.5 What are pttys? pttys(pttys:Pseudo-teletypes)是什么?
 3.6 How to handle a serial port or modem? 怎樣控制一個(gè)串行口和調制解調器(譯者注:modem: modulate-demodulate)
   3.6.1 Serial device names and types 串行設備和類(lèi)型
   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? 我怎樣檢查一個(gè)用戶(hù)的口令?
   4.2.1 How do I get a user‘s password? 我怎樣得到一個(gè)用戶(hù)的口令?
   4.2.2 How do I get shadow passwords by uid? 我怎樣通過(guò)用戶(hù)號(譯者注:uid: User ID)得到陰影口令文件中的口令?
   4.2.3 How do I verify a user‘s password? 我怎樣核對一個(gè)用戶(hù)的口令?
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? 什么是在程序中發(fā)送電子郵件的最好方法?
   5.2.1 The simple method: /bin/mail 簡(jiǎn)單方法:/bin/mail
   5.2.2 Invoking the MTA directly: /usr/lib/sendmail 直接啟動(dòng)郵件傳輸代理(譯者注: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函數產(chǎn)生的子進(jìn)程?
 6.2 How to build library from other libraries? 怎樣通過(guò)其他庫文件建立新的庫文件?
 6.3 How to create shared libraries / dlls? 怎樣創(chuàng )建動(dòng)態(tài)連接庫/dlls?
 6.4 Can I replace objects in a shared library? 我能更改一個(gè)動(dòng)態(tài)連接庫里的目標嗎?
 6.5 How can I generate a stack dump from within a running program? 我能在一個(gè)運行著(zhù)的程序中生成堆棧映象嗎?

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

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

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

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

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

擇一個(gè)。)
在范例章節有這個(gè)問(wèn)題的兩個(gè)完整解決方法;一個(gè)適用于SunOS 4,它需要root權
限執行并使用‘kvm_*’例程從內核數據結果讀取信息;另一種適用于SVR4系統
(包括Sun OS 5),它使用‘/proc’文件系統。
在具有SVR4.2風(fēng)格‘/proc’的系統上更簡(jiǎn)單;只要對于每一個(gè)感興趣的進(jìn)程號從
文件‘/proc/進(jìn)程號/psinfo’讀取一個(gè)psinfo_t結構。但是,這種可能是最清晰的方
法也許又是最不得到很好支持的方法。(在FreeBSD的‘/proc’上,你從
‘/proc/進(jìn)程號/status’讀取一個(gè)半未提供文檔說(shuō)明(semi-undocumented)的可打印字
符串;Linux有一些與其類(lèi)似的東西)
1.9  給定一個(gè)進(jìn)程號,我怎樣知道它是個(gè)正在運行的程序?
=====================================================
使用‘kill()’函數,而已0作為信號代碼(signal number)。
從這個(gè)函數返回有四種可能的結果:
  * ‘kill()’返回0
      - 這意味著(zhù)一個(gè)給定此進(jìn)程號的進(jìn)程退出,系統允許你向它發(fā)送信號。該進(jìn)
        程是否可以是僵死進(jìn)程與不同系統有關(guān)。
 * ‘kill()’返回-1,‘errno == ESRCH’
      - 要么不存在給定進(jìn)程號的進(jìn)程,要么增強的安全機制導致系統否認它的存
        在。(在一些系統上,這個(gè)進(jìn)程有可能是僵死進(jìn)程。)
  * ‘kill()’返回-1,‘errno == EPERM’
      - 系統不允許你殺死(kill)這個(gè)特定進(jìn)程。這意味著(zhù)要么進(jìn)程存在(它又可能是
        僵死進(jìn)程),要么嚴格的增強安全機制起作用(比如你的進(jìn)程不允許發(fā)送信號
        給*任何人*)。
   * ‘kill()’返回-1,伴以其它‘errno’值
      - 你有麻煩了!
用的最多的技巧是認為調用“成功”或伴以‘EPERM’的“失敗”意味著(zhù)進(jìn)程存
在,而其它錯誤意味著(zhù)它不存在。
如果你特別為提供‘/proc’文件系統的系統(或所有類(lèi)似系統)寫(xiě)程序,一個(gè)替換
方法存在:檢查‘proc/進(jìn)程號’是否存在是可行的。
1.10  system函數,pclose函數,waitpid函數的返回值是什么?
==========================================================
    ‘system()’,‘pclose()’或者‘waitpid()’的返回值不象是我進(jìn)程的退出值(exit
       value)(譯者注:退出值指調用exit() 或_exit()時(shí)給的參數)... 或者退出值左移了8
      位...這是怎么搞的?
手冊頁(yè)是對的,你也是對的! 如果查閱手冊頁(yè)的‘waitpid()’你會(huì )發(fā)現進(jìn)程的返回
值被編碼了。正常情況下,進(jìn)程的返回值在高16位,而余下的位用來(lái)作其它事。
如果你希望可移植,你就不能憑借這個(gè),而建議是你該使用提供的宏。這些宏總
是在‘wait()’或‘wstat’的文檔中說(shuō)明了。
為了不同目的定義的宏(在‘<sys/wait.h>’)包括(stat是‘waitpid()’返回的值):
`WIFEXITED(stat)‘
    如果子進(jìn)程正常退出則返回非0
`WEXITSTATUS(stat)‘
    子進(jìn)程返回的退出碼
`WIFSIGNALED(stat)‘
    如果子進(jìn)程由與信號而 終止則返回非0
`WTERMSIG(stat)‘
    終止子進(jìn)程的信號代碼
`WIFSTOPPED(stat)‘
    如果子進(jìn)程暫停(stopped)則返回非0
`WSTOPSIG(stat)‘
    使子進(jìn)程暫停的信號代碼
`WIFCONTINUED(stat)‘
    如果狀態(tài)是表示子進(jìn)程繼續執行則返回非0
`WCOREDUMP(stat)‘
    如果‘WIFSIGNALED(stat)’為非0,而如果這個(gè)進(jìn)程產(chǎn)生一個(gè)內存映射文件
    (core dump)則返回非0
1.11 我怎樣找出一個(gè)進(jìn)程的存儲器使用情況?
=========================================
如果提供的話(huà),參看‘getrusage()’手冊頁(yè)
1.12 為什么進(jìn)程的大小不縮減?
=============================
當你使用‘free()’函數釋放內存給堆時(shí),幾乎所有的系統都*不*減少你程序的
對內存的使用。被‘free()’釋放的內存仍然屬于進(jìn)程地址空間的一部份,并將
被將來(lái)的‘malloc()’請求所重復使用。
如果你真的需要釋放內存給系統,參看使用‘mmap()’分配私有匿名內存映射
(private anonymous mappings)。當這些內存映射被取消映射時(shí),內存真的將其釋放給
系統。某些‘malloc()’的實(shí)現方法(比如在GNU C庫中)在允許時(shí)自動(dòng)使用‘mmap()’
實(shí)施大容量分配;這些內存塊(blocks)隨著(zhù)‘free()’被釋放回系統。
當然,如果你的程序的大小增加而你認為它不應該這樣,你可能有一個(gè)‘內存泄
露’(‘memory leak’)- 即在你的的程序中有缺陷(bug)導致未用的內存沒(méi)釋放。
1.13 我怎樣改變我程序的名字(即“ps”看到的名字)?
=================================================
在BSD風(fēng)格的系統中,‘ps’程序實(shí)際上審視運行進(jìn)程的地址空間從而找到當前
的‘argv[]’,并顯示它。這使得程序可以通過(guò)簡(jiǎn)單的修改‘argv[]’以改變它的
名字。
在SysV風(fēng)格的系統中,命令的名字和參數的一般頭80字節是存放在進(jìn)程的u-區(
u-area), 所以不能被直接修改??赡苡幸粋€(gè)系統調用用來(lái)修改它(不象是這樣),

但是其它的話(huà),只有一個(gè)方法就是實(shí)施一個(gè)‘exec()’,或者些內核內存(危險,
而且只有root才有可能)。
一些系統(值得注意的是Solaris)可以有‘ps’的兩種不同版本,一種是在
‘/usr/bin/ps’擁有SysV的行為,而另一種在‘/usr/ucb/ps’擁有BSD的行為。

這些系統中,如果你改變‘argv[]’,那么BSD版的‘ps’將反映這個(gè)變化,而
SysV版將不會(huì )。
檢查你的系統是否有一個(gè)函數‘setproctitle()’。
1.14 我怎樣找到進(jìn)程的相應可執行文件?
=====================================
這個(gè)問(wèn)題可以作為‘常見(jiàn)未回答問(wèn)題’(‘Frequently Unanswered Questions’)的一
個(gè)好候選,因為事實(shí)上提出這個(gè)問(wèn)題經(jīng)常意味著(zhù)程序的設計有缺陷。
:)
你能作的‘最佳猜測’(‘best guess’)是通過(guò)審視‘argv[0]’的值而獲得。如果
它包括一個(gè)‘/’,那么它可能是可執行程序的絕對或相對(對于在程序開(kāi)始時(shí)的
當前目錄而言)路徑。如果不包括,那么你可以仿效shell對于‘PATH’變量的查
詢(xún)來(lái)查找這個(gè)程序。但是,不能保證成功,因為有可能執行程序時(shí)‘argv[0]’是
一些任意值,也不排除這個(gè)可執行文件在執行后可能已經(jīng)被更名或刪除的情況。
如果所有你想做的只是能打印一個(gè)和錯誤消息一起出現的合適的名字,那么最好
的方法在‘main()’函數中將‘argv[0]’的值保存在全局變量中以供整個(gè)程序使
用。雖然沒(méi)有保證說(shuō)‘argv[0]’的值總是有意義,但在大多數情況下它是最好的
選擇。
人們詢(xún)問(wèn)這個(gè)問(wèn)題的最普通原因是意圖定位他們程序的配置文件。這被認為是
不好的形式;包含可執行文件的目錄應當*只*包含可執行文件,而且基于管理的
要求經(jīng)常試圖將配置文件放置在和可執行文件不同的文件系統。
試圖做這個(gè)的一個(gè)比較不普通但更正規的理由是允許程序調用‘exec()’執行它
自己;這是一種用來(lái)完全重新初始化進(jìn)程(比如被用于一些‘sendmail’的版本)的
辦法(比如當一個(gè)守護程序捕獲一個(gè)‘SIGHUP’信號)。
1.14.1 So where do I put my configuration files then?
-----------------------------------------------------
1.14.1 那么,我把配置文件放在哪里里呢?
為配置文件安排正確的目錄總是取決于你使用的Unix系統的特點(diǎn);
‘/var/opt/PACKAGE’,‘/usr/local/lib’,‘/usr/local/etc’,或者任何其它一
些可能的地方。用戶(hù)自定義的配置文件通常是在‘$HOME’下的以“.”開(kāi)始的隱藏文件(
比如‘$HOME/.exrc’)。
從一個(gè)在不同系統上都能使用的軟件包(package)的角度看,它通常意味著(zhù)任何站
點(diǎn)范圍(sitewide)的配置文件的位置有個(gè)已設定的缺省值,可能情況是使用一個(gè)在
配置腳本程序里的‘--prefix’選項(Autoconf 腳本程序集做這個(gè)工作)。
你會(huì )希望允
許這個(gè)缺省值在程序執行時(shí)被一個(gè)環(huán)境變量重載。(如果你沒(méi)使用配置腳本程序,
那么在編譯時(shí),將這個(gè)位置缺省值作為‘-D’選項放入項目文件(Makefile),或者
將其放入一個(gè)‘config.h’頭文件,或做其它類(lèi)似的工作)
--
用戶(hù)自定義配置需要放置于一個(gè)在‘$HOME’下的文件名“.”打頭的文件,或者
在需要多個(gè)配置文件時(shí),建立文件名“.”打頭的子目錄。(在列目錄時(shí),文件名以
“.”打頭的文件或目錄缺省情況下被忽略。)避免在‘$HOME’建立多個(gè)文件,因
為這會(huì )造成非常雜亂的情況。當然,你也應該允許用戶(hù)通過(guò)一個(gè)環(huán)境變量重載這個(gè)
位置。即使不能找到某個(gè)用戶(hù)的配置文件,程序仍應當以適宜的方式執行。
1.15 為何父進(jìn)程死時(shí),我的進(jìn)程未得到SIGHUP信號?
===============================================
因為本來(lái)就沒(méi)有設想是這樣做的。
‘SIGHUP’是一個(gè)信號,它按照慣例意味著(zhù)“終端線(xiàn)路被掛斷”。它與父進(jìn)程
無(wú)關(guān),而且通常由tty驅動(dòng)程序產(chǎn)生(并傳遞給前臺的進(jìn)程組)。
但是,作為會(huì )話(huà)管理系統(session management system)的一部份,確切說(shuō)有兩種情況
下‘SIGHUP’會(huì )在一個(gè)進(jìn)程死時(shí)發(fā)送出:
 * 當一個(gè)終端設備與一個(gè)會(huì )話(huà)相關(guān)聯(lián),而這個(gè)會(huì )話(huà)的會(huì )話(huà)首領(lǐng)進(jìn)程死時(shí),
    ‘SIGHUP’被發(fā)送至這個(gè)終端設備的所有前臺進(jìn)程組。
  * 當一個(gè)進(jìn)程死去導致一個(gè)進(jìn)程組變成孤兒,而且該進(jìn)程組里一個(gè)或多個(gè)進(jìn)程
    處于*暫停*狀態(tài)時(shí),那么‘SIGHUP’和‘SIGCONT’被發(fā)送至這個(gè)孤兒進(jìn)程
    組的所有成員進(jìn)程。(一個(gè)孤兒進(jìn)程組是指在該進(jìn)程組中沒(méi)有一個(gè)成員進(jìn)程的
    父進(jìn)程屬于和該進(jìn)程組相同的會(huì )話(huà)的其它進(jìn)程組。)
1.16 我怎樣殺死一個(gè)進(jìn)程的所有派生進(jìn)程?
=======================================
沒(méi)有一個(gè)完全普遍的方法來(lái)做這個(gè)。雖然你可以通過(guò)處理‘ps’的輸出確定進(jìn)
程間的相互關(guān)系,但因為它只表示系統的一瞬間的狀態(tài)(snapshot)所以并不可靠。
但是,如果你啟動(dòng)一個(gè)子進(jìn)程,而它可能生成它自己的子進(jìn)程,而你意圖一次殺
死整個(gè)生成的事務(wù)(job),解決方法是將最先啟動(dòng)的子進(jìn)程置于一個(gè)新的進(jìn)程組,
當你需要時(shí)殺死整個(gè)進(jìn)程組。
建議為創(chuàng )建進(jìn)程組而使用的函數是‘setpgid()’。在可能情況下,使用這個(gè)函數
而不使用‘setpgrp()’,因為后一個(gè)在不同系統中有所不同(在一些系統上‘setgrp();’
等同于‘setpgid(0,0);’,在其它系統上,‘setpgrp()’和‘setpgid()’相同)。
參見(jiàn)范例章節的事務(wù)-控制范例程序。
放置一個(gè)子進(jìn)程于其自身的進(jìn)程組有一些影響。特別的,除非你顯式地將該進(jìn)程
組放置于前臺,它將被認為是一個(gè)后臺事務(wù)并具有以下結果:
  * 試圖從終端讀取的進(jìn)程將被‘SIGTTIN’信號暫停。
  * 如果設置終端模式‘tostop’,那么試圖向終端寫(xiě)的進(jìn)程將被‘SIGTTOU’
    信號暫停。(試圖改變終端模式也導致這個(gè)結果,且不管當前‘tostop’是否
    設置)
  * 子進(jìn)程將不會(huì )收到從終端發(fā)出的鍵盤(pán)信號(比如‘SIGINT’或‘SIGQUIT’)
在很多應用程序中輸入和輸出總會(huì )被重定向,所以最顯著(zhù)的影響將是喪失鍵盤(pán)
信號。父進(jìn)程需要安排程序起碼捕獲‘SIGINIT’和‘SIGQUIT’(可能情況下,
還有‘SIGTERM’),并在需要情況下清除后臺事務(wù)。

2. 一般文件操作(包括管道和套接字)
*********************************
請同時(shí)參考套接字FAQ,在
http://www.lcg.org/sock-faq/
2.1 如何管理多個(gè)連接?
======================
  “我想同時(shí)監控一個(gè)以上的文件描述符(fd)/連接(connection)/流(stream),
應該怎么辦?”
使用 select() 或 poll() 函數。
注意:select() 在BSD中被引入,而poll()是SysV STREAM流控制的產(chǎn)物。因此,
這里就有了平臺移植上的考慮:純粹的BSD系統可能仍然缺少poll(),而早一些
的SVR3系統中可能沒(méi)有select(),盡管在SVR4中將其加入。
目前兩者都是POSIX.
1g標準,(譯者注:因此在Linux上兩者都存在)
select()和poll()本質(zhì)上來(lái)講做的是同一件事,只是完成的方法不一樣。兩者都
通過(guò)檢驗一組文件描述符來(lái)檢測是否有特定的時(shí)間將在上面發(fā)生并在一定的時(shí)間
內等待其發(fā)生。
[重要事項:無(wú)論select()還是poll()都不對普通文件起很大效用,它們著(zhù)重用
于套接口(socket)、管道(pipe)、偽終端(pty)、終端設備(tty)和其他一些字符
設備,但是這些操作都是系統相關(guān)(system-dependent)的。]
2.2.1 我如何使用select()函數?
------------------------------
select()函數的接口主要是建立在一種叫‘fd_set‘類(lèi)型的基礎上。它(‘fd_set‘)
是一組文件描述符(fd)的集合。由于fd_set類(lèi)型的長(cháng)度在不同平臺上不同,因此

應該用一組標準的宏定義來(lái)處理此類(lèi)變量:
   fd_set set;
   FD_ZERO(&set);       /* 將set清零 */
   FD_SET(fd, &set);    /* 將fd加入set */
   FD_CLR(fd, &set);    /* 將fd從set中清除 */
   FD_ISSET(fd, &set);  /* 如果fd在set中則真 */
在過(guò)去,一個(gè)fd_set通常只能包含少于等于32個(gè)文件描述符,因為fd_set其實(shí)只
用了一個(gè)int的比特矢量來(lái)實(shí)現,在大多數情況下,檢查fd_set能包括任意值的
文件描述符是系統的責任,但確定你的fd_set到底能放多少有時(shí)你應該檢查/修
改宏FD_SETSIZE的值。*這個(gè)值是系統相關(guān)的*,同時(shí)檢查你的系統中的select()
的man手冊。有一些系統對多于1024個(gè)文件描述符的支持有問(wèn)題。[譯者注:

Linux就是這樣的系統!你會(huì )發(fā)現sizeof(fd_set)的結果是128(*8 =
FD_SETSIZE=1024) 盡管很少你會(huì )遇到這種情況。]
select的基本接口十分簡(jiǎn)單:
   int select(int nfds, fd_set *readset, fd_set *writeset,
              fd_set *exceptset, struct timeval *timeout);
其中:
nfds :      需要檢查的文件描述符個(gè)數,數值應該比是三組fd_set中最大數
           更大,而不是實(shí)際文件描述符的總數。
readset:    用來(lái)檢查可讀性的一組文件描述符。
writeset:   用來(lái)檢查可寫(xiě)性的一組文件描述符。
exceptset:  用來(lái)檢查意外狀態(tài)的文件描述符。(注:錯誤并不是意外狀態(tài)
)
timeout:    NULL指針代表無(wú)限等待,否則是指向timeval結構的指針,代表最

           長(cháng)等待時(shí)間。(如果其中tv_sec和tv_usec都等于0, 則文件描述符
           的狀態(tài)不被影響,但函數并不掛起)
函數將返回響應操作的對應操作文件描述符的總數,且三組數據均在恰當位置被
修改,只有響應操作的那一些沒(méi)有修改。接著(zhù)應該用FD_ISSET宏來(lái)查找返回的文
件描述符組。
這里是一個(gè)簡(jiǎn)單的測試單個(gè)文件描述符可讀性的例子:
    int isready(int fd)
    {
        int rc;
        fd_set fds;
        struct timeval tv;
   
        FD_ZERO(&fds);
        FD_SET(fd,&fds);
        tv.tv_sec = tv.tv_usec = 0;
   
        rc = select(fd+1, &fds, NULL, NULL, &tv);
        if (rc < 0)
          return -1;
   
        return FD_ISSET(fd,&fds) ? 1 : 0;
    }
當然如果我們把NULL指針作為fd_set傳入的話(huà),這就表示我們對這種操作的發(fā)生
不感興趣,但select() 還是會(huì )等待直到其發(fā)生或者超過(guò)等待時(shí)間。
[譯者注:在Linux中,timeout指的是程序在非sleep狀態(tài)中度過(guò)的時(shí)間,而不是
實(shí)際上過(guò)去的時(shí)間,這就會(huì )引起和非Linux平臺移植上的時(shí)間不等問(wèn)題。移植問(wèn)
題還包括在System V風(fēng)格中select()在函數退出前會(huì )把timeout設為未定義的
NULL狀態(tài),而在BSD中則不是這樣,Linux在這點(diǎn)上遵從System V,因此在重復利
用timeout指針問(wèn)題上也應該注意。]
2.1.2 我如何使用poll()?
------------------------
poll()
接受一個(gè)指向結構‘struct pollfd‘列表的指針,其中包括了你想測試的
文件描述符和事件。事件由一個(gè)在結構中事件域的比特掩碼確定。當前的結構在
調用后將被填寫(xiě)并在事件發(fā)生后返回。在SVR4(可能更早的一些版本)中的
"poll.h"文件中包含了用于確定事件的一些宏定義。事件的等待時(shí)間精確到毫秒
(但令人困惑的是等待時(shí)間的類(lèi)型卻是int),當等待時(shí)間為0時(shí),poll()函數立即
返回,-1則使poll()一直掛起直到一個(gè)指定事件發(fā)生。下面是pollfd的結構。
    struct pollfd {
        int fd;        /* 文件描述符
*/
        short events;  /* 等待的事件
*/
        short revents; /* 實(shí)際發(fā)生了的事件
*/
    };
于select()十分相似,當返回正值時(shí),代表滿(mǎn)足響應事件的文件描述符的個(gè)數,
如果返回0則代表在規定事件內沒(méi)有事件發(fā)生。如發(fā)現返回為負則應該立即查看
errno,因為這代表有錯誤發(fā)生。
如果沒(méi)有事件發(fā)生,revents會(huì )被清空,所以你不必多此一舉。
這里是一個(gè)例子:
  /* 檢測兩個(gè)文件描述符,分別為一般數據和高優(yōu)先數據。如果事件發(fā)生
     則用相關(guān)描述符和優(yōu)先度調用函數handler(),無(wú)時(shí)間限制等待,直到
     錯誤發(fā)生或描述符掛起。*/
 
  #include <stdlib.h>
  #include <stdio.h>
 
  #include <sys/types.h>
  #include <stropts.h>
  #include <poll.h>
 
  #include <unistd.h>
  #include <errno.h>
  #include <string.h>
 
  #define NORMAL_DATA 1
  #define HIPRI_DATA 2
 
  int poll_two_normal(int fd1,int fd2)
  {
      struct pollfd poll_list[2];
      int retval;
 
      poll_list[0].fd = fd1;
      poll_list[1].fd = fd2;
      poll_list[0].events = POLLIN|POLLPRI;
      poll_list[1].events = POLLIN|POLLPRI;
 
      while(1)
      {
          retval = poll(poll_list,(unsigned long)2,-1);
          /* retval 總是大于0或為-1,因為我們在阻塞中工作
*/
 
          if(retval < 0)
          {
              fprintf(stderr,"poll錯誤
: %s\n",strerror(errno));
              return -1;
          }
   
          if(((poll_list[0].revents&POLLHUP) == POLLHUP) ||
             ((poll_list[0].revents&POLLERR) == POLLERR) ||
             ((poll_list[0].revents&POLLNVAL) == POLLNVAL) ||
             ((poll_list[1].revents&POLLHUP) == POLLHUP) ||
             ((poll_list[1].revents&POLLERR) == POLLERR) ||
             ((poll_list[1].revents&POLLNVAL) == POLLNVAL))
            return 0;
 
          if((poll_list[0].revents&POLLIN) == POLLIN)
            handle(poll_list[0].fd,NORMAL_DATA);
          if((poll_list[0].revents&POLLPRI) == POLLPRI)
            handle(poll_list[0].fd,HIPRI_DATA);
          if((poll_list[1].revents&POLLIN) == POLLIN)
            handle(poll_list[1].fd,NORMAL_DATA);
          if((poll_list[1].revents&POLLPRI) == POLLPRI)
            handle(poll_list[1].fd,HIPRI_DATA);
      }
  }
2.1.3 我是否可以同時(shí)使用SysV IPC和select()/poll()?
---------------------------------------------------
*不能。* (除非在A(yíng)IX上,因為它用一個(gè)無(wú)比奇怪的方法來(lái)實(shí)現這種組合)
一般來(lái)說(shuō),同時(shí)使用select()或poll()和SysV 消息隊列會(huì )帶來(lái)許多麻煩。SysV
IPC的對象并不是用文件描述符來(lái)處理的,所以它們不能被傳遞給select()和
poll()。這里有幾種解決方法,其粗暴程度各不相同:
  - 完全放棄使用SysV IPC。
:-)
  - 用fork(),然后讓子進(jìn)程來(lái)處理SysV IPC,然后用管道或套接口和父進(jìn)程

    說(shuō)話(huà)。父進(jìn)程則使用select()。
  - 同上,但讓子進(jìn)程用select(),然后和父親用消息隊列交流。
  - 安排進(jìn)程發(fā)送消息給你,在發(fā)送消息后再發(fā)送一個(gè)信號。*警告*:要做好
    這個(gè)并不簡(jiǎn)單,非常容易寫(xiě)出會(huì )丟失消息或引起死鎖的程序。
……另外還有其他方法。
2.2 我如何才能知道和對方的連接被終止?
======================================
如果你在讀取一個(gè)管道、套接口、FIFO等設備時(shí),當寫(xiě)入端關(guān)閉連接時(shí),你將會(huì )
得到一個(gè)文件結束符(EOF)(read()返回零字節讀取)。如果你試圖向一個(gè)管道或
套接口寫(xiě)入,當讀取方關(guān)閉連接,你將得到一個(gè)SIGPIPE的信號,它會(huì )使進(jìn)程終
止除非指定處理方法。(如果你選擇屏蔽或忽略信號,write()會(huì )以EPIPE錯誤退
出。)
2.3 什么是讀取目錄的最好方法?
==============================
歷史上曾有過(guò)許多不同的目錄讀取方法,但目前你應該使用POSIX.1標準的
<dirent.h>接口。
opendir()函數打開(kāi)一個(gè)指定的目錄;readdir()將目錄以一種標準的格式讀入;
closedir()關(guān)閉描述符。還有一些其他如rewinddir()、telldir()和seekdir()
等函數,相信不難理解。
如果你想用文件匹配符(‘*‘,‘?‘),那么你可以使用大多數系統中都存在的glob()
函數,或者可以查看fnmatch()函數來(lái)得到匹配的文件名,或者用ftw()來(lái)遍歷整

個(gè)目錄樹(shù)。
2.4 我如何才能知道一個(gè)文件被另外進(jìn)程打開(kāi)?
==========================================
這又是一個(gè)“經(jīng)常不被回答的問(wèn)題”,因為一般來(lái)說(shuō)你的程序不會(huì )關(guān)心文件是否
正被別人打開(kāi)。如果你需要處理文件的并發(fā)操作,那你應該使用咨詢(xún)性文件鎖。
一般來(lái)說(shuō)要做到這點(diǎn)很難,像fuser或lsof這樣可以告訴你文件使用情況的工具
通過(guò)解析內核數據來(lái)達到目的,但這種方法十分不健康!而且你不能從你的程序
中調用它們來(lái)獲取信息,因為也許當它們執行完成之后,文件的使用狀況在瞬間
又發(fā)生了變化,你無(wú)法保證這些信息的正確。
2.5 我如何鎖住一個(gè)文件?
========================
有三種不同的文件鎖,這三種都是“咨詢(xún)性”的,也就是說(shuō)它們依靠程序之間的
合作,所以一個(gè)項目中的所有程序封鎖政策的一致是非常重要的,當你的程序需
要和第三方軟件共享文件時(shí)應該格外地小心。
有些程序利用諸如 FIlENAME.lock 的文件鎖文件,然后簡(jiǎn)單地測試此類(lèi)文件是否
存在。這種方法顯然不太好,因為當產(chǎn)生文件的進(jìn)程被殺后,鎖文件依然存在,
這樣文件也許會(huì )被永久鎖住。UUCP中把產(chǎn)生文件的進(jìn)程號PID存入文件,但這樣做
仍然不保險,因為PID的利用是回收型的。
這里是三個(gè)文件鎖函數:
    flock();
    lockf();
    fcntl();
flock()是從BSD中衍生出來(lái)的,但目前在大多數UNIX系統上都能找到,在單個(gè)主
機上flock()簡(jiǎn)單有效,但它不能在NFS上工作。Perl中也有一個(gè)有點(diǎn)讓人迷惑的

flock()函數,但卻是在perl內部實(shí)現的。
fcntl()是唯一的符合POSIX標準的文件鎖實(shí)現,所以也是唯一可移植的。它也同
時(shí)是最強大的文件鎖——也是最難用的。在NFS文件系統上,fcntl()請求會(huì )被遞
交給叫rpc.lockd的守護進(jìn)程,然后由它負責和主機端的lockd對話(huà),和flock()
不同,fcntl()可以實(shí)現記錄層上的封鎖。
lockf()只是一個(gè)簡(jiǎn)化了的fcntl()文件鎖接口。
無(wú)論你使用哪一種文件鎖,請一定記住在鎖生效之前用sync來(lái)更新你所有的文件
輸入/輸出。
     lock(fd);
     write_to(some_function_of(fd));
     flush_output_to(fd); /* 在去鎖之前一定要沖洗輸出 */
     unlock(fd);
     do_something_else;   /* 也許另外一個(gè)進(jìn)程會(huì )更新它 */
     lock(fd);
     seek(fd, somewhere); /* 因為原來(lái)的文件指針已不安全 */
     do_something_with(fd);
     ...
一些有用的fcntl()封鎖方法(為了簡(jiǎn)潔略去錯誤處理):

    #include <fcntl.h>
    #include <unistd.h>
   
    read_lock(int fd)   /* 整個(gè)文件上的一個(gè)共享的文件鎖 */
    {
        fcntl(fd, F_SETLKW, file_lock(F_RDLCK, SEEK_SET));
    }
   
    write_lock(int fd)  /* 整個(gè)文件上的一個(gè)排外文件鎖 */
    {
        fcntl(fd, F_SETLKW, file_lock(F_WRLCK, SEEK_SET));
    }
   
    append_lock(int fd) /* 一個(gè)封鎖文件結尾的鎖,
                           其他進(jìn)程可以訪(fǎng)問(wèn)現有內容 */
    {
        fcntl(fd, F_SETLKW, file_lock(F_WRLCK, SEEK_END));
    }
前面所用的file_lock函數如下:
    struct flock* file_lock(short type, short whence)
    {
        static struct flock ret ;
        ret.l_type = type ;
        ret.l_start = 0 ;
        ret.l_whence = whence ;
        ret.l_len = 0 ;
        ret.l_pid = getpid() ;
        return &ret ;
    }
2.6 我如何能發(fā)現一個(gè)文件已由另外一個(gè)進(jìn)程更新?
==============================================
這又幾乎是一個(gè)經(jīng)常不被回答的問(wèn)題,因為問(wèn)這個(gè)問(wèn)題的人通常期待能有一個(gè)系
統級的告示來(lái)反映當前目錄或文件被修改,但沒(méi)有什么保證移植性的實(shí)現方法,
IRIX有一個(gè)非標準的功能用來(lái)監測文件操作,但從未聽(tīng)說(shuō)在其他平臺上也有相類(lèi)
似的功能。
一般來(lái)說(shuō),你能盡的最大努力就是用fstat()函數,通過(guò)監視文件的mtime和ctime
你能得知文件什么時(shí)候被修改了,或者被刪除/連接/改名了,聽(tīng)起來(lái)很復雜,所

以你應該反思一下為什么你要做這些。
2.7 請問(wèn)du是怎樣工作的?
========================
du
只簡(jiǎn)單地用stat()(更準確地說(shuō)是用lstat()函數)遍歷目錄結構中的每個(gè)文件
和目錄,并將它們所占用的磁盤(pán)塊加在一起。
如果你想知道其中細節,總是這么一句話(huà):“讀下源代碼吧,老兄!”
BSD(FreeBSD、NetBSD和OpenBSD)的源代碼在這些發(fā)行的FTP網(wǎng)站的源碼目錄里,
GNU版本的源碼當然可以在任何一個(gè)GNU鏡像站點(diǎn)中找到——前提是你自己懂得如
何解包。
2.8 我如何得到一個(gè)文件的長(cháng)度?
==============================
用stat()或在文件打開(kāi)后用fstat()。
這兩個(gè)調用會(huì )將文件信息填入一個(gè)結構中, 其中你能找到諸如文件主人、屬性、
大小、最后訪(fǎng)問(wèn)時(shí)間、最后修改時(shí)間等所有關(guān)于此文件的東西。
下面的程序大體示范如何用stat()得到文件大小。
    #include <stdlib.h>
    #include <stdio.h>
   
    #include <sys/types.h>
    #include <sys/stat.h>
   
    int get_file_size(char *path,off_t *size)
    {
      struct stat file_stats;
   
      if(stat(path,&file_stats))
        return -1;
   
      *size = file_stats.st_size;
      return 0;
    }
2.9 我如何像shell里一樣擴展在文件名里的‘~‘?
============================================
‘~‘
的標準用法如下:如果單獨使用或者后面跟一個(gè)‘/‘,那么‘~‘就被當作當前
用戶(hù)的home目錄,[譯者注:事實(shí)上‘~‘就被替換為$HOME環(huán)境變量],如果‘~‘后
直接跟一個(gè)用戶(hù)名,則被替換的就是那個(gè)用戶(hù)的home目錄。如果沒(méi)有合適的匹
配,則shell不會(huì )做任何改動(dòng)。
請注意,有可能一些文件的確是以‘~‘打頭的,不分青紅皂白地將‘~‘替換會(huì )使你
的程序無(wú)法打開(kāi)這些文件。一般來(lái)說(shuō),從shell通過(guò)命令行或環(huán)境變量傳遞入程
序的文件名不須要進(jìn)行替換,因為shell已經(jīng)替你做好,而程序自己生成的、用
戶(hù)輸入的,或從配置文件中讀取的卻應該進(jìn)行替換。
這里是一段用標準string類(lèi)的C++實(shí)現:
  string expand_path(const string& path)
  {
      if (path.length() == 0 || path[0] != ‘~‘)
        return path;
 
      const char *pfx = NULL;
      string::size_type pos = path.find_first_of(‘/‘);
 
      if (path.length() == 1 || pos == 1)
      {
          pfx = getenv("HOME");
          if (!pfx)
          {
              // 我們想替換"~/",但$HOME卻沒(méi)有設置
              struct passwd *pw = getpwuid(getuid());
              if (pw)
                pfx = pw->pw_dir;
          }
      }
      else
      {
          string user(path,1,(pos==string::npos) ? string::npos : pos-1);
          struct passwd *pw = getpwnam(user.c_str());
          if (pw)
            pfx = pw->pw_dir;
      }
      // 如果我們不能找到能替換的選擇,則將path返回
 
      if (!pfx)
        return path;
 
      string result(pfx);
 
      if (pos == string::npos)
        return result;
 
      if (result.length() == 0 || result[result.length()-1] != ‘/‘)
        result += ‘/‘;
 
      result += path.substr(pos+1);
 
      return result;
  }
2.10 有名管道(FIFO)能做什么?
=============================
2.10.1 什么是有名管道?
-----------------------
有名管道是一個(gè)能在互不相關(guān)進(jìn)程之間傳送數據的特殊文件。一個(gè)或多個(gè)進(jìn)程向
內寫(xiě)入數據,在另一端由一個(gè)進(jìn)程負責讀出。有名管道是在文件系統中可見(jiàn)的,
也就是說(shuō)ls可以直接看到。(有名管道又稱(chēng)FIFO,也就是先入先出。)
有名管道可以將無(wú)關(guān)的進(jìn)程聯(lián)系起來(lái),而無(wú)名的普通管道一般只能將父子進(jìn)程聯(lián)
系起來(lái)——除非你很努力地去嘗試——當然也能聯(lián)系兩個(gè)無(wú)關(guān)進(jìn)程。有名管道是
嚴格單向的,盡管在一些系統中無(wú)名管道是雙向的。
2.10.2 我如何建立一個(gè)有名管道?
-------------------------------
在shell下交互地建立一個(gè)有名管道,你可以用mknod或mkfifo命令。在有些系統
中,mknod產(chǎn)生的文件可能在/etc目錄下,也就是說(shuō),可能不在你的目錄下出現,
所以請查看你系統中的man手冊。[譯者注:在Linux下,可以看一下fifo(4)]
要在程序中建立一個(gè)有名管道:
  /* 明確設置umask,因為你不知道誰(shuí)會(huì )讀寫(xiě)管道 */
  umask(0);
  if (mkfifo("test_fifo", S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP))
  {
      perror("mkfifo");
      exit(1);
  }
也可以使用mknod。[譯者注:在Linux下不推薦使用mknod,因為其中有許多臭蟲(chóng)
在NFS下工作更要小心,能使用mkfifo就不要用mknod,因為mkfifo()是POSIX.1
標準。]
  /* 明確設置umask,因為你不知道誰(shuí)會(huì )讀寫(xiě)管道 */
  umask(0);
  if (mknod("test_fifo",
             S_IFIFO | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP,
             0))
  {
      perror("mknod");
      exit(1);
  }
2.10.3 我如何使用一個(gè)有名管道?
-------------------------------
使用有名管道十分簡(jiǎn)單:你如同使用一個(gè)普通文件一樣打開(kāi)它,用read()和
write()進(jìn)行操作。但對管道使用open()時(shí)可能引起阻塞,下面一些常用規律可
以參考。
  * 如果你同時(shí)用讀寫(xiě)方式(O_RDWR)方式打開(kāi),則不會(huì )引起阻塞。
  * 如果你用只讀方式(O_RDONLY)方式打開(kāi),則open()會(huì )阻塞一直到有寫(xiě)方打
    開(kāi)管道,除非你指定了O_NONBLOCK,來(lái)保證打開(kāi)成功。
  * 同樣以寫(xiě)方式(O_WRONLY)打開(kāi)也會(huì )阻塞到有讀方打開(kāi)管道,不同的是如果
    O_NONBLOCK被指定open()會(huì )以失敗告終。
當對有名管道進(jìn)行讀寫(xiě)的時(shí),注意點(diǎn)和操作普通管道和套接字時(shí)一樣:當寫(xiě)入方
關(guān)閉連接時(shí)read()返回EOF,如果沒(méi)有聽(tīng)眾write()會(huì )得到一個(gè)SIGPIPE的信號,
對此信號進(jìn)行屏蔽或忽略會(huì )引發(fā)一個(gè)EPIPE錯誤退出。
2.10.4 能否在NFS上使用有名管道?
--------------------------------
不能,在NFS協(xié)議中沒(méi)有相關(guān)功能。(你可能可以在一個(gè)NFS文件系統中用有名管
道聯(lián)系兩個(gè)同在客戶(hù)端的進(jìn)程。)
2.10.5 能否讓多個(gè)進(jìn)程同時(shí)向有名管道內寫(xiě)入數據?
-----------------------------------------------
如果每次寫(xiě)入的數據少于PIPE_BUF的大小,那么就不會(huì )出現數據交叉的情況。但
由于對寫(xiě)入的多少沒(méi)有限制,而read()操作會(huì )讀取盡可能多的數據,因此你不能
知道數據到底是誰(shuí)寫(xiě)入的。
PIPE_BUF的大小根據POSIX標準不能小于512,一些系統里在<limits.h>中被定
義,[譯者注:Linux中不是,其值是4096。]這可以通過(guò)pathconf()或
fpathconf()
對單獨管道進(jìn)行咨詢(xún)得到。
2.10.6 有名管道的應用
---------------------
   
“我如何時(shí)間服務(wù)器和多個(gè)客戶(hù)端的雙向交流?”
一對多的形式經(jīng)常出現,只要每次客戶(hù)端向服務(wù)器發(fā)出的指令小于PIPE_BUF,它
們就可以通過(guò)一個(gè)有名管道向服務(wù)器發(fā)送數據??蛻?hù)端可以很容易地知道服務(wù)器
傳發(fā)數據的管道名。
但問(wèn)題在于,服務(wù)器不能用一個(gè)管道來(lái)和所有客戶(hù)打交道。如果不止一個(gè)客戶(hù)在
讀一個(gè)管道,是無(wú)法確保每個(gè)客戶(hù)都得到自己對應的回復。
一個(gè)辦法就是每個(gè)客戶(hù)在向服務(wù)器發(fā)送信息前都建立自己的讀入管道,或讓服務(wù)
器在得到數據后建立管道。使用客戶(hù)的進(jìn)程號(pid)作為管道名是一種常用的方
法??蛻?hù)可以先把自己的進(jìn)程號告訴服務(wù)器,然后到那個(gè)以自己進(jìn)程號命名的管
道中讀取回復。

3. 終端輸入/輸出
****************
3.1 我怎樣使我的程序不回射輸入?
================================
    我怎樣能使我的程序不回射輸入,就象登錄時(shí)詢(xún)問(wèn)我的口令時(shí)那樣?
有一個(gè)簡(jiǎn)單方法,也有一個(gè)稍微復雜點(diǎn)的方法:
簡(jiǎn)單方法是使用‘getpass()’函數,它幾乎能在所有Unix系統上找到。它以一個(gè)
給定的字符串參數作為提示符(prompt)。
它讀取輸入直到讀到一個(gè)‘EOF’或換
行符(譯者注:‘EOF’用‘^d’輸入,而換行符為‘^m’或回車(chē))然后返回一個(gè)
指向位于靜態(tài)內存區包含鍵入字符的字符串指針。(譯者注:字符串不包含換行
符)
復雜一點(diǎn)的方法是使用‘tcgetattr()’函數和‘tcsetattr()’函數,兩個(gè)函數都使用
一個(gè)‘struct termios’結構來(lái)操縱終端。
下面這兩段程序應當能設置回射狀態(tài)和
不回射狀態(tài)。
    #include <stdlib.h>
    #include <stdio.h>
    #include <termios.h>
    #include <string.h>
    static struct termios stored_settings;
    void echo_off(void)
    {
        struct termios new_settings;
        tcgetattr(0,&stored_settings);
        new_settings = stored_settings;
        new_settings.c_lflag &= (~ECHO);
        tcsetattr(0,TCSANOW,&new_settings);
        return;
    }
    void echo_on(void)
    {
        tcsetattr(0,TCSANOW,&stored_settings);
        return;
    }
兩段程序使用到的都是在POSIX標準定義的。
3.2 我怎樣從終端讀取單個(gè)字符?
==============================
    我怎樣從終端讀取單個(gè)字符?我的程序總是要等著(zhù)用戶(hù)按回車(chē)。
終端通常在標準(canonical)模式,在此模式輸入總是經(jīng)編輯后以行讀入。你可以
設置終端為非標準(non-canonical)模式,而在此模式下你可以設置在輸入傳遞給
你的程序前讀入多少字符。你也可以設定非標準模式的計時(shí)器為0,這個(gè)計時(shí)器
根據設定的時(shí)間間隔清空你的緩沖區。這樣做使你可以使用‘getc()’函數立即
獲得用戶(hù)的按鍵輸入。我們使用的‘tcgetattr()’函數和‘tcsetattr()’函數都
是在POSIX中定義用來(lái)操縱‘termios’結構的。
    #include <stdlib.h>
    #include <stdio.h>
    #include <termios.h>
    #include <string.h>
    static struct termios stored_settings;
    void set_keypress(void)
    {
        struct termios new_settings;
        tcgetattr(0,&stored_settings);
        new_settings = stored_settings;
        /* Disable canonical mode, and set buffer size to 1 byte */
        new_settings.c_lflag &= (~ICANON);
        new_settings.c_cc[VTIME] = 0;
        new_settings.c_cc[VMIN] = 1;
        tcsetattr(0,TCSANOW,&new_settings);
        return;
    }
    void reset_keypress(void)
    {
        tcsetattr(0,TCSANOW,&stored_settings);
        return;
    }
3.3 我怎樣檢查是否一個(gè)鍵被摁下?
================================
   我怎樣檢查是否一個(gè)鍵被摁下?在DOS上我用‘kbhit()’函數,但是在UNIX
   上看來(lái)沒(méi)有相同作用的函數?
如果你設定了終端為單一字符模式(參見(jiàn)上一個(gè)問(wèn)題解答),那么(在大多數系統)
上你可以使用‘select()’函數或‘poll()’函數測試輸入是否可讀。
3.4 我怎樣將光標在屏幕里移動(dòng)?
==============================
    我怎樣將光標在屏幕里移動(dòng)?我想不用curses而做全屏編輯。(譯者注:curses
    是一個(gè)C/C++編程工具庫,它提供編程者許多函數調用,在不用關(guān)心終端類(lèi)型

    的情況下操縱終端的顯示)。
不開(kāi)玩笑,你也許不應該想去做這個(gè)。Curses工具庫知道怎樣控制不同終端類(lèi)型
所表現出的奇特的東西(oddities);當然termcap/terminfo數據會(huì )告訴你任何終端類(lèi)型
具有的這些奇特東西,但你可能會(huì )發(fā)現正確把握所有這些奇特組合是一件艱巨的
工作。(譯者注:在Linux系統上,termcap數據位于/etc/termcap,而terminfo數據位于
/usr/share/terminfo下按不同終端類(lèi)型首字母存放的不同文件,目前終端類(lèi)型數已逾
兩千種)
但是,你堅決要把你自己搞的手忙腳亂(getting your hands dirty),那么去研究一下
‘termcap’的函數集,特別是‘tputs()’,‘tparm()’和‘tgoto()’函數。
3.5 pttys是什么?
=================
Pseudo-teletypes(pttys, ptys,或其它不同的縮寫(xiě))是具有兩部份的偽設備(pseudo-devices):
一部份為“主人”一邊,你可以認為是一個(gè)‘用戶(hù)’,另一部份是“仆人”一邊,
它象一個(gè)標準的tty設備一樣工作。
它們之所以存在是為了提供在程序控制下的一種模擬串行終端行為的方法。比
如,‘telnet’在遠端系統使用一個(gè)偽終端;服務(wù)器的遠端登錄shell程序只是從“仆
人”一邊的tty設備期待著(zhù)得到操作行為,而在“主人”一邊的偽終端由一個(gè)守護程
序控制,同時(shí)守護程序將所有數據通過(guò)網(wǎng)絡(luò )轉發(fā)。pttys也被其它程序使用,比如
‘xterm’,‘expect’,‘script’,‘screen’,‘emacs’和其它很多程序。
3.6 怎樣控制一個(gè)串行口和調制解調器?
====================================
Unix系統下對于串行口的控制很大程度上受串行終端傳統的使用影響。以往,
需要不同ioctls函數調用的組合和其它黑客行為才能控制一個(gè)串行設備的正確操
作,不過(guò)值得慶幸的是,POSIX在這個(gè)方面的標準化作了一些努力。
如果你使用的系統不支持‘<termios.h>’頭文件,‘tcsetattr()’和其它相關(guān)函數,
那么你只能到其它地方去找點(diǎn)資料(或將你的老古董系統升級一下)。
但是不同的系統仍然有顯著(zhù)的區別,主要是在設備名,硬件流控制的操作,和
調制解調器的信號方面。(只要可能,盡量讓設備驅動(dòng)程序去做握手(handshaking)
工作,而不要試圖直接操縱握手信號。)
打開(kāi)和初始華串行設備的基本步驟是:
   * 調用‘open()’函數打開(kāi)設備;而且可能需要使用特定標志作為參數:
   `O_NONBLOCK‘
         除非使用這個(gè)標志,否則打開(kāi)一個(gè)供撥入(dial-in)或由調制解調器控制的設
         備會(huì )造成‘open()’調用被阻塞直到線(xiàn)路接通(carrier is present)。一個(gè)非

         阻塞的打開(kāi)操作給你在需要時(shí)令調制解調器控制失效的機會(huì )。(參見(jiàn)下面的
         CLOCAL)
   `O_NOCTTY‘
         在自4.4BSD演化的系統上這個(gè)標志是多余的,但在其它系統上它控制串行

         設備是否成為會(huì )話(huà)的控制終端。在大多數情況下你可能不想獲得一個(gè)控制
         終端,所以就要設置這個(gè)標志,但是也有例外情況。
  * 調用‘tcgetattr()’函數獲得當前設備模式。雖然有人會(huì )經(jīng)常取消(ignore)得到的
     大多數或全部初始設定,但它仍然不失為一個(gè)初始化‘struct termios’結構的
     便利方法。
  * 設置termios 結構里‘c_iflag’,‘c_oflag’,‘c_flag’,‘c_lfag’和‘c_cc’
     為合適的值。(參見(jiàn)下面部分。)
  * 調用‘cfsetispeed()’和‘cfsetospeed()’設定設想的波特率。很少系統允許你
    設置不同的輸入和輸出速度,所以普通規律是你需要設成同一個(gè)設想的值。
 * 調用‘tcsetattr()’設定設備模式。
  * 如果你是用‘O_NONBLOCK’標志打開(kāi)的端口,你可能希望調用‘fcntl()’
    函數將‘O_NONBLOCK’標志重新設置成關(guān)閉。因為不同系統看來(lái)對是否

    由非阻塞打開(kāi)的端口對今后的‘read()’調用造成影響有不同的處理;所以
    最好顯式地設置好。
一旦你打開(kāi)并設置了端口,你可以正常地調用‘read()’函數和‘write()’函數。
注意到‘read()’函數的行為將受到你調用‘tcsetattr()’函數時(shí)的標志參數設定
的控制。
‘tcflush()’,‘tcdrain()’,‘tcsendbreak()’和‘tcflow()’是其它一些你應當
注意的函數。
當你使用完端口想要關(guān)閉時(shí),要注意一些系統上特別惡心的小危險;如果有任何
輸出數據等待被寫(xiě)到設備(比如輸出流被硬件或軟件握手而暫停),你的進(jìn)程將因
為‘close()’函數調用而被掛起(hang)直到輸出數據排空,而且這時(shí)的進(jìn)程是*不
可殺的*(unkillably)。所以調用‘tcflush()’函數丟棄待發(fā)數據可能是個(gè)明智之舉。
(在我的經(jīng)驗中,在tty設備上被阻塞的輸出數據是造成不可殺進(jìn)程最普通的原因。)
3.6.1 串行設備和類(lèi)型
--------------------
不同系統用于串行端口設備的設備名大相徑庭。以下是不同系統的一些例子:
  * ‘/dev/tty[0-9][a-z]’作為直接訪(fǎng)問(wèn)設備,而
    ‘/dev/tty[0-9][A-Z]’ 作為調制解調器控制設備(比如SCO Unix)
 * ‘/dev/cua[0-9]p[0-9]’作為直接訪(fǎng)問(wèn)設備,‘/dev/cul[0-9]p[0-9]’作為撥出設
     備,而‘/dev/ttyd[0-9]p[0-9]’作為撥入設備(比如HP-UX)
  * ‘/dev/cua[a-z][0-9]’作為撥出設備而‘/dev/tty[a-z][0-9]’作為撥入設備(比如
     FreeBSD)
是否正確地同所用設備名交互,并在任何硬件握手信號線(xiàn)上產(chǎn)生相應的效果是
與系統,配置和硬件有關(guān)的,但是差不多總是遵循下面這些規則(假設硬件是
RS-232 DTE)
  - 對于任何設備的一個(gè)成功打開(kāi)操作應當設置DTR和RTS
  - 一個(gè)對于由調制解調器控制或供撥入的設備的阻塞打開(kāi)操作將等待DCD(并且
    可能DSR和/或CTS也需要)被設置,通常在設置DTR/RTS之后。
  - 如果一個(gè)對于撥出設備的打開(kāi)操作正巧趕上一個(gè)對于相應撥入設備的打開(kāi)操作
    因為等待線(xiàn)路接通而阻塞,那么打開(kāi)撥出的操作*也許*造成打開(kāi)撥入的操作完
    成,但*也許也不*造成。一些系統為撥入和撥出端口實(shí)現一個(gè)簡(jiǎn)單的共享方案,
    當撥出端口在使用時(shí),撥入端口被有效地設置成睡眠狀態(tài)(“put to sleep”);其
    它系統不這樣做,在這種系統上為避免競爭(contention)問(wèn)題,需要外部的協(xié)助才
    能使撥入和撥出共享端口(比如UUCP鎖定文件的使用)。
3.6.2 設置termios的標志位
-------------------------
這里是對你在使用你自己打開(kāi)的串行設備時(shí)設置termios標志的一些提示(即與你使
用已存在的控制tty相反)
3.6.2.1 c_iflag
...............
你也許希望將*所有*‘c_iflag’的標志位設成0,除非你希望使用軟件流控制(ick),
在這種情況下你設置‘IXON’和‘IXOFF’。(譯者注:有三個(gè)標志控制流控制:
IXON,IXOFF ,和IXANY,如果IXON被設置,那么tty輸入隊列的軟件流控制
被設置。當程序無(wú)法跟上輸入隊列的速度,tty傳輸一個(gè)STOP字符,而當輸入隊
列差不多空時(shí)發(fā)送START字符。如果IXON被設置,那么tty輸出隊列的軟件流控
制被設置。當tty所連接的設備跟不上輸出速度,tty將阻塞程序對tty的寫(xiě)操作。如果
IXANY被設置,那么一旦tty從設備收到任何字符,被暫定的輸出將繼續 - 譯自SCO
Unix 網(wǎng)上文檔http://uw7doc.sco.com/SDK_sysprog/CTOC-TermDevCntl.html,“
TTY flow
control”章節,第五,六段)
3.6.2.2 c_oflag
...............
大部分‘c_oflag’的標志位是為了能使對于慢終端的輸出可以正常工作所做的這
樣或那樣的黑客行為,由此,一些較新的系統認為幾乎所有這些標志位已經(jīng)過(guò)
時(shí)從而擯棄了它們(特別是所有血淋淋(gory)的輸出排列對齊(output-padding)選項)。
如同‘c_iflag’,將它設置成全0對于大部分應用程序來(lái)說(shuō)是合理的。
3.6.2.3 c_cflag
...............
當設置字符的大小時(shí),記住首先使用‘CSIZE’屏蔽,比如設置8位字符,需要:
        attr.c_cflag &= ~CSIZE;
        attr.c_cflag |= CS8;
在‘c_cflag’里的其它你有可能需要設置為*真*的標志包括‘CREAD’和
‘HUPCL’。
如果你需要產(chǎn)生偶校驗,那么設置‘PARENB’并清除‘PARODD’;如果你
需要產(chǎn)生奇校驗,那么同時(shí)設置‘PARENB’和‘PARODD’。
如果你根本不
想設置校驗,那么確認清除‘PARENB’。
清除‘CSTOPB’ ,除非你真需要產(chǎn)生兩個(gè)停止位。
設置硬件流控制的標志可能也能在‘c_cflag’中找到,但它們不是被標準化的(
這是一個(gè)遺憾)
3.6.2.4 c_lflag
...............
大部分應用程序可能需要關(guān)閉‘ICANON’(標準狀態(tài),即基于行的,并進(jìn)行輸
入處理),‘ECHO’和‘ISIG’。
‘IEXTEN’是個(gè)更復雜的問(wèn)題。如果你不把它關(guān)閉,具體實(shí)現允許你作一些非
標準的事情(比如在‘c_cc’中定義增加的控制字符)從而可能導致不可預料的接
果,但是在一些系統上,你可能需要保持‘IEXTEN’標志為真以得到一些有用
的特征,比如硬件流控制。
3.6.2.5 c_cc
............
這是一個(gè)包括輸入中帶有特殊含義字符的數組。這些字符被命名為‘VINTR’,
‘VSTOP’等等;這些名字是這個(gè)數組的索引。
(這些字符中的兩個(gè)其實(shí)根本不是字符,而是當‘ICANON’被關(guān)閉時(shí)對于
‘read()’函數行為的控制;它們是‘VMIN’和‘VTIME’。)
這些索引名字經(jīng)常被提及的方式會(huì )讓人以為它們是實(shí)在的變量,比如“設置
VMIN為1” 其實(shí)意味著(zhù)“設置c_cc[VMIN]為1”。這種簡(jiǎn)寫(xiě)是有用的并且只是
偶爾引起誤會(huì )。
‘c_cc’的很多變量位置只有當其它標志被設定時(shí)才會(huì )用到。
只有‘ICANON’被設置,才用到以下變量:
    ‘VEOF’,‘VEOL’,‘VERASE’,‘VKILL’(如果定義了而且
    ‘IEXTEN’被設定,那么‘VEOL2’,‘VSTATUS’和‘VWERASE’
       也用到)
只有‘ISIG’被設置,才用到以下變量:
    ‘VINTR’,‘VQUIT’,‘VSUSP’(如果定義了而且‘IEXTEN’被設定,
      那么‘VDSUSP’也用到)
只有‘IXON’或‘IXOFF’被設置,才用到以下變量:
    ‘VSTOP’,‘VSTART’
只有‘ICANON’被取消,才用到以下變量:
    ‘VMIN’,‘VTIME’
不同系統實(shí)現會(huì )定義增加的‘c_cc’變量。謹慎的做法是在設定你希望使用的值
以前,使用‘_POSIX_VDISABLE’初始化這些變量(常量‘NCCS’提供這個(gè)數
組的大小)
‘VMIN’和‘VTIME’(根據不同的實(shí)現方法,它們有可能和‘VEOF’和‘VEOL’
分享相同兩個(gè)變量)具有以下含義。‘VTIME’的值(如果不為0)總是被解釋為以十
分之一秒為單位的計時(shí)器)(譯者注:VTIME變量是一個(gè)字節長(cháng),所以1表示0.1秒,
最大為255,表示25.5秒)
`c_cc[VMIN] > 0, c_cc[VTIME] > 0‘
    只要輸入已經(jīng)有VMIN字節長(cháng),或者輸入至少有一個(gè)字符而在讀取最后一個(gè)字

    符之前VTIME已經(jīng)過(guò)期,或者被信號中斷,‘read()’將返回。
`c_cc[VMIN] > 0, c_cc[VTIME] == 0‘
    只要輸入已經(jīng)有VMIN字節長(cháng),或者被信號中斷,‘read()’將返回。否則,將

    無(wú)限等待下去。
`c_cc[VMIN] == 0, c_cc[VTIME] > 0‘
    只要有輸入‘read()’就返回;如果VTIME過(guò)期卻沒(méi)有數據,它會(huì )返回沒(méi)有讀
    到字符。(這和調制解調器掛斷時(shí)的文件結束標志有一點(diǎn)沖突;使用1作為VMIN,

    調用‘alarm()’或‘select()’函數并給定超時(shí)參數可以避免這個(gè)問(wèn)題。)
`c_cc[VMIN] == 0, c_cc[VTIME] == 0‘
     ‘read()’總是立刻返回;如果沒(méi)有數據則返回沒(méi)有讀到字符。(與上面的問(wèn)題

       相同)
4. 系統信息
***********
4.1 怎樣知道我的系統有多少存儲器容量
=====================================
這是另一個(gè)‘經(jīng)常未回答的問(wèn)題’。在多數情況下,你不該試圖去找到答案.
如果你必需得到答案,問(wèn)題的答案通常是有的,但非常依賴(lài)于不同的操作系統。
例如,在Solaris中,可以用 `sysconf(_SC_PHYS_PAGES)‘ 和 `sysconf(_SC_PAGESIZE)‘;
在FreeBSD中,可以用`sysctl()‘; 在Linux中可以通過(guò)讀取并處理`/proc/meminfo‘得到
(使用該文件時(shí)需小心你的程序,它要接受歷史上任何不同合法格式). 其它的操作
系統有各自的方式,我也沒(méi)有意識到更多可移植的方法。
在HP-UX(9版和10版)中,可以使用如下的代碼:
    struct pst_static pst;
    if (pstat_getstatic(&pst, sizeof(pst), (size_t) 1, 0) != -1)
    {
        printf(" Page Size: %lu\n", pst.page_size);
        printf("Phys Pages: %lu\n", pst.physical_memory);
    }
4.2 我怎樣檢查一個(gè)用戶(hù)的口令
=============================
4.2.1 我怎樣得到一個(gè)用戶(hù)的口令
-------------------------------
在多數的UNIX系統中, 用戶(hù)口令通常存放在`/etc/passwd‘文件中.  該文件一般是
這種格式:
用戶(hù)名:口令:用戶(hù)號:用戶(hù)組號:注釋:用戶(hù)目錄:登錄shell
(username:password:uid:gid:gecos field:home directory:login shell)
但是這些標準隨著(zhù)時(shí)間而不斷改變, 現在的用戶(hù)信息可能存放在其它機器上, 或
者說(shuō)并不存放在 `/etc/passwd‘ 文件中。 當今系統實(shí)現也使用 `shadow‘ 文件保存用
戶(hù)口令以及一些敏感信息. 該文件只允許有特定權限的用戶(hù)讀取.
為安全考慮,用戶(hù)口令一般是加密的,而不是用明文表示的。
POSIX 定義了一組訪(fǎng)問(wèn)用戶(hù)信息的函數. 取得一個(gè)用戶(hù)信息的最快方式是使用`getpwnam()‘
和`getpwuid()‘ 函數. 這兩個(gè)函數都返回一個(gè)結構指針, 該結構包含了用戶(hù)的所有信
息. `getpwnam()‘ 接收用戶(hù)名字符串(username), `getpwuid()‘ 接收用戶(hù)號(uid),
(`uid_t‘類(lèi)型在POSIX中有定義). 若調用失敗則返回NULL.
但是, 正如前面所講, 當今的操作系統都有一個(gè)shadow文件存放敏感信息,即用戶(hù)口令。
有些系統當調用者用戶(hù)號是超級用戶(hù)時(shí)返回用戶(hù)口令, 其它用戶(hù)要求你使用其它方式存取
shadow文件. 這時(shí)你可以使用`getspnam()‘, 通過(guò)輸入用戶(hù)名得到一個(gè)有關(guān)用戶(hù)信息的結
構. 再者, 為了能夠成功的完成這些, 你需要有一定的權限. (在一些系統中, 如HP-UX和
SCO, 你可以用`getprpwnam()‘代替。)
4.2.2 我怎樣通過(guò)用戶(hù)號得到陰影口令文件中的口令
-----------------------------------------------
     我的系統使用一組getsp*函數獲得重要用戶(hù)信息的. 然而, 我沒(méi)有`getspuid()‘,
     只有`getspnam()‘. 我怎樣做才能通過(guò)用戶(hù)號獲得用戶(hù)信息呢?
變通方法是相對非常容易的。下面的函數可以直接放入你個(gè)人的應用函數庫:
    #include <stdlib.h>
    #include <stdio.h>
    #include <pwd.h>
    #include <shadow.h>
    struct spwd *getspuid(uid_t pw_uid)
    {
      struct spwd *shadow;
      struct passwd *ppasswd;
      if( ((ppasswd = getpwuid(pw_uid)) == NULL)
          || ((shadow = getspnam(ppasswd->pw_name)) == NULL))
        return NULL;
      return shadow;
    }
現在的問(wèn)題是, 有些系統在陰影文件中并不保存用戶(hù)號(uid)以及其它的信息。
4.2.3 我怎樣核對一個(gè)用戶(hù)的口令
-------------------------------
一個(gè)基本的問(wèn)題是, 存在各種各樣的認證系統, 所以口令也就并不總是象它們看上去
那樣。 在傳統的系統中, 使用UNIX風(fēng)格的加密算法,加密算法是不同的,有些系統使
用DES(譯者注:DES:Data Encryption Standard,為NIST[National Institute of
Standard and Technology]確認的標準加密算法,最新消息表明,NIST將采用一種新
的加密標準Rijndael逐步取代DES)算法,其它的系統, 如FreeBSD國際版使用MD5(譯者
注:MD5是當今最為廣泛使用的單項散列算法,由Ron Rivest發(fā)明,詳細資料參見(jiàn)RFC 1321
http://www.faqs.org/rfcs/rfc1321.html)算法。
最常用的方法是使用一種單項加密算法(譯者注:即單項散列[Hash]算法)。輸入的
明文口令被加密,然后與文件中存放的加密口令比較。怎樣加密的詳細信息可以
查看`crypt()‘的手冊頁(yè), 這里有一個(gè)通常做法的版本:
    /*  輸入明文口令和加密口令, 檢查是否匹配,
     *  成功返回 1, 其它情況返回 0。
    */
    int check_pass(const char *plainpw, const char *cryptpw)
    {
        return strcmp(crypt(plainpw,cryptpw), cryptpw) == 0;
    }
這個(gè)函數之所以能工作是因為加密函數使用的添加(salt)字串存放在加密口令字串的前部。
*警告:* 在一些系統中, 口令加密是使用一種‘crypt()’的變體,即‘bigcrypt()’函數。

5. 編程雜技
***********
5.1 我怎樣使用通配字符比較字符串?
==================================
對于它的回答依賴(lài)于你所謂‘通配字符’一詞的確切含義。
有兩種很不相同的概念被認定為‘通配字符’。它們是:
*文件名通配模式*(filename patterns)
    這是shell用來(lái)進(jìn)行文件名匹配替換的(expansion)(或稱(chēng)為‘globbing’)
*正則表達式*
    這是供編輯器用的,比如‘grep’,等等。它是用來(lái)匹配正文,而它們正常

    情況下不應用于文件名。
5.1.1 我怎樣使用文件名通配模式比較字符串?
------------------------------------------
除非你不走運,你的系統應該有函數‘fnmatch()’供你進(jìn)行文件名匹配。它一
般只允許Bourne Shell風(fēng)格的模式。它識別‘*’,‘[...]’和‘?’,但可能不
支持在Korn和Bourne-Again shell程序下才有的更神秘(arcane)的模式。
如果你沒(méi)有這個(gè)函數,那么比閉門(mén)造車(chē)更好的方法是你可以從BSD或GNU原程
序那里去抄(snarfing)一個(gè)過(guò)來(lái)。
而且,在普通的匹配實(shí)際文件名情況下,查閱‘glob()’函數,它將搜索到匹配
一個(gè)給定模式的所有存在文件。
5.1.2 我怎樣使用正則表達式比較字符串?
--------------------------------------
有很多稍有句法不同的正則表達式;大部分系統起碼使用兩種:一種是‘ed’
程序可以識別的,有時(shí)候被記作‘基本正則表達式’,另一種是‘egrep’程序
可以識別的,記作‘擴充正則表達式’。Perl(譯者注:Perl: Practical Extract and
Report Language,實(shí)用析取與報表語(yǔ)言)語(yǔ)言擁有它自己稍有不同的風(fēng)格,
Emacs
也是。
為了支持這么多格式,相應的有很多實(shí)現。系統一般有正則表達式匹配函數(通
常為‘regcomp()’函數和‘regexec()’函數)提供,但是要小心使用;有些系統
有超過(guò)一種實(shí)現可用,附之以不同的接口。另外,還有很多可用的軟件庫的實(shí)
現(順便說(shuō)一下,一般都是將一個(gè)正則表達式編譯成內部形式然后再使用,因為
總是假設你有很多字符串要比較同一正則表達式。)
一個(gè)可用的軟件庫是‘rx’軟件庫,從GNU的鏡像站點(diǎn)可以得到。它看來(lái)是正在
開(kāi)發(fā)中,基于你不同的觀(guān)點(diǎn)這是一件又好又不好的事情 :-)
5.2 什么是在程序中發(fā)送電子郵件的最好方法?
==========================================
有好幾種從Unix程序發(fā)電子郵件的方法。根據不同情況最好的選擇有所不同,
所以我將提供兩個(gè)方法。還有第三種方法,這里沒(méi)有說(shuō)道,是連接本地主機的SMTP
(譯者注:SMTP:Simple Mail Transfer Protocol簡(jiǎn)單郵件傳輸協(xié)議)端口并直接使

用SMTP協(xié)議,參見(jiàn)RFC 821(譯者注:RFC:Request For Comments)。
5.2.1 簡(jiǎn)單方法:/bin/mail
-------------------------
對于簡(jiǎn)單應用,執行‘mail’程序已經(jīng)是足夠了(通常是‘/bin/mail’,但一些系統
上有可能是‘/usr/bin/mail’)。
*警告:*UCB Mail程序的一些版本甚至在非交互模式下也會(huì )執行在消息體(message
body)中以‘~!’或‘~|’打頭的行所表示的命令。這可能有安全上的風(fēng)險。
象這樣執行:‘mail -s ‘標題‘ 收件人地址 ...’,程序將把標準輸入作為消息體,
并提供缺省得消息頭(其中包括已設定的標題),然后傳遞整個(gè)消息給‘sendmail’
進(jìn)行投遞。
這個(gè)范例程序在本地主機上發(fā)送一封測試消息給‘root’:
    #include <stdio.h>
    #define MAILPROG "/bin/mail"
    int main()
    {
        FILE *mail = popen(MAILPROG " -s ‘Test Message‘ root", "w");
        if (!mail)
        {
            perror("popen");
            exit(1);
        }
        fprintf(mail, "This is a test.\n");
        if (pclose(mail))
        {
            fprintf(stderr, "mail failed!\n");
            exit(1);
        }
    }
如果要發(fā)送的正文已經(jīng)保存在一個(gè)文件中,那么可以這樣做:
        system(MAILPROG " -s ‘file contents‘ root </tmp/filename");
這個(gè)方法可以擴展到更復雜的情況,但是得當心很多潛在的危險(pitfalls):
  * 如果使用system()或popen(),你必須非常當心將參數括起來(lái)從而保護它們不被
    錯誤的進(jìn)行文件名匹配替換或單詞分割。
  * 基于用戶(hù)設置數據來(lái)構造命令行是緩沖區越界錯誤和其它安全漏洞的普遍原
    因。
  * 這種方法不允許設定CC:(譯者注:CC:Carbon Copy 抄送)或 BCC:(譯者注:
    BCC:Blind Carbon Copy:盲送,指投遞地址不在消息中出現的抄送)的收件人。
    (一些/bin/mail程序的版本允許,其它則不允許)
5.2.2 直接啟動(dòng)郵件傳輸代理(譯者注:MTA: mail transfer agent):/usr/bin/sendmail
-------------------------------------------------------------------------------
mail’程序是“郵件用戶(hù)代理”(Mail User Agent)的一個(gè)例子,它旨在供用戶(hù)
執行以收發(fā)電子郵件,但它并不負責實(shí)際的傳輸。
一個(gè)用來(lái)傳輸郵件的程序被
稱(chēng)為“郵件傳輸代理”(MTA),而在Unix系統普遍能找到的郵件傳輸代理被稱(chēng)為
‘sendmail’。也有其它在使用的郵件傳輸代理,比如‘MMDF’,但這些程序
通常包括一個(gè)程序來(lái)模擬‘sendmail’的普通做法。
歷史上,‘sendmail’通常在‘/usr/lib’里找到,但現在的趨勢是將應用庫程序從
‘/usr/lib’挪出,并挪入比如‘/usr/sbin’或‘/usr/libexec’等目錄。
結果是,一般
總是以絕對路徑啟動(dòng)‘sendmail’程序,而路徑是由系統決定的。
為了了解‘sendmail’程序怎樣工作,通常需要了解一下“信封”(envelope)的概
念。這非常類(lèi)似書(shū)面信件;信封上定義這個(gè)消息投遞給誰(shuí),并注明由誰(shuí)發(fā)出(
為了報告錯誤的目的)。在信封中包含的是“消息頭”和“消息體”,之間由一個(gè)

空行隔開(kāi)。消息頭的格式主要在RFC 822中提供;并且參見(jiàn)MIME 的RFC文檔。(
譯者注:MIME的文檔包括:RFC1521,RFC1652)
有兩種主要的方法使用‘sendmail’程序以生成一個(gè)消息:要么信封的收件人能
被顯式的提供,要么‘sendmail’程序可被指示從消息頭中推理出它們。兩種方
法都有優(yōu)缺點(diǎn)。
5.2.2.1 顯式提供信封內容
.........................
消息的信封內容可在命令行上簡(jiǎn)單的設定。它的缺點(diǎn)在于郵件地址可能包含的
字符會(huì )造成‘system()’和‘popen() ’程序可觀(guān)的以外出錯(grief),比如單引號,
被括起的字符串等等。傳遞這些指令給shell程序并成功解釋可以預見(jiàn)潛在的危
險。(可以將命令中任何一個(gè)單引號替換成單引號、反斜杠、單引號、單引號的
順序組合,然后再將整個(gè)地址括上單引號??膳?,呃?)
以上的一些不愉快可以通過(guò)避開(kāi)使用‘system()’或‘popen()’函數并求助于‘
fork()’和‘exec()’函數而避免。
這有時(shí)不管怎樣也是需要的;比如,用戶(hù)
自定義的對于SIGCHLD信號的處理函數通常會(huì )打斷‘pclose()’函數從而影響到
或大或小的范圍。
這里是一個(gè)范例程序:
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <fcntl.h>
    #include <sysexits.h>
    /* #include <paths.h> 如果你有的話(huà) */
    #ifndef _PATH_SENDMAIL
    #define _PATH_SENDMAIL "/usr/lib/sendmail"
    #endif
    /* -oi 意味著(zhù) "不要視‘ .’為消息的終止符"
     * 刪除這個(gè)選項 ,"--" 如果使用sendmail 8版以前的版本 (并希望沒(méi)有人
     * 曾經(jīng)使用過(guò)一個(gè)以減號開(kāi)頭的收件人地址)
     * 你也許希望加 -oem (report errors by mail,以郵件方式報告錯誤)
     */
    #define SENDMAIL_OPTS "-oi","--"
    /* 下面是一個(gè)返回數組中的成員數的宏 */
    #define countof(a) ((sizeof(a))/sizeof((a)[0]))
    /* 發(fā)送由FD所包含以讀操作打開(kāi)的文件之內容至設定的收件人;前提是這
     * 個(gè)文件中包含RFC822定義的消息頭和消息體,收件人列表由NULL指針
     * 標志結束;如果發(fā)現錯誤則返回-1,否則返回sendmail的返回值(它使用
     * <sysexits.h>中提供的有意義的返回代碼)
     */
    int send_message(int fd, const char **recipients)
    {
        static const char *argv_init[] = { _PATH_SENDMAIL, SENDMAIL_OPTS };
        const char **argvec = NULL;
        int num_recip = 0;
        pid_t pid;
        int rc;
        int status;
        /* 計算收件人數目 */
        while (recipients[num_recip])
            ++num_recip;
        if (!num_recip)
            return 0;    /* 視無(wú)收件人為成功 */
        /* 分配空間給參數矢量 */
        argvec = malloc((sizeof char*) * (num_recip+countof(argv_init)+1));
        if (!argvec)
            return -1;
        /* 初始化參數矢量 */
        memcpy(argvec, argv_init, sizeof(argv_init));
        memcpy(argvec+countof(argv_init),
               recipients, num_recip*sizeof(char*));
        argvec[num_recip + countof(argv_init)] = NULL;
        /* 需要在此增加一些信號阻塞 */
        /* 產(chǎn)生子進(jìn)程 */
        switch (pid = fork())
        {
        case 0:   /* 子進(jìn)程 */
            /* 建立管道 */
            if (fd != STDIN_FILENO)
                dup2(fd, STDIN_FILENO);
            /* 其它地方已定義 -- 關(guān)閉所有>=參數的文件描述符對應的參數 */
            closeall(3);
            /* 發(fā)送: */
            execv(_PATH_SENDMAIL, argvec);
            _exit(EX_OSFILE);
        default:  /* 父進(jìn)程 */
            free(argvec);
            rc = waitpid(pid, &status, 0);
            if (rc < 0)
                return -1;
            if (WIFEXITED(status))
                return WEXITSTATUS(status);
            return -1;
        case -1:  /* 錯誤 */
            free(argvec);
            return -1;
        }
    }
5.2.2.2 允許sendmail程序推理出收件人
.....................................
sendmail’的‘-t’選項指令‘sendmail’程序處理消息的頭信息,并使用所有
包含收件人(即:‘To:’,‘Cc:’和‘Bcc:’)的頭信息建立收件人列表。
它的優(yōu)
點(diǎn)在于簡(jiǎn)化了‘sendmail’的命令行,但也使得設置在消息頭信息中所列以外的
收件人成為不可能。(這通常不是一個(gè)問(wèn)題)
作為一個(gè)范例,以下這個(gè)程序將標準輸入作為一個(gè)文件以MIME附件方式發(fā)送給
設定的收件人。為簡(jiǎn)潔起見(jiàn)略去了一些錯誤檢查。這個(gè)程序需要調用‘metamail’
分發(fā)程序包的‘mimecode’程序。
    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>
    /* #include <paths.h> 如果你有的話(huà) */
    #ifndef _PATH_SENDMAIL
    #define _PATH_SENDMAIL "/usr/lib/sendmail"
    #endif
    #define SENDMAIL_OPTS "-oi"
    #define countof(a) ((sizeof(a))/sizeof((a)[0]))
    char tfilename[L_tmpnam];
    char command[128+L_tmpnam];
    void cleanup(void)
    {
        unlink(tfilename);
    }
    int main(int argc, char **argv)
    {
        FILE *msg;
        int i;
        if (argc < 2)
        {
            fprintf(stderr, "usage: %s recipients...\n", argv[0]);
            exit(2);
        }
        if (tmpnam(tfilename) == NULL
            || (msg = fopen(tfilename,"w")) == NULL)
            exit(2);
        atexit(cleanup);
        fclose(msg);
        msg = fopen(tfilename,"a");
        if (!msg)
            exit(2);
        /* 建立收件人列表 */
        fprintf(msg, "To: %s", argv[1]);
        for (i = 2; i < argc; i++)
            fprintf(msg, ",\n\t%s", argv[i]);
        fputc(‘\n‘,msg);
        /* 標題 */
        fprintf(msg, "Subject: file sent by mail\n");
        /* sendmail程序會(huì )自動(dòng)添加 From:, Date:, Message-ID: 等消息頭信息 */
        /* MIME的處理 */
        fprintf(msg, "MIME-Version: 1.0\n");
        fprintf(msg, "Content-Type: application/octet-stream\n");
        fprintf(msg, "Content-Transfer-Encoding: base64\n");
        /* 消息頭結束,加一個(gè)空行 */
        fputc(‘\n‘,msg);
        fclose(msg);
        /* 執行編碼程序 */
        sprintf(command, "mimencode -b >>%s", tfilename);
        if (system(command))
            exit(1);
        /* 執行信使程序 */
        sprintf(command, "%s %s -t <%s",
                _PATH_SENDMAIL, SENDMAIL_OPTS, tfilename);
        if (system(command))
            exit(1);
        return 0;
    }

6. 工具的使用
*************
6.1 我怎樣調試fork函數產(chǎn)生的子進(jìn)程?
====================================
根據可用的工具有兩種不同的方法:
你的調試器(debugger)可能有允許你選擇是否跟蹤調用‘fork()’以后的父或子進(jìn)程
的選項,對于某些目的來(lái)說(shuō)那已經(jīng)足夠了。
替換方法是,你的調試器可能有一個(gè)選項允許你將它依附(attach)到一個(gè)正在執行
的程序。這樣你可以依附調試器到一個(gè)已經(jīng)開(kāi)始執行的子進(jìn)程。如果你不需要從
子進(jìn)程一開(kāi)始就開(kāi)始測試,這通常已經(jīng)足夠。否則,你會(huì )希望在子進(jìn)程的‘fork()’
調用后插入一個(gè)‘sleep()’調用,或者插入如下的循環(huán):
    {
        volatile int f = 1;
        while(f);
    }
這樣子進(jìn)程將一直在此循環(huán)不往下執行直到你用調試器設定‘f’為0。
并且記住,使用調試器并非是找到你程序中錯誤的唯一方法;在很多Unix系統
上有一些工具程序可用來(lái)跟蹤系統調用和信號,而且豐富的日志經(jīng)常也是有
用的。
6.2 怎樣通過(guò)其他庫文件建立新的庫文件?
======================================
前提是我們所說(shuō)的是歸檔(archive)(靜態(tài))庫,最簡(jiǎn)單的方法是將所有選擇的庫
文件使用‘ar x’命令在一個(gè)空目錄里拆分成它們原始的單個(gè)目標文件(object),
然后再合并成一個(gè)。當然,文件名有可能會(huì )重復,但是如果這個(gè)庫文件很大,
你也許一開(kāi)始就不想將它們合并在一起。
6.3 怎樣創(chuàng )建動(dòng)態(tài)連接庫(shared library)/dlls?
=============================================
創(chuàng )建動(dòng)態(tài)連接庫(shared libraries)的方法根據不同的系統有所不同。這個(gè)過(guò)程主要
分兩步;第一步要求包括在動(dòng)態(tài)連接庫中的目標必須首先是編譯好的,通常需
要某個(gè)編譯選項指示這串代碼是位置無(wú)關(guān)的(position-indepenent);第二步,是將
這些目標連接在一起形成一個(gè)庫文件。
這里是一個(gè)演示以上道理的小程序:
    /* shrobj.c 文件 */
    const char *myfunc()
    {
        return "Hello World";
    }
    /* shrobj.c 結束 */
    /* hello.c 文件 */
    #include <stdio.h>
    extern const char *myfunc();
    main()
    {
        printf("%s\n", myfunc());
        return 0;
    }
    /* hello.c 結束 */
    $ gcc -fpic -c shrobj.c
    $ gcc -shared -o libshared.so shrobj.o
    $ gcc hello.c libshared.so
    $ ./a.out
    Hello World
到目前為止,如果你希望庫文件和它的創(chuàng )建過(guò)程是都可以移植的話(huà),那么最好
的辦法是使用GNU Libtool程序包。它是個(gè)小型的工具程序套件,這些工具程序
知道建立動(dòng)態(tài)連接庫的平臺無(wú)關(guān)性;你可以只發(fā)布你的程序必要的部分,從而
當一個(gè)安裝者配置你的軟件包時(shí),他能決定生成什么庫。Libtool程序包在不支持
動(dòng)態(tài)連接庫的系統上也能工作得很好。它而且知道與GNU Autoconf程序和GNU
Automake程序掛鉤(如果你使用這些工具來(lái)管理你程序的編譯創(chuàng )建過(guò)程)。
如果你不想使用Libtool程序包,那么對于gcc以外的編譯器,你需要按照下面所
列修改編譯器參數:
AIX 3.2 使用 xlc (未證實(shí))
    取消‘-fpic’選項,以‘-bM:SRE -bE:libshared.exp’取代‘-shared’。你并且
    需要創(chuàng )建一個(gè)名為‘libshared.exp’的文件保存一個(gè)所有輸出符號(symbols to export)
    的列表,比如以上的范例程序,就需要輸出‘myfunc’符號。另外,在連接庫

    時(shí)使用‘-e _nostart’參數(在較新的AIX版本上,我相信應該將其變成‘-bnoentry’)。
SCO OpenServer 5 使用 SCO 開(kāi)發(fā)系統(Development System) (未證實(shí))
    如果你使用ELF(譯者注:ELF:執行與連接格式Executable and Linking Forrmat,
    一種Unix可執行目標文件的格式)格式,那么共享庫只能在OS5上可用,而它
    需要‘-belf’選項。并以‘-Kpic’取代‘-fpic’,在連接時(shí)使用‘cc -belf -G’。
Solaris 使用 SparcWorks 編譯器
    以‘-pic’取代‘-fpic’,并以‘ld -G’取代‘gcc -shared’。
(鼓勵大家提供更多的材料豐富上述列表)
其它要當心的問(wèn)題:
  * AIX和(我相信)Digital Unix不需要-fpic選項,因為所有代碼都是位置無(wú)關(guān)的。
  * AIX一般需要你創(chuàng )建一個(gè)‘輸出文件’,即一個(gè)保存所有動(dòng)態(tài)連接庫中輸出
    符號的列表。一些連接器(linker)版本(可能只有SLHS連接器,是svld?)有一個(gè)
    選項可以輸出所有符號。
  * 如果你對于連接器想使用普遍的‘-l’參數來(lái)引用你的動(dòng)態(tài)連接庫,你必須
    理解你的系統在實(shí)際運行時(shí)是怎樣尋找動(dòng)態(tài)連接庫的。最普通的方法是使用
    ‘LD_LIBRARY_PATH’環(huán)境變量,但是通常在連接時(shí)有一種其它選項可以
    設定。
  * 大多數實(shí)現方法是在程序內部記錄所希望的動(dòng)態(tài)連接庫在運行時(shí)的位置。這
    樣把一個(gè)動(dòng)態(tài)連接庫從一個(gè)目錄移到另一個(gè)目錄將導致程序無(wú)法工作。許多
    系統對于連接器有一個(gè)選項用以設定希望運行時(shí)動(dòng)態(tài)連接庫的位置(比如在
    Solaris系統上是‘-R’連接器選項,或者是‘LD_RUN_PATH’環(huán)境變量)。
  * ELF和a.out的實(shí)現方法可能有一個(gè)連接器選項‘-Bsymbolic’,它導致在庫本
    身內部的引用被解釋好。否則,在這些系統上,所有符號的解釋將推遲到最
    后連接時(shí)再進(jìn)行,這樣在main程序中的單一函數將重載庫中的對應函數。
6.4 我能更改一個(gè)動(dòng)態(tài)連接庫里的目標嗎?
======================================
一般不行。
在大多數系統上(除了AIX),當你連接目標并形成一個(gè)動(dòng)態(tài)連接庫時(shí),它就象連
接一個(gè)可執行程序;目標并不保留它們單一的特征。結果是,一般不能從一個(gè)動(dòng)
態(tài)連接庫里析取出或更換一個(gè)單一的目標。
6.5 我能在一個(gè)運行著(zhù)的程序中生成堆棧映象嗎?
============================================
一些系統提供庫函數可以提取(unwinding)出堆棧,從而(比如)你可以在一個(gè)錯誤
處理函數中生成一個(gè)堆棧映象,但是只有一小部分系統有這些函數。
一個(gè)可能的變通方法(workaround)是讓你的程序執行一個(gè)調試器調試*它自己* -
詳細方法仍然根據不同系統稍有不同,但一般的概念是這樣:
    void dump_stack(void)
    {
        char s[160];
        sprintf(s, "/bin/echo ‘where\ndetach‘ | dbx -a %d", getpid());
        system(s);
        return;
    }
你需要根據你不同的系統對dbx的參數和命令進(jìn)行加工,或者甚至換另一個(gè)調試
器,比如‘gdb’,但這仍然是我見(jiàn)過(guò)的對于這種問(wèn)題最普遍的解決方法。為此,
榮譽(yù)授予Ralph Corderoy。
下面列表包含在一些系統上需要用到的命令行:
大多數使用dbx的系統
   `"/bin/echo ‘where\ndetach‘ | dbx /path/to/program %d"‘
AIX
    `"/bin/echo ‘where\ndetach‘ | dbx -a %d"‘
IRIX
    `"/bin/echo ‘where\ndetach‘ | dbx -p %d"‘
 
范例程序
********
捕獲 SIGCHLD 信號
=================
    #include <sys/types.h>  /* 在任何其它 sys 下的頭文件之前引用這個(gè)頭文件 */
    #include <sys/wait.h>   /* waitpid()和一些不同的宏所需的頭文件 */
    #include <signal.h>     /* 信號函數的頭文件 */
    #include <stdio.h>      /* fprintf函數的頭文件 */
    #include <unistd.h>     /* fork函數的頭文件 */  
    void sig_chld(int);     /* 我們的 SIGCHLD 信號處理函數的原形(prototype) */
   
    int main()
    {
        struct sigaction act;
        pid_t pid;
   
        /* 設定sig_chld函數作為我們SIGCHLD信號的處理函數 */
        act.sa_handler = sig_chld;
   
        /* 在這個(gè)范例程序里,我們不想阻塞其它信號 */
        sigemptyset(&act.sa_mask);
   
        /*
         * 我們只關(guān)心被終止的子進(jìn)程,而不是被中斷
         * 的子進(jìn)程 (比如用戶(hù)在終端上按Control-Z)
         */
        act.sa_flags = SA_NOCLDSTOP;
   
        /*
         * 使這些設定的值生效. 如果我們是寫(xiě)一個(gè)真實(shí)的應用程序,
         * 我們也許應該保存這些原有值,而不是傳遞一個(gè)NULL。
         */
        if (sigaction(SIGCHLD, &act, NULL) < 0)
        {
            fprintf(stderr, "sigaction failed\n");
            return 1;
        }
   
        /* fork */
        switch (pid = fork())
        {
        case -1:
            fprintf(stderr, "fork failed\n");
            return 1;
   
        case 0:                         /* 是子進(jìn)程,直接結束 */
            _exit(7);                   /* 退出狀態(tài) = 7 */
   
        default:                        /* 父進(jìn)程 */
            sleep(10);                  /* 給子進(jìn)程完成的時(shí)間 */
        }
   
        return 0;
    }
   
    /*
     * 信號處理函數 -- 只有當接收到一個(gè)SIGCHLD信號才被調用,
     * 即有一個(gè)子進(jìn)程終止
     */
    void sig_chld(int signo)
    {
        int status, child_val;
   
        /* 非阻塞地等待任何子進(jìn)程結束 */
        if (waitpid(-1, &status, WNOHANG) < 0)
        {
            /*
             * 不建議在信號處理函數中調用標準輸入/輸出函數,
             * 但在一個(gè)類(lèi)似這個(gè)的玩具程序里或許沒(méi)問(wèn)題
             */
            fprintf(stderr, "waitpid failed\n");
            return;
        }
   
        /*
         * 我們現在有保存在‘status’變量中的子進(jìn)程退出信息并可以使用
         * wait.h中定義的宏對其進(jìn)行操作
         */
        if (WIFEXITED(status))                /* 子進(jìn)程是正常退出嗎? */
        {
            child_val = WEXITSTATUS(status); /* 獲取子進(jìn)程的退出狀態(tài) */
            printf("child‘s exited normally with status %d\n", child_val);
        }
    }
讀取進(jìn)程表 - SUNOS 4 版
=======================
    #define _KMEMUSER
    #include <sys/proc.h>
    #include <kvm.h>
    #include <fcntl.h>
   
    char regexpstr[256];
    #define INIT            register char *sp=regexpstr;
    #define GETC()          (*sp++)
    #define PEEKC()         (*sp)
    #define UNGETC(c)       (--sp)
    #define RETURN(pointer) return(pointer);
    #define ERROR(val)
    #include <regexp.h>
 
    pid_t
    getpidbyname(char *name,pid_t skipit)
    {
        kvm_t *kd;
        char **arg;
        int error;
        char *p_name=NULL;
        char expbuf[256];
        char **freeme;
        int curpid;
        struct user * cur_user;
        struct user myuser;
        struct proc * cur_proc;
   
   
        if((kd=kvm_open(NULL,NULL,NULL,O_RDONLY,NULL))==NULL){
            return(-1);
        }
        sprintf(regexpstr,"^.*/%s$",name);
        compile(NULL,expbuf,expbuf+256,‘\0‘);
   
        while(cur_proc=kvm_nextproc(kd)){
            curpid = cur_proc->p_pid;
            if((cur_user=kvm_getu(kd,cur_proc))!=NULL){
                error=kvm_getcmd(kd,cur_proc,cur_user,&arg,NULL);
                if(error==-1){
                    if(cur_user->u_comm[0]!=‘\0‘){
                        p_name=cur_user->u_comm;
                    }
                }
                else{
                    p_name=arg[0];
                }
            }
            if(p_name){
                if(!strcmp(p_name,name)){
                    if(error!=-1){
                        free(arg);
                    }
                    if(skipit!=-1 && ourretval==skipit){
                        ourretval=-1;
                    }
                    else{
                        close(fd);
                        break;
                    }
                    break;
                }
                else{
                    if(step(p_name,expbuf)){
                        if(error!=-1){
                            free(arg);
                        }
                        break;
                    }
                }
            }
            if(error!=-1){
                free(arg);
            }
            p_name=NULL;
        }
        kvm_close(kd);
        if(p_name!=NULL){
            return(curpid);
        }
        return (-1);
    }
讀取進(jìn)程表 - SYSV 版
====================
    pid_t
    getpidbyname(char *name,pid_t skipit)
    {
        DIR  *dp;
        struct dirent *dirp;
        prpsinfo_t retval;
        int fd;
        pid_t ourretval=-1;
   
        if((dp=opendir("/proc"))==NULL){
            return -1;
        }
        chdir("/proc");
        while((dirp=readdir(dp))!=NULL){
            if(dirp->d_name[0]!=‘.‘){
                if((fd=open(dirp->d_name,O_RDONLY))!=-1){
                    if(ioctl(fd,PIOCPSINFO,&retval)!=-1){
                        if(!strcmp(retval.pr_fname,name)){
                            ourretval=(pid_t)atoi(dirp->d_name);
                            if(skipit!=-1 && ourretval==skipit){
                                ourretval=-1;
                            }
                            else{
                                close(fd);
                                break;
                            }
                        }
                    }
                    close(fd);
                }
            }
        }
        closedir(dp);
        return ourretval;
    }
讀取進(jìn)程表 - AIX 4.2 版
=======================
    #include <stdio.h>
    #include <procinfo.h>
   
    int getprocs(struct procsinfo *, int, struct fdsinfo *,
                 int, pid_t *, int);
   
    pid_t getpidbyname(char *name, pid_t *nextPid)
    {
      struct procsinfo  pi;
      pid_t             retval = (pid_t) -1;
      pid_t             pid;
   
      pid = *nextPid;
   
      while(1)
      {
        if(getprocs(&pi, sizeof pi, 0, 0, &pid, 1) != 1)
          break;
   
        if(!strcmp(name, pi.pi_comm))
        {
          retval = pi.pi_pid;
          *nextPid = pid;
          break;
        }
      }
   
      return retval;
    }
   
    int main(int argc, char *argv[])
    {
      int   curArg;
      pid_t pid;
      pid_t nextPid;
   
      if(argc == 1)
      {
        printf("syntax: %s <program> [program ...]\n",argv[0]);
        exit(1);
      }
   
      for(curArg = 1; curArg < argc; curArg++)
      {
        printf("Process IDs for %s\n", argv[curArg]);
   
        for(nextPid = 0, pid = 0; pid != -1; )
          if((pid = getpidbyname(argv[curArg], &nextPid)) != -1)
            printf("\t%d\n", pid);
      }
    }
使用popen函數和ps命令讀取進(jìn)程表
===============================
    #include <stdio.h>      /* FILE, sprintf, fgets, puts */
    #include <stdlib.h>     /* atoi, exit, EXIT_SUCCESS */
    #include <string.h>     /* strtok, strcmp */
    #include <sys/types.h>  /* pid_t */
    #include <sys/wait.h>   /* WIFEXITED, WEXITSTATUS */
   
    char *procname(pid_t pid)
    {
       static char line[133], command[80], *linep, *token, *cmd;
       FILE *fp;
       int status;
   
       if (0 == pid) return (char *)0;
   
       sprintf(command, "ps -p %d 2>/dev/null", pid);
       fp = popen(command, "r");
       if ((FILE *)0 == fp) return (char *)0;
   
       /* 讀取標題行 */
       if ((char *)0 == fgets(line, sizeof line, fp))
       {
          pclose(fp);
          return (char *)0;
       }
   
       /* 從標題欄分析出命令名所在列。
        * (BSD風(fēng)格的系統將指示命令的"COMMAND"字符串放在第5列,SysV好象將
        * 指示命令的“CMD”或“COMMAND”字符串放在第4列)
        */
       for (linep = line; ; linep = (char *)0)
       {
          if ((char *)0 == (token = strtok(linep, " \t\n")))
          {
             pclose(fp);
             return (char *)0;
          }
          if (0 == strcmp("COMMAND", token) || 0 == strcmp("CMD", token))
          { /*  我們找到COMMAND所在列 */
             cmd = token;
             break;
          }
       }
   
       /* 讀取 ps(1) 輸出行 */
       if ((char *)0 == fgets(line, sizeof line, fp))
       {
          pclose(fp);
          return (char *)0;
       }
   
       /* 抓COMMAND標題下面的詞 ... */
       if ((char *)0 == (token = strtok(cmd, " \t\n")))
       {
          pclose(fp);
          return (char *)0;
       }
   
       status = pclose(fp);
       if (!WIFEXITED(status) || 0 != WEXITSTATUS(status))
         return (char *)0;
   
       return token;
    }
   
    int main(int argc, char *argv[])
    {
       puts(procname(atoi(argv[1])));
       exit(EXIT_SUCCESS);
    }
守護程序工具函數
================
    #include <unistd.h>
    #include <stdlib.h>
    #include <fcntl.h>
    #include <signal.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <errno.h>
   
    /* closeall() -- 關(guān)閉所有>=給定值的文件描述符 */
   
    void closeall(int fd)
    {
        int fdlimit = sysconf(_SC_OPEN_MAX);
   
        while (fd < fdlimit)
          close(fd++);
    }
   
    /* daemon() - 將進(jìn)程從用戶(hù)端脫離并消失進(jìn)入后臺,若失敗返回-1,
     * 但是在那種情況下你只能退出,因為我們可能已經(jīng)生成了子進(jìn)程。
     * 這是基于BSD的版本,所以調用方需負責類(lèi)似umask等等其它的工作。

     */
   
    /* 相信在所有Posix系統上都能工作 */
   
    int daemon(int nochdir, int noclose)
    {
        switch (fork())
        {
            case 0:  break;
            case -1: return -1;
            default: _exit(0);          /* 原進(jìn)程退出 */
        }
   
        if (setsid() < 0)               /* 不應該失敗 */
          return -1;
   
        /* 如果你希望將來(lái)獲得一個(gè)控制tty,則排除(dyke)以下的switch語(yǔ)句 */
        /* -- 正常情況不建議用于守護程序 */
   
        switch (fork())
        {
            case 0:  break;
            case -1: return -1;
            default: _exit(0);
        }
   
        if (!nochdir)
          chdir("/");
   
        if (!noclose)
        {
            closeall(0);
            open("/dev/null",O_RDWR);
            dup(0); dup(0);
        }
   
        return 0;
    }
   
    /* fork2() -- 類(lèi)似fork函數,但子進(jìn)程立刻變成孤兒進(jìn)程
     *            (當它退出時(shí)不產(chǎn)生僵死進(jìn)程)
     * 返回1給父進(jìn)程,不是任何有意義的進(jìn)程號.
     * 父進(jìn)程不能使用wait函數等待子進(jìn)程結束 (它們是無(wú)關(guān)的
).
     */
   
    /* 這個(gè)版本假設你沒(méi)有捕獲和忽略SIGCHLD信號.
*/
    /* 如果你有設定,則不管怎樣應使用fork函數 */
   
    int fork2()
    {
        pid_t pid;
        int rc;
        int status;
   
        if (!(pid = fork()))
        {
            switch (fork())
            {
              case 0:  return 0;
              case -1: _exit(errno);    /* 假設錯誤碼都小于256 */
              default: _exit(0);
            }
        }
   
        if (pid < 0 || waitpid(pid,&status,0) < 0)
          return -1;
   
        if (WIFEXITED(status))
          if (WEXITSTATUS(status) == 0)
            return 1;
          else
            errno = WEXITSTATUS(status);
        else
          errno = EINTR;  /* 唉,類(lèi)似這個(gè) :-) */
   
        return -1;
    }
一個(gè)使用以上函數的范例程序:
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <syslog.h>
    #include <errno.h>
   
    int daemon(int,int);
    int fork2(void);
    void closeall(int);
   
    #define TCP_PORT 8888
   
    void errexit(const char *str)
    {
        syslog(LOG_INFO, "%s failed: %d (%m)", str, errno);
        exit(1);
    }
   
    void errreport(const char *str)
    {
        syslog(LOG_INFO, "%s failed: %d (%m)", str, errno);
    }
   
    /* 實(shí)際的子進(jìn)程在此. */
   
    void run_child(int sock)
    {
        FILE *in = fdopen(sock,"r");
        FILE *out = fdopen(sock,"w");
        int ch;
   
        setvbuf(in, NULL, _IOFBF, 1024);
        setvbuf(out, NULL, _IOLBF, 1024);
   
        while ((ch = fgetc(in)) != EOF)
          fputc(toupper(ch), out);
   
        fclose(out);
    }
   
    /* 這是守護程序的主要工作 -- 偵聽(tīng)連接并生成子進(jìn)程 */
   
    void process()
    {
        struct sockaddr_in addr;
        int addrlen = sizeof(addr);
        int sock = socket(AF_INET, SOCK_STREAM, 0);
        int flag = 1;
        int rc = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
                            &flag, sizeof(flag));
   
        if (rc < 0)
          errexit("setsockopt");
   
        addr.sin_family = AF_INET;
        addr.sin_port = htons(TCP_PORT);
        addr.sin_addr.s_addr = INADDR_ANY;
   
        rc = bind(sock, (struct sockaddr *) &addr, addrlen);
        if (rc < 0)
          errexit("bind");
   
        rc = listen(sock, 5);
        if (rc < 0)
          errexit("listen");
   
        for (;;)
        {
            rc = accept(sock, (struct sockaddr *) &addr, &addrlen);
   
            if (rc >= 0)
              switch (fork2())
              {
                case 0:  close(sock); run_child(rc); _exit(0);
                case -1: errreport("fork2"); close(rc); break;
                default: close(rc);
              }
        }
    }
   
    int main()
    {
        if (daemon(0,0) < 0)
        {
            perror("daemon");
            exit(2);
        }
   
        openlog("test", LOG_PID, LOG_DAEMON);
   
        process();
   
        return 0;
    }
調制解調器控制范例程序
======================
    /* 發(fā)出一些簡(jiǎn)單調制解調器命令
     * 需要串行設備的設備名 (最好是撥出設備,
     * 或者是非調制解調器控制設備) 作為它唯一的參數
.
     
* 如果你沒(méi)有可共使用的撥出設備, 那么以CFLAGS_TO_SET取代CLOCAL。
     */
   
    #include <stdio.h>
    #include <stdlib.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/time.h>
    #include <sys/ioctl.h>   /* 也許需要;和系統有關(guān) */
    #include <termios.h>
    #include <errno.h>
    #include <string.h>
    #include <ctype.h>
   
    #define CFLAGS_TO_SET (CREAD | HUPCL)
    #define CFLAGS_TO_CLEAR (CSTOPB | PARENB | CLOCAL)
   
    enum flowmode { NoFlow, HardFlow, SoftFlow };
   
    /* 和系統有關(guān) */
    #define CFLAGS_HARDFLOW (CRTSCTS)
   
   
    #define EXAMPLE_BAUD B19200
    #define EXAMPLE_FLOW HardFlow
   
   
    static void die(const char *msg)
    {
        fprintf(stderr, "%s\n", msg);
        exit(1);
    }
   
    static int close_and_complain(int fd, const char *msg, int err)
    {
        fprintf(stderr, "%s: %s\n", msg, strerror(err));
        if (fd >= 0)
            close(fd);
        errno = err;
        return -1;
    }
   
   
    int open_port(const char *name, speed_t baud, enum flowmode flow)
    {
        int flags;
        struct termios attr;
   
        int fd = open(name, O_RDWR | O_NONBLOCK | O_NOCTTY);
   
        if (fd < 0)
            return close_and_complain(-1, "open", errno);
   
        /* 設定一些不明確是否敏感的值 */
   
        if (tcgetattr(fd, &attr) < 0)
            return close_and_complain(fd, "tcgetattr", errno);
   
        /* 無(wú)特殊輸入或輸出處理 */
   
        attr.c_iflag = (flow == SoftFlow) ? (IXON | IXOFF) : 0;
        attr.c_oflag = 0;
   
        /* 設定8位字符寬和一些雜項控制模式 */
   
        attr.c_cflag &= ~(CSIZE | CFLAGS_TO_CLEAR | CFLAGS_HARDFLOW);
        attr.c_cflag |= (CS8 | CFLAGS_TO_SET);
        if (flow == HardFlow)
            attr.c_cflag |= CFLAGS_HARDFLOW;
   
        /* 本機模式 */
   
        attr.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHOK | ISIG);
   
        /* 特殊字符 -- 許多已被先前的設定取消 */
   
        {
            int i;
    #ifdef _POSIX_VDISABLE
            attr.c_cc[0] = _POSIX_VDISABLE;
    #else
            attr.c_cc[0] = fpathconf(fd, _PC_VDISABLE);
    #endif
            for (i = 1; i < NCCS; i++)
                attr.c_cc[i] = attr.c_cc[0];
        }
   
        attr.c_cc[VSTART] = 0x11;
        attr.c_cc[VSTOP] = 0x13;
   
        /* 對read()函數的計時(shí)控制 */
   
        attr.c_cc[VMIN] = 1;
        attr.c_cc[VTIME] = 0;
   
        /* 波特律 */
   
        cfsetispeed(&attr, baud);
        cfsetospeed(&attr, baud);
   
        /* 寫(xiě)入設定 */
   
        if (tcsetattr(fd, TCSANOW, &attr) < 0)
            return close_and_complain(fd, "tcsetattr", errno);
   
        /* 如果系統記住了先前的O_NONBLOCK設定,就取消它 */
   
        flags = fcntl(fd, F_GETFL, 0);
        if (flags < 0)
            return close_and_complain(fd, "fcntl(GETFL)", errno);
        if (fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) < 0)
            return close_and_complain(fd, "fcntl(SETFL)", errno);
   
        return fd;
    }
   
    /* 一些簡(jiǎn)單的計時(shí)工具函數 */
   
    /* 向*TV加 SECS 和USECS */
   
    static void timeradd(struct timeval *tv, long secs, long usecs)
    {
        tv->tv_sec += secs;
        if ((tv->tv_usec += usecs) >= 1000000)
        {
            tv->tv_sec += tv->tv_usec / 1000000;
            tv->tv_usec %= 1000000;
        }
    }
   
    /* 設定 *RES = *A - *B, 返回結果的符號 */
   
    static int timersub(struct timeval *res,
                        const struct timeval *a, const struct timeval *b)
    {
        long sec = a->tv_sec - b->tv_sec;
        long usec = a->tv_usec - b->tv_usec;
   
        if (usec < 0)
            usec += 1000000, --sec;
   
        res->tv_sec = sec;
        res->tv_usec = usec;
   
        return (sec < 0) ?
(-1) : ((sec == 0 && usec == 0) ? 0 : 1);
    }
   
   
    /* 這個(gè)函數不試圖處理非正常的字符串 (比如
ababc)
     * 超時(shí)以微妙計

     * 一個(gè)更通常的做法是使用alarm()函數處理超時(shí).
     
* 這個(gè)函數為簡(jiǎn)便起見(jiàn)不使用信號處理并試圖提供一種替換方法
     */
   
    int expect(int fd, const char *str, int timeo)
    {
        int matchlen = 0;
        int len = strlen(str);
        struct timeval now,end,left;
        fd_set fds;
        char c;
   
        gettimeofday(&end, NULL);
        timeradd(&end, timeo/1000, timeo%1000);
   
        while (matchlen < len)
        {
            gettimeofday(&now, NULL);
            if (timersub(&left, &end, &now) <= 0)
                return -1;
   
            FD_ZERO(&fds);
            FD_SET(fd, &fds);
            if (select(fd+1, &fds, NULL, NULL, &left) <= 0)
                return -1;
   
            if (read(fd, &c, 1) != 1)
                return -1;
   
            if (isprint((unsigned char)c) || c == ‘\n‘ || c == ‘\r‘)
                putchar(c);
            else
                printf("\\x%02x", c);
   
            if (c == str[matchlen])
                ++matchlen;
            else
                matchlen = 0;
        }
   
        return 0;
    }
   
   
    int main(int argc, char **argv)
    {
        int fd;
        unsigned char c;
   
        if (argc < 2)
            die("no port specified");
   
        setvbuf(stdout, NULL, _IONBF, 0);
   
        fd = open_port(argv[1], EXAMPLE_BAUD, EXAMPLE_FLOW);
        if (fd < 0)
            die("cannot open port");
   
        write(fd, "AT\r", 3);
        if (expect(fd, "OK", 5000) < 0)
        {
            write(fd, "AT\r", 3);
            if (expect(fd, "OK", 5000) < 0)
            {
                tcflush(fd, TCIOFLUSH);
                close(fd);
                die("no response to AT");
            }
        }
   
        write(fd, "ATI4\r", 5);
        expect(fd, "OK", 10000);
   
        putchar(‘\n‘);
   
        tcflush(fd, TCIOFLUSH);
        close(fd);
   
        return 0;
    }
事務(wù)控制范例程序
================

    /* 生成前臺/后臺事務(wù)的函數 */
   
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <fcntl.h>
    #include <signal.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <errno.h>    
   
    /* 一些下面的函數會(huì )因為無(wú)法定位控制tty和調用方不在前臺而失敗。
     
* 第一種情況時(shí),我們假設一個(gè)前臺程序會(huì )有為標準輸入,標準輸出或標準錯誤輸出打開(kāi)的ctty,
     * 而如果沒(méi)有則返回ENOTTY。
     * 第二種情況時(shí),除foreground_self()函數的特殊情況以外,
     * 若一個(gè)非前臺程序打算輸出一些東西到前臺,我們返回EPERM。
     * (也許想得太多了)
     */
   
   
    /* 為給定的pgrp安排一個(gè)終端 (打開(kāi)一個(gè)ctty) .
     * 這個(gè)tcsetpgrp()外殼程序只是因為POSIX中特別錯誤(bogusity)的地方而需要;

     * 遵照標準的系統在一個(gè)非前臺進(jìn)程調用tcsetpgrp函數時(shí)傳遞SIGTTOU
     * 信號(差不多總是這樣)。這是虛假的一致性之于一般想法的勝利。

     */
   
    int assign_terminal(int ctty, pid_t pgrp)
    {
        sigset_t sigs;
        sigset_t oldsigs;
        int rc;
   
        sigemptyset(&sigs);
        sigaddset(&sigs,SIGTTOU);
        sigprocmask(SIG_BLOCK, &sigs, &oldsigs);
   
        rc = tcsetpgrp(ctty, pgrp);
   
        sigprocmask(SIG_SETMASK, &oldsigs, NULL);
   
        return rc;
    }
   
   
    /* 類(lèi)似fork函數,但做事務(wù)控制。如果新建立的進(jìn)程放在前臺則設fg為真。

     * (這樣隱式地將調用方進(jìn)程放置到后臺,所以做完這個(gè)后要當心tty的輸入/輸出)
     * 設定pgrp為-1以創(chuàng )建一個(gè)新事務(wù),在此情況下返回的進(jìn)程號即是新事務(wù)的進(jìn)程組號,

     * 或者設定一個(gè)同一會(huì )話(huà)中存在的事務(wù)(一般只用來(lái)啟動(dòng)管道操作的第二個(gè)或第二個(gè)以后
     * 的進(jìn)程)。
     */
   
    pid_t spawn_job(int fg, pid_t pgrp)
    {
        int ctty = -1;
        pid_t pid;
   
        /* 如果生成一個(gè)*新*的前臺事務(wù),起碼要求標準輸入,標準輸出或
         * 標準錯誤輸出的其中一個(gè)指向的是控制tty,并且當前進(jìn)程在前臺。
         * 只有當在存在事務(wù)中開(kāi)始一個(gè)新前臺進(jìn)程時(shí)才檢查控制中的tty。

         * 一個(gè)沒(méi)有控制tty的會(huì )話(huà)只能有后臺事務(wù)。
         */
   
        if (fg)
        {
            pid_t curpgrp;
   
            if ((curpgrp = tcgetpgrp(ctty = 2)) < 0
                && (curpgrp = tcgetpgrp(ctty = 0)) < 0
                && (curpgrp = tcgetpgrp(ctty = 1)) < 0)
                return errno = ENOTTY, (pid_t)-1;
   
            if (pgrp < 0 && curpgrp != getpgrp())
                return errno = EPERM, (pid_t)-1;
        }
   
        switch (pid = fork())
        {
            case -1: /* fork失敗 */
                return pid;
   
            case 0: /* 子進(jìn)程 */
   
                /* 建立新進(jìn)程組, 如果需要則將我們放到前臺
                 * 不知道如果setpgid函數調用失敗該怎么辦(“不會(huì )發(fā)生”)
                 */
   
                if (pgrp < 0)
                    pgrp = getpid();
   
                if (setpgid(0,pgrp) == 0 && fg)
                    assign_terminal(ctty, pgrp);
   
                return 0;
   
            default: /* 父進(jìn)程 */
   
                /* 這里也建立自進(jìn)程組. */
   
                if (pgrp < 0)
                    pgrp = pid;
   
                setpgid(pid, pgrp);
   
                return pid;
        }
   
        /*不會(huì )執行到這里*/
    }
   
   
    /* 用SIGNO表示的信號殺死PGRP表示的事務(wù) */
   
    int kill_job(pid_t pgrp, int signo)
    {
        return kill(-pgrp, signo);
    }
   
   
    /* 中斷PGRP表示的事務(wù) */
   
    int suspend_job(pid_t pgrp)
    {
        return kill_job(pgrp, SIGSTOP);
    }
   
   
    /* 繼續在后臺執行PGRP表示的事務(wù) */
   
    int resume_job_bg(pid_t pgrp)
    {
        return kill_job(pgrp, SIGCONT);
    }
   
   
    /* 繼續在前臺執行PGRP表示的事務(wù) */
   
    int resume_job_fg(pid_t pgrp)
    {
        pid_t curpgrp;
        int ctty;
   
        if ((curpgrp = tcgetpgrp(ctty = 2)) < 0
            && (curpgrp = tcgetpgrp(ctty = 0)) < 0
            && (curpgrp = tcgetpgrp(ctty = 1)) < 0)
            return errno = ENOTTY, (pid_t)-1;
   
        if (curpgrp != getpgrp())
            return errno = EPERM, (pid_t)-1;
   
        if (assign_terminal(ctty, pgrp) < 0)
            return -1;
   
        return kill_job(pgrp, SIGCONT);
    }
   
   
    /* 將我們自己放置到前臺,比如在中斷一個(gè)前臺事務(wù)之后調用
     */
   
    int foreground_self()
    {
        pid_t curpgrp;
        int ctty;
   
        if ((curpgrp = tcgetpgrp(ctty = 2)) < 0
            && (curpgrp = tcgetpgrp(ctty = 0)) < 0
            && (curpgrp = tcgetpgrp(ctty = 1)) < 0)
            return errno = ENOTTY, (pid_t)-1;
   
        return assign_terminal(ctty, getpgrp());
    }
   
   
    /* closeall() - 關(guān)閉所有>=給定FD的文件描述符 */
   
    void closeall(int fd)
    {
        int fdlimit = sysconf(_SC_OPEN_MAX);
   
        while (fd < fdlimit)
            close(fd++);
    }
   
   
    /* 類(lèi)似system()函數,但將給定的命令作為后臺事務(wù)執行,返回shell進(jìn)程
     * 的進(jìn)程號(并且也是這個(gè)事務(wù)的進(jìn)程組號,適用于kill_job等等)。
     * 如果參數INFD,OUTFD或ERRFD為非NULL,則打開(kāi)一個(gè)管道和一個(gè)文件描述

     * 符保存與該管道有關(guān)的父進(jìn)程端,然后在子進(jìn)程中將被從定向到/dev/null。
     * 并且在子進(jìn)程中關(guān)閉所有>2的文件描述符(一個(gè)經(jīng)常過(guò)份估計的工作)
     */
   
    pid_t spawn_background_command(const char *cmd,
                                   int *infd, int *outfd, int *errfd)
    {
        int nullfd = -1;
        int pipefds[3][2];
        int error = 0;
   
        if (!cmd)
            return errno = EINVAL, -1;
   
        pipefds[0][0] = pipefds[0][1] = -1;
        pipefds[1][0] = pipefds[1][1] = -1;
        pipefds[2][0] = pipefds[2][1] = -1;
   
        if (infd && pipe(pipefds[0]) < 0)
            error = errno;
        else if (outfd && pipe(pipefds[1]) < 0)
            error = errno;
        else if (errfd && pipe(pipefds[2]) < 0)
            error = errno;
   
        if (!error && !(infd && outfd && errfd))
        {
            nullfd = open("/dev/null",O_RDWR);
            if (nullfd < 0)
                error = errno;
        }
   
        if (!error)
        {
            pid_t pid = spawn_job(0, -1);
            switch (pid)
            {
                case -1: /* fork失敗 */
                    error = errno;
                    break;
   
                case 0: /* 子進(jìn)程 */
   
                    dup2(infd ? pipefds[0][0] : nullfd, 0);
                    dup2(outfd ? pipefds[1][1] : nullfd, 1);
                    dup2(errfd ? pipefds[2][1] : nullfd, 2);
                    closeall(3);
   
                    execl("/bin/sh","sh","-c",cmd,(char*)NULL);
   
                    _exit(127);
   
                default: /* 父進(jìn)程 */
   
                    close(nullfd);
                    if (infd)
                        close(pipefds[0][0]), *infd = pipefds[0][1];
                    if (outfd)
                        close(pipefds[1][1]), *outfd = pipefds[1][0];
                    if (errfd)
                        close(pipefds[2][1]), *errfd = pipefds[2][0];
   
                    return pid;
            }
        }
   
        /* 只在錯誤時(shí)執行到這里 */
   
        {
            int i,j;
            for (i = 0; i < 3; ++i)
                for (j = 0; j < 2; ++j)
                    if (pipefds[i][j] >= 0)
                        close(pipefds[i][j]);
        }
   
        if (nullfd >= 0)
            close(nullfd);
   
        return errno = error, (pid_t) -1;
    }
   
   
    /*---------------------------------------*/
    /* 這里是使用上述函數一個(gè)小例子.         */
   
    pid_t bgjob = -1;
    volatile int signo = 0;
   
    #ifndef WCOREDUMP
     /* 如果沒(méi)有 WCOREDUMP, 你也許會(huì )希望在你的平臺上為它設置一個(gè)準確的定義
      * (這通常是(status & 0x80) 但也不總是這樣),或者就賭沒(méi)有core dumps(
      * 就象這個(gè)程序所做)
      */
    # define WCOREDUMP(status) (0)
    #endif
   
    int check_children()
    {
        pid_t pid;
        int status;
        int count = 0;
   
        while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0)
        {
            if (pid == bgjob && !WIFSTOPPED(status))
                bgjob = -1;
   
            ++count;
   
            if (WIFEXITED(status))
                fprintf(stderr,"Process %ld exited with return code %d\n",
                        (long)pid, WEXITSTATUS(status));
            else if (WIFSIGNALED(status))
                fprintf(stderr,"Process %ld killed by signal %d%s\n",
                        (long)pid, WTERMSIG(status),
                        WCOREDUMP(status) ? " (core dumped)" : "");
            else if (WIFSTOPPED(status))
                fprintf(stderr,"Process %ld stopped by signal %d\n",
                        (long)pid, WSTOPSIG(status));
            else
                fprintf(stderr,"Unexpected status - pid=%ld, status=0x%x\n",
                        (long)pid, status);
        }
   
        return count;
    }
   
   
    void sighandler(int sig)
    {
        if (sig != SIGCHLD)
            signo = sig;
    }
   
   
    int main()
    {
        struct sigaction act;
        int sigcount = 0;
   
        act.sa_handler = sighandler;
        act.sa_flags = 0;
        sigemptyset(&act.sa_mask);
        sigaction(SIGINT,&act,NULL);
        sigaction(SIGQUIT,&act,NULL);
        sigaction(SIGTERM,&act,NULL);
        sigaction(SIGTSTP,&act,NULL);
        sigaction(SIGCHLD,&act,NULL);
   
        fprintf(stderr,"Starting background job ‘sleep 60‘\n");
        bgjob = spawn_background_command("sleep 60", NULL, NULL, NULL);
        if (bgjob < 0)
        {
            perror("spawn_background_command");
            exit(1);
        }
        fprintf(stderr,"Background job started with id %ld\n", (long)bgjob);
        while (bgjob >= 0)
        {
            if (signo)
            {
                fprintf(stderr,"Signal %d caught\n", signo);
                if (sigcount++)
                    kill_job(bgjob, SIGKILL);
                else
                {
                    kill_job(bgjob, SIGTERM);
                    kill_job(bgjob, SIGCONT);
                }
            }
   
            if (!check_children())
                pause();
        }
   
        fprintf(stderr,"Done - exiting\n");
        return 0;
    }
本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
進(jìn)程間通信之popen和pclose函數
Linux的進(jìn)程通訊之匿名管道
Linux系統編程之我的學(xué)習筆記1_linux函數學(xué)習心得
Linux下的c語(yǔ)言網(wǎng)絡(luò )編程-將普通進(jìn)程轉換為守護進(jìn)程 - 開(kāi)源教程 - linux學(xué)習 ...
linux C程序中獲取shell腳本輸出
linux一切皆文件|細節知多少
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久