亚洲av成人无遮挡网站在线观看,少妇性bbb搡bbb爽爽爽,亚洲av日韩精品久久久久久,兔费看少妇性l交大片免费,无码少妇一区二区三区

  免費注冊 查看新帖 |

Chinaunix

  平臺 論壇 博客 文庫
最近訪問板塊 發(fā)新帖
查看: 67424 | 回復(fù): 76
打印 上一主題 下一主題

[FreeBSD] FreeBSD系統(tǒng)編程[簡體中文版] [復(fù)制鏈接]

論壇徽章:
1
榮譽版主
日期:2011-11-23 16:44:17
跳轉(zhuǎn)到指定樓層
1 [收藏(0)] [報告]
發(fā)表于 2006-02-13 19:04 |只看該作者 |倒序瀏覽
參考鏈接:
【FreeBSD system programming 】中文翻譯計劃及所有異義提交處



FreeBSD系統(tǒng)編程


Copyright(C) 2001~2006 Nathan Boeger 和Mana Tominaga 版權(quán)所有,簡體中文版由ChinaUnix論壇提供翻譯。

目錄
第一章: FreeBSD的Make
第二章: BSD自舉
第三章: 進程和內(nèi)核服務(wù)
第四章: 高級進程控制和信號
第五章: 基本I/O
第六章: 高級I/O
第七章: 進程資源和系統(tǒng)限制
第八章: FreeBSD 5.x

第一章 FreeBSD的make

譯者:雨絲風(fēng)片@chinaunix.net


1.1 FreeBSD的make

作為常用的和基本的Unix軟件開發(fā)工具,make是一個可以跟蹤全部的文件依賴關(guān)系的非常好的簿記工具程序。要管理依賴關(guān)系這樣的項目細節(jié)常常需要花費很多的時間,甚至?xí)涎娱_發(fā)進度。當(dāng)多個開發(fā)人員合作一個項目的時候,依賴關(guān)系的跟蹤就可能變得相當(dāng)困難了。事實上,正確地使用make可以幫助我們加快應(yīng)用程序的開發(fā),從而提高生產(chǎn)效率。

雖然make最初的設(shè)計是用來對應(yīng)用程序版本構(gòu)建的維護過程進行管理的,我們實際上還可以通過創(chuàng)建一系列的基于目標(biāo)依賴關(guān)系的Unix shell命令來讓make完成多種多樣的額外工作。這些依賴關(guān)系可以用很多種方式定義——包括需要進行編譯的源文件、所需的庫文件、shell命令以及其它的目標(biāo)。

make有多種風(fēng)格的版本,其中包括GNU make和System V make。并不是在每個make版本中都有我們接下來討論的那些特性,具體使用哪個版本完全取決于你的個人喜好。我們將主要關(guān)注跟隨FreeBSD一起發(fā)布的make(也叫做bmake或pmake),尤其是如何通過它來編譯和更新FreeBSD系統(tǒng),也就是所謂的make world。雖然我們關(guān)注的是FreeBSD make,但我們在這里討論的所有東西對于各種BSD版本來說都是適用的。

我們首先會講述一個Makefile的基本文件布局和語法。如果這對于你來說太簡單了,那你可以直接跳到本章結(jié)束處的示例部分去閱讀。(注意,我們給出的代碼示例只用于演示我們關(guān)于make目標(biāo)和依賴關(guān)系的討論,它們并不一定是可以運行的代碼。)

當(dāng)然,和其它工具程序一樣,最開始應(yīng)該先去看看man page,以對make提供的命令行選項的概要和細節(jié)有一個正式的了解。同時,和其它工具程序一樣,學(xué)習(xí)make的最好方法就是使用它。創(chuàng)建一些小型的源文件(可以使用任何語言),然后嘗試一些下面給出的例子。我們希望讀完本章之后你除了理解make的語法規(guī)則之外,還知道它是如何工作的。

1.2 Makefile布局

總的說來,你使用make的方式就是讓它去讀一個Makefile,你需要在Makefile里指定一個目標(biāo)及其依賴關(guān)系。在運行的時候,make會按順序搜索名字為Makefile或makefile的文件。這個Makefile通常是放在一個工程的根目錄下的,如果想指定其它的Makefile,可以在命令行上用-f (filename)的選項給出。

  1. make -f OtherMakefile
復(fù)制代碼


1.3 語法

一個Makefile的結(jié)構(gòu)由四個基本行組成,它們都可以通過在行尾添加‘\’字符來擴展到下一行(和shell編程相似)。注釋是以‘#’號開始的,至行尾結(jié)束。

  1. ########################################
  2. # Simple Makefile with comment example #
  3. ########################################

  4. # when run, it will just echo hello
  5. all:
  6.    echo "hello"
復(fù)制代碼


要使用make來編譯一個工程,首先需要確定在你的當(dāng)前工作目錄中已有一個正確的Makefile,然后再通過下列命令之一來使用make:

  1. bash$ make   

  2. bash$ make all

  3. bash$ make <target name>
復(fù)制代碼


1.4 目標(biāo)

用來指定目標(biāo)的方式有很多種,不過最常用的就是用目標(biāo)文件或一個工程的名字。工程名字不應(yīng)當(dāng)包含有空格或標(biāo)點符號,不過這只是個慣例而已;少量的空格和標(biāo)點符號也是允許的。這個名字必須寫在一個新行的開頭,必須以單冒號(:)、雙冒號(::)或感嘆號(!)三者之一結(jié)束。

  1. myprog:
  2.      <some commands to the compile myprog target>

  3. another::
  4.      <some commands to the compile another target>

  5. sample!
  6.      <some commands to the compile sample target>
復(fù)制代碼


在這些目標(biāo)名字之后是所需的依賴條件,包括名字、變量以及其它的目標(biāo)等等。如果你的依賴條件太多的話,可以用一個‘\’和一個newline來將它們分開。所有的依賴條件都必須Makefile內(nèi)定義或者存在于某個外部文件中,否則make將無法知道如何去完成依賴操作。

一些示例如下:

  1. all: driver.cpp network_class.cpp file_io_class.cpp network_libs.cpp file_io_libs.cpp

  2. all: myprog.o

  3. myprog.o:
復(fù)制代碼


上例中,all和myprog.o是要make的目標(biāo)。注意myprog.o既是一個目標(biāo)又是一個依賴條件。make首先會到myprog.o那兒,執(zhí)行它的命令,然后返回到all那兒,再執(zhí)行它的命令。這種操作序列是make的功能基礎(chǔ)。

按照慣例,all:目標(biāo)是你的目標(biāo)中的最高者,這意味著make將從這兒開始去尋找要完成all:目標(biāo)都需要哪些東西。不過all:目標(biāo)并不是必需的,如果沒有的話,make就會簡單地選擇所有列出的目標(biāo)中的第一個,只對其實施操作,除非你在命令行上指定了某個目標(biāo)。對于那些有一個核心的應(yīng)用程序需要維護和構(gòu)建的工程來說,我們建議你使用all:目標(biāo);這是一個通用的慣例,有助于避免錯誤和不必要的任務(wù)。

上例所示的依賴序列只是很簡單的一個。下面是一個更為復(fù)雜和靈活的依賴序列,我們沒有給出用于具體目標(biāo)的命令:

  1. all: myprog.o lib

  2. lib: lex

  3. lex:

  4. myprog.o: app.h
復(fù)制代碼


注意,在這個例子中,all:目標(biāo)有兩個依賴條件:myprog.o和lib。這兩個依賴條件本身又都是目標(biāo),make將首先去編譯myprog.o。在make編譯myprog.o的時候,會發(fā)現(xiàn)有一個和app.h的依賴關(guān)系。app.h并沒有在這個makefile里定義,但app.h卻是一個在當(dāng)前目錄中的頭文件。

用于myprog.o的命令完成之后,make即返回到all:處,繼續(xù)處理下一個依賴條件,在此例中是lib。依賴條件lib本身也有一個依賴條件lex,所以make在完成lib之前會先去完成lex:。

注意:正如你所看到的,這些依賴關(guān)系可能會非常長,或者嵌套得很深。如果你有一個很大的Makefile,那一定要好好地組織一下,把目標(biāo)的順序弄好。

1.5 求值規(guī)則

依賴關(guān)系是按照依賴于目標(biāo)名字結(jié)束符號的嚴(yán)格規(guī)則來求值的。一旦make認(rèn)為滿足規(guī)則,它將通過執(zhí)行相應(yīng)的命令來創(chuàng)建特定的目標(biāo)(比如編譯該目標(biāo))。例如,使用單冒號:可以讓你對需要進行編譯的目標(biāo)進行更為精細的控制。也就是說,你可以指定某個特定的目標(biāo)文件每次都需要重新編譯或者僅當(dāng)它的源文件過時之后才編譯。這些規(guī)則都是基于目標(biāo)名字的結(jié)束符號的,如下:

如果目標(biāo)名字以單冒號(:)結(jié)束,它將根據(jù)以下兩個規(guī)則來創(chuàng)建:
  • 如果目標(biāo)尚未存在,就像我們在上面舉的例子里的all:一樣,make就會創(chuàng)建它。
  • 如果任意一個源文件具有比當(dāng)前目標(biāo)更新的時間戳。在上例中如果app.h或myprog.c具有更新的時間戳,myprog.o就會被make。這種情況只需簡單地用一下touch命令即可出現(xiàn)

  1.     touch myprog.c
復(fù)制代碼


如果目標(biāo)名字以雙冒號(::)結(jié)束,它將根據(jù)以下三個規(guī)則來創(chuàng)建:
  • 如果任意一個源文件具有比當(dāng)前目標(biāo)更新地時間戳。
  • 該目標(biāo)不存在。
  • 該目標(biāo)沒有與之關(guān)聯(lián)的源文件。


如果目標(biāo)名字以感嘆號(!)結(jié)束,只要make把它所需的全部依賴條件都創(chuàng)建完畢就會來創(chuàng)建它。

你只能在目標(biāo)或源文件的最后一個組成部分中使用通配表達式?、*和[],而且只能用于描述已經(jīng)存在的文件。比如:

  1. myprog.[oc]
復(fù)制代碼


而使用花括號{}的表達式則不一定非得描述已經(jīng)存在的文件。比如:

  1. {mypgog,test}.o

  2. # the expression above would match myprog.o test.o only
復(fù)制代碼


最后需要注意一點:可變表達式是按照目錄順序來處理的,而非字母順序,就跟在shell表達式中一樣。例如,如果你的目標(biāo)有某些基于字母順序的依賴條件,下面這個表達式可能就不對了:

  1. {dprog,aprog,bprog,cprog}.cpp
復(fù)制代碼


1.6 變量

make能夠使用變量這一點是非常重要的。例如,你有一個名字為a.c的源文件,由于某種原因你想把它的名字改成b.c。通常情況下,你得把你的makefile里的每個a.c的實例都改成b.c。但是,如果你寫成以下方式:

  1. MYSRC = a.c
復(fù)制代碼


你只需要把這一行更新成新的名字即可,如下:

  1. MYSRC = b.c
復(fù)制代碼


你因此而節(jié)省了時間,這也正是make的首要角色:項目管理。

變量可用$(<變量名字>)或就用一個$來引用,但后者未被廣泛使用,因此建議別那樣寫。

  1. $(GCC) = /usr/local/bin/gcc
復(fù)制代碼


make有四種不同類型的變量,下面將按照被搜索的順序列出它們。(make的搜索將一直進行到發(fā)現(xiàn)某個數(shù)值的第一個實例為止。)
  • 局部變量:這些是賦給特定目標(biāo)的數(shù)值。
  • 命令行:命令行變量是在命令行上傳給make的。
  • 全局變量:全局變量是在該Makefile或任何所包含的Makefile內(nèi)賦值的。你在一個Makefile內(nèi)最?吹降木褪沁@些變量。
  • 環(huán)境變量:環(huán)境變量是在Makefile之外,也就是運行make的shell里設(shè)置的。


這些變量可以在Makefile里用以下五種操作符進行定義:

  • 等號“=”是最常用的操作符,這和shell的情況是類似的。數(shù)值被直接賦給了變量。例如:
    1. VAR = < value >
    復(fù)制代碼

  • 加號等號“+=”的意思是附加賦值,通過把所賦數(shù)值附加到當(dāng)前數(shù)值的后面來完成對變量的賦值。例如:
    1. VAR += < value to append >
    復(fù)制代碼

  • 問號等號“?=”的意思是條件賦值,僅當(dāng)該變量從未賦值時才進行賦值。條件賦值在向一個字符串?dāng)?shù)值添加前綴的時候非常有用。例如:
    1. VAR ?= < value if unset >
    復(fù)制代碼

  • 冒號等號“:=”的意思是擴展賦值,在賦值前會對所賦數(shù)值進行擴展;通常這種擴展是在所賦變量被引用的時候才進行的。例如:
    1. VAR := < value to expand >
    復(fù)制代碼

  • 感嘆號等號“!=”的意思是shell命令賦值。在命令被擴展并發(fā)給shell執(zhí)行完畢之后,將命令結(jié)果賦給變量。結(jié)果中的newline字符將被空格取代。例如:
    1. VAR != < shell command to execute >
    復(fù)制代碼



注意:有些變量是在外部的系統(tǒng)級的Makefile內(nèi)定義的(在/etc/make.conf或/etc/defaults/make.conf中),如果你在設(shè)置環(huán)境變量方面遇到了問題,就去檢查一下系統(tǒng)級文件里的設(shè)置。
      
一個完整的例子如下:

  1.   #####################
  2.   # Example make file #
  3.   #####################
  4.   CURDIR != pwd
  5.   CFLAGS ?= -g
  6.   CFLAGS += -Wall -O2
  7.   
  8.   all:
  9.       echo $CFLAGS
  10.   #######################
  11.   
  12.   bash$ CFLAGS="-g -Wall" make
復(fù)制代碼


在上例中,CURDIR被設(shè)置成了shell命令pwd的結(jié)果。(注意,這種賦值并不需要backtick命令(即' ')。)CFLAGS如果從未設(shè)置,會首先被設(shè)置成-g,然后不管它的當(dāng)前值是什么,都會被附加上-Wall -O2。

1.7 命令

如果沒有命令,那make什么都不是,只有把命令告訴make它才能完成它的工作。make只能運行這些命令,然后基于shell的退出狀態(tài)判斷這些命令是否成功。因此,如果命令失敗,shell返回一個錯誤,make將會因錯誤而退出并于該處終止。make可以就被看作是另外一個命令——除了運行其它命令之外,它和那些命令并沒有什么實際的交互。

命令必須與一個目標(biāo)相關(guān)聯(lián),任何一個目標(biāo)都可以有多個命令。例如:

  1. # example for an all-install target

  2. all-install:
  3.    $(CC) $(CFLAGS)  $(MYSRC)
  4.    cp  $(MYPROG) $(INSTALL_DIR)
  5.    echo "Finished the build and install"
復(fù)制代碼

每個命令都必須在一個目標(biāo)之后以新行開始,在實際命令起始位置之前必須要有一個tab鍵,如上所示。

對于大多數(shù)情況而言,Makefile里的命令只要是個有效的shell命令就行,命令還經(jīng)常會包括變量。例如:

  1. CPP = -g++
  2. CFLAGS = -Wall -O2

  3. myprog.o:
  4.     $(CPP) $(CFLAGS) -c myprog.c
復(fù)制代碼

下面這個例子告訴make使用給定值編譯myprog.c。這些命令可能會比一行要長,它們也可以被寫來完成其它的任務(wù)。這是非常重要的,因為編譯器可以接受相當(dāng)多的命令行選項、環(huán)境設(shè)置以及定義等等,比如:

  1. CLFAGS = $(LINK_FLAGS) $(LINK_LIBS) $(OTHER_LIBS) \
  2. $(OPTIMIZER_FLAGS) $(DEFINES) $(NO_KERNEL) $(OBJS) \
  3. $(CPU_TYPE_FLAGS) $(USE_MY_MALLOC) $(UDEFINES) \
  4. $(SRC_FILE)  $(OTHER_SRC_FILES)
復(fù)制代碼

下面這個例子告訴make刪除所有的object文件、core文件以及應(yīng)用程序本身,然后把log文件移走,這些操作都可以很方便的完成。

  1. CPP = -g++
  2. CFLAGS = -Wall -O2
  3. APP = myapp
  4. DATE != date +%m%d%y_%H_%M
  5. LOG = debug


  6. myprog.o:
  7.       $(CPP) $(CFLAGS) -c myprog.c
  8.       rm -f  *.o  *.core $(APP)
  9.       mv  $(LOG).log  $(LOG)_$(DATE).log

  10. clean:
  11.     rm -f *.o *.core $(APP)
  12.     mv $(LOG).log $(LOG)_$(DATE).log
復(fù)制代碼

但是,如果并不存在log文件,make就會因錯誤而退出。為了避免這種情況,可以在命令之前加上“-”號。通過在命令前面加上減號,你就告訴了make忽略執(zhí)行命令時遇到的錯誤。(不過make仍然會打印出錯誤信息。)因此,在某個命令出現(xiàn)錯誤之后make仍將繼續(xù)。例如:

  1. clean:
  2.     -rm -f *.o *.core $(APP)
  3.     -mv $(LOG).log $(LOG)_$(DATE).log
復(fù)制代碼


這將使得make忽略掉rm和mv命令可能遇到的錯誤。

你還可以讓make禁止掉“echo”之類的命令的輸出。echo首先告訴make打印出包括echo語句在內(nèi)的整個命令,然后再執(zhí)行命令并將字符串打印到屏幕上:
  1.     echo $(SOME_DEFINED_STRING)
復(fù)制代碼


要避免這種情況,可以在echo命令之前加上“@”符號,這將告訴make只打印字符串,比如:
  1.     @echo $(SOME_DEFINED_STRING)
復(fù)制代碼


“-”號和“@”號都既可用于變量,又可用于形為字符串的命令,但需要確定你對變量命令的引用是正確的。下例演示了如何對命令使用@操作符:
  1. ECHO = echo
  2. MESSAGE = "Print this message"

  3. msg::
  4.   @$(ECHO) $(MESSAGE)  
復(fù)制代碼


1.8 條件語句(#if,#ifndef等等)

如果你對C和C++比較熟悉,那你肯定知道條件預(yù)處理命令。功能繁多的make也有一個類似的特性。條件語句使你可以選擇Makefile里的哪個部分需要被處理。這些條件語句最多可以嵌套30層,并且可以放在Makefile里的任何地方。每條語句都必須以一個圓點(.)開始,而且條件語句塊必須以.endif結(jié)束。

條件語句允許使用邏輯操作符,比如邏輯AND“&&”、邏輯OR“||”,整條語句還可以用“!”操作符取反!!”操作符具有最高優(yōu)先級,其后依次是邏輯AND和邏輯OR。括號可以被用來指定優(yōu)先級順序。關(guān)系運算符也是可以使用的,比如“>”、“>=”、“<”、“<=”、“==”和“!=”。這些操作符可被用于十進制和十六進制的數(shù)值。對于字符串則可以使用“==”和“!=”操作符。如果沒有給出操作符,那么將會把數(shù)值和0進行比較。

在下面的例子中,在對VER變量進行賦值之后對條件進行測試。注意,如果VER變量未被賦值,則最后的.else條目將被求值為真,TAG將被賦成2.4_stable的值。

  1. .if $(VER) >= 2.4
  2.   TAG = 2.4_current
  3. .elif $(VER) == 2.3
  4.   TAG = 2.3_release
  5. .else
  6.   TAG = 2.4_stable
  7. .endif
復(fù)制代碼


條件語句可用于測試變量,也可以用于下面這種函數(shù)風(fēng)格的表達式。

這些用法中有些是有簡寫形式的。出于兼容性方面的考慮,我們列出了簡寫形式。非簡寫形式意思更為確定,也更容易被理解,但需要你敲入更多的字符。

在使用簡寫形式的時候是不必使用括號的。此外,簡寫形式還可以和if/else語句以及其它的簡寫形式混合在一起:

make( < arg > ) short hand [ .ifmake, .ifnmake, .elifmake, .elifnmake ]

在上例中,make將以一個目標(biāo)名字作為它的參數(shù)。如果這個目標(biāo)已在命令行上給出,或者它就是缺省進行make的目標(biāo),那么該值就為真。下例中將根據(jù)make()表達式的規(guī)則給CFLAGS賦值:

  1. .if make(debug)
  2.   CFLAGS += -g
  3. .elif make(production)
  4.   CFLAGS += -O2
  5. .endif
復(fù)制代碼


下面是用簡寫形式表示的相同代碼:

  1. .ifmake debug
  2.   CFLAGS += -g
  3. .elifmake production
  4.   CFLAGS += -O2
  5. .endif
復(fù)制代碼


target( < arg > )

這種形式將以一個目標(biāo)名字作為參數(shù)。僅當(dāng)該target已被定義時該值才為真。這個表達式?jīng)]有簡寫形式。例如:

  1. .if target(debug)
  2.   FILES += $(DEBUG_FILES)
  3. .endif
復(fù)制代碼


在上例中,如果debug目標(biāo)返回真的話就會對FILES變量進行附加操作。

empty ( < arg > )

這種形式以一個變量為參數(shù),并允許使用修飾符。當(dāng)變量被擴展之后是一個空字符串時該值為真。這個表達式?jīng)]有簡寫形式。此外需要注意的是,你在使用這個表達式的時候并不需要對數(shù)值進行引用,記住是VAR而不是$(VAR)。例如:

  1. .if empty (CFLAGS)
  2.    CFLAGS = -Wall -g
  3. .endif
復(fù)制代碼


defined( < arg > ) short hand [ .ifdef , .ifndef , .elifdef, elifndef ]

下面這個例子采用一個變量作為參數(shù)。僅當(dāng)變量已被定義時該值為真。

  1. .if defined(OS_VER)
  2.   .if $(OS_VER) == 4.4
  3.      DIRS += /usr/local/4.4-STABLE_src
  4.   .endif
  5. .else
  6.   DIRS += /usr/src
  7. .endif
復(fù)制代碼


下面是簡寫形式:

  1. .ifdef OS_VER
  2. . if $(OS_VER) == 4.4
  3.   DIRS += /usr/local/4.4-STABLE_src
  4. . endif
  5. .else
  6.   OS_VER = 3.2
  7.   DIRS += /usr/src
  8. .endif
復(fù)制代碼


正如你所看到的,make允許嵌套的條件和define表達式。和C不同的是,你不能對if語句和變量賦值語句進行縮進。如果想讓你的條件語句塊更清晰一點的話,你可以在圓點之后、if之前加一些空格。示例如下:

  1. .if $(DEBUG) == 1
  2.    $(CFLAGS) = -g
  3. .     ifndef $(DEBUG_FLAGS)
  4.          $(FLAGS) = $(DEBUG_FLAGS)
  5. .     endif
  6. .endif
復(fù)制代碼


exists( < arg > )

下面的例子演示了如何使用exists,以及如何給一個目標(biāo)添加條件語句。如果存在tmp目錄,make將運行-rm tmp/*.o命令。正如你在這個例子中看到的,.if語句僅在clean目標(biāo)中被求值;所運行的命令必須遵從常規(guī)的命令語法。

  1. clean:
  2.     -rm -f *.o *.core
  3. .if exists(tmp)
  4.     -rm tmp/*.o
  5. .endif
復(fù)制代碼


1.9 系統(tǒng)Makefiles,模板以及.include指令

C的一個重要特性就是它的簡單明了的預(yù)處理指令,也就我們常說的#include。這個特性也在make中實現(xiàn)了。所不同的是,如果你想包含另外一個Makefile,那你得在文件末尾包含它,而不是像C那樣在文件頭包含,這是因為變量是按順序進行賦值的。Makefile中又有Makefile可能會造成混淆,不過包含一個Makefile的基本語法卻是很簡單的。注意,在include單詞前面必須要加一個圓點(.)。基本語法如下:

  1. .include file name
復(fù)制代碼


如果要指定一個位于系統(tǒng)make目錄中的Makefile,可以使用尖括號:

  1. .include <file name>
復(fù)制代碼


如果所指定的文件位于當(dāng)前目錄或者是由-I命令行選項指定的目錄中,則使用雙引號,這和C的#include是類似的:

  1. .include "file name"
復(fù)制代碼


在下面的例子中,如果已在project.mk中定義了CFLAGS變量,它將被這里的新聲明所取代。當(dāng)包含多個Makefile時這就可能造成一些麻煩。

  1. .include "../project.mk"

  2. CFLAGS = -g
復(fù)制代碼


FreeBSD系統(tǒng)有很多的系統(tǒng)Makefile可供包含,與之對應(yīng)的是完成各種任務(wù)的例程。在大多數(shù)的FreeBSD系統(tǒng)中,這些Makefile位于/usr/share/mk/目錄里。除此之外,還有一個/etc/defaults/make.conf,它是可以被/etc/make.conf取代的。(細節(jié)請參見man make.conf。)

下面是一個常用的Makefile的簡單列表。如果你正準(zhǔn)備進行大量的內(nèi)核編程或應(yīng)用程序移植,那就好好利用Makefile吧。這些Makefile的形式為bsd.<type>.mk,其中<type>表示這些Makefile的用途。
  • bsd.dep.mk:這是一個用來處理Makefile依賴關(guān)系的非常有用的包含文件。
  • bsd.port.mk:這是一個在構(gòu)建應(yīng)用程序的ports的時候使用的包含文件。

使用.include指令的好處就是你可以把你的工程的Makefile分成很多的片斷。比如,你的工程可以有一個主Makefile,由所有其它的子Makefile進行包含。這個主Makefile可以包含編譯器變量及其所需的選項。這樣一來,就不必在每個Makefile里都指定編譯器和所需選項了,對編譯器名字的引用也因此而得到了簡化。這些公共使用的片斷之后還可以用于其它的Makefile,對編譯程序等例程的修改也將隨之在所有的Makefile中生效。

1.10 高級選項

高級的make選項主要為了增加靈活性。我們建議你仔細閱讀一下make的man page,以便對make有一個深入的了解。下面這些選項是我們最常使用的。

局部變量

make可以使用專門定義的局部變量,其范圍僅限于當(dāng)前目標(biāo)。下面列出了七個這樣的局部變量,同時還給出了它們的System V兼容的老的表示形式。(不建議使用System V的老的表示形式,之所以列出它們,只是出于向后兼容的考慮。)

這個變量就是目標(biāo)的名字:

  1. .TARGET
  2. old style notation: '@'
復(fù)制代碼


這個變量包含了當(dāng)前目標(biāo)的全部源文件的列表:

  1. .ALLSRC
  2. old style notation: '>'
復(fù)制代碼


這個變量是當(dāng)前目標(biāo)所隱含的源文件。它的數(shù)值是這個目標(biāo)的源文件的名字和路徑。(示例見下面的.SUFFIX部分。)

  1. .IMPSRC
  2. old style notation: '<'
復(fù)制代碼


這個變量保存的是已經(jīng)被確定為過期的源文件的列表:

  1. .OODATE
  2. old style notation: '?'
復(fù)制代碼


這個變量的值是不包括后綴和路徑的文件名:

  1. .PREFIX
  2. old style notation: '*'
復(fù)制代碼


這個變量的值是檔案文件的名字:

  1. .ARCHIVE
  2. old style notation: '!'
復(fù)制代碼


這個變量的值是檔案成員的名字:

  1. .MEMBER
  2. old style notation: '%'
復(fù)制代碼


當(dāng)在依賴關(guān)系行中使用這些局部變量的時候,只有.TARGET、.PREFIX、.ARCHIVE和.MEMBER具有該目標(biāo)的值。

另外還有一個非常不錯的指令:

  1. .undef <variable>
復(fù)制代碼


這會讓你非常方便地取消對一個變量的定義。注意,只有全局變量才能取消定義。例如:

  1. .ifdef DEBUG
  2. .undef RELEASE
  3. .endif
復(fù)制代碼


1.11 轉(zhuǎn)換規(guī)則(后綴規(guī)則)

轉(zhuǎn)換規(guī)則規(guī)定了如何去創(chuàng)建一個目標(biāo)。你可以利用這些規(guī)則,省下為每個目標(biāo)文件編寫規(guī)則的時間。語法很簡單:.SUFFIXES: (suffix list)

注意,可能有多個不同的后綴共享相同的轉(zhuǎn)換后綴(.cpp、.cc、.c都可以轉(zhuǎn)換成a.o)。如果沒有列出后綴,make將把之前的所有后綴都刪除。這可能會在你有多個.SUFFIXES規(guī)則的時候造成很大的混亂。我們建議你只使用一個規(guī)則,并把它放在Makefile的底部。把.SUFFIXES語句塊中列出的東西和target進行比較,你就容易理解它的結(jié)構(gòu)了。

  1. .SUFFIXES: .cpp .c .o .sh

  2. .c.o:
  3.      $(CC) $(CFLAGS) -c ${.IMPSRC}

  4. .cpp.o:
  5.      $(CPP) $(CXXFLAGS) -c ${.IMPSRC}

  6. .sh:
  7.      cp ${.IMPSRC} ${.TARGET}
  8.      chmod +x ${.TARGET}
復(fù)制代碼


上面給出的這些規(guī)則將會編譯C和C++源文件。不過對于.sh:規(guī)則而言,它也可以告訴make去創(chuàng)建相應(yīng)的shell腳本。注意,如果想在這個例子中列出某個shell腳本作為目標(biāo),那就不能添加.sh擴展名,不過那個shell腳本本身卻必須是帶有.sh后綴的。

如果我們把不帶.sh后綴的install_script列出來作為一個依賴條件,那就必須存在一個帶有.sh后綴的shell腳本,名字為install_script.sh。也就是說,如果某個文件可以被列出來作為一個目標(biāo),那么在這個文件的存在期間,僅當(dāng)make認(rèn)為那個目標(biāo)和該文件比起來過了期之后才會去創(chuàng)建它,make并不會去創(chuàng)建這個文件。示例見下;更多的信息請參見apps.h的例子:

  1. all: install_script $(OBJS)
  2.      $(CPP) $(CFLAGS) -o $(APP) $(OBJS) $(LINK)
復(fù)制代碼


1.12 有用的命令行選項

下面給出了一些非常容易學(xué)習(xí)和使用的命令行選項。這并不是一個完整的列表,要想知道其它的選項還得去看man page。

-D <variable name to define>

這個選項將在命令行上定義一個變量,如果你的Makefile里面有.ifdef語句,那么使用這個選項就非常方便了。例如:

  1. .ifdef DEBUG
  2.   CFLAGS += -g -D__DEBUG__
  3. .endif
復(fù)制代碼


于是,當(dāng)你運行命令make -D DEBUG的時候,make就會正確的設(shè)置CFLAGS,在編譯你的應(yīng)用程序的時候也把調(diào)試語句編進去。

-E < variable name to override >

這個選項將用環(huán)境變量的值取代Makefile中給變量賦的值。在使用這個選項之前,需要先設(shè)置你的shell中的環(huán)境變量。例如:

  1. bash $ CFLAGS="-O2 -Wall" make -E CFLAGS
復(fù)制代碼


-e

和大寫形式類似,-e將用環(huán)境變量取代Makefile中的所有變量。如果某個變量沒有定義相應(yīng)的環(huán)境變量,則按正常方式賦值。

-f <makefile to use>

這個選項使你可以在命令行上指定Makefile,這一點在你需要多個Makefile時很有用。例如:

  1. bash$  make -f Makefile.BSD
復(fù)制代碼


-j < number of max_jobs >

這個選項使你可以指定make能夠派生出多少個job來。一般來說make只會派生出一個,但對于一個非常大的工程而言,如果想讓你的硬件物盡其用的話,那就指定4個吧,如下:

  1. make -j 4 world
復(fù)制代碼


如果超過了4個,有時反而會延長執(zhí)行時間,不過有些人倒是可能覺得CPU被六個或更多的job折騰的樣子很有趣。

-n

這個選項對于Makefile的調(diào)試很有用,它可以讓你看到make究竟會執(zhí)行哪些命令,但又不會實際去執(zhí)行它們。對于有很多命令的大型工程,最好是把輸出重定向到一個外部文件中,否則就會有浩如煙海的命令顯示出來。示例如下:

  1. bash $ make -n >> make_debug.log 2>&1
復(fù)制代碼


-V < variable name >

這個選項將基于全局的上下文打印變量的值。與前一個選項類似,make也不會去構(gòu)建任何目標(biāo)。你可以在命令行上指定多個-V選項,如下:

  1. make -V CFLAGS -V LINK -V OBJS
復(fù)制代碼


1.13 一個最后的例子

下面列出的Makefile是一個可以重復(fù)使用的Makefile的例子。在你包含了它之后,它就知道從列出的.SUFFIXES規(guī)則中得知如何去編譯C++源文件。它還知道如何去安裝應(yīng)用程序和清除開發(fā)目錄。這顯然并不是一個很好理解的Makefile,但它卻是一個很好的創(chuàng)建通用模板風(fēng)格的Makefile的范例,這種Makefile包含了那些用于開發(fā)的公共例程。這不只是節(jié)省了在創(chuàng)建每個Makefile時重復(fù)輸入這些公共規(guī)則的時間,它還能讓開發(fā)人員重復(fù)使用已知的好例程。

  1. ########################################################
  2. #
  3. # FILE: Makefile
  4. #
  5. # AUTHOR: Nathan Boeger
  6. #
  7. # NOTES:
  8. #  This is a generic Makefile for *BSD make, you will
  9. #  need to customize the listed variables below inside
  10. #  the Makefile for your application.
  11. #
  12. # INSTALL_DIR = name of the directory that you want to install
  13. #   this applicaion (Ex: /usr/local/bin/ )
  14. #
  15. # APP          = name of the application
  16. #
  17. # C_SRC      = C source files (Ex: pstat.c )
  18. #
  19. # CPP_SRC  = CPP source files (Ex: node.cpp)
  20. #
  21. #
  22. # $Id: ch01.html,v 1.5 2004/08/10 14:41:39 nathan Exp $
  23. #########################################################

  24. # Make the OBJ's from our defined C & C++ files
  25. .ifdef CPP_SRC
  26. OBJS            =       ${CPP_SRC:.cpp=.o}
  27. .endif

  28. .ifdef C_SRC
  29. OBJS            +=      ${C_SRC:.c=.o}
  30. .endif

  31. # define the  Compiler. The compiler flags will be appended to
  32. # if defined, else they are just assigned the values below
  33. CPP             =        g++
  34. CFLAGS          +=       -Wall -Wmissing-prototypes -O
  35. LINK            +=       -lc

  36. # Add a debug flag.
  37. .ifdef DEBUG
  38.   CFLAGS += -g
  39. .endif

  40. # Targets
  41. all: ${OBJS}
  42.     $(CPP) $(CFLAGS) -o $(APP) ${OBJS} $(LINK)

  43. depend:
  44.     $(CPP) -E -MM ${C_SRC} ${CPP_SRC}  > .depend

  45. #######################################################
  46. #
  47. #        INSTALL SECTION
  48. #
  49. # install will copy the defined application (APP) into the
  50. # directory INSTALL_DIR and chmod it 0755
  51. # for more information on install read MAN(1) install
  52. ########################################################
  53. install: all
  54.     install -b -s $(APP) $(INSTALL_DIR)
  55.   
  56. clean
  57.      rm -f $(APP) *.o *.core

  58. # SUFFIX RULES
  59. .SUFFIXES: .cpp .c .o

  60. .c.o:
  61.        $(CPP) $(CFLAGS) -c ${.IMPSRC}
  62. .cpp.o:
  63.        $(CPP) $(CFLAGS) -c ${.IMPSRC}
復(fù)制代碼

下面給出的Makefile是需要你在工程目錄內(nèi)部創(chuàng)建的:

  1. #######################################################
  2. #       PROJECT Makefile
  3. #
  4. # This is what the programs makefile would look like
  5. # These are the only variables you will need to define
  6. ######################################################

  7. APP            = myapp
  8. C_SRC          = debug_logger.c
  9. CPP_SRC        = myapp.cpp  base_classes.cpp
  10. INSTALL_DIR    = /usr/local/bin/

  11. # And include the template Makefile, make sure its
  12. # path is correct.  

  13. .include "../../bsd_make.mk"
復(fù)制代碼






第二章  自舉BSD

翻譯(meilincore@chinaunix)

2.1 自舉BSD
自舉:在很少或者沒有協(xié)助的情況下主動,努力的提升和發(fā)展(把她自己自舉到頂點)
Bootstrap : to promote or develop by initiative and effort with little or no assistance <bootstrapped herself to the top> www.m-w.com

自舉計算機是指加載操作系統(tǒng)的過程,該過程為:初始化硬件,讀取一小部分代碼到內(nèi)存并執(zhí)行.這一點代碼接著加載一個大的操作系統(tǒng).一旦操作系統(tǒng)被加載,它就需要創(chuàng)建自己的整個環(huán)境.這個過程,稱做自舉計算機,是一個復(fù)雜的,高度平臺相關(guān)的過程.

本章我們將從細節(jié)上探索FreeBSD在i386平臺上的自舉過程.相關(guān)的概念和過程與NetBSD和OpenBSD在i386平臺上的自舉程序相類似.注意一些匯編代碼對于從實際上完成i386基本系統(tǒng)的啟動任務(wù)是必須的.然而,我們不會從細節(jié)上回顧匯編代碼而是主要集中在高層概念上,因此就算你不是專家討論也是有意義的.

注意:雖然本章中討論的一些概念,特別是"實模式"和"保護模式",并不存在與現(xiàn)代象PPC或Alpha這些硬件構(gòu)架當(dāng)中.但是i386 BSD基本系統(tǒng)是目前為止最為廣泛的系統(tǒng)并會繼續(xù)如此(一個值得注意的例外是Mac OS X),而且適應(yīng)很多情況.如果你對啟動系統(tǒng)細節(jié)有興趣,你可能需要自定義內(nèi)核,自定義文件系統(tǒng),設(shè)備驅(qū)動.同時.i386構(gòu)架還廣泛應(yīng)用與嵌入式系統(tǒng).給定一個安裝平臺(Given the install base),i386平臺及其成果將繼續(xù)在未來一些年里適用.甚至新的64位CPU到目前我們所知道的為止還是一樣的啟動過程.

2.2 FreeBSD的自舉過程

FreeBSD使用一種三段啟動過程,當(dāng)你打開計算機電源或者重新啟動.一旦BIOS完成系統(tǒng)檢測.它將從DISK0加載第一個軌道到內(nèi)存中.(每一個過程使用512字節(jié)的程序.剛好占用硬盤的一個塊)第一軌道就是我們熟知的主引導(dǎo)記錄(MBR),也就是boot0,第一個被計算機加載執(zhí)行的程序.第二個程序,boot1,又是固定大小為512字節(jié)并知道如何讀取slice信息和加載boot2.一旦boot2被加載它就有能力直接啟動系統(tǒng)或者加載加載器(loader program),加載器固定大小為512字節(jié).相當(dāng)精致并被設(shè)計來允許對系統(tǒng)精確啟動作更多的控制.

boot0

從BIOS加載的第一個程序,boot0,是一個固定大小為512字節(jié)的小程序,位于主引導(dǎo)記錄(MBR).你可以在/usr/src/sys/boot/i386/boot找到該程序的源代碼.當(dāng)然現(xiàn)代計算機的BIOS可以設(shè)置成從各種不同的驅(qū)動器包括光驅(qū),軟驅(qū)和IDE硬盤啟動.對于本章來說,我們將假設(shè)計算機是從第一個硬盤啟動的.也就是磁盤驅(qū)動器0,C:,或者,對BIOS而言,0X80.

從第一個磁盤的第一個扇區(qū),512字節(jié)被讀到內(nèi)存位置0X7C00.然后,BIOS會檢驗內(nèi)存位置0X7DFE的數(shù)字0XAA55(啟動塊代碼的最后兩個字節(jié)).這個位置索引數(shù)字0XAA55對i386是如此重要而被賦予了一個適當(dāng)?shù)拿?---魔數(shù).這意味著,只有在這個數(shù)字存在于內(nèi)存位置0X7DFE的情況下BIOS才會把控制權(quán)轉(zhuǎn)移給boot0被安置的內(nèi)存位置0X7C00.

這提出了在Intel i386系統(tǒng)上編寫啟動代碼的一個要點:記著你代碼中的第一個內(nèi)存位置處(0X0)必須是一條指令.并且,當(dāng)BIOS轉(zhuǎn)移控制權(quán)給內(nèi)存位置0X7C00的時候該位置必須包含一條指令.這可以是一個簡單的跳轉(zhuǎn)到其他位置或者啟動程序主過程的入口.另外當(dāng)啟動代碼被執(zhí)行時你對CPU到底在干什么是完全沒有控制的.因為這時候寄存器的狀態(tài)也是未知的,你別指望已經(jīng)設(shè)置好了適當(dāng)?shù)亩魏蜅<拇嫫?這個小工作必須由啟動代碼來完成.因為目前操作系統(tǒng)還沒有加載起來;所有的I/O必須通過BIOS過程來完成(Intel CPU 文檔有一個完整列表)

在boot0被加載并獲得控制權(quán)以后,它將設(shè)置自己的寄存器和棧信息.接著,boot0重定位自己到一個低的內(nèi)存位置并跳轉(zhuǎn)到它主過程的新的偏移地址.這時候boot0必須還能夠定位和啟動其他磁盤和分區(qū),在它的最后,boot0代碼有一個小的常見的可啟動類型列表,它們都必須在其最后兩個字節(jié)包含魔數(shù)才可啟動.

最終,當(dāng)boot0完成搜索可啟動磁盤和分區(qū)以后,將提示用戶作一個選擇.如果在一個短的時間段里面沒有任何選擇或者某個鍵被按下,boot0將加載下一個啟動塊到內(nèi)存并轉(zhuǎn)移控制權(quán)給它.還是那樣,這可以是任何操作系統(tǒng)的啟動代碼----你可以設(shè)置它加載Linux的自舉代碼或者甚至DOS.對BSD而言,下階段的啟動程序是boot1.

boot1

與boot0相似,boot1是一個非常簡單且總共才512字節(jié)的程序;它也必須在最后兩個字節(jié)包含魔數(shù).其目的是定位和讀取FreeBSD磁盤分區(qū),接著定位和加載boot2.

雖然在大多數(shù)情況下,boot1是被boot0加載的,但是這個順序不必是唯一存在的選項.由于FreeBSD的靈活性你可以使用被稱為專用磁盤的選項(說的就是那個更為聲名狼籍的危險的專用磁盤)一個專用磁盤就是整個磁盤,或者BIOS的每一個扇區(qū)都屬于FreeBSD.通常,你會在PC機磁盤上發(fā)現(xiàn)一個fdisk表,或者一個slice表,用來允許多個操作系統(tǒng)從單個PC機磁盤上啟動.你可以選擇使用一個專用磁盤并且直接從boot1啟動;boot0完全沒有必要安裝在磁盤上.不管你采用那種實現(xiàn),boot1都是非常重要的啟動塊并需要被加載

Boot1被加載到內(nèi)存位置0X7C00并操作在實模式;環(huán)境沒有設(shè)置,寄存器也在未知狀態(tài).boot1必須設(shè)置好棧,定義所有的寄存器,并使用BIOS接口來做所有的I/O.一旦boot1被加載到內(nèi)存并且控制權(quán)已經(jīng)轉(zhuǎn)移過來.它必須在自己的第一個內(nèi)存位置(0X0)包含一條指令.所有這些都成功以后,boot1將讀取系統(tǒng)磁盤搜索boot2.

一旦boot2被加載.程序必須設(shè)置好boot2的環(huán)境;boot2是一個BTX客戶端(boot Extender 啟動擴展器)并且比前面的boot0和boot1稍微復(fù)雜一些.boot1需要加載boot2到內(nèi)存位置0X9000并且它的入口是0X9010.然后,甚至在boot1加載并轉(zhuǎn)移控制權(quán)給boot2以后,還有一個被boot2使用的過程存在于boot1.如果你閱讀boot1的源代碼你會注意到一個函數(shù)呼叫xread.這個函數(shù)用來通過BIOS從磁盤讀取數(shù)據(jù).因此,boot1在內(nèi)存中的位置是非常重要的并且boot2必須知道它的位置才能正常工作.

boot2

目前為止我們已經(jīng)加載了兩個啟動塊和一個大的程序到內(nèi)存中.轉(zhuǎn)移了兩次控制權(quán)且每次都重新設(shè)置了一個小的環(huán)境(棧,段寄存器等等...),并通過BIOS執(zhí)行了一些有限的I/O.我們依然沒有觸及到加載操作系統(tǒng)的點子上來.如果你看過FreeBSD啟動過程中的屏幕,到目前為止你可能看見F1和可愛的ASCII螺旋線.你可能印象當(dāng)中不會覺得這都是,但他的卻是原原本本,精確原生的匯編代碼讓這個啟動過程看起來如此幽雅而輕巧

現(xiàn)在到最后一個自舉過程了,boot2.最后階段很簡單并且可以發(fā)生兩件事中的一件:boot2加載加載器(我們將在下一節(jié)討論這個)或者,boot2加載內(nèi)核并直接啟動而完全不使用加載器.如果你曾經(jīng)打斷boot2的加載過程,你可能已經(jīng)看過這個boot2輸出到屏幕的東西:

  1. >>FreeBSD/i386 BOOT
  2. Defualt: 0:ad(0,a)/boot/loader
  3. boot:
復(fù)制代碼


如果你按下回車,boot2會簡單的加載默認(rèn)加載器,象列出的那樣.當(dāng)然,如果你只是鍵入"boot kernel"接著它就會加載內(nèi)核(/kernel)并啟動.你可以,當(dāng)然,改變這些默認(rèn)值.如果你想找到更多信息請閱讀boot( 8 )的文檔

早先我們提到過boot2是一個BTX客戶端(Boot Extender 啟動擴展器).這留下什么呢?BTX提供者是一個基礎(chǔ)的虛擬86尋址環(huán)境.該來討論一下Intel硬件內(nèi)存尋址的歷史了.

目前為止我們都避免提及內(nèi)存尋址方式.這可能會比較混淆因為Intel CPU要忍受歷史問題,并且啟動代碼設(shè)計通常是留給那些非寫不可的開發(fā)人員.除非你要移植一個系統(tǒng)到新的構(gòu)架因而你的代碼必須完全是平臺依賴的,通常,一個程序員永遠也不會被派去寫一個啟動加載器.然而啟動過程對于需要編寫設(shè)備驅(qū)動代碼或者內(nèi)核相關(guān)編程的開發(fā)人員來說是非常重要的.這就是一些開發(fā)人員會在這里碰到BTX加載器.

大約從8088到80186,Intel處理器只有一種方式進行內(nèi)存尋址,叫做實模式.這些早期的CPU有巨大的16位寄存器和20位內(nèi)存地址.然后問題來了,你如何在16位的寄存器中構(gòu)成一個20位的地址呢?答案就是,用兩個16位寄存器,一個提供基地址而另一個提供這個基地址的偏移量.基地址寄存器被左移4位這樣當(dāng)兩個合并的時候一個令人驚異的20位地址就被算出來了.用所有這些靈巧的段寄存器和位移,早期的Intel處理器可以尋址總共1M空間.今天這甚至還不夠容納一個Word文檔,作為一個傲慢的例子.

一旦80386席卷而來,尋址1M就不夠了;用戶需要更多的內(nèi)存并且程序開始使用更多的內(nèi)存.一種新的尋址模式叫做保護模式被發(fā)明了出來.新的保護模式允許尋址高達4G內(nèi)存.

新方式的另一個好處就是對于匯編程序員來說更容易實現(xiàn).主要的區(qū)別就是你的擴展寄存器(它們還是那些16為寄存器不過386現(xiàn)在是32位了)可以包含一個完整的32位地址.甚至你以前的段寄存器現(xiàn)在都被保護了.程序不能寫入或者讀取他們.這些段寄存器現(xiàn)在被用來定位你的內(nèi)存中的真實地址,這個過程包括權(quán)限驗校位(讀寫等等...)并引入了MMU(memory management unit內(nèi)存管理單元)

現(xiàn)在回到BTX客戶端的問題.我們使用BTX程序有什么好處呢?很簡單:靈活性.BTX提供了足夠的服務(wù)因此一個小的,擁有漂亮接口的程序可以被寫出并能在加載內(nèi)核的過程中非常靈活.在FreeBSD系統(tǒng)中這就是加載器.從下一節(jié)有將看到加載器真的是多么的漂亮和靈活.因此本節(jié)余下的部分我們將涵蓋基本的BTX服務(wù).

BTX服務(wù)可以被歸類到兩個基本組.第一組是由直接函數(shù)呼叫(類似于系統(tǒng)呼叫)提供的系統(tǒng)服務(wù).另外一組是不由客戶端直接呼叫的環(huán)境服務(wù).這些服務(wù)類似于一個操作系統(tǒng),然而BTX程序是象單任務(wù)環(huán)境那樣操作的.

直接呼叫提供的BTX服務(wù)由兩個系統(tǒng)呼叫組成.exit 和 exec.當(dāng)exit被呼叫的時候BTX加載器結(jié)束并且系統(tǒng)重啟.最后的一個系統(tǒng)呼叫exec將轉(zhuǎn)移控制權(quán)給提供的地址.在個控制權(quán)移交是在超級用戶模式下完成,并且新的程序可以離開被保護的CPU模式

BTX加載器提供的環(huán)境服務(wù)是非常基礎(chǔ)的.BTX加載器處理所有的中斷.這些中斷被送到合適的BIOS處理器.BTX也模擬BIOS擴展內(nèi)存?zhèn)鬏敽艚?并且最后還模擬一些匯編指令,它們是pushf,popf,cli,sti.iret和hlt.

最后注意:所有編寫來運行在BTX環(huán)境中的程序都必須被編譯并連接到btxld.更多信息請閱讀BTX加載器聯(lián)機手冊

2.3加載器

最后的啟動階段是由加載器組成的.加載器程序是一個標(biāo)準(zhǔn)命令(引用為"內(nèi)建命令")和一個Forth解釋器的聯(lián)合(基于ficl).加載器將允許用戶同系統(tǒng)啟動進行交互或者允許系統(tǒng)恢復(fù)和維護.通過加載器用戶可以選擇載入或者卸載內(nèi)核模塊.用戶也可以設(shè)置或者取消設(shè)置相關(guān)變量,比如rootdevice(根設(shè)備)和module_path(模塊路徑).這些也可以在/boot/loader.conf里面改變.加載器讀取的默認(rèn)文件在/boot/defaults/loader.conf.默認(rèn)文件也包含了許多可用的選項.這寫文件都被構(gòu)造得和/etc/rc.conf類似.

加載器對于內(nèi)核和設(shè)備驅(qū)動除錯是非常有用的.通過加載器你可以通知內(nèi)核在除錯器起用的狀態(tài)下啟動.或者,你可以為設(shè)備驅(qū)動測試加載相關(guān)的內(nèi)核模塊.如果你準(zhǔn)備編寫任何內(nèi)核模塊或者設(shè)備驅(qū)動的話你最好是閱讀加載器的所有文檔.首先從聯(lián)機手冊開始,然后復(fù)習(xí)/boot/loader.conf中的所有選項.一路上加載器在你需要擴展BSD或者診斷內(nèi)核崩潰的時候?qū)⒎浅S杏?

2.4開始內(nèi)核服務(wù)
我們終于到了內(nèi)核已經(jīng)被載入內(nèi)存,CPU的控制權(quán)的也被傳給它的階段了.一旦內(nèi)核被加載,它需要從初始化開始運行并準(zhǔn)備系統(tǒng)進入多任務(wù)模式.該初始化包括三個主要組成部分.前兩個是機器相關(guān)的,由c和匯編混合編寫成.前兩個階段準(zhǔn)備系統(tǒng)和初始化CPU內(nèi)存管理單元(MMU,如果它存在)并處理硬件初始化.最后一個階段繼續(xù)建立基本內(nèi)核環(huán)境并讓系統(tǒng)準(zhǔn)備好運行進程1(/sbin/init).前兩個階段是高度平臺依賴的.因為每一種構(gòu)架都有相關(guān)需要.我們將提供一個對這兩個階段的高層次瀏覽并在本書稍后當(dāng)我們涵蓋到設(shè)備驅(qū)動的時候從細節(jié)上來了解這些概念.

階段 1 & 2 內(nèi)核裝配和c起始過程

雖然內(nèi)核一旦被加載就不再對系統(tǒng)做任何假設(shè),加載器還是會傳送一些信息給內(nèi)核,比如啟動設(shè)備和啟動標(biāo)記.另外,內(nèi)核必須創(chuàng)建自己的環(huán)境并讓系統(tǒng)準(zhǔn)備好運行進程0(下面解釋).這些任務(wù)包括CPU檢測,運行時刻任務(wù)建立,和內(nèi)存數(shù)量檢測.

CPU識別是非常重要的一步,因為每個平臺可以有多種不同的CPU(i386是其中之一),不是所有的CPU都會支持同樣的特性.比如,拿MMX指令集來說,雖然它對于內(nèi)核來說不是一個重要特性,但是對于浮點單元就是,因此如果這個特性不被該CPU所支持的話就必須用軟件來模擬.這對于所有其他不被支持的特性和已知錯誤或者CPU的固有特性來說都是成立的

第二個階段會初始化系統(tǒng)硬件和內(nèi)存.這包括探測硬件,初始化I/O設(shè)備,為內(nèi)核結(jié)構(gòu)分配內(nèi)存,建立系統(tǒng)消息緩存.該階段你會看到啟動屏幕中閃過很多硬件列表.按照BSD傳統(tǒng),這個階段是通過呼叫cpu_startup()函數(shù)來初始化.

第3階段 和 進程 0

一旦cpu_startup()函數(shù)返回,內(nèi)核就需要建立進程0.進程0通常稱為交換器,如果你運行ps命令會看到它在活動.然而從感官上來說并沒有這樣一個名為swapper的二進制文件附屬于該進程.這對于其他四個在現(xiàn)代FreeBSD系統(tǒng)上找到進程都是成立的:pagedaemon,vmdaemon,bufdaemon和syncer.為避免復(fù)雜化,我們只說這些進程是VM子系統(tǒng)的一部分;我們在進程一章中再討論它們.著重理解他們是在啟動過程中由內(nèi)核創(chuàng)建而不是文件系統(tǒng)中的二進制程序,并且是相當(dāng)平臺依賴性的.他們是用c語言寫成并在起始平臺環(huán)境初始化完畢以后開始的

init和系統(tǒng)shell腳本

在所有的匯編和平臺依賴性代碼執(zhí)行完畢以后,內(nèi)核終于執(zhí)行第一個真正的程序:/sbin/init.該程序是相當(dāng)簡短的(在FreeBSD上,總共約1,700行).象我們在BSD進程一章所討論的那樣,這就是那個所有進程都遺傳自的進程.這樣設(shè)計的實力在于,因為/sbin/init只是文件系統(tǒng)中的一個二進制程序,你可以自己寫一個自定義版的.對于/sbin/init在啟動時的主要目標(biāo)就是運行系統(tǒng)啟動腳本并為系統(tǒng)進入多用戶模式做準(zhǔn)備.留意信號:/sbin/init應(yīng)當(dāng)能優(yōu)雅的處理信號,否則你的系統(tǒng)可能結(jié)束在/sbin/init程序的一個奇怪的狀態(tài)并且在系統(tǒng)啟動時崩潰掉.同時運行中/sbin/init可以接受信號來執(zhí)行某些任務(wù).比如,如果你想讓系統(tǒng)不為某個相關(guān)的終端生產(chǎn)進程.象列在/etc/ttys里面的.你可以標(biāo)記想要的終端為關(guān)閉狀態(tài)并執(zhí)行下面的命令使init讀取/etc/ttys并只為列出的標(biāo)記狀態(tài)為開的終端生產(chǎn)進程.

  1. bash$kill -HUP 1
復(fù)制代碼


注意除非你很小心,否則可能會以一個你不能登錄的系統(tǒng)告終(到關(guān)于信號的一章里面查看詳細信息.)

init程序會在啟動過程中設(shè)置系統(tǒng)以進入多用戶模式.這是很靈巧的.引入了象開始每一個守護進程并設(shè)置網(wǎng)絡(luò)信息這樣枯燥的任務(wù).在UNIX系統(tǒng)中.有一些這樣的途徑,主要引入shell腳本.在某些版本的Linux和System V系統(tǒng)中,對應(yīng)與某個運行級別的可啟動腳本位于/etc/rc<n>.d.然而BSD使用了簡單得多的辦法.這就是在/etc/里面找到的的rc腳本

這些腳本通常是不能被編輯的,而是,在/etc/fc.conf里面設(shè)置變量(來改變行為).象PicoBSD的自定義安裝你可能需要建立自己的腳本;PicoBSD是高度磁盤空間敏感的,并有相關(guān)的文件系統(tǒng)需要.一個重要的提示,/usr/local/etc/rc.d/文件夾是特別的.該文件夾的特別意義在于其中找到的每個.sh可執(zhí)行文件都會在/etc/rc腳本之后被執(zhí)行.為了方便系統(tǒng)管理這個文件夾替代了老的/etc/rc.local文件(/etc/rc.local是以前在系統(tǒng)啟動尾聲的時候啟動自定義腳本或者程序的辦法)

BSD rc腳本包括值得注意的條目列表如下:

  1. /etc/rc - 主腳本并且第一個被呼叫,掛接文件系統(tǒng)并運行所有需要的rc腳本
  2. /etc/rc.atm - 用來配置ATM網(wǎng)絡(luò).
  3. /etc/rc.devfs - 設(shè)置/dev/權(quán)限和連接
  4. /etc/rc.diskless1 - 第一個diskless客戶端腳本
  5. /etc/rc.diskless2 - 第二個diskless客戶端腳本
  6. /etc/rc.firewall - 用于防火墻規(guī)則
  7. /etc/rc.firewall6 - 用于IPV6防火墻規(guī)則
  8. /etc/rc.i386 - intel系統(tǒng)相關(guān)需要
  9. /etc/rc.isdn - isdn網(wǎng)絡(luò)設(shè)置
  10. /etc/rc.network - IPV4網(wǎng)絡(luò)設(shè)置
  11. /etc/rc.network6 - IPV6網(wǎng)絡(luò)設(shè)置
  12. /etc/rc.pccard - 膝上電腦pc card控制器
  13. /etc/rc.serial - 設(shè)置串口設(shè)備
  14. /etc/rc.sysctl - 在系統(tǒng)啟動時設(shè)置sysctl選項
  15. /usr/local/etc/rc.d/ - 存有自定義啟動腳本的普通文件夾
復(fù)制代碼


這里有一個例子:

如果你想讓rsync作為守護進程這個腳本會起作用:

  1. #!/bin/sh

  2. if [ -x /usr/local/bin/rsync ]; then
  3.         /usr/local/bin/rsync --deamon
  4. fi
復(fù)制代碼

這將首先檢測rsync是否存在然后以守護模式運行它



第三章: 進程和內(nèi)核服務(wù)

譯者:孫軒@chinaunix.net


該章節(jié)的例程:
http://www.khmere.com/freebsd_book/src/03/index.html

3.1 進程和內(nèi)核服務(wù)
process:一系列用于實現(xiàn)目標(biāo)的行為和操作;特別指:制造中指定的連續(xù)的工作和處理。

  前面的章節(jié)有提到BSD系統(tǒng)的啟動過程,F(xiàn)在讓我們看看一旦內(nèi)核被導(dǎo)入并運行會發(fā)生什么。像大部分其他的現(xiàn)代操作系統(tǒng),BSD是一個多用戶多任務(wù)操作系統(tǒng),這意味著它支持多用戶運行不同的多進程來使用系統(tǒng)資源。進程的概念提出了一個對操作系統(tǒng)管理的所有活動的有力的提取。這個概念作為在多道程序設(shè)計環(huán)境中面向工作的一個特定術(shù)語是在19世紀(jì)60年代由Multics(一個失敗的分時系統(tǒng)計劃)的設(shè)計者提出的。

3.2 調(diào)度

  一個進程是一個運行程序的單獨實例。例如,當(dāng)你在BSD系統(tǒng)中運行Netscape,它將在被運行時創(chuàng)建一個進程。
如果有3個用戶登陸B(tài)SD系統(tǒng)并全部同時運行相同的Netscape程序,每一個用戶將擁有區(qū)別于其他用戶的自己的
Netscape實例。BSD系統(tǒng)可以支持同時多個這樣的進程。每一個進程將和一個進程標(biāo)志號(PID)關(guān)聯(lián)。這些進程
需要資源并且可能不得不使用一些設(shè)備比如外部存儲。

  當(dāng)多個程序在一個系統(tǒng)上運行的時候,操作系統(tǒng)操控出他們都在運行的假象。操作系統(tǒng)特定的調(diào)度算法將管理
程序的優(yōu)先權(quán)指派。該領(lǐng)域的計算科學(xué)是廣闊的,和十分專業(yè)的。更多的信息請看資源章節(jié)。

  操作系統(tǒng)實際上將運行的多進程不斷調(diào)入或調(diào)出CPU(s),通過這種方法,每個進程得到一段特定的CPU(s)運行
時間。這段時間被叫做時間片。時間片的長短基本上由內(nèi)核的調(diào)度算法決定。該算法有一個可以調(diào)整的值叫‘nice’,它提供給進程修改運行優(yōu)先權(quán)的程序能力。這些優(yōu)先權(quán)的值如下:
      Nice values                Priority
        -20    - 0            Higher priority for execution
         0      - 20          Lower priority for execution
      
注意:一個高的nice值導(dǎo)致低的運行級別,這可能看上去有點怪異。然而,考慮到下面這個計算:
(scheduling algo calculation) + (nice value)
這個簡單的算術(shù)表明:添加一個負(fù)值到一個正值將得到一個較小的數(shù),而當(dāng)這些數(shù)被排序的時候,較小的數(shù)將排在
運行隊列的前面。

所有進程默認(rèn)的nice值是0,運行的進程可以提高自己的nice值(就是降低自己的優(yōu)先權(quán)),但是只有以root運行的
進程可以降低nice(就是提高優(yōu)先權(quán))。BSD系列提供了兩個基本的借口用于改變和獲得這些值。它們是:


  1. int getpriority(int which, int who);
  2. int setpriority(int which, int who, int prio);
復(fù)制代碼



  1. int nice(int incr);
復(fù)制代碼


nice函數(shù)會把調(diào)用它的進程的nice值設(shè)置成傳給它的參數(shù)incr的值?梢钥吹絥ice函數(shù)是比較不好用的,因為它不是
十分靈活,實際上nice函數(shù)不過是對前兩個函數(shù)的封裝。首選的方法是使用setpriority()。

因為可能的的優(yōu)先權(quán)值可以為-1,所以getpriority的返回值不能作為程序運行成功與否的判斷,而應(yīng)該檢查errno(錯誤號)是否被設(shè)置。因此在調(diào)用getpriority的時候要清空該值(errno)。(更多的關(guān)于使用errno信息,可以使用man(2)察看關(guān)于erron的頁面和讀例程:http://www.khmere.com/freebsd_book/src/03/nice_code.c.html)setpriority 和 getpriority可以通過設(shè)置‘which’和‘who’的值作用于外部進程的優(yōu)先級,這以后在論述。

例程nice_code.c示范如何獲取和設(shè)置當(dāng)前進程的nice值。如果它被root以-20的命令行參數(shù)運行,系統(tǒng)將看上去沒有反應(yīng)。這是因為這個程序擁有了最高的運行級別,將會比系統(tǒng)優(yōu)先。所以當(dāng)設(shè)置丟了小于0的值時,需要謹(jǐn)慎使用和更多的耐心。取決于CPU,完全運行完可能要消耗20分鐘。建議使用time指令來運行程序,如下:

  1. bash$ time nice_code 10
復(fù)制代碼

然后,調(diào)整參數(shù)的值。這樣進程消耗了多少運行時間就很明顯了。比如調(diào)整參數(shù)使之小于0:

  1. $ time nice_code -1
復(fù)制代碼

下面把參數(shù)調(diào)大:

  1. bash$ time nice_code 20
復(fù)制代碼


同時,嘗試用其他非root用戶運行程序,并使用小于0的參數(shù)。系統(tǒng)將會拒絕操作。只有root運行的進程可以降低他們的nice值。(因為Unix系統(tǒng)時多用戶系統(tǒng),每個進程應(yīng)該獲得合理的CPU時間片,只有root可以改變進程的優(yōu)先級到0以下,這樣可以避免用戶通過降低優(yōu)先級來獨占CPU資源.)

3.3 系統(tǒng)進程
保護模式的概念在前一章已有介紹,簡單的說,它是現(xiàn)代CPU系列支持的一個特定的模式,通過該模式操作系統(tǒng)可以保護內(nèi)存.據(jù)此,現(xiàn)在有兩種這樣的操作模式。一是內(nèi)核態(tài),意味著進程將運行在內(nèi)核的內(nèi)存空間,并因此,以有內(nèi)核特權(quán)的保護模式運行于CPU中。二是用戶態(tài),即運行并且不是運作在內(nèi)核保護模式的一切。

這區(qū)別是十分重要的,因為所有的給定進程都在內(nèi)核態(tài)和用戶態(tài)使用資源。這些資源有多種格式,就如用來標(biāo)志進程的內(nèi)核結(jié)構(gòu),進程指派的內(nèi)存,打開的文件,和其他執(zhí)行段。

在一個FreeBSD系統(tǒng)中,有一些關(guān)鍵的進程幫助內(nèi)核來執(zhí)行任務(wù)。這些進程中的一部份是完全運行于內(nèi)核空間,而有些運行于用戶態(tài)。這些進程如下:

PID  Name
0    swapper
1    init
2    pagedaemon
3    vmdaemon
4    bufdaemon
5    syncer

以上所有的進程除了init,都是運行于內(nèi)核。這意味著它們不存在相應(yīng)的二進制程序。這些進程有點類似用戶態(tài)進程,并且因為它們在內(nèi)核內(nèi)運行,他們運作于內(nèi)核特權(quán)模式。這種體系結(jié)構(gòu)是出于多種設(shè)計考慮。例如,pagedaemon進程,為了減少負(fù)載,只在系統(tǒng)缺乏內(nèi)存時被喚醒。因此如果系統(tǒng)許多空閑的內(nèi)存,它就沒有被喚醒的必要。如此,比這個進程運行于用戶態(tài)的優(yōu)點就是除非真正需要,可以避免使用CPU。但是,它在系統(tǒng)增加了一個需要被調(diào)度的進程。不過這些調(diào)度計算消耗如此小幾乎可以忽略不計。

init進程是所有進程的宗主進程。除了運行于內(nèi)核態(tài)特權(quán)模式的進程,每個進程都是init的后裔。并且,僵尸或孤兒進程都會被init接管。它也運行一些管理任務(wù),比如調(diào)用產(chǎn)生系統(tǒng)ttys的gettys,和運行系統(tǒng)的有序關(guān)機。

3.4 進程創(chuàng)建和進程ID系統(tǒng)

就像上面所說,當(dāng)一個進程被運行,內(nèi)核指派給它一個唯一PID。這個PID是個正整數(shù)并且它的值在0到PID_MAX之間,由系統(tǒng)決定。(對于FreeBSD,在/usr/include/sys/proc.h中設(shè)定PID_MAX為99999。)內(nèi)核將用下一個相續(xù)的可用的值來指派PID。因此,當(dāng)PID_MAX到達的時候,值將會循環(huán)。這個循環(huán)是重要的,當(dāng)使用PID于當(dāng)前進程統(tǒng)計的時候。
每一個在系統(tǒng)上運行的進程都是由其他進程產(chǎn)生的。這是有一些系統(tǒng)調(diào)用完成的,將在下一章論述。當(dāng)一個進程創(chuàng)建一個新的進程,原進程就成為父進程,新進程就是子進程。這種父子關(guān)系提供了很好的類推-每一個父進程可以有很多子進程而父進程們是由另外一個進程派生的。進程可以通過getpid/getppid函數(shù)得到自己或他們父進程的PID。

進程也可以用進程組來分組。這些進程組可以通過一個唯一的grpID來標(biāo)志。進程組作為一個機制被引入BSD是為了使shells能夠控制工作?聪旅孢@個例子:

  1. bash$ ps auwx  | grep root  | awk {'print $2' }
復(fù)制代碼

這些程序,ps,grep,awk和print都歸屬于一個相同的組,這允許所有的這些指令可以通過一個單一的工作來控制。

一個進程可以獲得它的組ID/和父ID通過調(diào)用getpgrp或getppid:

  1.   #include <sys/types.h>
  2.   #include <unistd.h>
  3.   
  4.      pid_t   getpid(void);
  5.      pid_t   getppid(void);
  6.      pid_t   getpgrp(void);
復(fù)制代碼

所有以上例出的函數(shù)都使絕對可以運行成功的。然而,F(xiàn)reeBSD man pages 強烈建議,PID不應(yīng)該用來創(chuàng)建一個唯一文件,因為這個PID只在創(chuàng)建的時候唯一。一旦進程退出,PID值會回到未使用PID池,將會被接著被使用(當(dāng)然是在系統(tǒng)一直運行情況下)。

一個獲得這些值的源程序代碼在 proc_ids.c中列出。
http://www.khmere.com/freebsd_book/src/03/proc_ids.c.html

當(dāng)你運行下面程序:

  1. bash$ sleep 10 |  ps auwx |  awk {'print $2'}  | ./proc_ids
復(fù)制代碼

同時在另一個終端運行:

  1. bash$ ps -j
復(fù)制代碼


只有這些指令將被一個shell運行并且每個都有相同的PPID和PGID。

3.5 子進程

進程可以由其他進程創(chuàng)建,在BSD中有3個方法來實現(xiàn)這個目的。他們是:fork,vfork 和rfork 。其他的調(diào)用例如system不過是對這3個的封裝。

fork

當(dāng)一個進程調(diào)用frok,一個新的復(fù)制父進程的進程獎被創(chuàng)建:


  1. #include <sys/types.h>
  2.   #include <unistd.h>

  3.      pid_t   fork(void);
復(fù)制代碼

不像其他的函數(shù)調(diào)用,在成功的時候,fork會反饋兩次-一次在父進程,返回的是新創(chuàng)建的子進程的PID,而第二次在子進程,返回將是0。這樣一來,你就可以區(qū)分兩個進程。當(dāng)一個新的進程被fork創(chuàng)建,它幾乎是父進程的精確復(fù)制。他們的共同點包括一下:(不是創(chuàng)建的順序)

Controlling terminal
Working directory
Root directory
Nice value
Resource limits
Real, effective and saved user ID
Real, effective and saved group ID
Process group ID
Supplementary group ID
The set-user-id bits
The set-group-id bits
All saved environment variables
All open file descriptors and current offsets
Close-on-exec flags
File mode creation (umask)
Signals handling settings (SIG_DFL, SIG_IGN, addresses)
Signals mask

在子進程中不同的是它的PID,而PPID被設(shè)置成父進程的PID,進程的資源利用值是0,并且拷貝了父進程的文件描述符。子進程可以關(guān)閉文件描述符而不干擾父進程。如果子進程希望讀寫它們,將會保持父進程的偏移量。注意一個潛在的問題,如果父子進程同時試圖讀寫相同的文件描述符會導(dǎo)致輸出異常,或程序崩潰。

當(dāng)一個新的進程被創(chuàng)建后,運行的順序?qū)⒉豢芍W詈每刂七\行順序的方法是使用semiphores,pipes,或signals,這以后論述。通過這些,讀寫就可以控制,使得一個進程可以避免擾亂其他進程導(dǎo)致一起崩潰。

wait

父進程應(yīng)該使用下面wait系統(tǒng)調(diào)用的一種來搜集子進程的退出狀態(tài):


  1.     #include <sys/types.h>
  2.     #include <sys/wait.h>

  3.      pid_t     wait(int *status);


  4.      #include <sys/time.h>
  5.      #include <sys/resource.h>

  6.      pid_t     waitpid(pid_t wpid, int *status, int options);
  7.      pid_t     wait3(int *status, int options, struct rusage *rusage);
  8.      pid_t     wait4(pid_t  wpid, int *status, int options, struct rusage *rusage);

復(fù)制代碼


在調(diào)用wait的時候,options參數(shù)是一個位值,或者是以下的一個:

WNOHANG -在調(diào)用的時候不阻塞程序,他會導(dǎo)致wait馬上反饋即使沒有子進程被終止。

WUNTRACED -當(dāng)你想要等待停止并且不可跟蹤的子進程(可能由SIGTTIN, SIGTTOU, SIGTSTP或SIGSTOP信號導(dǎo)致)的狀態(tài)時設(shè)置該值。

WLINUXCLONE -如果你想等待通過linux_clone產(chǎn)生的內(nèi)核線程。

當(dāng)使用wait4和waitpid調(diào)用時,要注意wpid參數(shù)是要等待的PID值。如果將其設(shè)置成-1將會導(dǎo)致該調(diào)用等待所有可能的子進程的終止。在調(diào)用時使用rusage結(jié)構(gòu),如果結(jié)構(gòu)不是指向NULL將返回終止進程的資源使用統(tǒng)計。當(dāng)然如果進程已經(jīng)停止,那么這些使用信息就沒有什么用處。

wait函數(shù)調(diào)用提供給父進程一個方法去獲取它子進程退出的信息。一當(dāng)調(diào)用,調(diào)用它的進程將被阻塞知道一個子進程終止或接收到一個信號。這可以通過設(shè)置WNOHANG 值來避免。當(dāng)調(diào)用成功時,status參數(shù)將包含結(jié)束進程的信息。如果調(diào)用進程對退出狀態(tài)沒有興趣,可以將status設(shè)置成NULL。關(guān)于使用WNOHANG 的更多細節(jié)將在信號章節(jié)描述。

如果對退出狀態(tài)信息有興趣,它們的宏定義在/usr/include/sys/wait.h中。最好使用它們來獲得更好的跨平臺兼容性。下面列出它們3個和使用說明:

WIFEXITED(status) -如果返回true(也就是返回非0值)那么進程是通過調(diào)用exit()或_exit()正常終止的。

WIFSIGNALED(status) -如果返回true那么進程是由信號終止的。

WIFSTOPPED(status) -如果返回true那么進程已經(jīng)停止并且可以重新開始。這個宏應(yīng)該和WUNTRACED一起使用,或當(dāng)子進程被跟蹤的時候(就像使用ptrace)

如果有必要,以下的宏可以進一步提取保存的狀態(tài)中信息

WEXITSTATUS(status) - 這只能在WIFEXITED(status) 宏評估為true時使用,他會評估exit()和_exit()傳遞參數(shù)的低8位。

WTERMSIG(status) -這只能在WIFSIGNALED(status)評估為true時使用,他會得到導(dǎo)致進程終止的信號的值。

WCOREDUMP(status) -這只能在WIFSIGNALED(status)評估為true時使用,如果終止的進程在收到信號的點創(chuàng)建了core dump文件那么該宏會返回true。

WSTOPSIG(status) -這只能在WIFSTOPPED(status) 評估為true時使用。該紅會得到導(dǎo)致進程停止的信號。

如果父進程沒有收集子進程的退出狀態(tài),或父進程在子進程結(jié)束前就已結(jié)束,init將會接管子進程并收集它們的退出狀態(tài)。

vfork 和 rfork

vfork函數(shù)和fork相似,是在2.9BSD被引入的。它們兩者的區(qū)別是,vfork會停止父進程的運行并且使用父進程的運行線程。這是為了調(diào)用execv函數(shù)設(shè)計的,這樣可以避免低效的復(fù)制父進程的地址空間。實際上vfork是不被建議使用的,因為它不是跨平臺的。例如Irix的5.x就沒有vfork.下面是vfork的調(diào)用例子:


  1. #include <sys/types.h>
  2. #include <unistd.h>
  3.   
  4.     int  vfork(void);
復(fù)制代碼


rfork函數(shù)也和fork與vfork相似.他是在Plan9引入的。它的主要目的是增加更成熟的方法來控制進程的創(chuàng)建和創(chuàng)建內(nèi)核線程。FreeBSD/OpenBSD支持偽線程和Linux clone調(diào)用。換句話說,rfork允許比fork更快更小的進程創(chuàng)建。這個調(diào)用可以設(shè)置子進程可以和它們共享的資源。下面是一個rfork調(diào)用例程:


  1.   #include <unistd.h>

  2.     int    rfork(int flags);
復(fù)制代碼


rfork可以選擇的資源如下:

RFPROC -設(shè)置該值表示你想創(chuàng)建一個新的進程;而其他的標(biāo)志將只影響當(dāng)前進程。該值是默認(rèn)值。

RFNOWAIT -設(shè)置該值表示你希望創(chuàng)建一個和父進程分離的子進程。一當(dāng)該子進程被退出,他不會留下狀態(tài)等待父進程收集。

RFFDG -設(shè)置該值表示你希望復(fù)制父進程的文件描述符表。否則父子進程將會共享一個文件描述符表。

RFCFDG  -該標(biāo)志和RFDG標(biāo)志互斥。設(shè)置該值表示你希望子進程擁有一個干凈的文件描述符表。

RFMEM -設(shè)置該值表示你想強制內(nèi)核共享完整的地址空間。這是直接共享硬件頁面表的典型做法。這不能直接在C中調(diào)用,因為子進程會返回和父進程相同堆棧。如果你想這么做,最好使用下面列出的rfork_thread函數(shù):


  1. #include <unistd.h>

  2.    int     rfork_thread(int flags, void *stack, int (*func)(void*arg), void *arg);
復(fù)制代碼


這將創(chuàng)建一個新的進程運行于指定的堆棧,并且調(diào)用參數(shù)指定的函數(shù)。和fork不同,成功的時候只會返回新創(chuàng)建的進程PID給父進程,因為子進程將直接運行指定的函數(shù)。如果調(diào)用失敗將返回-1,并且設(shè)置errno的值。

RFSIGSHARE -這是一個FreeBSD特有的標(biāo)志并且最近眾所周知地被用于FreeBSD4.3的緩沖區(qū)溢出。該值將允許子進程和父進程共享信號。(通過共享sigacts結(jié)構(gòu))。

RFLINUXTHPN -這是另一個FreeBSD特有的值。這將導(dǎo)致內(nèi)核在子進程退出時使用信號SIGUSR1代替SIGCHILD。我們會在下一章論述它,現(xiàn)在可以把它看作rfork模仿linux clone調(diào)用。

rfork的返回值和fork相似。子進程獲得0值而父進程獲得子進程的PID。一個微小的區(qū)別是-rfork會在需要時休眠直到必要的系統(tǒng)資源可用。而一個fork調(diào)用和使用RFFDG | RFPROC來調(diào)用rfork相似。但是他不設(shè)計向后兼容。如果rfork調(diào)用失敗會返回-1并且設(shè)置errno。(更多的error 標(biāo)號信息可以看man page和頭部文件。)

顯然,rfork調(diào)用提供了一個較小消耗的fork版本。缺點是有一些眾所周知的安全漏洞。并且他即使在跨BSD系列平臺上也不是很兼容,在不同的Unix版本中是獨一無二的。比如當(dāng)前的NetBSD-1.5就不支持rfork,而且不是所有FreeBSD可用的標(biāo)志都可以用于OpenBSD。正因為如此,推薦的線程接口是使用pthreads。

3.6 運行二進制程序

一個進程如果僅僅是父進程的拷貝是沒有很大用途的。因此,需要使用exec函數(shù)。這些是設(shè)計來使用新的進程鏡像代替當(dāng)前進程的鏡像。好,舉個例子,shell運行l(wèi)s指令。首先shell會按照執(zhí)行運行fork或vfork接著它會調(diào)用exec函數(shù)。一旦exec被成功調(diào)用一個新的進程將由ls代替運行,并且exec自己沒有返回。如果像shell或perl這樣的腳本是目標(biāo)程序,這個過程就像二進制程序運行。那里有一個附加的程序調(diào)用解析器。那就是,頭兩個字符將是#! 例如,下面展示了一個帶參數(shù)的解析器調(diào)用:

  1. #!  interpreter [arg]

復(fù)制代碼

下面的指令將使用-w參數(shù)調(diào)用Perl解析器:


  1. #!/usr/bin/perl  -w
復(fù)制代碼


下面列出了所有的exec調(diào)用。源代碼 exec.c http://www.khmere.com/freebsd_book/src/03/exec.c.html
調(diào)用它們的基本結(jié)構(gòu)是(目標(biāo)程序),(參數(shù)),(必要的環(huán)境變量)。


  1.   #include <sys/types.h>
  2.   #include <unistd.h>
  3.    
  4.     extern char **environ;
  5.    
  6.     int     execl(const char *path, const char *arg0, ...  const char *argn,   NULL);
  7.     int     execle(const char *path, const char *arg0, ... const char  *argn, NULL, char *const envp[]);
  8.     int     execlp(const char *file, const char *arg0, ... const char *argn , NULL );
  9.     int     exect(const char *path, char *const argv[], char *const envp[]);
  10.     int     execv(const char *path, char *const argv[]);
  11.     int     execvp(const char *file, char *const argv[]);
復(fù)制代碼


注意;在使用帶有參數(shù)arg的exec時,arg參數(shù)必須是以NULL截止的字符串,就如在arg0,arg1,arg2 ..argn中必須以NULL或0結(jié)尾。如果沒有確定結(jié)束符調(diào)用會失敗。這字符串將成為目標(biāo)程序運行的參數(shù)。

調(diào)用exec使用*argv[]參數(shù)時其必須是一組包含結(jié)束符的字符串的數(shù)組,它們將成為目標(biāo)程序運行的參數(shù)。

另一個區(qū)分二進制或腳本運行exec的是目標(biāo)程序是如何指定的。如果目標(biāo)沒有帶路徑,函數(shù)execlp和execvp會搜尋你的環(huán)境目錄來查找目標(biāo)程序。而其他函數(shù)調(diào)用將要求絕對路徑。

簡索:

數(shù)據(jù)參數(shù)和序列參數(shù)

array: exect, execv, execvp

sequence: execl, execle, eseclp

路徑搜索和直接文件

path: execl, execle, exect, execv

file: execlp, exevp

指定環(huán)境和繼承環(huán)境

specify: execle, exect

inherit: execl, execlp, execv, execvp

system


  1.    
  2.    #include <stdlib.h>

  3.      int     system(const char *string);
復(fù)制代碼


另外一個關(guān)鍵的運行函數(shù)調(diào)用是system調(diào)用。這個函數(shù)十分直觀。提供的參數(shù)將直接傳送給shell。如果指定的參數(shù)
為NULL,如果shell可用函數(shù)會返回1否則返回0。一旦調(diào)用,進城會等待shell結(jié)束并返回shell的結(jié)束狀態(tài)。如果返回-1表示fork或waitpid失敗,127表示shell運行失敗。

一旦調(diào)用,一些信號例如SIGINT和SIGQUIT將被忽略,并且會阻塞SIGCHILD。同樣如果子進程寫輸出到標(biāo)準(zhǔn)錯誤輸出可能會使調(diào)用進程崩潰。

顯然,BSD結(jié)構(gòu)提供了簡單同時豐富的進程創(chuàng)建接口。這種成熟的設(shè)計可以和任何一個現(xiàn)在操作系統(tǒng)媲美。下一章將包括信號和進程管理,包含資源使用,線程和進程限制。


第四章 高級進程控制和信號

翻譯: gvim@chinaunix/bsd

一些說明:
1 很高興有這個機會參與到這項活動中來。
2 由于英語語言的習(xí)慣,文中有不少復(fù)雜長句,按照字面翻譯出來并不適合中國人的閱讀習(xí)慣。我在保留文章原意的基礎(chǔ)上作了一些語言組織上的調(diào)整,將大部分長句子組織成中文擅長的短句型,所以你會發(fā)現(xiàn)翻譯品一部分內(nèi)容不是按照原文字面翻譯的。
3 考慮到該書的入門引導(dǎo)作用,在原文中一些比較少見的詞后面我添加了少許譯注,希望減少入門兄弟的負(fù)擔(dān)。
4 由于2中的語言調(diào)整和3中的譯注,是我的個人行為,可能會有我對 原文理解模糊,概念不清,或是語言組織不順暢的地方,還請大家一定多多指出來。謝謝。
個人聲明:
本翻譯品受控于chinaunix BSD翻譯小組。如需單獨轉(zhuǎn)載,請保留“翻譯:gvim@chinaunix/bsd”和以上幾點說明。


4.1 高級進程控制和信號
信號:4 a:一種對象,用于傳輸或負(fù)載人類聲音之外的信息。

到目前為止,我們已經(jīng)討論了進程的創(chuàng)建和其他系統(tǒng)調(diào)用,F(xiàn)在是討論下面這些問題的時候了:你要在多個進程間通訊以獲得更好的進程控制粒度,或者要其他程序或操作者用信號通知你的程序。例如,你可能希望你的程序重新讀取它的配置文件。或者,你的數(shù)據(jù)庫程序需要在退出之前將事務(wù)從主存寫入后備存儲器,然后再退出。這兩個例子可能只是使用信號的很小一部分。雖然已經(jīng)有套接字,先入先出隊列,管道,信號量等多種方式來完成類似的任務(wù),但是我們將把討論的焦點放在信號和其它進程控制機制上。在現(xiàn)實中,信號和進程控制機制可以提供大部分你所需要的特性和功能。

4.2 信號
信號與硬件中斷很相似。當(dāng)設(shè)備需要中斷服務(wù)的時候它可以產(chǎn)生一個硬件中斷來通知CPU。與硬件中斷類似的,當(dāng)進程需要將一些事件通知給其他進程的時候可以使用信號來完成。

大多數(shù)Unix系統(tǒng)管理員會比較熟悉SIGHUP信號。當(dāng)你通過kill命令向后臺服務(wù)進程發(fā)出SIGHUP信號后,大多數(shù)進程要么重新讀入他們的配置文件要么重新啟動。這些信號之中,一些與硬件有直接關(guān)系,如SIGFPE(浮點異常),SIGILL(非法指令);其它則是與軟件相關(guān),如SIGSYS(未實現(xiàn)的系統(tǒng)調(diào)用被調(diào)用)。

一旦進程接收到信號之后,該信號的行為與信號本身和進程對它的使用目的兩個因素有關(guān)。一些信號可以被阻塞,忽略,或者捕獲,而另外一些則不可以。如果進程需要捕獲一個信號并履行一些相關(guān)操作,你可以為進程設(shè)定這個特定信號的信號處理句柄。處理句柄僅僅是一個函數(shù),在這個信號被進程接收之后調(diào)用。或者更確切的說,處理句柄是一個函數(shù)調(diào)用,你可以對它進行指派(specify)。

當(dāng)信號沒有指定處理句柄時,將會執(zhí)行操作系統(tǒng)默認(rèn)的行為。這些缺省行為可以是從終止進程到完全核心轉(zhuǎn)儲等不同的操作。注意,有兩個信號不能被捕獲或忽略:SIGSTOP和SIGKILL,下面會解釋。

在BSD系統(tǒng)中定義的有許多信號;我們討論在/usr/include/sys/signals.h(譯注:在我的FB5.2.1中是signal.h)文件中定義的標(biāo)準(zhǔn)信號。注意, NetBSD系統(tǒng)中定義的信號數(shù)量稍微多一點,并且我們沒有將它們的討論放在這里。所以如果需要使用某個下面沒有涉及到的信號的時候,請查閱你的系統(tǒng)的頭文件。

  1. #define SIGHUP          1       /* hangup */
復(fù)制代碼

SIGHUP是Unix系統(tǒng)管理員很常用的一個信號。許多后臺服務(wù)進程在接受到該信號后將會重新讀取它們的配置文件。然而,該信號的實際功能是通知進程它的控制終端被斷開。缺省行為是終止進程。
  1. #define SIGINT          2       /* interrupt */
復(fù)制代碼

對于Unix使用者來說,SIGINT是另外一個常用的信號。許多shell的CTRL-C組合使得這個信號被大家所熟知。該信號的正式名字是中斷信號。缺省行為是終止進程。
  1. #define SIGQUIT         3       /* quit */
復(fù)制代碼

SIGQUIT信號被用于接收shell的CTRL-/組合。另外,它還用于告知進程退出。這是一個常用信號,用來通知應(yīng)用程序從容的(譯注:即在結(jié)束前執(zhí)行一些退出動作)關(guān)閉。缺省行為是終止進程,并且創(chuàng)建一個核心轉(zhuǎn)儲。
  1. #define SIGILL          4       /* illegal instr. (not reset when caught) */
復(fù)制代碼

如果正在執(zhí)行的進程中包含非法指令,操作系統(tǒng)將向該進程發(fā)送SIGILL信號。如果你的程序使用了線程,或者pointer functions,那么可能的話可以嘗試捕獲該信號來協(xié)助調(diào)試。(注意:原文這句為:“If your program makes use of use of threads, or pointer functions, try to catch this signal if possible for aid in debugging.”。中間的兩個use of use of,不知是原書排版的瑕疵還是我確實沒有明白其意義;另外,偶經(jīng)常聽說functions pointer,對于pointer functions,google了一下,應(yīng)該是fortran里面的東西,不管怎樣,還真不知道,確切含義還請知道的兄弟斧正。)缺省行為是終止進程,并且創(chuàng)建一個核心轉(zhuǎn)儲。
  1. #define SIGTRAP         5       /* trace trap (not reset when caught) */
復(fù)制代碼

SIGTRAP這個信號是由POSIX標(biāo)準(zhǔn)定義的,用于調(diào)試目的。當(dāng)被調(diào)試進程接收到該信號時,就意味著它到達了某一個調(diào)試斷點。一旦這個信號被交付,被調(diào)試的進程就會停止,并且它的父進程將接到通知。缺省行為是終止進程,并且創(chuàng)建一個核心轉(zhuǎn)儲。
  1. #define SIGABRT         6       /* abort() */
復(fù)制代碼

SIGABRT提供了一種在異常終止(abort)一個進程的同時創(chuàng)建一個核心轉(zhuǎn)儲的方法。然而如果該信號被捕獲,并且信號處理句柄沒有返回,那么進程不會終止。缺省行為是終止進程,并且創(chuàng)建一個核心轉(zhuǎn)儲。
  1. #define SIGFPE          8       /* floating point exception */
復(fù)制代碼

當(dāng)進程發(fā)生一個浮點錯誤時,SIGFPE信號被發(fā)送給該進程。對于那些處理復(fù)雜數(shù)學(xué)運算的程序,一般會建議你捕獲該信號。缺省行為是終止進程,并且創(chuàng)建一個核心轉(zhuǎn)儲。
  1. #define SIGKILL         9       /* kill (cannot be caught or ignored) */
復(fù)制代碼

SIGKILL是這些信號中最難對付的一個。正如你在它旁邊的注釋中看到的那樣,這個信號不能被捕獲或忽略。一旦該信號被交付給一個進程,那么這個進程就會終止。然而,會有一些極少數(shù)情況SIGKILL不會終止進程。這些罕見的情形在處理一個“非中斷操作”(比如磁盤I/O)的時候發(fā)生。雖然這樣的情形極少發(fā)生,然而一旦發(fā)生的話,會造成進程死鎖。唯一結(jié)束進程的辦法就只有重新啟動了。缺省行為是終止進程。
  1. #define SIGBUS          10      /* bus error */
復(fù)制代碼

如同它的名字暗示的那樣,CPU檢測到數(shù)據(jù)總線上的錯誤時將產(chǎn)生SIGBUS信號。當(dāng)程序嘗試去訪問一個沒有正確對齊的內(nèi)存地址時就會產(chǎn)生該信號。缺省行為是終止進程,并且創(chuàng)建一個核心轉(zhuǎn)儲。
  1. #define SIGSEGV         11      /* segmentation violation */
復(fù)制代碼

SIGSEGV是另一個C/C++程序員很熟悉的信號。當(dāng)程序沒有權(quán)利訪問一個受保護的內(nèi)存地址時,或者訪問無效的虛擬內(nèi)存地址(臟指針,dirty pointers,譯注:由于沒有和后備存儲器中內(nèi)容進行同步而造成。關(guān)于野指針,可以參見http://en.wikipedia.org/wiki/Wild_pointer 的解釋。)時,會產(chǎn)生這個信號。缺省行為是終止進程,并且創(chuàng)建一個核心轉(zhuǎn)儲。
  1. #define SIGSYS          12      /* non-existent system call invoked */
復(fù)制代碼

SIGSYS信號會在進程執(zhí)行一個不存在的系統(tǒng)調(diào)用時被交付。操作系統(tǒng)會交付該信號,并且進程會被終止。缺省行為是終止進程,并且創(chuàng)建一個核心轉(zhuǎn)儲。
  1. #define SIGPIPE         13      /* write on a pipe with no one to read it */
復(fù)制代碼

管道的作用就像電話一樣,允許進程之間的通信。如果進程嘗試對管道執(zhí)行寫操作,然而管道的另一邊卻沒有回應(yīng)者時,操作系統(tǒng)會將SIGPIPE信號交付給這個討厭的進程(這里就是那個打算寫入的進程)。缺省行為是終止進程。
  1. #define SIGALRM         14      /* alarm clock */
復(fù)制代碼

在進程的計時器到期的時候,SIGALRM信號會被交付(delivered)給進程。這些計時器由本章后面將會提及的setitimer和alarm調(diào)用設(shè)置。缺省行為是終止進程。
  1. #define SIGTERM         15      /* software termination signal from kill */
復(fù)制代碼

SIGTERM信號被發(fā)送給進程,通知該進程是時候終止了,并且在終止之前做一些清理活動。SIGTERM信號是Unix的kill命令發(fā)送的缺省信號,同時也是操作系統(tǒng)關(guān)閉時向進程發(fā)送的缺省信號。缺省行為是終止進程。
  1. #define SIGURG          16      /* urgent condition on IO channel */
復(fù)制代碼

在進程已打開的套接字上發(fā)生某些情況時,SIGURG將被發(fā)送給該進程。如果進程不捕獲這個信號的話,那么將被丟棄。缺省行為是丟棄這個信號。
  1. #define SIGSTOP         17      /* sendable stop signal not from tty */
復(fù)制代碼

本信號不能被捕獲或忽略。一旦進程接收到SIGSTOP信號,它會立即停止(stop),直到接收到另一個SIGCONT信號為止。缺省行為是停止進程,直到接收到一個SIGCONT信號為止。
  1. #define SIGTSTP         18      /* stop signal from tty */
復(fù)制代碼

SIGSTP與SIGSTOP類似,它們的區(qū)別在于SIGSTP信號可以被捕獲或忽略。當(dāng)shell從鍵盤接收到CTRL-Z的時候就會交付(deliver)這個信號給進程。缺省行為是停止進程,直到接收到一個SIGCONT信號為止。
  1. #define SIGCONT         19      /* continue a stopped process */
復(fù)制代碼

SIGCONT也是一個有意思的信號。如前所述,當(dāng)進程停止的時候,這個信號用來告訴進程恢復(fù)運行。該信號的有趣的地方在于:它不能被忽略或阻塞,但可以被捕獲。這樣做很有意義:因為進程大概不愿意忽略或阻塞SIGCONT信號,否則,如果進程接收到SIGSTOP或SIGSTP的時候該怎么辦?缺省行為是丟棄該信號。
  1. #define SIGCHLD         20      /* to parent on child stop or exit */
復(fù)制代碼

SIGCHLD是由Berkeley Unix引入的,并且比SRV 4 Unix上的實現(xiàn)有更好的接口。(如果信號是一個沒有追溯能力的過程(not a retroactive process),那么BSD的SIGCHID信號實現(xiàn)會比較好。在system V Unix的實現(xiàn)中,如果進程要求捕獲該信號,操作系統(tǒng)會檢查是否存在有任何未完成的子進程(這些子進程是已經(jīng)退出(exit)的子進程,并且在等待調(diào)用wait的父進程收集它們的狀態(tài))。如果子進程退出的時候附帶有一些終止信息(terminating information),那么信號處理句柄就會被調(diào)用。所以,僅僅要求捕獲這個信號會導(dǎo)致信號處理句柄被調(diào)用(譯注:即是上面說的“信號的追溯能力”),而這是卻一種相當(dāng)混亂的狀況。)

一旦一個進程的子進程狀態(tài)發(fā)生改變,SIGCHLD信號就會被發(fā)送給該進程。就像我在前面章節(jié)提到的,父進程雖然可以fork出子進程,但沒有必要等待子進程退出。一般來說這是不太好的,因為這樣的話,一旦進程退出就可能會變成一個僵尸進程?墒侨绻高M程捕獲SIGCHLD信號的話,它就可以使用wait系列調(diào)用中的某一個去收集子進程狀態(tài),或者判斷發(fā)生了什么事情。當(dāng)發(fā)送SIGSTOP,SIGSTP或SIGCONF信號給子進程時,SIGCHLD信號也會被發(fā)送給父進程。缺省行為是丟棄該信號。
  1. #define SIGTTIN         21      /* to readers pgrp upon background tty read */
復(fù)制代碼

當(dāng)一個后臺進程嘗試進行一個讀操作時,SIGTTIN信號被發(fā)送給該進程。進程將會阻塞直到接收到SIGCONT信號為止。缺省行為是停止進程,直到接收到SIGCONT信號。
  1. #define SIGTTOU         22      /* like TTIN if (tp->t_local&LTOSTOP) */
復(fù)制代碼

SIGTTOU信號與SIGTTIN很相似,不同之處在于SIGTTOU信號是由于后臺進程嘗試對一個設(shè)置了TOSTOP屬性的tty執(zhí)行寫操作時才會產(chǎn)生。然而,如果tty沒有設(shè)置這個屬性,SIGTTOU就不會被發(fā)送。缺省行為是停止進程,直到接收到SIGCONT信號。
  1. #define SIGIO           23      /* input/output possible signal */
復(fù)制代碼

如果進程在一個文件描述符上有I/O操作的話,SIGIO信號將被發(fā)送給這個進程。進程可以通過fcntl調(diào)用來設(shè)置。缺省行為是丟棄該信號。
  1. #define SIGXCPU         24      /* exceeded CPU time limit */
復(fù)制代碼

如果一旦進程超出了它可以使用的CPU限制(CPU limit),SIGXCPU信號就被發(fā)送給它。這個限制可以使用隨后討論的setrlimit設(shè)置。缺省行為是終止進程。
  1. #define SIGXFSZ         25      /* exceeded file size limit */
復(fù)制代碼
如果一旦進程超出了它可以使用的文件大小限制,SIGXFSZ信號就被發(fā)送給它。稍后我們會繼續(xù)討論這個信號。缺省行為是終止進程。
  1. #define SIGVTALRM       26      /* virtual time alarm */
復(fù)制代碼

如果一旦進程超過了它設(shè)定的虛擬計時器計數(shù)時,SIGVTALRM信號就被發(fā)送給它。缺省行為是終止進程。
  1. #define SIGPROF         27      /* profiling time alarm */
復(fù)制代碼

當(dāng)設(shè)置了計時器時,SIGPROF是另一個將會發(fā)送給進程的信號。缺省行為是終止進程。
  1. #define SIGWINCH        28      /* window size changes */
復(fù)制代碼

當(dāng)進程調(diào)整了終端的行或列時(比如增大你的xterm的尺寸),SIGWINCH信號被發(fā)送給該進程。缺省行為是丟棄該信號。
  1. #define SIGUSR1         29      /* user defined signal 1 */
  2. #define SIGUSR2         30      /* user defined signal 2 */
復(fù)制代碼

SIGUSR1和SIGUSR2這兩個信號被設(shè)計為用戶指定。它們可以被設(shè)定來完成你的任何需要。換句話說,操作系統(tǒng)沒有任何行為與這兩個信號關(guān)聯(lián)。缺省行為是終止進程。(譯注:按原文的意思翻譯出來似乎這兩句話有點矛盾。)

[size=-1]4.3 系統(tǒng)調(diào)用
那么,你該如何使用信號呢?有時候甚至拿不準(zhǔn)是否應(yīng)該使用信號。例如,當(dāng)信號被交付的時候,一方面你可以在行為發(fā)生之前,分析當(dāng)前情況,找出信號發(fā)生的原因,或者找到這些信號是從哪里發(fā)出來的;另一方面,其他一些時候你也可以只是希望簡單的退出程序,并且在清除之后創(chuàng)建一個核心轉(zhuǎn)儲文件。參見最后部分的簡單代碼可以獲得這些函數(shù)的較詳細的例子。

Kill函數(shù)
kill函數(shù)對于那些經(jīng)常在命令行使用kill命令殺死進程的人來說是再熟悉不過的。基本語法是:
  
  1. int   kill(pid_t pid, int sig);
復(fù)制代碼

Kill函數(shù)將指定的信號發(fā)送給進程號為pid的進程。只有當(dāng)進程符合下面幾點情況的時候信號才會被交付:
•        發(fā)送與接收進程有相同的有效用戶ID(UID);
•        發(fā)送進程有適當(dāng)?shù)臋?quán)限(例如:setuid命令);
•        發(fā)送進程有超級用戶(root)的UID。

注意:SIGCONT信號是一個特例,它可以由當(dāng)前進程發(fā)送給任何一個該進程的派生進程。

使用不同的調(diào)用參數(shù)使得kill函數(shù)的行為差別非常大。這些行為如下所述:
(譯注:下面的PID應(yīng)該指的是上面kill函數(shù)原型中的那個pid,我在這里做出說明并保留原文)
•        如果PID大于0,并且發(fā)送進程有適當(dāng)?shù)臋?quán)限,那么參數(shù)sig指定的信號將被交付。
•        如果PID等于0,那么sig信號將被交付給所有那些與發(fā)送進程有相同組ID的進程。(發(fā)送進程同樣需要滿足權(quán)限需求。)
•        如果PID是 -1,那么信號將被發(fā)送給所有那些與發(fā)送進程有相同有效用戶ID的進程(不包含發(fā)送進程在內(nèi))。然而,如果發(fā)送進程的有效用戶ID與超級用戶(root)的相同,那么信號被交付給除了系統(tǒng)進程(由它們的proc結(jié)構(gòu)中的p_flag域是否是P_SYSTEM來定義)之外的所有進程。在這個特殊的例子中,如果某些進程不能被發(fā)送(could not be sent)sig信號,kill函數(shù)并不返回一個錯誤。
•        如果sig是0,kill函數(shù)只檢查錯誤(例如,無效權(quán)限,不存在的進程等)。該用法有時候用來檢查一個指定進程是否存在。
•        如果成功的話kill函數(shù)返回0,否則返回-1。kill調(diào)用失敗時會在errno全局變量中設(shè)置相應(yīng)的錯誤值。


kill的另一個版本是raise函數(shù):
   
  1. int    raise(int sig);
復(fù)制代碼

raise函數(shù)會向當(dāng)前進程發(fā)送sig信號。該函數(shù)用處不是很大,因為它只能夠給當(dāng)前進程發(fā)送信號。raise函數(shù)調(diào)用成功時返回0,否則返回-1。調(diào)用失敗時會在errno全局變量中設(shè)置相應(yīng)的錯誤值,效果和signal函數(shù)的返回類似:(譯注:原文只有兩個單詞“as in:”,我并不知道作者把signal列在這里所要表達的意思,所以我按照我的理解+猜測來翻譯的。如果大家有什么建議,或是需要糾正的話,請一定告訴我。)
  
  1. void (*signal(int sig, void (*func)(int)))(int);
復(fù)制代碼


4.4 信號處理
現(xiàn)在我們知道何時會產(chǎn)生信號,也知道如何發(fā)送信號,那么我們怎么處理它們呢?

signal函數(shù)

signal系統(tǒng)函數(shù)調(diào)用提供了一種最簡單的范例。然而,由于C原形聲明的緣故使它看起來比實際復(fù)雜。signal函數(shù)將一個給定的函數(shù)和一個特定的信號聯(lián)系。這里是FreeBSD中的定義(和一個typedef一起):
typedef void (*sig_t) (int);

sig_t   signal(int sig, sig_t func);

第一個參數(shù)是目標(biāo)信號,可以是上面列舉的所有信號中的任何一個。func參數(shù)是一個指針,指向某個處理該信號的函數(shù)。這個處理信號函數(shù)帶有一個int型參數(shù),并應(yīng)返回void。signal函數(shù)中的func參數(shù)也可以設(shè)定為下面的一些值:
SIG_IGN: 如果func參數(shù)被設(shè)置為SIG_IGN,該信號將被忽略。

SIG_DFL: 如果func參數(shù)被設(shè)置為SIG_DFL,該信號會按照確定行為處理。


sigaction函數(shù)

sigaction函數(shù)是一個比signal更通用的方案。第一個參數(shù)是目標(biāo)信號。下一個名為act的參數(shù)(指向)sigaction結(jié)構(gòu),該結(jié)構(gòu)包含一些用于信號處理的信息。最后一個參數(shù)oact是一個指針,指向一個可以存儲上一次設(shè)置信號處理的信息的地方。
int  sigaction(int sig, const struct sigaction *act, struct sigaction *oact);

sigaction結(jié)構(gòu)有下面這些個成員:
void     (*sa_handler)(int);

這個結(jié)構(gòu)成員是一個指向函數(shù)的指針,該函數(shù)帶有一個簡單的整形參數(shù),并返回(void)。這與signal函數(shù)的func參數(shù)相同,也可以被設(shè)置為SIG_IGN和SIG_DFL,并且與調(diào)用signal得到的效果也一樣。
void     (*sa_sigaction)(int, siginfo_t *, void *);

該結(jié)構(gòu)成員是一個指向函數(shù)的指針,返回(void)并需要三個參數(shù)。這些參數(shù)依次為:一個整形參數(shù)指定信號發(fā)送;一個指向siginfo_t結(jié)構(gòu)的指針用來保存關(guān)于信號的信息;最后一個也是一個指針,指向信號交付時的特定上下文(context)空間。
sigset_t sa_mask;

該結(jié)構(gòu)成員是一個位掩碼(bitwise mask),用來指示信號交付時哪些信號會被阻塞。阻塞SIGKILL和SIGSTOP信號的做法會被忽略。接下來,被阻塞的信號將被推遲,直到它們被開啟(unblock)。參見sigprocmask獲得更多關(guān)于全局掩碼(global masks)的信息。
int      sa_flags;

該數(shù)據(jù)成員是一個擁有下面這些標(biāo)志的位掩碼:
SA_NOCLDSTOP: 如果SA_NOCLDSTOP位被置位并且目標(biāo)信號是SIGCHLD,除非子進程退出,而在子進程停止(stop)時父進程將不會收到通知。

SA_NOCLDWAIT: SA_NOCLDWAIT標(biāo)志會阻止子進程成為僵尸進程。在目標(biāo)信號是SIGCHLD的時候使用。如果進程設(shè)置了這個標(biāo)志,接著調(diào)用某個wait系統(tǒng)調(diào)用,進程將被阻塞直到子進程全部終止,最后返回-1(譯注:此處在APUE2ed中的解釋是返回1),設(shè)置errno全局變量為ECHILD。

SA_ONSTACK: 一些時候需要在特定的堆棧上進行信號的處理。sigaction系統(tǒng)調(diào)用提供了這個方式。如果該位被置位,那么信號將會被交付到指定的堆棧上。

SA_NODEFER: 如果SA_NODEFER位被置位,那么當(dāng)前信號正被處理時,系統(tǒng)不會屏蔽該信號以后的交付。

SA_RESETHAND: 如果SA_RESETHAND被置位,一旦信號被交付,信號處理句柄將被置為SIG_DEF。

SA_SIGINFO: 被置位時,由結(jié)構(gòu)體sigaction 的成員sa_sigaction指向的函數(shù)被使用。注意:使用SIG_IGN或SIG_DFL時不應(yīng)該設(shè)置這個標(biāo)志。成功調(diào)用sigaction之后,返回0或-1,并且將error設(shè)置成相關(guān)錯誤值。


4.5信號掩碼(阻塞與開啟信號)

進程可以阻塞或設(shè)置某個信號。一旦該信號被阻塞,關(guān)于它的交付將被推遲,直到進程重新開啟它。在這樣的情況下是非常有用的:進程進入代碼中某個部分,不能被中斷但仍希望可以接受、處理可能丟失的信號?煽拷桓缎盘柕哪芰χ钡4.2BSD引入之后(不久被SVR3采用),操作系統(tǒng)才擁有該能力。

隨著可靠信號的出現(xiàn),信號的生命和交付(life and delivery)都有所改變。信號可以在之前產(chǎn)生和交付。現(xiàn)在,一旦信號是掛起的(pending),進程可以在接收它之前決定怎么處理。進程可能會去處理它,也可能設(shè)置為缺省行為,或者丟棄信號

注意:如果許多信號都掛起,系統(tǒng)將會首先交付會改變進程狀態(tài)的信號,例如SIGBUS。

sigprocmask

任何進程可以使用sigprocmask函數(shù)來阻塞信號。語法如下:
int     sigprocmask(int how, const sigset_t *set, sigset_t *oset);

sigprocmask函數(shù)會修改或檢查(modify or examine)當(dāng)前信號掩碼。當(dāng)set參數(shù)不是null的時候,sigprocmask的行為和第一個參數(shù)how有關(guān)。函數(shù)行為和相關(guān)意義列舉如下:
SIG_BLOCK: 在set參數(shù)中指定的信號被阻塞,并且添加進阻塞信號列表。

SIG_UNBLOCK: 在set參數(shù)中指定的信號會從信號掩碼中移除。

SIG_SETMASK: set參數(shù)將完全替代當(dāng)前信號掩碼。如果oset參數(shù)不為null,則會被設(shè)置為前一個信號掩碼。如果set值是null,how參數(shù)被忽略并且信號掩碼保持不變。所以,為了檢查信號掩碼,我們可以將傳入set null值,oset為非null值來調(diào)用sigprocmask函數(shù)。一旦掩碼得到之后,你可能需要對他進行檢查或操作?梢允褂孟旅娴倪^程(routine)。注意當(dāng)前這些過程是宏的實現(xiàn)。
  1. int    sigemptyset(sigset_t *set)
復(fù)制代碼

如果調(diào)用這個過程,set參數(shù)將被初始化指向一個空信號集。
  1. int    sigfillset(sigset_t *set)
復(fù)制代碼

如果調(diào)用這個過程,set參數(shù)將被初始化指向一個包括所有信號的信號集。
  1. int   sigaddset(sigset_t *set, int signo)
復(fù)制代碼

如果調(diào)用這個過程,signo指定的信號將被添加進set參數(shù)指定的信號集。
  1. int    sigdelset(sigset_t *set, int signo)
復(fù)制代碼

如果調(diào)用這個過程,signo指定的信號將從set參數(shù)指定的信號集中移除。
  1. int   sigismember(const sigset_t *set, int signo)
復(fù)制代碼

如果調(diào)用這個過程,如果由signo指定的信號存在于set參數(shù)指定的信號集中時,返回1,否則返回0。
  1. int     sigpending(sigset_t *set);
復(fù)制代碼

進程可以使用sigpending函數(shù)去查出當(dāng)前那些信號被掛起。sigpending函數(shù)會返回一個包含所有掛起信號的掩碼。該掩碼可以使用上面介紹的過程去檢查。sigpending成功時返回0,否則返回-1,并且設(shè)置errno為相應(yīng)錯誤值。

4.6 自定義行為
一些時候程序要求信號處理句柄運行在一個特定的堆棧上。為了實現(xiàn)這個目的,一個備用(alternate)堆棧區(qū)間必須用signaltstack函數(shù)指出來。這個函數(shù)使用的數(shù)據(jù)結(jié)構(gòu)為signaltstack:
  1. int    sigaltstack(const struct sigaltstack *ss, struct sigaltstack *oss);
復(fù)制代碼

它的結(jié)構(gòu)成員解釋如下。
  1. char    *ss_sp;
復(fù)制代碼

該成員指向一個被用作堆棧的區(qū)域。系統(tǒng)中有個MINSIGSTKSZ常量,它定義了進行信號處理時所需的最小內(nèi)存空間。系統(tǒng)中還有一個SIGSTKSZ常量,它定義了通常情況下處理時所需內(nèi)存空間。該內(nèi)存空間需要在調(diào)用signaltstack函數(shù)之間分配。
  1. size_t  ss_size;
復(fù)制代碼

數(shù)據(jù)成員ss_size指出新堆棧的大小。如果這個值是錯誤的(inaccurate),當(dāng)信號處理句柄執(zhí)行時,它的行為就變得不可預(yù)知(你不能明確知道系統(tǒng)怎樣處理這個信號)。
  1. int     ss_flags;
復(fù)制代碼

根據(jù)調(diào)用環(huán)境(calling circumstances),數(shù)據(jù)成員ss_flags可以具有少數(shù)幾個不同的值。首先,當(dāng)進程希望停用備用堆棧的時候,ss_flags會被設(shè)為SS_DISABLE。在這個情況下,ss_sp和ss_size被忽略,備用堆棧被禁止。注意,備用堆棧只能在當(dāng)前句柄沒有處理時禁止。

如果使用一個non-null值作為oss的實參去調(diào)用signaltstack,ss_flags將包含指示當(dāng)前狀態(tài)的信息。它們是:
  1. SS_DISABLE: 備用堆棧被停用。
  2. SS_ONSTACK: 備用堆棧當(dāng)前正在被使用,并且現(xiàn)在不可以停用。
復(fù)制代碼

如果調(diào)用signaltstack 的oss實參不是null,會返回當(dāng)前狀態(tài)。調(diào)用成功返回0,否則返回-1。如果調(diào)用失敗,errno也會相應(yīng)的被設(shè)置。因為信號可以在任何一點被交付,所以很難被預(yù)測。出于這個原因,4.2BSD的缺省行為是:重新開始被中斷的系統(tǒng)調(diào)用,重新提供還沒有被轉(zhuǎn)送的數(shù)據(jù)。在大多數(shù)時候這個行為是很不錯的,并且也是所有BSD系統(tǒng)采用的缺省行為?墒牵灿幸恍┖币姷那闆r,你可能需要將這個特性關(guān)掉。你可以使用siginterrupt函數(shù)完成需求。使用很簡單:
  1. int   siginterrupt(int sig, int flag);
復(fù)制代碼

將sig參數(shù)設(shè)置為目標(biāo)信號,并且設(shè)置flag為真(在這個情況下是1)。如果flag參數(shù)被設(shè)為假(在這個情況下是0),那么缺省行為是重新啟動系統(tǒng)調(diào)用。

4.7 等待信號

sigsuspend函數(shù)可以暫時將當(dāng)前阻塞信號集改變?yōu)橛蓅igmask指定的信號集。改變后,sigsuspend會等待,直到一個信號被交付。一旦一個信號被交付后,原先的信號集被恢復(fù)。由于sigsuspend調(diào)用在信號交付之后總是被終止,它的返回值總是-1,errno總是EINTR。下面是它的語法:
  1. int     sigsuspend(const sigset_t *sigmask);
復(fù)制代碼

sigwait函數(shù)用set參數(shù)指定的信號集作為信號掩碼。它會檢查包含在這個特定集合內(nèi)的是否有任何掛起信號,如果有的話,它將清除這個掛起的信號,并在sig參數(shù)中返回這個被清除信號的數(shù)值。如果沒有信號掛起,sigwait將一直等待直到指定信號集合中的任何一個信號產(chǎn)生。下面是它的語法:
  1. int    sigwait(const sigset_t *set, int *sig);
復(fù)制代碼

當(dāng)信號被交付給進程(該進程安裝了相應(yīng)信號的處理句柄)時,進程將會切換到信號處理句柄中執(zhí)行。例如,假設(shè)你的程序監(jiān)聽一個由配置文件設(shè)定的端口。你的進程安裝了一個捕獲SIGHUP信號的處理句柄來重新讀取配置文件。一旦SIGHUP信號被交付給你的程序,進程將會執(zhí)行信號處理句柄來重新讀取配置文件。這里存在一個問題,你沒有辦法知道在進程執(zhí)行過程中信號被交付的確切地點。你雖然可以使用一些下面列出的函數(shù)來將范圍縮小,但是如果碰到像打開套接字,打開鏈接,或者其它那些首先需要清理后才能在新端口上監(jiān)聽 這些情況的時候呢?你怎樣確定清理活動在那里開始,什么時候開始?如果你的程序正在等到輸入,并且沒有數(shù)據(jù)被傳進來的時候,系統(tǒng)調(diào)用將被重啟(system call will be restarted),所以從SIGHUP的返回將會繼續(xù)等待。

這是使用setjmp和longjmp函數(shù)的一些情況,這些函數(shù)提供非本地分支(non-local branching)。為了使用這些setjmp函數(shù),需要提供一個evn參數(shù),如下:
  

  1. jmp_buff  env;
  2.   int    sigsetjmp(sigjmp_buf env, int savemask);
  3.   void   siglongjmp(sigjmp_buf env, int val);
  4.   int    setjmp(jmp_buf env);
  5.   void   longjmp(jmp_buf env, int val);
  6.   int     _setjmp(jmp_buf env);
  7.   void    _longjmp(jmp_buf env, int val);
  8.   void    longjmperror(void);
復(fù)制代碼

首先,調(diào)用setjmp的返回為0,當(dāng)前的環(huán)境將被保存在env中。接著,你可以在信號處理句柄內(nèi)部調(diào)用對應(yīng)的longjmp。一旦調(diào)用了longjmp,它將把執(zhí)行環(huán)境恢復(fù)為env中保存的環(huán)境,并返回到最初setjmp被調(diào)用時的環(huán)境中。最初的setjmp調(diào)用返回那個傳遞給longjmp的va參數(shù)的值。

關(guān)于setjmp和longjmp函數(shù)一些說明:首先,這兩個是不能混雜使用。也就是說,調(diào)用setjmp時保存的env變量不能傳遞給_longjmp調(diào)用。另外,調(diào)用setjmp的函數(shù)返回后,接下來調(diào)用longjmp將會發(fā)生錯誤。
The different calls have specific actions that they take. These actions are listed below:
不同的調(diào)用有特定的行為。這些行為在下面列出來:
  1. jmp和longjmp: 他們會保存(恢復(fù))信號掩碼,寄存器組和堆棧。
復(fù)制代碼
  1. _setjmp和_longjmp: 他們只保存(恢復(fù))寄存器組和堆棧。
復(fù)制代碼
  1. sigsetjmp和siglongjmp: 只要savemask參數(shù)不是0,他們就保存(恢復(fù))寄存器組,堆棧和信號掩碼。
復(fù)制代碼

由于一些原因,如果env參數(shù)保存的東西被破壞,或者調(diào)用setjmp的函數(shù)返回了,longjmp函數(shù)將會調(diào)用longjmperror函數(shù)。如果longjmperror也返回了,程序?qū)⒈划惓=K止。你可以使用與longjmperror有相同原形的函數(shù)來自定義longjmperror函數(shù)。缺省的longjmperror會在標(biāo)準(zhǔn)錯誤上輸出”longjmp botch”,然后返回。
4.8 Alarms
  1. unsigned int   alarm(unsigned int seconds);
復(fù)制代碼

alarm函數(shù)基本上是一個簡單的鬧鐘時鐘(alarm clock),同時也是一個很有用的函數(shù)。它允許進程在經(jīng)過指定秒數(shù)之后收到一個通知。一旦鬧鐘時間到,進程將收到一個SIGALRM信號。任何隨后的alarm調(diào)用都會覆蓋原先的調(diào)用設(shè)定。alarm不像sleep函數(shù),它不會被阻塞。

它有一些返回值需要值得你注意:首先,如果進程沒有設(shè)定定時器,那么返回值是0。其次,如果有一個定時器被設(shè)定但還沒有超時的話,那么會返回前一個調(diào)用到現(xiàn)在還有的剩余時間。

現(xiàn)在可以設(shè)定的最大時間是100,000,000秒 --- 已經(jīng)是相當(dāng)長的時間了。
  1. int getitimer(int which, struct itimerval *value);
復(fù)制代碼


getitimer函數(shù)會檢索由第一個參數(shù)(which參數(shù))描述的itimerval結(jié)構(gòu)。第一個參數(shù)可選的選項將在下面說明:
  1. int  setitimer(int which, const struct itimerval *value,  struct itimerval *ovalue);
復(fù)制代碼

setitmer比先前的alarm調(diào)用提供了更穩(wěn)定的接口。在BSD系統(tǒng)上,每個進程可以提供三種不同時間間隔的定時器。他們在下面講述:
  1. #define ITIMER_REAL      0
復(fù)制代碼

實時時鐘實時的遞減而不管進程在CPU上的實際花銷時間(換句話說,它追蹤自然時間natural time)。這允許進程設(shè)置一個基于自然實時時間(based on  atural real time)的定時器。當(dāng)實時定時器超時的時候,進程會收到SIGALRM信號。
  1. #define ITIMER_VIRTUAL   1
復(fù)制代碼

虛擬定時器僅只遞減進程在CPU上的執(zhí)行時間,允許進程設(shè)定一個基于CPU使用率的定時器。當(dāng)虛擬定時器超時的時候,進程收到SIGVTALRM信號
  1. #define ITIMER_PROF      2
復(fù)制代碼

Profile定時器遞減在CPU上的執(zhí)行時間和代表進程執(zhí)行的系統(tǒng)調(diào)用的時間。這對于那些要求靜態(tài)剖析的解釋程序是很有幫助的。當(dāng)profile定時器超時的時候,進程收到SIGPROF信號。然而并不像實時和虛擬定時器那樣,SIGPROF可以在系統(tǒng)調(diào)用的時候被發(fā)送;進程應(yīng)該準(zhǔn)備好重新執(zhí)行被中斷的系統(tǒng)調(diào)用。

本章將焦點放在了信號庫上。這些信號及他們的使用方法對于系統(tǒng)編程是很重要的。信號允許系統(tǒng)管理員通知應(yīng)用程序重新讀取配置文件,從而使程序更穩(wěn)定。其它重要的信號處理掛起在打開的文件描述符上的I/O操作。下一章講述怎樣利用這些I/O相關(guān)的信號。



第五章 基本輸入輸出

譯者:horseman
(多謝我的同事HUGH CHEN翻譯5.6,5.7跟部分5.4,5.5段落)


本章示范(示例)代碼:http://www.khmere.com/freebsd_book/src/05/index.html

5.1  基本輸入輸出

一般來說,Unix信奉簡單設(shè)計的哲理。"一切都是文件"是個很強大的特征--這就意味著你所編輯的文本文件具有和調(diào)制解調(diào)器、打印機或網(wǎng)卡相同的編程接口,就像編輯文本文件一樣,你應(yīng)該能夠?qū)λ鼈儯ū痪庉嬑募﹫?zhí)行基本的讀、寫操作等等。盡管這個想法現(xiàn)行的實現(xiàn)不完美,BSD Unix實際上做到非常接近了,這也正是BSD又一個強大的地方--簡潔而優(yōu)雅。有些不是真實的文件而是設(shè)備,它們的入口在/dev目錄下,有些設(shè)備只能用于特殊的操作,如塊的讀、寫,一個極端的例子是以太網(wǎng)設(shè)備,甚至它(以太網(wǎng)設(shè)備)在FreeBSD 5之前在/dev下面沒有入口。

操作系統(tǒng)看待每件事都像文件,一個好的示范是Plan 9,Plan 9用文件實現(xiàn)一切,甚至以太網(wǎng)和網(wǎng)絡(luò)協(xié)議;更詳細的信息可以參考Plan 9的主頁http://www.cs.bell-labs.com/plan9dist/

一般來說,文件是計算機上數(shù)據(jù)最基本、最初級的表現(xiàn)形式,本質(zhì)上是數(shù)據(jù)一位一位的線性序列。當(dāng)用exec命令執(zhí)行編譯過的程序時,系統(tǒng)將把二進制文件讀入內(nèi)存,代碼將在分配到內(nèi)存地址空間后被執(zhí)行,程序被定位在什么位置跟exec命令有關(guān),它可以在軟磁盤,硬盤,光驅(qū),甚至是加載的另一半還分布在世界其他角落的網(wǎng)絡(luò)文件系統(tǒng)上;跟基于網(wǎng)絡(luò)鏈路發(fā)送數(shù)據(jù)一樣,內(nèi)容被順序地一位一位地讀入。當(dāng)一個程序發(fā)送數(shù)據(jù)的時候,數(shù)據(jù)本身是一位一位的線性序列,有些時候叫做“流”,程序不關(guān)心它是否基于網(wǎng)絡(luò)鏈路發(fā)送數(shù)據(jù),它僅僅寫數(shù)據(jù);這兩種最基本的操作,讀跟寫,是計算雞用到的最多的。

本章將涉及最基本的I/O子系統(tǒng)跟進程資源。


5.2  基本輸入輸出

UNIX進程打開文件的時候,會保存文件描述符的參考值,該值是一個整數(shù),不論何時在UNIX系統(tǒng)上創(chuàng)建一個進程,都會給它3個文件描述符:
    0   標(biāo)準(zhǔn)輸入
    1   標(biāo)準(zhǔn)輸出
    2   標(biāo)準(zhǔn)錯誤

這些值能夠用于描述終端、文件的讀寫,甚至設(shè)置其他進程的描述。使shell重定向, cat /etc/hosts >> hosts.out, shell將打開文件hosts.out并且將cat /etc/hosts做為自己的參數(shù)執(zhí)行。不管怎樣,當(dāng)cat進程寫到標(biāo)準(zhǔn)輸出后(1),結(jié)果不會被tty得到,而是輸出到hosts.out文件。(cat程序根本不知道寫入了文件系統(tǒng)的文件,還是寫入到標(biāo)準(zhǔn)輸出的文件描述符,我們將在這章的后面一點看到究竟是怎么實現(xiàn)的)

最基本的兩個操作描述符是Open和Close函數(shù)

Open函數(shù)
      
  1. int     open(const char *path, int flags, /*  mode */ );,
復(fù)制代碼

成功地調(diào)用這個函數(shù)后,Open函數(shù)將返回文件及參數(shù)的描述符,這個整型描述符在進程文件描述表內(nèi)生成索引。該描述符的結(jié)構(gòu)能夠讓內(nèi)核知道如何操作這個文件。在BSD上,這個結(jié)構(gòu)叫做filedesc,并且能夠在/usr/include/sys/filedesc.h這個頭文件中找到。當(dāng)進程要在這個描述符上執(zhí)行一些操作的時候,它將要求文件描述符為讀、寫、可執(zhí)行操作指定一個整數(shù)值。

內(nèi)核保存了所有文件描述符的基準(zhǔn)值,這些基準(zhǔn)值將在時進程打開、復(fù)制它或者已經(jīng)處于打開狀態(tài)的文件描述符執(zhí)行exec調(diào)用、跨越fork的過程中增加。當(dāng)這個參考值為0的時候,文件關(guān)閉。意思是,如果你有一個程序執(zhí)行fork或者exec調(diào)用,并且close-on-exec位沒有指定,基準(zhǔn)值將增加;并且當(dāng)一個新的程序執(zhí)行了fork或者exec調(diào)用,基準(zhǔn)值將繼續(xù)增加。所以文件一直處于打開狀態(tài),直到基準(zhǔn)值被設(shè)置為0,或者直到所有的進程關(guān)閉它,或者退出。

Close函數(shù)
      
  1. int        close(int fd);
復(fù)制代碼

當(dāng)進程想移除或者關(guān)閉一個打開的文件描述符的時候,它將調(diào)用close函數(shù)。這將關(guān)閉指定的文件描述符,并且減少文件描述符的參考值。這個過程很像 exit————當(dāng)一個進程執(zhí)行exit,所有打開的文件描述符跟進程一起被自動地減少,自從文件參考值設(shè)為0,內(nèi)核將釋放所有的文件入口的副本.

getdtablesize函數(shù)
      
  1. int        getdtablesize();
復(fù)制代碼

getdtablesize函數(shù)返回文件描述表的大小,它能用作檢查系統(tǒng)限制,你也能夠用下面的命令做相同的事情。

  1. bash$ sysctl kern.maxfilesperproc
  2. kern.maxfilesperproc: 3722
復(fù)制代碼


依賴于你的系統(tǒng),你能夠在系統(tǒng)運行的時候調(diào)整它,或者從新編譯你的內(nèi)核,這個函數(shù)不能檢查當(dāng)前進程打開了多少文件,(跟getdtablesize一樣)僅僅返回你的進程能夠打開文件的最大個數(shù)。

fcntl函數(shù)
      
  1. int        fcntl(int fd, int cmd, ...);
復(fù)制代碼

fcntl 函數(shù)允許進程操作文件描述符,fcntl函數(shù)至少需要指定兩個參數(shù),一個有效的文件描述符,一個命令。根據(jù)使用的命令,決定fnctl是否需要第三個參數(shù),下面為命令定參數(shù)義了一些值。在FreeBSD上,你能在/usr/include/fcntl.h頭文件中找到它們。
      
  1. #define F_DUPFD        0
復(fù)制代碼

F_DUPFD用作創(chuàng)建一個新的很像原型的文件描述符,(你能夠用dup調(diào)用做到相同的事情,將在晚些時候涉及到),當(dāng)成功執(zhí)行帶F_DUPFD標(biāo)記的fcntl函數(shù),fcntl將返回下面屬性之一的新的文件描述符:

  • 假如指定了第三個參數(shù),描述符返回的值比最小可用的描述符,該值等于或者比給定的第三個參數(shù)的值大一點,返回的描述符將參照指定給fcntl第一個參數(shù)的文件描述符
  • 假如fcntl指定的文件描述符是一個文件,新文件描述符將有相同的文件偏移量,并且新文件描述符將有相同的訪問方式(如:O_RDONLY,O_RDWR,o_WRONLY)
  • 新文件描述符將共享文件狀態(tài)標(biāo)記
  • 新文件的“close-on-exec”標(biāo)志將被關(guān)閉,即新的文件描述符在exec調(diào)用之后仍將保持打開。


F_GETFD命令
      
  1. #define  F_GETFD     1
復(fù)制代碼

F_GETFD 命令用作獲取"close-on-exec"標(biāo)記的狀態(tài),跟FD_CLOSEXEC相與后返回值要么為0,要么為1。如果返回0,close-on- exec標(biāo)記沒有被設(shè)置過,那么文件描述符將保持調(diào)用交叉執(zhí)行調(diào)用(so the file descriptor will remain open across exec calls.),假如是1,close-on-exec標(biāo)志被設(shè)置過,文件描述符將在成功調(diào)用一個exec函數(shù)后被關(guān)閉。

F_SETFD命令
      
  1. #define  F_SETFD     2
復(fù)制代碼

F_SETFD命令用作設(shè)置文件描述符的close-on-exec標(biāo)志位。第三個參數(shù)要么是FD_CLOEXEC,設(shè)置close-on-exec標(biāo)記,要么是0,取消close-on-exec標(biāo)記的設(shè)置。

F_GETFL和F_SETFL命令
  1.      #define  F_GETFL    3
  2.         #define  F_SETFL    4
復(fù)制代碼

F_GETFL命令將使fcntl返回當(dāng)前文件描述符狀態(tài)標(biāo)記,當(dāng)返回值加上O_ACCMODE(#define O_ACCMODE 0x0003)能夠獲取到打開的方式,F(xiàn)_SETFL命令將根據(jù)第三個參數(shù)設(shè)置文件狀態(tài)標(biāo)記。

公共的標(biāo)記

下面這些標(biāo)記也用作調(diào)用open,并且只能被跟上期望的標(biāo)記調(diào)用open函數(shù)設(shè)置,這是最常見的,檢查你系統(tǒng)的頭文件能夠查到那些值
  1. #define  O_RDONLY 0x0000
復(fù)制代碼

如果O_RDONLY標(biāo)記被設(shè)置,那么文件只能以只讀方式打開。注意這個O_RDONLY標(biāo)記只能在打開的時候被設(shè)置,它不能被fnctl加F_SETFL命令設(shè)置。
  1. #define  O_WRONLY 0x0001
復(fù)制代碼

如果O_WRONLY標(biāo)記被設(shè)置,那么文件只能以只寫方式打開。這個標(biāo)記只能被open設(shè)置,不能被fcntl加F_SETFL命令設(shè)置。
  1. #define  O_RDWR      0x0002
復(fù)制代碼

如果O_RDWR標(biāo)記被設(shè)置,那么文件以可讀可寫方式打開。這個標(biāo)記也只能被open調(diào)用設(shè)置。
  1. #define  O_NONBLOCK  0x0004
復(fù)制代碼

如果O_NONBLOCK標(biāo)記被設(shè)置,文件描述符將不被阻塞而被直接返回替代。一個例子是打開tty。如果用戶不在終端調(diào)用里輸入任何東西,read將被阻塞,直到用戶有輸入,當(dāng)O_NONBLOCK標(biāo)記被設(shè)置,read調(diào)用將直接返回設(shè)置到EAGAIN的值
  1. #define  O_APPEND 0x0008
復(fù)制代碼

如果O_APPEND標(biāo)記被設(shè)置,文件將以追加方式打開,并且將從文件末尾開始寫入。
  1. #define  O_SHLOCK 0x0010
復(fù)制代碼

如果O_SHLOCK標(biāo)記被設(shè)置,文件描述符將在文件上生成一個共享鎖,在文件上設(shè)置了共享鎖,多個進程能在同一個文件夠執(zhí)行操作,文件共享鎖的詳細信息,可用看fnctl函數(shù)的F_GETLK跟F_SETL命令。
  1. #define  O_EXLOCK 0x0020
復(fù)制代碼

如果O_EXLOCK標(biāo)記被設(shè)置,文件描述符將在文件上生成一個可執(zhí)行鎖,一樣,更詳細的描述可以參照fcntl函數(shù)的F_SETLK跟F_GETLK命令。
  1. #define  O_ASYNC     0x0040
復(fù)制代碼

如果O_ASYNC標(biāo)記被設(shè)置,進程集將被發(fā)送的SIGIO信號通知,在文件描述符號的IO是可用的。詳細的描述請參照信號那一章。
  1. #define  O_FSYNC     0x0080
復(fù)制代碼

如果O_FSYNC標(biāo)記被設(shè)置,所有寫到文件描述符的寫操作將不被內(nèi)核緩存,取而代之的是將被寫到介質(zhì),并且所有的寫調(diào)用都將被阻塞,直到內(nèi)核完成(寫操作)。
  1. #define  O_NOFOLLOW  0x0100
復(fù)制代碼

如果O_NOFOLLOW標(biāo)記被設(shè)置,假如文件是一個符號連接,open調(diào)用將會失敗。如果在一個有效的文件描述符上設(shè)置了這個標(biāo)志,那么當(dāng)前文件就不是一個符號連接。
  1. #define  O_CREAT     0x0200
復(fù)制代碼

如果O_CREAT標(biāo)記被設(shè)置,假如執(zhí)行open調(diào)用的時候文件不存在,那么文件可用被創(chuàng)建。(這個錯誤的拼寫很有趣;when one of the original creators of C was asked "What one thing would you change about C?" he replied, "I would change O_CREAT to O_CREATE!", or at least how the rumor goes)
  1. #define  O_TRUNC     0x0400
復(fù)制代碼

如果O_TRUNC標(biāo)記被設(shè)置,成功地調(diào)用open后文件將被截除。
  1. #define  O_EXCL      0x0800
復(fù)制代碼

當(dāng)O_EXCL標(biāo)記被設(shè)置,假如文件已經(jīng)存在,open調(diào)用將產(chǎn)生一個錯誤。
  1. #define F_GETOWN          5
復(fù)制代碼

F_GETOWN命令用于描述符獲取當(dāng)前進程或者進程集收到的SIGIO信號。如果這個值是一個正數(shù),它表示一個進程,負(fù)數(shù)表示一個進程集。
  1. #define F_SETOWN  6
復(fù)制代碼

當(dāng)IO就緒的時候,F(xiàn)_SETOWN命令用作設(shè)置進程或者進程集使其接收SIGIO信號,指定一個進程,用一個正數(shù)(一個進程ID)作為fcntl的第三個參數(shù),否則,用一個負(fù)數(shù)作為fcntl的第三個參數(shù)指定進程集。

5.3文件上鎖

當(dāng)多個進程試圖寫同一個文件,將發(fā)生什么?它們相互沖突,已知的事情像文件上鎖。結(jié)果就是每個文件描都有自己的描述符跟偏移量,當(dāng)每個進程寫自己的文件時,偏移量預(yù)先獨立導(dǎo)致沒有進程知道其他的進程也正在執(zhí)行寫操作。最后的文件將因為多個獨立寫文件的操作使混合后的文件變得相當(dāng)于垃圾,直接給文件上鎖是解決這個問題的一種方式。在任意時刻只能讓一個進程能夠?qū)懙轿募,另一種辦法是允許在一個叫做高級文件鎖的scheme里的文件內(nèi)部進行區(qū)域鎖定。 fcntl函數(shù)能夠提供這個功能,通常來說,鎖有兩種,一種是寫,另一種是寫,不同之處在于讀鎖不會干擾其它進程讀取文件,但是特定的區(qū)域只能一個寫鎖存在。

當(dāng)使用顧問鎖的時候,下面的結(jié)構(gòu)用作fcntl的第三個參數(shù)。

  1.   struct flock {
  2.             off_t   l_start;    /* starting offset */
  3.             off_t   l_len;      /* len = 0 means until end of file */
  4.             pid_t   l_pid;      /* lock owner */
  5.             short   l_type;     /* lock type:   */
  6.             short   l_whence;   /* type of l_start */
  7.     };
復(fù)制代碼


讓我們繼續(xù)討論每個元素的細節(jié)。

l_start

這是一個相對于l_whence,單位為字節(jié)的偏移量,換句話說,要求的位置實際上是l_whence + l_start

l_len

需設(shè)置為期望位置的長度,單位為字節(jié),鎖將從l_whence + l_start開始鎖定l_len字節(jié),如果你想整個文件用一把鎖,那么設(shè)定l_len的值為0,如果l_len的值是一個負(fù)數(shù),結(jié)果是不可預(yù)測的。

l_pid

需要設(shè)置為工作在鎖上的進程的ID

l_type

需要設(shè)置為期望的鎖的類型,下面是能夠使用的值

    * F_RDLCK - 讀鎖定
    * F_WRLCK - 寫鎖定
    * F_UNLCK - 用作清除鎖定

l_whence

這是這個系統(tǒng)調(diào)用里面最混亂的部分,這個字段將決定l_start位置的偏移量,需要設(shè)為:
    * SEEK_CUR - 在當(dāng)前位置
    * SEEK_SET - 在文件開始
    * SEEK_END - 在文件末尾

fcntl的命令

下面的可用作fcntl的命令
  1. #define  F_GETLK      7
復(fù)制代碼

F_GETLK 命令嘗試檢查否能上鎖,當(dāng)使用這個命令,fnctl將檢查是否有相沖突的鎖,如果存在相沖突的鎖,fnctl將改寫flock結(jié)構(gòu),用沖突鎖消息通過檢查,如果沒有相沖突的鎖,那么在flock結(jié)構(gòu)最初的信息將被保留,除非l_type字段被設(shè)成F_UNLCK
  1. #define  F_SETLK      8
復(fù)制代碼

F_SETLK命令試圖獲得flock結(jié)構(gòu)描述的鎖,如果鎖不被承認(rèn),本次調(diào)用將不被阻塞。不管怎樣,fcntl將直接返回EAGAIN,同時將設(shè)置相應(yīng)的errno,當(dāng)flock結(jié)構(gòu)的l_type被設(shè)置為F_UNLCK時,你能夠使用這個命令清除一個鎖。
  1. #define  F_SETLKW     9
復(fù)制代碼

F_GETLK命令試圖獲得flock結(jié)構(gòu)描述的鎖,它將命令fnctl阻塞,直到賦予一個鎖

5.4為什么用FLOCK  

對大部分情況來說,高級文件鎖定機制是有好處的。然而,POSIX.1接口有幾個缺點。第一,當(dāng)一個文件的任何一個文件描述符被關(guān)閉時,與該文件關(guān)聯(lián)的所有鎖定必須被刪除。換句話說,如果你有一個進程打開一個文件,接著它調(diào)用一個函數(shù)打開同一個文件,讀取然后關(guān)閉它,這樣先前你對這個文件的所有鎖定都會被刪除。如果你并不確定一個庫例程會做什么,這將會引起嚴(yán)重問題。第二,鎖定是不會傳遞給子進程的,所以一個子進程必須獨立地創(chuàng)建屬于它自己的鎖定。第三,所有在一個exec調(diào)用之前獲得的鎖定在由exec啟動的進程釋放,關(guān)閉這個文件或結(jié)束之前不會被釋放。所以,如果你需要鎖定文件的某一部分,那么調(diào)用exec不需要釋放鎖定或者關(guān)閉文件描述符,那部分區(qū)域?qū)⒈绘i定直到進程結(jié)束,你想要的或許不是預(yù)期的結(jié)果,不論如何,BSD的設(shè)計者用許多flock創(chuàng)建了非常簡單的優(yōu)先級文件上鎖的接口.

flock用于鎖定整個文件,是BSD優(yōu)先選擇的方法,跟fcntl高級鎖定相反,flock機制允許鎖定進入子進程,使用flock調(diào)用的其他好處是可以在文件級并且不在文件描述符級別完成鎖定,在某些情況下可用優(yōu)先選擇,意味著多個參照同一個文件的文件描述符,例如DUP()調(diào)用,或者多個OPEN()調(diào)用,希望每一個參照相同的文件鎖,帶flock的文件鎖跟一個寫多個讀的fcntl鎖很類似,不論如何,當(dāng)下面的操作被定義,調(diào)用flock時,鎖的優(yōu)先級能夠被提升:
  1. #define   LOCK_SH        0x01      /* shared file lock */
復(fù)制代碼

LOCK_SH操作用于在文件上(類似fcntl的讀鎖)創(chuàng)建共享鎖,在一個文件上,多個進程能夠共享一把鎖。
  1. #define   LOCK_EX        0x02      /* exclusive file lock */
復(fù)制代碼

LOCK_EX操作用于在文件上創(chuàng)建互斥鎖,當(dāng)互斥鎖生效時,文件上不能存在其他的共享鎖,包括已共享的鎖。
  1. #define   LOCK_NB        0x04      /* don't block when locking */
復(fù)制代碼

使用這個,flock的調(diào)用將阻塞,直到鎖定生效,不論如何,假如期望的操作LOCK_NB是ORed,flock的調(diào)用將向EWOULDBLOCK返回錯誤號成功(0)或者失。1).
  1. #define   LOCK_UN        0x08      /* unlock file */
復(fù)制代碼

LOCK_UN用于移除文件上的鎖

通過調(diào)用期望的flock,flock鎖的優(yōu)先級能夠被提升或者降低。新的成功的調(diào)用將使最近生效的鎖替換先前的鎖。

DUP函數(shù)
  1. int dup(int old);
復(fù)制代碼

像fcntl調(diào)用能夠用作為現(xiàn)有的文件描述符復(fù)制描述符一樣,dup函數(shù)也能復(fù)制文件描述符,dup調(diào)用能夠返回一個跟old參數(shù)無法區(qū)別的新的文件描述符,這就意味著所有的read(),write()跟lseek()調(diào)用都會操作兩個描述符(復(fù)制出來的新的跟原來的描述符),同時,所有fcntl的選項將保留,close on exec位出外, close on exec位會被關(guān)閉,所以你可以復(fù)制一個文件描述符,然后允許子進程去調(diào)用一個exec函數(shù),這是dup函數(shù)非常普遍的一個用法。old參數(shù)用作復(fù)制,并且必須是一個有效的參數(shù),描述目標(biāo)描述符,新文件符至少應(yīng)該是沒使用過的文件描述符,它由成功調(diào)用dup函數(shù)后返回。它意味著如果你關(guān)閉STDIN_FILENO(該值為0)那么dup直接調(diào)用的新文件描述符的值將是STDIN_FILENO,假如dup函數(shù)調(diào)用因一些原因失敗,將返回-1并且相應(yīng)地設(shè)置errno.

DUP2函數(shù)
  1. int dup2(int old, int new);
復(fù)制代碼

除了new參數(shù)是希望的目標(biāo)值,dup2函數(shù)跟dup函數(shù)很相似。假如新參數(shù)已經(jīng)參照了一個有效打開的描述符,并且它的值跟老參數(shù)的值不一樣,那么新文件描述符將先被關(guān)閉。假如新參數(shù)等于老參數(shù),那么函數(shù)不會做任何操作。成功調(diào)用DUP2的返回值將等于新參數(shù)。假如DUP2調(diào)用失敗,將返回-1并且相應(yīng)地設(shè)置errno.

5.5  進程間通信

來自于System V的主要的功能是基本的進程間通信,或者說IPC,這些一直在BSD里面被非常廣泛地使用。IPC機制允許程序之間相互共享數(shù)據(jù)。這個很像我們已經(jīng)介紹過的重定向,但是重定向是單向處理而不是雙向處理。在示范程序中,重定向可以跟設(shè)置過STDIN_FILENO參數(shù)的CAT命令共享數(shù)據(jù),但是有一個問題:cat命令不能跟重定向的程序共享數(shù)據(jù),假設(shè)一個算法是從其他文件描述符讀但是用open不靈活的時候,我們可以修改他們使其可以雙向地相互共享數(shù)據(jù)。BSD提供了很多更好的用于進程間通信的方法。

PIPE函數(shù)
  1. int pipe(int *array);
復(fù)制代碼

通過給定一個有效的二維數(shù)組調(diào)用pipe函數(shù)(如:int array[2]),pipe函數(shù)將分配兩個文件描述符,假如成功,數(shù)組將包含兩個有區(qū)別的并且允許單向通信的文件描述符, 打開的第一個文件描述符(array[0])用于讀,打開的另一(array[1])個用于寫,所以,自從成功調(diào)用pipe函數(shù)后,你能在這兩個描述符之間得到一個單向通信通道,當(dāng)寫入其中一個時,你也將能夠從另一個讀取輸出,管道函數(shù)比重定向好的地方是你不必使用文件,

這些文件描述符的行為與普通的文件描述符完全一樣,然而它們沒有任何文件與之關(guān)聯(lián),管道功能使unix shell加到其他命令的管道變得很有用,例如

  1. bash$ find /  -user frankie  | grep -i jpg  | more
復(fù)制代碼


這個例子里面,find命令將其輸出通過管道傳送給grep命令,grep再將其輸出通過管道傳送給more命令。當(dāng)建立這個順序的時候,shell將處理管道實際的設(shè)置,這些程序本身沒有寫到其他程序的想法,因為他們真的不需要知道。從這個例子你能看見自從調(diào)用了管道,正常來說進程就會fork, 之后進程就能通信,假如要達到雙向通信的目標(biāo),你需要創(chuàng)建兩個管道,一個用于父進程到子進程的通信,另一個用作子進程到父進程的通信。

管道有下面兩條通信規(guī)則:
    1.如果管道讀的這邊關(guān)閉了,試圖寫到這個管道將會導(dǎo)致一個SIGPIPE信號發(fā)送給試圖寫的進程。
    2.如果管道寫的這邊關(guān)閉了,試圖從這個管道讀將導(dǎo)致讀返回0或文件結(jié)束。關(guān)閉寫端是發(fā)送文件結(jié)束到該管道讀端的唯一方法。

成功地調(diào)用pipe函數(shù)后將返回0,假如調(diào)用失敗,將返回-1并且errno將被相應(yīng)地設(shè)置,

備注:在更多的現(xiàn)代的BSD上,單個描述符上pipe函數(shù)支持雙向通信,不論如何,這個特性不是很輕便,也因為如此,這個方式不建議使用。


Mkfifo函數(shù)

  1. int mkfifo(const char *path, mode_t mode);
復(fù)制代碼


在相關(guān)的進程間通信時,管道是有用的,然而在沒有關(guān)聯(lián)關(guān)系的進程間通信時,使用mkfifo函數(shù)。Mkfifo函數(shù)實際上在文件系統(tǒng)創(chuàng)建一個文件。這個文件只是其他文件在通信時使用的一個標(biāo)志。這些文件就叫做FIFO管道(先進先出管道)。當(dāng)一個進程創(chuàng)建一個FIFO管道,寫到這個FIFO管道并不會寫到這個文件,而是被另一個進程讀取。這種行為與管道非常相似,所以FIFO也被叫做命名管道。Mkfifo函數(shù)有兩個參數(shù)。第一個參數(shù)是一個以null結(jié)尾的字符串,聲明路徑和文件名。第二個參數(shù)是該文件的存取模式。該存取模式是標(biāo)準(zhǔn)的unix文件所有者讀寫權(quán)限(參考/usr/include/sys/stat.h中的S_IRUSR, S_IRGRP等)。

一旦mkfifo函數(shù)成功調(diào)用,需要用open函數(shù)打開創(chuàng)建的fifo管道進行讀寫。如果調(diào)用失敗,會返回-1而且錯誤代碼會被相應(yīng)地設(shè)置。

創(chuàng)建fifo管道與創(chuàng)建文件相類似,進程也必須有足夠的權(quán)限來創(chuàng)建fifo管道,因為該fifo的用戶ID會被設(shè)置為該進程的有效用戶ID,而組ID會被設(shè)置為該進程的有效組ID。

關(guān)于FIFO重要的一點是,在缺省情況下,它們是阻塞的。因此,讀取一個fifo管道會被阻塞,直到另一端把數(shù)據(jù)寫進來,反之亦然。為了避免這種情況,可以使用O_NOBLOCK參數(shù)來打開。在這種情況下,你會獲得以下行為:對讀的調(diào)用會立即返回,返回值為0;或者對寫的調(diào)用會導(dǎo)致一個SIGPIPE信號。

5.6消息隊列

另一種進程間通信機制,消息隊列,為進程間通信提供另一種方式。然而和我提到的其他通信機制不同,你應(yīng)該盡量避免使用這種方式。如果你的程序使用消息隊列,試著用fifo或者甚至Unix Domain Socket重新實現(xiàn)它。在我討論原因之前,以下是一個快速概述。

消息隊列與FIFO相似,但它們使用一個鍵而不是一個文件作為引用。這個鍵是一個無符號整數(shù)。一旦一個消息隊列被創(chuàng)建,發(fā)送到這個消息隊列的數(shù)據(jù)會被內(nèi)核緩存。內(nèi)核分配給消息隊列的內(nèi)存是有限的。一旦一個消息隊列被填滿,那么在一個進程從這個消息隊列把數(shù)據(jù)讀走之前不能再往這個隊列發(fā)送數(shù)據(jù)。在這種情況下,如果兩個進程以不同的速度讀寫,隊列是可靠的而且在大多數(shù)情況下是非堵塞的。這與FIFO不同。在FIFO的機制中,一個慢的讀進程事實上是會拖慢一個比較快的寫進程的(除非設(shè)置了O_NONBLOCK參數(shù))。另一個好處是即使寫進程退出了,寫到消息隊列中的數(shù)據(jù)也會被保存起來直到另一個進程讀取它。而在FIFO的機制下,如果寫進程退出了,這個FIFO會被關(guān)閉,而讀進程會收到一個文件結(jié)束的標(biāo)志。

以上這些所有的消息隊列的好處看起來不錯,但讓我們再仔細看看。假定一個進程打開一個消息隊列,往里面寫了一大塊數(shù)據(jù),填滿了內(nèi)核的緩沖區(qū)然后退出,內(nèi)核將不得不保存這些數(shù)據(jù)直到另一個進程來讀取它,而其他任何想創(chuàng)建和寫消息隊列的進程將會被拒絕。這種情況將會持續(xù)直到一個進程讀走這些數(shù)據(jù)或者系統(tǒng)重啟。在這種情況下,創(chuàng)建一個簡單的針對消息隊列的拒絕服務(wù)是可能的。

另一個問題是鍵并不確保是唯一的。換句話說,一個進程無法確立一種方式來確定它是否是使用一個特定消息隊列的唯一進程。而在FIFO機制中,當(dāng)一個進程創(chuàng)建一個FIFO時,它有比較好的機會知道這個FIFO是否唯一因為可以聲明一個針對文件路徑的預(yù)先約定(比如:/usr/local/myapp/fifo_dir)。你的應(yīng)用程序可以在安裝時創(chuàng)建一個唯一的目錄,這樣幾乎可以確保一個唯一的FIFO路徑。有效的消息隊列鍵值可以通過調(diào)用一個函數(shù)ftok來生成以幫助減少相同的鍵值,但并不能確保唯一。這個問題的副作用難于確定—你的程序可能讀到它并不想讀的數(shù)據(jù),或者你的程序?qū)懭氲臄?shù)據(jù)正被其他并不想讀取的進程讀取。簡短來說,當(dāng)你使用消息隊列時,可能導(dǎo)致難于調(diào)試的奇怪行為和錯誤。

如果你仍然堅持使用消息隊列,參考以下的指南頁:ftok(3), msgget(3), msgctl(3), msgrcv(3), 和msgsnd(3)。

5.7 結(jié)論

這一章介紹了幾個操作打開文件描述符的系統(tǒng)調(diào)用,包含在我們fork和exec之前必須關(guān)閉文件描述符的場景。我們也討論了文件鎖定,設(shè)置和移除文件鎖定,以及一些特殊的文件描述符如FIFO和Queue等,在那種情況下根本不在文件系統(tǒng)上存儲數(shù)據(jù)。這些系統(tǒng)調(diào)用為BSD增加了很大的可編程性和靈活性。但當(dāng)一個進程有多個打開的文件描述符時會怎么樣?下一章將會討論高效地處理多個文件描述符。



第六章 高級I/O

翻譯:雨絲風(fēng)片@chinaunix.net


6.1 高級I/O和進程資源

正如我們在前面章節(jié)中看到的,程序可以同時打開多個文件描述符。這些文件描述符并不一定就是文件,還可以是fifo、pipe或者socket。于是,如何復(fù)用這些打開的描述符就很重要了。例如,考慮一個簡單的郵件閱讀程序,比如pine。它顯然應(yīng)當(dāng)允許用戶在讀寫email的同時也能去檢查是否有新郵件。這就意味著在任一給定時刻都至少能夠接收兩個來源的輸入:一個來源是用戶,另一個是用來檢查新郵件的描述符。處理描述符的復(fù)用是個復(fù)雜的問題。一種方法是把所有打開的描述符都標(biāo)記為非阻塞的(O_NONBLOCK),然后在它們之中循環(huán),直到找到一個可以進行I/O操作的描述符為止。這種方法的問題是程序會一直在循環(huán),如果長時間內(nèi)沒有I/O可用,進程就會一直占據(jù)CPU。當(dāng)有多個進程在一組很少的描述符上循環(huán)時,你的CPU的負(fù)載就會惡化。

另一種方法就是設(shè)置信號處理器去捕獲I/O變?yōu)榭捎玫氖录,然后就讓進程進入休眠狀態(tài)。如果你只打開了少量的描述符,而且并不經(jīng)常請求I/O的話,這種方法從理論上看倒是不錯。由于進程已經(jīng)休眠,就不會再占用CPU,僅當(dāng)I/O可用時它才恢復(fù)執(zhí)行。然而,這種方法的問題在于信號處理的開銷有點大。比如一個web服務(wù)器,每分鐘收到100個請求,那就幾乎一直都在捕獲信號。每秒鐘捕獲上百個信號的開銷是相當(dāng)大的,不單是進程,對于內(nèi)核發(fā)送信號的開銷而言也是一樣的。

到目前為止,我們看到的兩種選擇都有限制,效率也不高,它們需要解決的共同問題就是進程需要知道I/O究竟什么時候能用?然而,這個信息實際上只有內(nèi)核才能事先知道,因為是內(nèi)核在最終處理系統(tǒng)中的所有打開的描述符。例如,當(dāng)一個進程通過fifo向另一個進程發(fā)送數(shù)據(jù)的時候,發(fā)送進程會調(diào)用write,這是一個系統(tǒng)調(diào)用,因此會進入內(nèi)核。在發(fā)送方的write系統(tǒng)調(diào)用執(zhí)行完畢之前接收方對此是一無所知的。于是就引出了一個更好的復(fù)用文件描述符的方法:由內(nèi)核來替進程管理描述符。換句話說,就是把一個打開描述符的鏈表發(fā)送給內(nèi)核,然后等待,直到內(nèi)核發(fā)現(xiàn)某個或多個描述符已經(jīng)準(zhǔn)備好了或者已經(jīng)超時了為止。

這就是select()、poll()和kqueue()接口采用的方法。通過這些接口,內(nèi)核就會管理文件描述符,當(dāng)I/O可用時就去喚醒進程。這些接口巧妙地處理了上述問題。進程不必再在打開的文件描述符中循環(huán),也不必再去設(shè)置信號了。但進程在使用這些函數(shù)的時候還是會產(chǎn)生一點小問題。這是因為I/O操作是在從這些接口返回之后才去執(zhí)行的。所以它至少需要兩個系統(tǒng)調(diào)用才能完成其操作。例如,你的程序有兩個用于讀的描述符。你對它們使用select,然后等待它們直至有數(shù)據(jù)可讀。這就需要進程首先調(diào)用select,在select返回之后,就對該描述符調(diào)用read。更妙的是,你還可以對所有打開的描述符執(zhí)行一個整體的read。一旦其中有某個描述符準(zhǔn)備好讀之后,read就會返回,并把數(shù)據(jù)放在緩沖區(qū)中,同時還會給出一個標(biāo)識,用來指示這個數(shù)據(jù)是從哪個描述符讀進來的。

6.2 select

我首先要講的接口是select()。格式如下:

  1. int  select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,  struct timeval *timeout);
復(fù)制代碼


傳給select的第一個參數(shù)已經(jīng)造成了多年的混亂。nfds參數(shù)的正確用法是把它設(shè)成文件描述符的最大值加1。換句話說,如果你有一組文件描述符{0,1,8},nfds參數(shù)就應(yīng)當(dāng)被設(shè)置成9,因為你的描述符的最大值為8。有些人錯誤地以為這個參數(shù)的意思是文件描述符的總數(shù)加1,對于我們的例子而言就是4。記住,一個文件描述符只是一個整數(shù)而已,所以你的程序就需要指出你所想要在其上select的最大的描述符值。

select接下來會按順序針對所有尚未完成的讀、寫以及異常條件檢查其余的三個參數(shù),readfds、writefds和exceptfds。(詳細信息請參見man(2) select)。注意,如果readfds、writefds和execptfds中沒有設(shè)置描述符,那么傳給select的對應(yīng)參數(shù)應(yīng)當(dāng)被設(shè)置成NULL。

readfds、writefds和execptfds參數(shù)通過以下4個宏進行設(shè)置。

FD_ZERO(&fdset);

FD_ZERO宏用來對指定的描述符集合中的bit進行清零。有一點需要特別注意:只要使用select,就應(yīng)當(dāng)調(diào)用這個宏;否則select的行為將是不可預(yù)知的。

FD_SET(fd, &fdset);

FD_SET宏用于向一組激活的描述符中添加一個描述符。

FD_CLR(fd, &fdset);

FD_CLR宏用于從一組激活的描述符中刪除一個描述符。

FD_ISSET(fd, &fdset);

FD_ISSET宏是在select返回之后使用的,用于測試某個描述符是否已準(zhǔn)備好進行I/O操作。

select的最后的參數(shù)是一個超時值。如果超時值被設(shè)置為NULL,則對select的調(diào)用將以不確定的方式被阻塞,直至某個操作已準(zhǔn)備好為止。如果你需要一個確定的超時時間,那么超時值就得是一個非空的timeval結(jié)構(gòu)體。timeval結(jié)構(gòu)體如下:

  1.   struct timeval {
  2.       long    tv_sec;         /* seconds */
  3.       long    tv_usec;        /* microseconds */
  4.   };
復(fù)制代碼


如果select調(diào)用成功,將返回準(zhǔn)備好的描述符的數(shù)目。如果select因為超時而返回,則返回值為0。如果有錯誤發(fā)生,則返回-1,同時會相應(yīng)地設(shè)置errno。

6.3 poll

我們在這里對I/O的討論主要是針對BSD的。System V支持一種特殊類型的I/O,即所謂的STREAMS。和socket一樣,STREAMS也具有優(yōu)先級屬性,這種屬性有時也被成為數(shù)據(jù)帶。數(shù)據(jù)帶可用來給STREAMS中的特定數(shù)據(jù)設(shè)置較高的優(yōu)先級。BSD最初并不支持這一特性,不過有些人添加了System V仿真功能,可以對某些類型提供支持。由于我們并不關(guān)注System V,因此我們只會引用數(shù)據(jù)帶或數(shù)據(jù)優(yōu)先級帶的概念。詳細信息請參見System V STREAMS。

poll函數(shù)和select很相似:

  1.   int  poll(struct pollfd *fds, unsigned int nfds, int timeout);
復(fù)制代碼


和原產(chǎn)于BSD的select不同,poll是由System V Unix創(chuàng)建的,在早期的BSD版本中并不支持它。目前主流BSD系統(tǒng)中都已經(jīng)支持poll了。

和select相似,poll也是在一組給定的文件描述符上進行復(fù)用。在指定這些描述符的時候,你必須使用一個結(jié)構(gòu)體數(shù)組,其中每個結(jié)構(gòu)體代表一個文件描述符。和select相比,poll的好處就是你可以判斷一些很罕見的條件,而select則無法做到。這些條件是POLLERR、POLLHUP和POLLNVAL,我們稍后討論。盡管對于選擇select還是poll的問題已經(jīng)有了相當(dāng)多的討論,但這在很大程度上還是取決于你的個人愛好。poll所使用的結(jié)構(gòu)體是pollfd結(jié)構(gòu)體,如下:

  1.   struct pollfd {
  2.       int     fd;             /* which file descriptor to poll */
  3.       short   events;         /* events we are interested in */
  4.       short   revents;        /* events found on return */
  5.   };
復(fù)制代碼


fd

fd成員用于指定你想要poll的文件描述符。如果你想刪除一個描述符,那就把那個描述符的fd成員設(shè)置成-1。通過這種方法,你可以避免對整個數(shù)組進行混洗,同時還可以清除revents成員中列出的所有事件。

events, revents

events成員是一個bit掩碼,用于指定針對指定描述符所關(guān)心的事件。revents成員也是一個bit掩碼,但它的值是由poll設(shè)置的,用于記錄在指定描述符上發(fā)生的事件。這些事件的定義如下:

  1.   #define POLLIN          0x0001
復(fù)制代碼


POLLIN事件表明你的程序?qū)⑦x擇該描述符上的可讀數(shù)據(jù)事件。注意,此處的數(shù)據(jù)不包括高優(yōu)先級數(shù)據(jù),比如socket上的帶外數(shù)據(jù)。

  1.   #define POLLPRI         0x0002
復(fù)制代碼


POLLPRI事件表明你的程序準(zhǔn)備選擇該描述符上的任何高優(yōu)先級事件。

  1.   #define POLLOUT         0x0004
  2.   #define POLLWRNORM      POLLOUT
復(fù)制代碼


POLLOUT和POLLWRNOMR事件表明你的程序想知道什么時候可以對一個描述符執(zhí)行寫操作了。在FreeBSD和OpenBSD上這兩個事件是相同的;你可以在你的系統(tǒng)頭文件(/usr/include/poll.h)中查證這一點。從技術(shù)角度來說,它們之間的區(qū)別在于POLLWRNOMR僅當(dāng)數(shù)據(jù)優(yōu)先帶等于0的時候才去檢測是否可以進行寫操作。

  1.   #define POLLRDNORM      0x0040
復(fù)制代碼


POLLRDNORM事件表明你的程序準(zhǔn)備選擇該描述符上的常規(guī)數(shù)據(jù)。注意,在某些系統(tǒng)上,這個事件指定的操作和POLLIN完全一樣。但在NetBSD和FreeBSD上,這個事件和POLLIN并不相同。同樣,請去查看你的系統(tǒng)頭文件(/usr/include/poll.h)。嚴(yán)格地說,POLLRDNORM僅當(dāng)數(shù)據(jù)優(yōu)先帶等于0的時候采取檢測是否可以進行讀操作。

  1.   #define POLLRDBAND      0x0080
復(fù)制代碼


POLLRDBAND事件表明你的程序想知道什么時候能夠以一個非0的數(shù)據(jù)帶值從該描述符讀數(shù)據(jù)。

  1.   #define POLLWRBAND      0x0100
復(fù)制代碼


POLLWRBAND事件表明你的程序想知道什么時候能夠以一個非0的數(shù)據(jù)帶值向該描述符寫數(shù)據(jù)。

專用于FreeBSD的選項

下面的選項是專用于FreeBSD的,知道的和使用的人都不是太多。但它們還是值得提一下,因為它們可以提供更多的靈活性。這些都是新的選項,poll并不保證能夠檢測這些條件,而且它們只能用于UFS文件系統(tǒng)。如果你的程序需要檢測這些類型的事件,那最好使用kqueue接口,我們將在稍后介紹。

  1.   #define POLLEXTEND      0x0200
復(fù)制代碼


如果文件已經(jīng)被執(zhí)行,則設(shè)置POLLEXTEND事件。

  1.   #define POLLATTRIB      0x0400
復(fù)制代碼


如果有任一文件屬性發(fā)生改變,則設(shè)置POLLATTIB事件。

  1.   #define POLLNLINK       0x0800
復(fù)制代碼


如果文件被重命名、刪除或解除鏈接,則設(shè)置POLLNLINK事件。

  1.   #define POLLWRITE       0x1000
復(fù)制代碼


如果文件內(nèi)容被修改,則設(shè)置POLLWRITE事件。

下面的事件并不是pollfd events成員的有效標(biāo)志,poll也將忽略它們。它們是在pollfd revents中返回的,用于表明發(fā)生了某個事件。

  1.   #define POLLERR         0x0008
復(fù)制代碼


POLLERR事件表明有錯誤發(fā)生。

  1.   #define POLLHUP         0x0010
復(fù)制代碼


POLLHUP表明在對應(yīng)的STREAMS上發(fā)生了掛起事件。POLLHUP和POLLOUT是互斥事件,因為一個發(fā)生了掛起的STREAMS就不再是可寫的了。

  1.   #define POLLNVAL        0x0020
復(fù)制代碼


POLLNVAL表明對poll的請求是無效的。

poll的最后一個參數(shù)是超時值。可以通過這個參數(shù)告訴poll一個以微秒為單位的超時值。如果把超時值設(shè)置為-1,poll就會阻塞,直至所請求的事件發(fā)生為止。如果超時值設(shè)置為0,則poll將立即返回。

如果對poll的調(diào)用成功,則返回一個正整數(shù)。這個正整數(shù)的值表示有多少個描述符發(fā)生了事件。如果超時,poll將返回0。如果有錯誤發(fā)生,poll則會返回-1。

6.4 kqueue

到目前為止,poll和select已經(jīng)是相當(dāng)不錯的復(fù)用文件描述符的方法了。但為了使用這兩個函數(shù),你需要創(chuàng)建一個描述符的鏈表,然后把它們發(fā)送給內(nèi)核,在返回的時候又要再次查看這個鏈表。這看上去有點效率低下。一個更好一些的模型是把描述符鏈表交給內(nèi)核,然后就等待。一旦有某個或多個事件發(fā)生,內(nèi)核就把一個只包含有發(fā)生了事件的描述符的鏈表通知給進程,由此避免了每次函數(shù)返回的時候都要去遍歷整個鏈表。盡管對于只打開了幾個描述符的進程而言這點改進算不得什么,但對于那些打開了幾千個文件描述符的程序來說,這種性能改進就相當(dāng)顯著了。這就是kqueue誕生背后的主要目的。同時,設(shè)計者還希望進程能夠檢測更多類型的事件,比如文件修改、文件刪除、信號交付或者子進程退出,并提供一個包含了其它任務(wù)的靈活的函數(shù)調(diào)用。處理信號、復(fù)用文件描述符、以及等待子進程等操作都可以封裝到這個單一的kqueue接口中,因為它們都是在等待某個事件的發(fā)生。

另一個設(shè)計考慮就是如何讓一個進程毫無干擾地使用多個kqueue實例。如你所見,進程可以設(shè)置一個信號處理器,但是,當(dāng)代碼中的其它部分也想捕獲那個指定信號的時候該怎么辦?或者考慮更壞的情況,比如一個庫函數(shù)對你的程序想要捕獲的信號設(shè)置了信號處理器的時候?要想通過調(diào)試來找出你的程序為什么沒有執(zhí)行你所設(shè)置的信號處理器可能要花費幾個小時的時間。不過一般說來,這些情況并不會經(jīng)常發(fā)生。好的程序員應(yīng)該避免在庫函數(shù)中設(shè)置信號處理器。對于大型的、復(fù)雜的程序來說,這些情況就很難避免了,所以為了更完美一點,我們應(yīng)當(dāng)能夠檢測這些事件,而kqueue就可以。

kqueue API由兩個函數(shù)調(diào)用和一個輔助設(shè)置事件的宏組成。這些函數(shù)將在下面進行簡要介紹。

  1.   int kqueue(void);
復(fù)制代碼


kqueue函數(shù)啟動一個新的kqueue。如果調(diào)用成功,返回值將是一個用來和新創(chuàng)建的kqueue交互的描述符。每個kqueue都有一個與之關(guān)聯(lián)的唯一的描述符。因此,一個程序可以同時打開多個kqueue。kqueue描述符的行為和常規(guī)文件描述符類似:它們也可以被復(fù)用。

最后一點,這些描述符是不能被fork創(chuàng)建的子進程繼承的。如果子進程是通過rfork調(diào)用創(chuàng)建的,那就需要設(shè)置RFFDG標(biāo)志,以免這些描述符被子進程共享。如果kqueue函數(shù)失敗,將返回-1,同時相應(yīng)的設(shè)置errno。

  1.   int kevent(int kq, const struct kevent *changelist, int nchanges, struct kevent *eventlist, int nevents, const struct timespec *timeout);
復(fù)制代碼


kevent函數(shù)用于和kqueue的交互。第一個參數(shù)是kqueue返回的描述符。changelist參數(shù)是一個大小為nchanges的kevent結(jié)構(gòu)體數(shù)組。changelist參數(shù)用于注冊或修改事件,并且將在從kqueue讀出事件之前得到處理。

eventlist參數(shù)是一個大小為nevents的kevent結(jié)構(gòu)體數(shù)組。kevent通過把事件放在eventlist參數(shù)中來向調(diào)用進程返回事件。如果需要的話,eventlist和changelist參數(shù)可以指向同一個數(shù)組。最后一個參數(shù)是kevent所期待的超時時間。如果超時參數(shù)被指定為NULL,kevent將阻塞,直至有事件發(fā)生為止。如果超時參數(shù)不為NULL,則kevent將阻塞到超時為止。如果超時參數(shù)指定的是一個內(nèi)容為0的結(jié)構(gòu)體,kevent將立即返回所有當(dāng)前尚未處理的事件。

kevent的返回值指定了放在eventlist數(shù)組中的事件的數(shù)目。如果事件數(shù)目超過了eventlist的大小,可以通過后續(xù)的kevent調(diào)用來獲得它們。在處理事件的過程中發(fā)生的錯誤也會在還有空間的前提下被放到eventlist參數(shù)中。帶有錯誤的事件會設(shè)置EV_ERROR位,系統(tǒng)錯誤也會被放到data成員中。對于其它的所有錯誤都將返回-1,并相應(yīng)地設(shè)置errno。

kevent結(jié)構(gòu)體用于和kqueue的通信。FreeBSD上的頭文件位于/usr/include/sys/event.h。在這個文件中有對kevent結(jié)構(gòu)體的聲明,以及其它的一些選項和標(biāo)志。和select和poll比起來,kqueue還相當(dāng)?shù)哪贻p,所以它一直都在發(fā)展和添加新的特性。請查看你的系統(tǒng)頭文件以確定任何新的或者特定于系統(tǒng)的選項。

原始的kevent結(jié)構(gòu)體的聲明如下:

  1.   struct kevent {
  2.         uintptr_t       ident;
  3.         short           filter;
  4.         u_short         flags;
  5.         u_int           fflags;
  6.         intptr_t        data;
  7.         void            *udata;
  8.   };
復(fù)制代碼


現(xiàn)在,讓我們來看看各個成員:

ident

ident成員用于存儲kqueue的唯一標(biāo)識。換句話說,如果你想給一個事件添加一個文件描述符的話,ident成員就應(yīng)當(dāng)被設(shè)置成目標(biāo)描述符的值。

filter

filter成員用于指定你希望內(nèi)核用于ident成員的過濾器。

flags

flags成員將告訴內(nèi)核應(yīng)當(dāng)對該事件完成哪些操作和處理哪些必要的標(biāo)志。在返回的時候,flags成員可用于保存錯誤條件。

fflags

fflags成員用于指定你想讓內(nèi)核使用的特定于過濾器的標(biāo)志。在返回的時候,fflags成員可用于保存特定于過濾器的返回值。

data

data成員用于保存任何特定于過濾器的數(shù)據(jù)。

udata

udata成員并不由kqueue使用,kqueue會把它的值不加修改地透傳。這個成員可被進程用來發(fā)送信息甚至是一個函數(shù)給它自己,用于一些依賴于事件檢測的場合。

kqueue 過濾器

下面列出的是kqueue使用的過濾器。某些過濾器會有專用于它的標(biāo)志。這些標(biāo)志是在kevent結(jié)構(gòu)體的fflags成員中設(shè)置的。

  1.   #define EVFILT_READ     (-1)
復(fù)制代碼


EVFILT_READ過濾器用于檢測什么時候數(shù)據(jù)可讀。kevent的ident成員應(yīng)當(dāng)被設(shè)成一個有效的描述符。盡管這個過濾器的行為和select或這poll很像,但它返回的事件將是特定于所使用的描述符的類型的。

如果描述符引用的打開文件是一個vnode,該事件就表明讀取偏移量尚未到達文件末尾。data成員保存的是當(dāng)前距文件末尾的偏移量,這可以是負(fù)值。如果描述符引用的是一個pipe或者fifo,那么過濾器將在有實際數(shù)據(jù)可讀時返回。data成員保存的是可供讀取的字節(jié)數(shù)目。EV_EOF bit用于表示是哪個寫入者關(guān)閉了連接。(關(guān)于使用socket時EVFILT_READ的行為細節(jié)請參見kqueue的手冊頁。)

  1.   #define EVFILT_WRITE    (-2)
復(fù)制代碼


EVFILT_WRITE過濾器用于檢測是否可以對描述符執(zhí)行寫操作。如果描述符引用的是一個pipe、fifo或者socket,則data成員將存有寫緩沖區(qū)中可用的字節(jié)數(shù)目。EV_EOF bit表示讀取方已經(jīng)關(guān)閉了連接。這個標(biāo)志對于打開的文件描述符無效。

  1.   #define EVFILT_AIO      (-3)
復(fù)制代碼


EVFILT_AIO用于異步I/O操作,用于檢測和aio_error系統(tǒng)調(diào)用相似的條件。

  1.   #define EVFILT_VNODE    (-4)
復(fù)制代碼


EVFILT_VNODE過濾器用于檢測對文件系統(tǒng)上一個文件的某種改動。把ident成員設(shè)置成一個有效的打開文件描述符,用fflags成員指定所關(guān)心的事件。返回時,fflags成員將含有所發(fā)生事件的比特掩碼。這些事件如下:

  1.   #define NOTE_DELETE     0x0001
復(fù)制代碼


NOTE_DELETE fflag表示進程想知道該文件何時被刪。

  1.   #define NOTE_WRITE      0x0002
復(fù)制代碼


NOTE_WRITE fflag表示進程想知道該文件內(nèi)容何時被改變。

  1.   #define NOTE_EXTEND     0x0004
復(fù)制代碼


NOTE_EXTEND fflag表示進程想知道該文件何時被擴展。

  1.   #define NOTE_ATTRIB     0x0008
復(fù)制代碼


NOTE_ATTRIB fflag表示進程想知道該文件屬性何時被改變。

  1.   #define NOTE_LINK       0x0010
復(fù)制代碼


NOTE_LINK fflag表示進程想知道該文件的鏈接計數(shù)何時被改變。當(dāng)文件通過link函數(shù)調(diào)用進行硬鏈接的時候,它的鏈接計數(shù)就會改變。(詳情請參見man(2) link。)

  1.   #define NOTE_RENAME     0x0020
復(fù)制代碼


NOTE_RENAME fflag表示進程想知道該文件是否被重新命名了。

  1.   #define NOTE_REVOKE     0x0040
復(fù)制代碼


NOTE_REVOKE fflag表示對文件的訪問被revoke了。詳情請見man(2) revoke。

  1.   #define EVFILT_PROC     (-5)  /* attached to struct proc */
復(fù)制代碼


EVLILT_PROC過濾器被進程用來檢測發(fā)生在另外一個進程里的事件。所關(guān)心進程的PID存儲在ident成員中,fflags成員則被設(shè)成所關(guān)心的事件。返回時,事件將被放在fflags成員中。這些事件由下列事件按比特OR的方式設(shè)置:

  1.   #define NOTE_EXIT       0x80000000
復(fù)制代碼


NOTE_EXIT fflag用于檢測該進程何時退出。

  1.   #define NOTE_FORK       0x40000000
復(fù)制代碼


NOTE_FORK fflag用于檢測該進程何時調(diào)用fork。

  1.   #define NOTE_EXEC       0x20000000
復(fù)制代碼


NOTE_EXEC fflag用于檢測該進程何時調(diào)用exec函數(shù)。

  1.   #define NOTE_TRACK      0x00000001
復(fù)制代碼


NOTE_TRACK fflag讓kqueue去跟蹤一個跨越fork調(diào)用的進程。子進程返回時將設(shè)置fflags中的NOTE_CHILD標(biāo)志,父進程的PID將放在data成員中。

  1.   #define NOTE_TRACKERR   0x00000002
復(fù)制代碼


當(dāng)在跟蹤子進程的過程中有錯誤發(fā)生時,就會設(shè)置NOTE_TRACKERR fflag。這是一個僅用于返回的fflag。

  1.   #define NOTE_CHILD      0x00000004
復(fù)制代碼


NOTE_CHILD fflag在子進程內(nèi)設(shè)置。這是一個僅用于返回的fflag。

  1.   #define EVFILT_SIGNAL      (-6)
復(fù)制代碼


EVFILT_SIGNAL過濾器用于檢測是否有信號發(fā)送給該進程。每當(dāng)有信號發(fā)送時這個過濾器就會檢測到,并把計數(shù)值放在data成員中。這包括設(shè)置了SIG_IGN標(biāo)志的信號。事件將在執(zhí)行完常規(guī)的信號處理過程之后放到kqueue上。注意,這個過濾器將在內(nèi)部設(shè)置EV_CLEAR標(biāo)志。

  1.   #define EVFILT_TIMER    (-7)
復(fù)制代碼


EVFILT_TIMER過濾器會給kqueue創(chuàng)建一個定時器,用于記錄消逝的事件。如果需要一個一次性的定時器,可以設(shè)置EV_ONESHOT標(biāo)志。這個定時器是在ident成員中指定的,data成員用來指定以毫秒為單位的超時時間。返回值放在data成員中。注意,這個過濾器將在內(nèi)部設(shè)置EV_CLEAR標(biāo)志。

kqueue操作

kqueue操作由所需的操作和標(biāo)志以比特OR的方式進行設(shè)置。

  1.   #define EV_ADD          0x0001
復(fù)制代碼


EV_ADD操作向kqueue添加事件。由于kqueue中不允許出現(xiàn)重復(fù),所以如果你想添加一個已經(jīng)存在的事件的話,現(xiàn)有事件將被新的添加操作覆蓋。注意,在添加事件的時候,它們已經(jīng)被默認(rèn)激活了,除非你設(shè)置了EV_DISABLE標(biāo)志。

  1.   #define EV_DELETE       0x0002
復(fù)制代碼


EV_DELETE操作從kqueue中刪除事件。

  1.   #define EV_ENABLE       0x0004
復(fù)制代碼


EV_ENABLE用于激活kqueue中的事件。注意,新添加的事件默認(rèn)就是激活的。

  1.   #define EV_DISABLE      0x0008
復(fù)制代碼


EV_DISABLE禁止kqueue返回某個事件的信息。注意,kqueue并不會刪除過濾器。

kqueue操作標(biāo)志

kqueue的操作標(biāo)志定義如下。它們和上面列出的操作結(jié)合使用。它們是通過和所需操作進行比特OR來設(shè)置的。

  1.   #define EV_ONESHOT      0x0010
復(fù)制代碼


EV_ONESHOT標(biāo)志用于通知kqueue只返回第一個。

  1.   #define EV_CLEAR        0x0020
復(fù)制代碼


EV_CLEAR標(biāo)志用于通知kqueue,一旦進程從kqueue中獲取到了該事件就將該事件的狀態(tài)復(fù)位。

kqueue返回值

僅用于返回的值是放在kevent結(jié)構(gòu)體的flags成員中的。這些值的定義如下:

  1.   #define EV_EOF          0x8000
復(fù)制代碼


EV_EOF用于表示文件結(jié)束的情況。

  1.   #define EV_ERROR        0x4000
復(fù)制代碼


EV_ERROR用于表示有錯誤發(fā)生了。系統(tǒng)錯誤將被放到data成員中。

6.5 結(jié)論

本章研究了BSD中的描述符復(fù)用。作為一個程序員,你可以選擇三個接口:select、poll和kqueue。對于小數(shù)量的描述符來說,這三者的性能差不多,但是當(dāng)描述符數(shù)量很大時,kqueue則是最好的選擇。除此之外,kqueue還可以檢測比I/O事件更為豐富的條件。它可以檢測信號、文件修改以及子進程相關(guān)的事件。在下一章中,我們將針對FreeBSD 5.x中的新特性,研究其它的獲取子進程信息和當(dāng)前進程統(tǒng)計信息的方法。



                 第七章 進程資源和系統(tǒng)限制
                  譯者:FinalBSD
7.1 進程資源和系統(tǒng)限制
  為了支持多用戶同時登錄以及多個應(yīng)用連接,BSD UNIX系統(tǒng)給系統(tǒng)管理員提供了控制系統(tǒng)資源的許多方法。這種資源限制包括CPU時間、內(nèi)存使用量以及磁盤使用量。資源控制允許你調(diào)整系統(tǒng)到最佳的使用率。UNIX的早期版本中,一些在編譯時設(shè)置的系統(tǒng)限制如果需要修改,則需要重新編譯整個系統(tǒng)。然而,如果并非所有的運行中的系統(tǒng)資源都需要重新編譯整個系統(tǒng),那么現(xiàn)代的BSD系統(tǒng)可以調(diào)整大多數(shù)這些資源的限制。

  本章闡述和進程相關(guān)的限制,包括系統(tǒng)端和用戶使用的。我們將會看到如何發(fā)現(xiàn)這些限制以及怎么修改之,還將闡述進程是如何查詢它的資源使用率。


7.2  確定系統(tǒng)限制
getrlimit,setrlimit

        getrlimit允許一個進程查詢所受的的系統(tǒng)限制.這些系統(tǒng)限制通過一對硬/軟限制對來指定。當(dāng)一個軟限制被超過時,進程還可以繼續(xù),當(dāng)然這取決于限制的類型,同時一個信號會發(fā)送給進程。另一方面,進程不可以超過它的硬限制。軟限制值可以被進程設(shè)置在位于0和最大硬限制間的任意值。硬限制值不能被任何進程降低,僅僅超級用戶可以增加之。


  1. #include <sys/types.h>
  2.           #include <sys/time.h>
  3.           #include <sys/resource.h>

  4.     int  getrlimit(int resource, struct rlimit *rlp);
  5.     int  setrlimit(int resource, const struct rlimit *rlp);
復(fù)制代碼


getrlimit和setrlimit都使用下面的數(shù)據(jù)結(jié)構(gòu):
  1. struct rlimit {
  2.                 rlim_t rlim_cur;
  3.                 rlim_t rlim_max;
  4.         };
復(fù)制代碼

我們來看每個成員變量。rlim_cur為指定的資源指定當(dāng)前的系統(tǒng)軟限制。rlim_max將為指定的資源指定當(dāng)前的系統(tǒng)硬限制。

        getrlimit和setrlimit函數(shù)的第一個參數(shù)是資源參數(shù)。這個參數(shù)用來指定進程獲取信息的那個資源?赡艿馁Y源值列于下面。你也可以在/usr/include/sys/resource.h中找到它們:
  1. #define  RLIMIT_CPU  0     /* cpu time in milliseconds */
復(fù)制代碼

RLIMIT_CPU資源限制指定一個進程可以取得CPU執(zhí)行任務(wù)的毫秒數(shù)。一般地,一個進程僅僅有一個軟限制而沒有硬限制。如果超出軟限制,進程會收到一個SIGXCPU信號。
  1.   #define  RLIMIT_FSIZE   1     /* maximum file size */
復(fù)制代碼

RLIMIT_FSIZE限制指定一個進程可以創(chuàng)建的最大文件大小,以字節(jié)為單位。比如,如果RLIMIT_FSIZE設(shè)置為0,那么進程將根本不能創(chuàng)建文件。如果進程超出此限制,就會發(fā)出SIGFSZ信號。
  1. #define  RLIMIT_DATA 2     /* data size */
復(fù)制代碼

RLIMIT_DATA 限制指定一個進程數(shù)據(jù)段可占據(jù)的最大字節(jié)值。一個進程的數(shù)據(jù)段就是放置動態(tài)內(nèi)存的一個區(qū)域(C/C++中用malloc()分配的內(nèi)存)。如果超出限制,分配新內(nèi)存的操作將會遭到失敗。
  1. #define  RLIMIT_STACK   3     /* stack size */
復(fù)制代碼

RLIMIT_STACK限制指定進程棧可占據(jù)的最大字節(jié)數(shù)。一旦超出硬限制,進程會收到SIGSEV信號。
  1. #define  RLIMIT_CORE 4     /* core file size */
復(fù)制代碼

RLIMIT_CORE限制指定了進程可以創(chuàng)建的最大core文件的大小。如果此限制設(shè)為0,將不能創(chuàng)建。另外,當(dāng)達到此限制時,所有正在寫core文件的進程都將被中斷。
       
  1. #define  RLIMIT_RSS  5     /* resident set size */
復(fù)制代碼

RMIMIT_RSS限制了進程的常駐集大小(resident set size)可占據(jù)的最大字節(jié)數(shù).這個進程的常駐集和進程所使用的物理內(nèi)存數(shù)有關(guān)。
  1.   #define  RLIMIT_MEMLOCK 6     /* locked-in-memory address space */
復(fù)制代碼

RLIMIT_MEMLOCK限制指定了進程可以使用系統(tǒng)調(diào)用到mlock進行鎖定的最大字節(jié)數(shù)。
  1.   #define  RLIMIT_NPROC   7     /* number of processes */
復(fù)制代碼

RLIMIT_NPROC 限制指定了一個指定用戶可以開啟的最多并發(fā)進程數(shù)。這里的用戶是通過進程來確定的有效用戶ID.
  1. #define  RLIMIT_NOFILE  8     /* number of open files */
復(fù)制代碼

RLIMIT_NOFILE 限制指定了進程可以打開的最多文件數(shù)。
  1.   #define  RLIMIT_SBSIZE  9     /* maximum size of all socket buffers */
復(fù)制代碼

RLIMIT_SBSIZE限制指定用戶在任何時刻可使用的mbufs數(shù)。可以查看socket man頁來獲得mbufs的定義。
  1. #define  RLIMIT_VMEM 10    /* virtual process size (inclusive of mmap) */
復(fù)制代碼

RLIMIT_VMEM限制說明一個進程的映射地址空間可以占據(jù)的字節(jié)數(shù)。如果超出限制,分配動態(tài)內(nèi)存和到mmap的調(diào)用會失敗。
  1. #define  RLIM_INFINITY
復(fù)制代碼

RLIM_INFINITY宏用來去除對一個資源的限制。換句話說,將一個資源的硬限制設(shè)置為RLIM_INFINITY將會導(dǎo)致此種資源的使用沒有任何系統(tǒng)限制。 將軟限制設(shè)置為RLIM_INFINITY將會阻止進程收到任何軟限制警告。如果你的進程不想為那些會導(dǎo)致進程在超過軟限制時發(fā)送信號的資源設(shè)置一個信號處理器,這個參數(shù)將變得非常有用。
如果使用了getrlimit參數(shù),那么第二個參數(shù)需要設(shè)置為一個到rlimit結(jié)構(gòu)的有效指針。然后getrlimit會將適當(dāng)?shù)南拗浦捣湃氪私Y(jié)構(gòu)。另外,在改變限制時,setrlimit會使用在第二個參數(shù)中設(shè)置值。將值設(shè)置為0將會阻止使用此資源。將值設(shè)置為RLIM_INFINITY會除去對該資源的所有限制。這些函數(shù)都在執(zhí)行成功后都返回0,反之為-1.有任何錯誤產(chǎn)生,這些函數(shù)會相應(yīng)的設(shè)置errno。

getpagesize函數(shù)
  1. #include <unistd.h>   
  2. int  getpagesize(void);
復(fù)制代碼


  在介紹getrusage函數(shù)前,我們需要討論一下getpagesize函數(shù)。一些進程狀態(tài) 是根據(jù)使用的分頁(pages)來顯示的。分頁的內(nèi)存僅僅是一段內(nèi)存,通常為4096字節(jié)左右。但是分頁大小卻千差萬別,并且它不會固定編入(hard-coded)你的系統(tǒng)。取而代之的是,要確定本地系統(tǒng)的分頁大小(pagesize)需要使用getpagesezi函數(shù)。getpagesize的返回值就是每個分頁使用的字節(jié)數(shù)。

7.3 確定進程資源使用量

getrusage函數(shù)  
   現(xiàn)在我們知道如何查看系統(tǒng)限制,我們還需要知道如何確定當(dāng)前進程資源的使用量。getrusage函數(shù)就是用于此目的。此函數(shù)很容易被理解。一個進程可以確定它的內(nèi)存使用量、CPU執(zhí)行時間、甚至可獲得其子進程的相關(guān)信息。因此,getrusage函數(shù)的一個用途就是幫助系統(tǒng)避免逃逸進程(runaway)的出現(xiàn)。逃逸進程指的是那些不受系統(tǒng)控制的進程,它們或者使用了過多的CPU(比如循環(huán)調(diào)用)、或者是超過了內(nèi)存使用量的限制(導(dǎo)致內(nèi)存泄漏)。

  1.   #include <sys/types.h>
  2.   #include <sys/time.h>
  3.   #include <sys/resource.h>
  4.   
  5.   #define   RUSAGE_SELF     0
  6.   #define   RUSAGE_CHILDREN     -1
  7.   
  8.     int   getrusage(int who, struct rusage *rusage);
復(fù)制代碼


      getrusage函數(shù)有兩個參數(shù)。第一個參數(shù)可以設(shè)置為RUSAGE_SELF或者RUSAGE_CHILDREN。如果設(shè)置成RUSAGE_SELF,那么將會以當(dāng)前進程的相關(guān)信息來填充rusage(數(shù)據(jù))結(jié)構(gòu)。反之,如果設(shè)置成RUSAGE_CHILDREN,那么rusage結(jié)構(gòu)中的數(shù)據(jù)都將是當(dāng)前進程的子進程的信息。
       
    rusage(數(shù)據(jù))結(jié)構(gòu)定義在/usr/include/sys/resource.h中。它含有以下成員變量:
  1. struct   rusage {
  2.       struct timeval ru_utime;   /* user time used */
  3.       struct timeval ru_stime;   /* system time used */
  4.       long  ru_maxrss;           /* max resident set size */
  5.       long  ru_ixrss;            /* integral shared memory size */
  6.       long  ru_idrss;            /* integral unshared data */
  7.       long  ru_isrss;            /* integral unshared stack */
  8.       long  ru_minflt;           /* page reclaims */
  9.       long  ru_majflt;           /* page faults */
  10.       long  ru_nswap;            /* swaps */
  11.       long  ru_inblock;          /* block input operations */
  12.       long  ru_oublock;          /* block output operations */
  13.       long  ru_msgsnd;           /* messages sent */
  14.       long  ru_msgrcv;           /* messages received */
  15.       long  ru_nsignals;         /* signals received */
  16.       long  ru_nvcsw;            /* voluntary context switches */
  17.       long  ru_nivcsw;           /* involuntary " */
  18.   };
復(fù)制代碼
      我們來詳細分析每一個成員變量。
ru_utime,ru_stime
ru_utime和ru_stime成員變量包含了在用戶模式和系統(tǒng)模式中執(zhí)行時間的總和。它們都使用timeval結(jié)構(gòu)(請查看前一章來了解此結(jié)構(gòu)。)

ru_maxrss
ru_maxrss保存了常駐集的內(nèi)存使用數(shù)。其值根據(jù)內(nèi)存分頁的使用來確定。

ru_ixrss
ru_ixrss值指文本段(text segment)使用的內(nèi)存數(shù)乘以執(zhí)行滴答數(shù)。

ru_idrss
ru_idrss 值指進程所使用的私有內(nèi)存數(shù)(KB)乘以執(zhí)行滴答數(shù)來。

ru_isrss
ru_isrss 指棧使用的內(nèi)存數(shù)(KB為單位)乘以執(zhí)行滴答數(shù)。

ru_minflt
ru_minflt值指不需要I/O的頁缺失數(shù)。頁缺失發(fā)生在內(nèi)核需要得到一個內(nèi)存頁以供進程訪問時。

ru_majflt
ru_majflt值指需要I/O的頁缺失數(shù)。頁缺失發(fā)生在內(nèi)核需要得到一個內(nèi)存頁以供進程訪問時。

ru_nswap
有時,一個進程會被調(diào)出內(nèi)存,以提供空間給其他進程使用。ru_nswap指的就是一個進程將要調(diào)出內(nèi)存的次數(shù)。

ru_inblock
ru_inblock 指文件系統(tǒng)需要為一個讀請求執(zhí)行輸入操作的次數(shù)。

ru_oublock
ru_oublock指文件系統(tǒng)需要為一個寫入請求執(zhí)行輸出操作的次數(shù)。

ru_msgsnd
ru_msgsnd指發(fā)送的IPC信息總數(shù)

ru_msgrcv
ru_msgrcv指收到的IPC信息總數(shù)。

ru_nsignals
ru_nsignals指進程收到的信號總數(shù)。

ru_nvcsw
一個進程主動上下文且混總數(shù)。主動上下文切換發(fā)生在一個進程放棄它的CPU分時時。通常發(fā)生在一個進程等待某可用資源時。

ru_nivcsw
ru_nivcsw包含了因高優(yōu)先級進程運行導(dǎo)致的上下文切換總數(shù)。

7.4 小結(jié)
本章主要闡述了程序如何得到系統(tǒng)限制。這些限制不應(yīng)該固定寫入你的代碼。取而代之的是,你的程序應(yīng)該使用這些接口。這是因為這些限制是與平臺相關(guān)的,甚至不同系統(tǒng)間都會有差別。比如,系統(tǒng)管理員可以調(diào)整允許打開的最大文件數(shù),因此如果你需要增加或者減小這個值,就不能將值4096固定寫入程序中。另一個例子是分頁大小。一些64位系統(tǒng)使用8096作為默認(rèn)的分頁大小,但是大多數(shù)的32位系統(tǒng)將此默認(rèn)值設(shè)置位4096。再次強調(diào),這是個可調(diào)整的參數(shù),不應(yīng)該使用一個固定的值。
我們還學(xué)習(xí)了一個程序如何得到其資源的當(dāng)前使用量,或者查看其子進程的資源使用狀況。使用這些接口可以幫助我們檢測甚至避免出現(xiàn)失控的進程。它們用在程序試圖超越其軟/硬限制時調(diào)試錯誤也非常不錯。



                   第八章 FreeBSD 5.x
                                                 譯者:FinalBSD

8.1  FreeBSD 5.x
  2003年1月發(fā)布的FreeBSD-5.x 分支,是FreeBSD項目的一個重要的里程碑。在近3年的開發(fā)中,F(xiàn)reeBSD不管是在內(nèi)核還是基本系統(tǒng)上都有了許多的變化。大部分的這些變化會影響到系統(tǒng)管理員,F(xiàn)reeBSD 編程者則不會,因此也不會影響到此書中討論的任何一部分。一些例外將在下面進行闡述。

8.2  啟動布局(Boot Layout)
  第一個變化是啟動文件的組織方式。FreeBSD 5.x系統(tǒng)已將所有的模塊和內(nèi)核文件都移到/boot目錄下。和老版本的FreeBSD一樣,一些和系統(tǒng)啟動相關(guān)的配置文件也位于此目錄下。這個看起來很小的變化實際上提供了更多的便利,因為現(xiàn)在,在不同的設(shè)備間分離/和/boot(包含所有的內(nèi)核和內(nèi)核模塊)十分容易。

8.3  Devfs
  FreeBSD 5.x中,我們最喜歡的特性是devfs.之前的版本,/dev被塞滿了超過1000個文件。設(shè)備節(jié)點和大多數(shù)支持的設(shè)備都有一項作為文件保存在該目錄下。如你所想,這個目錄變得非常的大并且包含了許多不必要的文件。比如,一個有IDE設(shè)備的系統(tǒng)會在此目錄下包含SCSI設(shè)備文件,哪怕是該系統(tǒng)沒有任何SCSI設(shè)備。devfs中這一切弊病都之前將不復(fù)存在.
現(xiàn)在/dev下僅僅包含那些真實存在的設(shè)備項。實際上,F(xiàn)reeBSD 5.x并不和之前版本一樣將/dev當(dāng)作到文件系統(tǒng)的一個掛載點,并稱之為devfs。

  devfs文件系統(tǒng)和proc文件系統(tǒng)相近。二者都是掛載點,該掛載點包含在硬盤上不存在的文件。這些文件由內(nèi)核創(chuàng)建并且僅以文件方式出現(xiàn)。事實上,它們是在系統(tǒng)啟動后被建立的。Devfs提供了更多的便利性,因為你能支持多個devfs掛載點。比如,如果你想chroot或者jail一個進程,你不需要手動創(chuàng)建/dev目錄,取而代之的是,你可以簡單的為你的進程創(chuàng)建一個新的掛載點并掛載devfs.

  devfs的另一個優(yōu)點是可以告訴你系統(tǒng)真實存在哪些設(shè)備。你要做的僅僅是cd到/dev目錄然后列出那些文件。這就很方便用戶得到系統(tǒng)上所有的設(shè)備列表,更重要的是,檢測到哪些設(shè)備。

8.4  a.out
  FreeBSD 5.x系列已經(jīng)在基本系統(tǒng)中去除了對a.out二進制格式的支持。但是你仍然可以加入a.out二進制支持。這是因為a.out是種相當(dāng)老的格式,并且現(xiàn)在都會優(yōu)先選擇新的ELF格式。ELF格式更靈活而且目前被廣泛使用。

8.5  gcc-3.2工具鏈
  FreeBSD 5.x 現(xiàn)在使用gcc-3.2 工具鏈作為基本系統(tǒng)。這是個很重要的改變:gcc-3.x更接近ISO,并且它的C++ ABI更穩(wěn)定。然而,這或許會給一些人帶來麻煩。他們編寫的一些程序在使用gcc-3.x進行編譯前也許需要進行更新。如果你使用flex或者yacc,請確定你使用的是最新的版本,或者為你當(dāng)前的版本打好補丁,因為已經(jīng)確認(rèn)知道他們會導(dǎo)致問題出現(xiàn)。

8.6  SMPng
  FreeBSD 5.x 已經(jīng)改進為支持SMP的系統(tǒng)了,這一改進都來自于我們常說的SMPng(下一代SMP).盡管之前版本也支持SMP,但是性能有待提高。

8.7 內(nèi)核調(diào)度實體(KSE)
  另一個新的特性是內(nèi)核調(diào)度實體(KSE).KSE是個內(nèi)核支持的線程系統(tǒng),和Scheduler Activations在概念概念上很接近。特別的,在內(nèi)核端,KSE在于對FreeBSD的調(diào)度的修改。并且在用戶端使用的是POSIX線程實現(xiàn)方式,這種方式會利用內(nèi)核提供的額外工具。然而,你不需要配置任何特殊的內(nèi)核參數(shù),就可以編譯得到一個具有KSE相關(guān)修改的內(nèi)核。

  為了在應(yīng)用程序中使用KSE,你可以使用libpthreads來鏈入之。libpthreads默認(rèn)并沒編譯進系統(tǒng),所以你首先需要在系統(tǒng)上安裝好libpthreads。然后,在它的makefile中,將-pthread選項改為-lpthread并重新鏈入(relink).

8.8 小結(jié)
  FreeBSD在幾年的發(fā)展中已經(jīng)變得成熟,并且現(xiàn)在是一個可用的非常穩(wěn)定的操作系統(tǒng)了。有了SMP的增強支持和內(nèi)核線程,F(xiàn)reeBSD會一如既往的提供強穩(wěn)定性和高性能。已經(jīng)加入了對一些新平臺的支持,比如Sparc64和ia64.這些新的平臺會幫助BSD發(fā)行版多年來繼續(xù)提供高質(zhì)量的開源選擇。

公元二00六年二月十三日


[ 本帖最后由 雨絲風(fēng)片 于 2006-3-20 12:47 編輯 ]

論壇徽章:
1
寅虎
日期:2013-09-29 23:15:15
2 [報告]
發(fā)表于 2006-03-09 21:31 |只看該作者
謝謝szjungle兄提供PDF版本(3月14號)!
偶放到這里方便大家下載.

[ 本帖最后由 congli 于 2006-3-14 12:14 編輯 ]

freebsdsp.pdf

631.56 KB, 下載次數(shù): 6018

論壇徽章:
0
3 [報告]
發(fā)表于 2006-05-10 16:42 |只看該作者

狂頂

希望可以交個朋友。

論壇徽章:
0
4 [報告]
發(fā)表于 2006-05-10 18:27 |只看該作者
萬分感謝!太辛苦您了。

論壇徽章:
0
5 [報告]
發(fā)表于 2006-05-10 20:40 |只看該作者
收藏先!!
PDF慢慢看!
感謝分享!

論壇徽章:
0
6 [報告]
發(fā)表于 2006-05-10 21:35 |只看該作者
thank you

頂  


     

論壇徽章:
0
7 [報告]
發(fā)表于 2006-05-10 21:46 |只看該作者
這么快翻譯完了

論壇徽章:
0
8 [報告]
發(fā)表于 2006-05-10 22:00 |只看該作者
原帖由 isjfk 于 2006-5-10 21:46 發(fā)表
這么快翻譯完了


都已經(jīng)結(jié)束兩個月了。

論壇徽章:
0
9 [報告]
發(fā)表于 2006-05-10 22:52 |只看該作者
這個要頂

論壇徽章:
0
10 [報告]
發(fā)表于 2006-05-10 22:59 |只看該作者
感謝。。。
您需要登錄后才可以回帖 登錄 | 注冊

本版積分規(guī)則 發(fā)表回復(fù)

  

北京盛拓優(yōu)訊信息技術(shù)有限公司. 版權(quán)所有 京ICP備16024965號-6 北京市公安局海淀分局網(wǎng)監(jiān)中心備案編號:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年舉報專區(qū)
中國互聯(lián)網(wǎng)協(xié)會會員  聯(lián)系我們:huangweiwei@itpub.net
感謝所有關(guān)心和支持過ChinaUnix的朋友們 轉(zhuǎn)載本站內(nèi)容請注明原作者名及出處

清除 Cookies - ChinaUnix - Archiver - WAP - TOP