97精品在线,国产精品免费小视频,在线三级中文http://www.aygfsteel.com/tinysun/category/37798.htmlzh-cnThu, 23 Aug 2012 06:43:12 GMTThu, 23 Aug 2012 06:43:12 GMT60svn修訂版和最后修改的修訂版[轉(zhuǎn)]http://www.aygfsteel.com/tinysun/archive/2012/08/23/386100.html何克勤何克勤Thu, 23 Aug 2012 05:13:00 GMThttp://www.aygfsteel.com/tinysun/archive/2012/08/23/386100.htmlhttp://www.aygfsteel.com/tinysun/comments/386100.htmlhttp://www.aygfsteel.com/tinysun/archive/2012/08/23/386100.html#Feedback0http://www.aygfsteel.com/tinysun/comments/commentRss/386100.htmlhttp://www.aygfsteel.com/tinysun/services/trackbacks/386100.html

花了點時間把svnbook看了遍,對于svn有了個比較好的認識。svn info時,修訂版和最后修改的修訂版總是讓我感覺很困惑。要搞明白這個需要對下面幾個關(guān)鍵字有所了解。

HEAD:版本庫中的最新版本。

COMMITED:文件最后提交生成的版本號。

PREV:文件倒數(shù)第二次提交生成的版本號。

BASE:目錄簽出或者簽入生成的版本號。

HEAD、COMMITED和PREV比較好理解,BASE比較難于理解。假設(shè)一個目錄下有兩個文件configure.ac和Makefile.am,第一次將它們check out出來時,會生成一個新的revision,這個便是BASE了。此時使用svn info configure.ac/Makefile.am可以發(fā)現(xiàn)它們的修訂版是一樣的,但是最后修改的修訂版不同。這里的修訂版對應(yīng)其實就是BASE,而最后修改的修訂版則是COMMITED。插一句,很多人很容易誤解為啥修訂版號和最后修改的修訂版號不一致。

若將configure.ac修改并check in,這個時候會生成一個新的revision,configure.ac的BASE和COMMITED的值相當。而svn info Makefile.am,發(fā)現(xiàn)它的BASE和COMMITED沒有改變。svn up一下,發(fā)現(xiàn)Makefile.am的BASE會變成最新的,和configure.ac相同。

簽出代碼庫。

1[henshao@henshao ~/svn]$ svn co file:///Users/henshao/svn/dogg/learn_svn/ learn_svn2
2A    learn_svn2/trunk
3A    learn_svn2/trunk/configure.ac
4A    learn_svn2/trunk/Makefile.am
5Checked out revision 17.

顯示修訂版(BASE)和最后修改的修訂版(COMMITED)。

01[henshao@henshao ~/svn/learn_svn2/trunk]$ svn st -v
02                17       17 henshao      .
03                17       17 henshao      configure.ac
04                17       15 henshao      Makefile.am
05  
06[henshao@henshao ~/svn/learn_svn2/trunk]$ svn info Makefile.am
07Path: Makefile.am
08Name: Makefile.am
09URL: file:///Users/henshao/svn/dogg/learn_svn/trunk/Makefile.am
10Repository Root: file:///Users/henshao/svn/dogg
11Repository UUID: 7ee338c4-a6e3-468b-b576-d1b767dd90e2
12Revision: 17
13Node Kind: file
14Schedule: normal
15Last Changed Author: henshao
16Last Changed Rev: 15
17Last Changed Date: 2011-06-23 17:03:08 +0800 (四, 23  6 2011)
18Text Last Updated: 2011-06-23 18:37:50 +0800 (四, 23  6 2011)
19Checksum: 5b211a202b8ae001a86a557108d4989c

修改Makefile.am并簽入看看。

01[henshao@henshao ~/svn/learn_svn2/trunk]$ svn ci Makefile.am -m "LD_ADD add ssl library"
02Sending        Makefile.am
03Transmitting file data .
04Committed revision 18.
05  
06[henshao@henshao ~/svn/learn_svn2/trunk]$ svn info Makefile.am
07Path: Makefile.am
08Name: Makefile.am
09URL: file:///Users/henshao/svn/dogg/learn_svn/trunk/Makefile.am
10Repository Root: file:///Users/henshao/svn/dogg
11Repository UUID: 7ee338c4-a6e3-468b-b576-d1b767dd90e2
12Revision: 18
13Node Kind: file
14Schedule: normal
15Last Changed Author: henshao
16Last Changed Rev: 18
17Last Changed Date: 2011-06-23 18:41:41 +0800 (四, 23  6 2011)
18Text Last Updated: 2011-06-23 18:41:31 +0800 (四, 23  6 2011)
19Checksum: e4cc7bf424ff911c9619060a5f1c1030
20  
21[henshao@henshao ~/svn/learn_svn2/trunk]$ svn info configure.ac
22Path: configure.ac
23Name: configure.ac
24URL: file:///Users/henshao/svn/dogg/learn_svn/trunk/configure.ac
25Repository Root: file:///Users/henshao/svn/dogg
26Repository UUID: 7ee338c4-a6e3-468b-b576-d1b767dd90e2
27Revision: 17
28Node Kind: file
29Schedule: normal
30Last Changed Author: henshao
31Last Changed Rev: 17
32Last Changed Date: 2011-06-23 17:44:51 +0800 (四, 23  6 2011)
33Text Last Updated: 2011-06-23 18:37:50 +0800 (四, 23  6 2011)
34Checksum: 6b49ae8f3346120311e11843c23b0b00

svn update一下看看。

01[henshao@henshao ~/svn/learn_svn2/trunk]$ svn up
02At revision 18.
03  
04[henshao@henshao ~/svn/learn_svn2/trunk]$ svn info configure.ac
05Path: configure.ac
06Name: configure.ac
07URL: file:///Users/henshao/svn/dogg/learn_svn/trunk/configure.ac
08Repository Root: file:///Users/henshao/svn/dogg
09Repository UUID: 7ee338c4-a6e3-468b-b576-d1b767dd90e2
10Revision: 18
11Node Kind: file
12Schedule: normal
13Last Changed Author: henshao
14Last Changed Rev: 17
15Last Changed Date: 2011-06-23 17:44:51 +0800 (四, 23  6 2011)
16Text Last Updated: 2011-06-23 18:37:50 +0800 (四, 23  6 2011)
17Checksum: 6b49ae8f3346120311e11843c23b0b00
18  
19[henshao@henshao ~/svn/learn_svn2/trunk]$ svn st -v
20                18       18 henshao      .
21                18       17 henshao      configure.ac
22                18       18 henshao      Makefile.am

svn一個版本庫的revision是全局的,不管是在trunk還是branch,也不管使用merge合并代碼還是消除修改,簽入和簽出都會生成一個新的revision。當項目中一個文件簽入時會導(dǎo)致別的文件的BASE暫時低于HEAD,但是一旦update,二者將保持一致。



何克勤 2012-08-23 13:13 發(fā)表評論
]]>
linux分布式編譯distcc和ccache的部署 http://www.aygfsteel.com/tinysun/archive/2012/02/24/370682.html何克勤何克勤Fri, 24 Feb 2012 06:49:00 GMThttp://www.aygfsteel.com/tinysun/archive/2012/02/24/370682.htmlhttp://www.aygfsteel.com/tinysun/comments/370682.htmlhttp://www.aygfsteel.com/tinysun/archive/2012/02/24/370682.html#Feedback0http://www.aygfsteel.com/tinysun/comments/commentRss/370682.htmlhttp://www.aygfsteel.com/tinysun/services/trackbacks/370682.html

unset LANGUAGE
export LANG="en"
cd /home/kingsoft
mkdir distcc
cd distcc

 

 

rpm包用:rpm -ivh ...
bz2包用:tar -xvf ...
進入distcc解壓后的目錄
./configure && make && make install
mkdir /usr/lib/distcc
mkdir /usr/lib/distcc/bin
cd /usr/lib/distcc/bin
ln -s /usr/local/bin/distcc gcc
ln -s /usr/local/bin/distcc cc
ln -s /usr/local/bin/distcc g++
ln -s /usr/local/bin/distcc c++

 


進入ccache解壓目錄
./configure && make && make install

mkdir /Data
mkdir /Data/Cache
mkdir /Data/Cache/CCache
cd /Data/Cache
touch /var/log/distccd.log

vim ~/.bash_profile
把 /usr/lib/distcc/bin 加到PATH
并添加下面內(nèi)容
## ----- Distcc -----
#
DISTCC_HOSTS="localhost 192.168.1.1"
DISTCC_VERBOSE=1
DISTCC_LOG="/var/log/distcc.log"
export DISTCC_HOSTS PATH DISTCC_VERBOSE DISTCC_LOG
#
## ----- End -----

## ----- Ccache -----
#
# export CCACHE_DISABLE=1
CCACHE_DIR=/Data/Cache/CCache
CCACHE_LOGFILE=/Data/Cache/CCache.log
CCACHE_PREFIX="distcc"
CC="ccache gcc"
CXX="ccache g++"
export CCACHE_DIR CCACHE_LOGFILE CCACHE_PREFIX CC CXX
#
## ----- End -----


vim /etc/rc.local
distccd --daemon --allow 10.20.0.0/16


==========================================
啟動監(jiān)控:distccd --daemon --allow 10.20.0.0/16
查看監(jiān)控:distccmon-text 1



何克勤 2012-02-24 14:49 發(fā)表評論
]]>
網(wǎng)絡(luò)編程之nagle算法和TCP_NODELAY http://www.aygfsteel.com/tinysun/archive/2011/05/20/350659.html何克勤何克勤Fri, 20 May 2011 01:27:00 GMThttp://www.aygfsteel.com/tinysun/archive/2011/05/20/350659.htmlhttp://www.aygfsteel.com/tinysun/comments/350659.htmlhttp://www.aygfsteel.com/tinysun/archive/2011/05/20/350659.html#Feedback0http://www.aygfsteel.com/tinysun/comments/commentRss/350659.htmlhttp://www.aygfsteel.com/tinysun/services/trackbacks/350659.htmlTCP/IP協(xié)議中,無論發(fā)送多少數(shù)據(jù),總是要在數(shù)據(jù)前面加上協(xié)議頭,同時,對方接收到數(shù)據(jù),也需要發(fā)送ACK表示確認。為了盡可能的利用網(wǎng)絡(luò)帶寬,TCP總是希望盡可能的發(fā)送足夠大的數(shù)據(jù)。(一個連接會設(shè)置MSS參數(shù),因此,TCP/IP希望每次都能夠以MSS尺寸的數(shù)據(jù)塊來發(fā)送數(shù)據(jù))。
Nagle算法就是為了盡可能發(fā)送大塊數(shù)據(jù),避免網(wǎng)絡(luò)中充斥著許多小數(shù)據(jù)塊。

Nagle算法的基本定義是任意時刻,最多只能有一個未被確認的小段。 所謂“小段”,指的是小于MSS尺寸的數(shù)據(jù)塊,所謂“未被確認”,是指一個數(shù)據(jù)塊發(fā)送出去后,沒有收到對方發(fā)送的ACK確認該數(shù)據(jù)已收到。

舉個例子,比如之前的blog中的實驗,一開始client端調(diào)用socket的write操作將一個int型數(shù)據(jù)(稱為A塊)寫入到網(wǎng)絡(luò)中,由于此時連接是空閑的(也就是說還沒有未被確認的小段),因此這個int型數(shù)據(jù)會被馬上發(fā)送到server端,接著,client端又調(diào)用write操作寫入‘\r\n’(簡稱B塊),這個時候,A塊的ACK沒有返回,所以可以認為已經(jīng)存在了一個未被確認的小段,所以B塊沒有立即被發(fā)送,一直等待A塊的ACK收到(大概40ms之后),B塊才被發(fā)送。整個過程如圖所示:

 

這里還隱藏了一個問題,就是A塊數(shù)據(jù)的ACK為什么40ms之后才收到?這是因為TCP/IP中不僅僅有nagle算法,還有一個ACK延遲機制 。當Server端收到數(shù)據(jù)之后,它并不會馬上向client端發(fā)送ACK,而是會將ACK的發(fā)送延遲一段時間(假設(shè)為t),它希望在t時間內(nèi)server端會向client端發(fā)送應(yīng)答數(shù)據(jù),這樣ACK就能夠和應(yīng)答數(shù)據(jù)一起發(fā)送,就像是應(yīng)答數(shù)據(jù)捎帶著ACK過去。在我之前的時間中,t大概就是40ms。這就解釋了為什么'\r\n'(B塊)總是在A塊之后40ms才發(fā)出。

如果你覺著nagle算法太搗亂了,那么可以通過設(shè)置TCP_NODELAY將其禁用 。當然,更合理的方案還是應(yīng)該使用一次大數(shù)據(jù)的寫操作,而不是多次小數(shù)據(jù)的寫操作。


本文來自CSDN博客,轉(zhuǎn)載請標明出處:http://blog.csdn.net/historyasamirror/archive/2011/05/15/6423235.aspx



何克勤 2011-05-20 09:27 發(fā)表評論
]]>
理解可執(zhí)行程序的各種神器 http://www.aygfsteel.com/tinysun/archive/2011/01/19/343184.html何克勤何克勤Tue, 18 Jan 2011 16:05:00 GMThttp://www.aygfsteel.com/tinysun/archive/2011/01/19/343184.htmlhttp://www.aygfsteel.com/tinysun/comments/343184.htmlhttp://www.aygfsteel.com/tinysun/archive/2011/01/19/343184.html#Feedback0http://www.aygfsteel.com/tinysun/comments/commentRss/343184.htmlhttp://www.aygfsteel.com/tinysun/services/trackbacks/343184.html

ldd查看應(yīng)用程序鏈接了哪些動態(tài)庫。

nm列出目標文件中包含的符號信息。

size列出各個段的大小及總的大小。

strings列出文件中的字符串。

readelf讀取elf文件的完整結(jié)構(gòu)。

objdump導(dǎo)出目標文件的相關(guān)信息(elf文件相關(guān)工具的源頭)。

gdb對文件的執(zhí)行過程進行調(diào)試分析,設(shè)置斷點(b)、單步執(zhí)行(n)、函數(shù)調(diào)用追蹤(bt)、反匯編(disassemble)。

strace跟蹤程序中的系統(tǒng)調(diào)用及信號處理信息。

LD_DEBUG通過設(shè)置這個環(huán)境變量,可以方便的看到 loader 的加載過程(包括庫的加載,符號解析等過程),使用【LD_DEBUG=help 可執(zhí)行文件路徑】可查看使用幫助。

LD_PRELOAD環(huán)境變量指定的共享庫會被預(yù)先加載,如果出現(xiàn)重名的函數(shù),預(yù)先加載的函數(shù)將會被調(diào)用,如在預(yù)先加載的庫中包含自定義的puts函數(shù),則在執(zhí)行程序時將使用自定義版本的puts函數(shù),而不是libc庫中的puts函數(shù)。

proc文件系統(tǒng)中包含進程的地址空間映射關(guān)系,具體查看/proc/進程id/maps文件的內(nèi)容。

valgrind工具對可執(zhí)行程序文件進行內(nèi)存檢查(還有cache模擬、調(diào)用過程跟蹤等功能),以避免內(nèi)存泄露等問題。

addrline將可執(zhí)行文件中的地址轉(zhuǎn)換為其在源文件中對應(yīng)的位置(文件名:行號)。



何克勤 2011-01-19 00:05 發(fā)表評論
]]>
進程上下文和中斷上下文http://www.aygfsteel.com/tinysun/archive/2010/11/07/337492.html何克勤何克勤Sun, 07 Nov 2010 15:59:00 GMThttp://www.aygfsteel.com/tinysun/archive/2010/11/07/337492.htmlhttp://www.aygfsteel.com/tinysun/comments/337492.htmlhttp://www.aygfsteel.com/tinysun/archive/2010/11/07/337492.html#Feedback0http://www.aygfsteel.com/tinysun/comments/commentRss/337492.htmlhttp://www.aygfsteel.com/tinysun/services/trackbacks/337492.html進程上下文和中斷上下文是操作系統(tǒng)中很重要的兩個概念,這兩個概念在操作系統(tǒng)課程中不斷被提及,是最經(jīng)常接觸、看上去很懂但又說不清楚到底怎么回事。造成這種局面的原因,可能是原來接觸到的操作系統(tǒng)課程的教學(xué)總停留在一種淺層次的理論層面上,沒有深入去研究。

處理器總處于以下狀態(tài)中的一種:
1、內(nèi)核態(tài),運行于進程上下文,內(nèi)核代表進程運行于內(nèi)核空間;
2、內(nèi)核態(tài),運行于中斷上下文,內(nèi)核代表硬件運行于內(nèi)核空間;
3、用戶態(tài),運行于用戶空間。

用戶空間的應(yīng)用程序,通過系統(tǒng)調(diào)用,進入內(nèi)核空間。這個時候用戶空間的進程要傳遞很多變量、參數(shù)的值給內(nèi)核,內(nèi)核態(tài)運行的時候也要保存用戶進程的一些寄存器值、變量等。所謂的“進程上下文”,可以看作是用戶進程傳遞給內(nèi)核的這些參數(shù)以及內(nèi)核要保存的那一整套的變量和寄存器值和當時的環(huán)境等。

硬件通過觸發(fā)信號,導(dǎo)致內(nèi)核調(diào)用中斷處理程序,進入內(nèi)核空間。這個過程中,硬件的一些變量和參數(shù)也要傳遞給內(nèi)核,內(nèi)核通過這些參數(shù)進行中斷處理。所謂的“中斷上下文”,其實也可以看作就是硬件傳遞過來的這些參數(shù)和內(nèi)核需要保存的一些其他環(huán)境(主要是當前被打斷執(zhí)行的進程環(huán)境)。


關(guān)于進程上下文LINUX完全注釋中的一段話:

   當一個進程在執(zhí)行時,CPU的所有寄存器中的值、進程的狀態(tài)以及堆棧中的內(nèi)容被稱為該進程的上下文。當內(nèi)核需要切換到另一個進程時,它需要保存當前進程的所有狀態(tài),即保存當前進程的上下文,以便在再次執(zhí)行該進程時,能夠必得到切換時的狀態(tài)執(zhí)行下去。在LINUX中,當前進程上下文均保存在進程的任務(wù)數(shù)據(jù)結(jié)構(gòu)中。在發(fā)生中斷時,內(nèi)核就在被中斷進程的上下文中,在內(nèi)核態(tài)下執(zhí)行中斷服務(wù)例程。但同時會保留所有需要用到的資源,以便中繼服務(wù)結(jié)束時能恢復(fù)被中斷進程的執(zhí)行。


本文來自CSDN博客,轉(zhuǎn)載請標明出處:http://blog.csdn.net/eroswang/archive/2007/11/28/1905830.aspx



何克勤 2010-11-07 23:59 發(fā)表評論
]]>
Socket send函數(shù)和recv函數(shù)詳解http://www.aygfsteel.com/tinysun/archive/2010/10/20/335699.html何克勤何克勤Wed, 20 Oct 2010 08:21:00 GMThttp://www.aygfsteel.com/tinysun/archive/2010/10/20/335699.htmlhttp://www.aygfsteel.com/tinysun/comments/335699.htmlhttp://www.aygfsteel.com/tinysun/archive/2010/10/20/335699.html#Feedback0http://www.aygfsteel.com/tinysun/comments/commentRss/335699.htmlhttp://www.aygfsteel.com/tinysun/services/trackbacks/335699.html

int send( SOCKET s,      const char FAR *buf,      int len,      int flags );  

不論是客戶還是服務(wù)器應(yīng)用程序都用send函數(shù)來向TCP連接的另一端發(fā)送數(shù)據(jù)。

客戶程序一般用send函數(shù)向服務(wù)器發(fā)送請求,而服務(wù)器則通常用send函數(shù)來向客戶程序發(fā)送應(yīng)答。

該函數(shù)的第一個參數(shù)指定發(fā)送端套接字描述符;

第二個參數(shù)指明一個存放應(yīng)用程序要發(fā)送數(shù)據(jù)的緩沖區(qū);

第三個參數(shù)指明實際要發(fā)送的數(shù)據(jù)的字節(jié)數(shù);

第四個參數(shù)一般置0。

這里只描述同步Socket的send函數(shù)的執(zhí)行流程。當調(diào)用該函數(shù)時,send先比較待發(fā)送數(shù)據(jù)的長度len和套接字s的發(fā)送緩沖的 長度,如果len大于s的發(fā)送緩沖區(qū)的長度,該函數(shù)返回SOCKET_ERROR;如果len小于或者等于s的發(fā)送緩沖區(qū)的長度,那么send先檢查協(xié)議是否正在發(fā)送s的發(fā)送緩沖中的數(shù)據(jù),如果是就等待協(xié)議把數(shù)據(jù)發(fā)送完,如果協(xié)議還沒有開始發(fā)送s的發(fā)送緩沖中的數(shù)據(jù)或者s的發(fā)送緩沖中沒有數(shù)據(jù),那么 send就比較s的發(fā)送緩沖區(qū)的剩余空間和len,如果len大于剩余空間大小send就一直等待協(xié)議把s的發(fā)送緩沖中的數(shù)據(jù)發(fā)送完,如果len小于剩余空間大小send就僅僅把buf中的數(shù)據(jù)copy到剩余空間里(注意并不是send把s的發(fā)送緩沖中的數(shù)據(jù)傳到連接的另一端的,而是協(xié)議傳的,send僅僅是把buf中的數(shù)據(jù)copy到s的發(fā)送緩沖區(qū)的剩余空間里)。如果send函數(shù)copy數(shù)據(jù)成功,就返回實際copy的字節(jié)數(shù),如果send在copy數(shù)據(jù)時出現(xiàn)錯誤,那么send就返回SOCKET_ERROR;如果send在等待協(xié)議傳送數(shù)據(jù)時網(wǎng)絡(luò)斷開的話,那么send函數(shù)也返回SOCKET_ERROR。

要注意send函數(shù)把buf中的數(shù)據(jù)成功copy到s的發(fā)送緩沖的剩余空間里后它就返回了,但是此時這些數(shù)據(jù)并不一定馬上被傳到連接的另一端。如果協(xié)議在后續(xù)的傳送過程中出現(xiàn)網(wǎng)絡(luò)錯誤的話,那么下一個Socket函數(shù)就會返回SOCKET_ERROR。(每一個除send外的Socket函數(shù)在執(zhí)行的最開始總要先等待套接字的發(fā)送緩沖中的數(shù)據(jù)被協(xié)議傳送完畢才能繼續(xù),如果在等待時出現(xiàn)網(wǎng)絡(luò)錯誤,那么該Socket函數(shù)就返回 SOCKET_ERROR)

注意:在Unix系統(tǒng)下,如果send在等待協(xié)議傳送數(shù)據(jù)時網(wǎng)絡(luò)斷開的話,調(diào)用send的進程會接收到一個SIGPIPE信號,進程對該信號的默認處理是進程終止。

通過測試發(fā)現(xiàn),異步socket的send函數(shù)在網(wǎng)絡(luò)剛剛斷開時還能發(fā)送返回相應(yīng)的字節(jié)數(shù),同時使用select檢測也是可寫的,但是過幾秒鐘之后,再send就會出錯了,返回-1。select也不能檢測出可寫了。

recv函數(shù)

int recv( SOCKET s,     char FAR *buf,      int len,     int flags     );   

不論是客戶還是服務(wù)器應(yīng)用程序都用recv函數(shù)從TCP連接的另一端接收數(shù)據(jù)。

該函數(shù)的第一個參數(shù)指定接收端套接字描述符;

第二個參數(shù)指明一個緩沖區(qū),該緩沖區(qū)用來存放recv函數(shù)接收到的數(shù)據(jù);

第三個參數(shù)指明buf的長度;

第四個參數(shù)一般置0。

這里只描述同步Socket的recv函數(shù)的執(zhí)行流程。當應(yīng)用程序調(diào)用recv函數(shù)時,recv先等待s的發(fā)送緩沖中的數(shù)據(jù)被協(xié)議傳送完畢,如果協(xié)議在傳送s的發(fā)送緩沖中的數(shù)據(jù)時出現(xiàn)網(wǎng)絡(luò)錯誤,那么recv函數(shù)返回SOCKET_ERROR,如果s的發(fā)送緩沖中沒有數(shù)據(jù)或者數(shù)據(jù)被協(xié)議成功發(fā)送完畢后,recv先檢查套接字s的接收緩沖區(qū),如果s接收緩沖區(qū)中沒有數(shù)據(jù)或者協(xié)議正在接收數(shù)據(jù),那么recv就一直等待,只到協(xié)議把數(shù)據(jù)接收完畢。當協(xié)議把數(shù)據(jù)接收完畢,recv函數(shù)就把s的接收緩沖中的數(shù)據(jù)copy到buf中(注意協(xié)議接收到的數(shù)據(jù)可能大于buf的長度,所以 在這種情況下要調(diào)用幾次recv函數(shù)才能把s的接收緩沖中的數(shù)據(jù)copy完。recv函數(shù)僅僅是copy數(shù)據(jù),真正的接收數(shù)據(jù)是協(xié)議來完成的),recv函數(shù)返回其實際copy的字節(jié)數(shù)。如果recv在copy時出錯,那么它返回SOCKET_ERROR;如果recv函數(shù)在等待協(xié)議接收數(shù)據(jù)時網(wǎng)絡(luò)中斷了,那么它返回0。

注意:在Unix系統(tǒng)下,如果recv函數(shù)在等待協(xié)議接收數(shù)據(jù)時網(wǎng)絡(luò)斷開了,那么調(diào)用recv的進程會接收到一個SIGPIPE信號,進程對該信號的默認處理是進程終止。



何克勤 2010-10-20 16:21 發(fā)表評論
]]>
。。。。http://www.aygfsteel.com/tinysun/archive/2010/10/11/334455.html何克勤何克勤Mon, 11 Oct 2010 09:20:00 GMThttp://www.aygfsteel.com/tinysun/archive/2010/10/11/334455.htmlhttp://www.aygfsteel.com/tinysun/comments/334455.htmlhttp://www.aygfsteel.com/tinysun/archive/2010/10/11/334455.html#Feedback0http://www.aygfsteel.com/tinysun/comments/commentRss/334455.htmlhttp://www.aygfsteel.com/tinysun/services/trackbacks/334455.html http://blog.csdn.net/eroswang/category/322381.aspx?PageNumber=2

何克勤 2010-10-11 17:20 發(fā)表評論
]]>
信號“未決”與“阻塞”http://www.aygfsteel.com/tinysun/archive/2010/10/11/334368.html何克勤何克勤Mon, 11 Oct 2010 07:47:00 GMThttp://www.aygfsteel.com/tinysun/archive/2010/10/11/334368.htmlhttp://www.aygfsteel.com/tinysun/comments/334368.htmlhttp://www.aygfsteel.com/tinysun/archive/2010/10/11/334368.html#Feedback0http://www.aygfsteel.com/tinysun/comments/commentRss/334368.htmlhttp://www.aygfsteel.com/tinysun/services/trackbacks/334368.html信號狀態(tài):
    信號的”未決“是一種狀態(tài),指的是從信號的產(chǎn)生到信號被處理前的這一段時間;信號的”阻塞“是一個開關(guān)動作,指的是阻止信號被處理,但不是阻止信號產(chǎn)生。
    APUE例題在sleep前用sigprocmask阻塞了退出信號,然后sleep,然后在sleep的過程中產(chǎn)生一個退出信號,但是此時退出信號被阻 塞過,(中文的”阻塞”在這里容易被誤解為一種狀態(tài),實際上是一種類似于開關(guān)的動作,所以說“被阻塞過”,而不是“被阻塞”)所以處于“未決”狀態(tài),在 sleep后又用sigprocmask關(guān)掉退出信號的阻塞開關(guān),因為之前產(chǎn)生的退出信號一直處于未決狀態(tài),當關(guān)上阻塞開關(guān)后,馬上退出“未決”狀態(tài),得到處理,這一切發(fā)生在sigprocmask返回之前。

信號生命周期:
    對于一個完整的信號生命周期(從信號發(fā)送到相應(yīng)的處理函數(shù)執(zhí)行完畢)來說,可以分為三個重要的階段,這三個階段由四個重要事件來刻畫:1.信號誕生;2. 信號在進程中注冊完畢;3.信號在進程中的注銷完畢;4.信號處理函數(shù)執(zhí)行完畢。相鄰兩個事件的時間間隔構(gòu)成信號生命周期的一個階段。
    下面闡述四個事件的實際意義:
1.信號"誕生"。信號的誕生指的是觸發(fā)信號的事件發(fā)生(如檢測到硬件異常、定時器超時以及調(diào)用信號發(fā)送函數(shù)kill()或sigqueue()等)。

2.信號在目標進程中"注冊";
    進程的task_struct結(jié)構(gòu)中有關(guān)于本進程中未決信號的數(shù)據(jù)成員:
struct sigpending pending;
struct sigpending
{
    struct sigqueue *head, **tail;
    sigset_t signal;
};
第一、第二個成員分別指向一個sigqueue類型的結(jié)構(gòu)鏈(稱之為"未決信號信息鏈")的首尾,第三個成員是進程中所有未決信號集,信息鏈中的每個sigqueue結(jié)構(gòu)體刻畫一個特定信號所攜帶的信息,并指向下一個sigqueue結(jié)構(gòu):
struct sigqueue
{
    struct sigqueue *next;
    siginfo_t info;
};
    信號在進程中注冊指的就是信號值加入到進程的未決信號集中(sigpending結(jié)構(gòu)的第二個成員sigset_t signal),并且信號所攜帶的信息被保留到未決信號信息鏈的某個sigqueue結(jié)構(gòu)中。只要信號在進程的未決信號集中,表明進程已經(jīng)知道這些信號的 存在,但還沒來得及處理,或者該信號被進程阻塞。
注:
    當一個實時信號發(fā)送給一個進程時,不管該信號是否已經(jīng)在進程中注冊,都會被再注冊一次,因此,信號不會丟失,因此,實時信號又叫做"可靠信號"。這意味著同一個實時信號可以在同一個進程的未決信號信息鏈中占有多個sigqueue結(jié)構(gòu)(進程每收到一個實時信號,都會為它分配一個結(jié)構(gòu)來登記該信號信息,并把該結(jié)構(gòu)添加在未決信號鏈尾,即所有誕生的實時信號都會在目標進程中注冊);
當一個非實時信號發(fā)送給一個進程時,如果該信號已經(jīng)在進程中注冊,則該信號將被丟棄,造成信號丟失。因此,非實時信號又叫做"不可靠信號"。 這意味著同一個非實時信號在進程的未決信號信息鏈中,至多占有一個sigqueue結(jié)構(gòu)(一個非實時信號誕生后,(1)、如果發(fā)現(xiàn)相同的信號已經(jīng)在目標結(jié) 構(gòu)中注冊,則不再注冊,對于進程來說,相當于不知道本次信號發(fā)生,信號丟失;(2)、如果進程的未決信號中沒有相同信號,則在進程中注冊自己)。

3. 信號在進程中的注銷。在目標進程執(zhí)行過程中,會檢測是否有信號等待處理(每次從系統(tǒng)空間返回到用戶空間時都做這樣的檢查)。如果存在未決信號等待處理且該 信號沒有被進程阻塞,則在運行相應(yīng)的信號處理函數(shù)前,進程會把信號在未決信號鏈中占有的結(jié)構(gòu)卸掉。是否將信號從進程未決信號集中刪除對于實時與非實時信號 是不同的。對于非實時信號來說,由于在未決信號信息鏈中最多只占用一個sigqueue結(jié)構(gòu),因此該結(jié)構(gòu)被釋放后,應(yīng)該把信號在進程未決信號集中刪除(信 號注銷完畢);而對于實時信號來說,可能在未決信號信息鏈中占用多個sigqueue結(jié)構(gòu),因此應(yīng)該針對占用gqueue結(jié)構(gòu)的數(shù)目區(qū)別對待:如果只占用 一個sigqueue結(jié)構(gòu)(進程只收到該信號一次),則應(yīng)該把信號在進程的未決信號集中刪除(信號注銷完畢)。否則,不在進程的未決信號集中刪除該信號 (信號注銷完畢)。進程在執(zhí)行信號相應(yīng)處理函數(shù)之前,首先要把信號在進程中注銷。

4.信號生命終止。進程注銷信號后,立即執(zhí)行相應(yīng)的信號處理函數(shù),執(zhí)行完畢后,信號的本次發(fā)送對進程的影響徹底結(jié)束。
注:
1) 信號注冊與否,與發(fā)送信號的函數(shù)(如kill()或sigqueue()等)以及信號安裝函數(shù)(signal()及sigaction())無關(guān),只與信 號值有關(guān)(信號值小于SIGRTMIN的信號最多只注冊一次,信號值在SIGRTMIN及SIGRTMAX之間的信號,只要被進程接收到就被注冊)。
2)在信號被注銷到相應(yīng)的信號處理函數(shù)執(zhí)行完畢這段時間內(nèi),如果進程又收到同一信號多次,則對實時信號來說,每一次都會在進程中注冊;而對于非實時信號來說,無論收到多少次信號,都會視為只收到一個信號,只在進程中注冊一次。


何克勤 2010-10-11 15:47 發(fā)表評論
]]>
linux的內(nèi)存管理FAQhttp://www.aygfsteel.com/tinysun/archive/2010/09/30/333559.html何克勤何克勤Thu, 30 Sep 2010 08:22:00 GMThttp://www.aygfsteel.com/tinysun/archive/2010/09/30/333559.htmlhttp://www.aygfsteel.com/tinysun/comments/333559.htmlhttp://www.aygfsteel.com/tinysun/archive/2010/09/30/333559.html#Feedback0http://www.aygfsteel.com/tinysun/comments/commentRss/333559.htmlhttp://www.aygfsteel.com/tinysun/services/trackbacks/333559.html一、Linux內(nèi)存管理到底是分段還是分頁?

在實模式下沒有引入段式內(nèi)存管理模式前,程序都是使用絕對地址來進行內(nèi)存操作的,這樣造成了程序的可移植性差等問題。于 是在8086時代開始引入段式內(nèi)存管理,段式管理引入了地址映射的概念,不但解決了可移植性問題,也解決了16位ALU和段寄存器的可尋址限制,即支持更 大的內(nèi)存尋址.每個段的大小是64KB.

在實模式下,由于系統(tǒng)程序和用戶程序在訪問內(nèi)存等資源時權(quán)限沒有區(qū)分,所以很可能造成操作系統(tǒng)出現(xiàn)異常.于是Intel在80286時代開發(fā)出來了保護模式.

而Intel為了兼容80386前的處理器就規(guī)定:在IA(Intel Arch)32保護模式下,不能禁止分段,但是分頁是Optional的。/*When operating in protected mode, some form of segmentation must be used. There is no mode bit to disable segmentation. The use of paging, however, is optional.*/.

既然linux在內(nèi)存管理上不能禁止分段,那么linux又認為分段會造成大量的內(nèi)存碎片,所以就通過將邏輯地址中的段基址置為0,加上偏移量形成線性地址,來簡化段式管理內(nèi)存而使用分頁管理內(nèi)存的。

在IA32下,每頁是4K.

二、linux的三種類型內(nèi)存地址及關(guān)系是什么樣的?

分段                     分頁

邏輯地址————> 線性地址 —————->物理地址

在 x86 架構(gòu)中,內(nèi)存被劃分成 3 種類型的地址:

邏輯地址 (logical address) 由16位段選擇符:32位偏移量來表示,它有可能直接對應(yīng)于一個物理位置(Intel實模式下)。
線性地址 (linear address)或虛擬地址 是由段描述符中的32位段基址和邏輯地址中的32位偏移量相加構(gòu)成的。例如:0x08048394,由于linux把所有的段基址都置為0x00000000.所以線性地址=0x00000000+偏移量=邏輯地址,在不分頁的情況下:線性地址就直接是物理地址;

物理地址 (physical address) 是使用物理地址總線中的位表示的地址。內(nèi)存管理單元可以將邏輯地址轉(zhuǎn)換成物理地址。

三、為什么在32位的操作系統(tǒng)下每個進程會有4G的內(nèi)存使用限制?

由于32位操作系統(tǒng)下,由于虛擬地址空間是2的32次方=4 294 967 296,通常的數(shù)據(jù)對象都以字節(jié)為單位,所以一個進程的可尋址范圍就是4G內(nèi)存

四、為什么linux服務(wù)器是32位的還可以認出來多于4G的內(nèi)存?

本來在IA32下,機器只能認出4G的內(nèi)存,但Intel為了讓32位的OS能支持更多的內(nèi)存。采取了PAE(Physical Address Extension, 物理地址擴展)技術(shù).PAE的支持,不僅需要處理器的支持,OS的內(nèi)核也需要支持才可行(我們通常采用的RHEL3/4/5 kernel都支持PAE),也就是處理器內(nèi)部增加了PAE寄存器,用于記錄多出的4條地址總線,所以系統(tǒng)就是2^36=64G.

五、在32位的linux操作系統(tǒng)下,為什么linux系統(tǒng)認出來的內(nèi)存大于4G,而我的程序每個進程卻只能分配4G以下的內(nèi)存?

雖然Intel為其處理器為支持PAE增加了4條地址線,但因為虛擬地址是32位的,所以單個進程還是只能分配4G以下的內(nèi)存

Note: Linux can use up to 64 Gigabytes of physical memory on x86 systems. However, the address space of 32-bit x86 processors is only 4 Gigabytes large. Thus means that, if you have a large amount of physical memory, not all of it can be “permanently mapped” by the kernel. The physical memory that’s not permanently mapped is called “high memory”.

Redhat網(wǎng)站http://www.redhat.com/rhel/compare/

六、32位的RedHat linux”smp” kernel”hugemem” kernel中內(nèi)存支持方面有什么建議?

Smp kernel和hugemem kernel都支持PAE支持,也就意味著最大能支持64G的內(nèi)存。而RedHat建議當你的物理內(nèi)存在16GB之內(nèi),用SMP” kernel,在16GB-64GB之間使用”Hugemem” kernel.這是因為虛擬地址空間里有1G用于內(nèi)核空間,而3G用于用戶空間。而關(guān)健的一些數(shù)據(jù)結(jié)構(gòu)是存放在1G內(nèi)核空間的,在管理32G內(nèi)存當中,需 要用到0.5G來用于管理這些物理內(nèi)存(容易觸發(fā)OOM killer).雖然32位OS下,內(nèi)核和用戶空間的比例都是1:3,但Hugemem打了一個補丁,使比例成為4G:4G,即使內(nèi)核空間和用戶空間相互 獨立,所以也會有性能上的損失,因為應(yīng)用程序的運行,通常會有內(nèi)核和用戶空間的切換。所以如果你的內(nèi)存大于16G,建議你使用64位的OS。

/* The “SMP” kernel supports a maximum of 16GB of main memory. Systems with more than 16GB of main memory use the “Hugemem” kernel. In certain workload scenarios it may be advantageous to use the “Hugemem” kernel on systems with more than 12GB of main memory. */

七、我們通常malloc4K內(nèi)存,到底是怎么對應(yīng)到物理內(nèi)存的?

在linux下,處理器在得到內(nèi)存的線性地址進行內(nèi)存尋址時,不是直接在內(nèi)存的物理地址里查找的,而是通過線性地址轉(zhuǎn)換到主內(nèi)存的物理地 址,TLB(Translation lookaside buffer,可以簡單的理解為:一種存儲線性地址和物理地址的硬件高速緩沖器)就是負責(zé)將虛擬內(nèi)存地址翻譯成實際的物理內(nèi)存地址,而CPU尋址時會優(yōu)先 在TLB中進行尋址。如果TLB里沒有,則從頁表里進行線性地址到物理地址的轉(zhuǎn)換.hugepage能增加TLB的命中率,所以會在某些方面能大大提高系 統(tǒng)性能)。

1、邏輯地址轉(zhuǎn)線性地址

LL.jpg

a、首先根據(jù)指令的性質(zhì)來確定該使用哪一個段寄存器。
b、根據(jù)段寄存器的內(nèi)容,到GDT中找到相應(yīng)的“段描述結(jié)構(gòu)”

c、根據(jù)linux把所有的段基址都置為0×00000000.所以0×00000000+偏移量就是線性地址了.在不分頁的情況下:線性地址就直接是物理地址;
同時,在上面過程中,由于有對訪問權(quán)限的檢查,就實現(xiàn)了保護。

2、線性地址轉(zhuǎn)物理地址

在保護模式下,控制寄存器CR0的最高位PG位(PE位控制是否為保護模式)控制著分頁管理機制是否生效,如果PG=1,分頁機制生效,需通過頁表查找才能把線性地址轉(zhuǎn)換物理地址。如果PG=0,則分頁機制無效,線性地址就直接做為物理地址。

頁式內(nèi)存管理中,32位的線性地址劃分為三個部分:10位的頁目錄表下標、10位的頁面表下標、12位的頁內(nèi)偏移量。CPU增加了一個CR3寄存器存放指向當前頁目錄表的指針。尋址方式就改為:
a、從CR3取得頁目錄表的基地址;
b、根據(jù)10位頁目錄表下標和從CR3取得的基地址,得到相應(yīng)頁表的基地址;
c、根據(jù)10位頁面表下標和b中得到的頁表基地址,從頁面表中取得相應(yīng)的頁面描述項;
d、將頁面描述項中的頁面基地址和線性地址中的12位頁內(nèi)地址偏移相加,得到物理地址。
同時,在地址轉(zhuǎn)換的過程中也有越界和權(quán)限的檢查,就不贅述了。

LP

注:傳統(tǒng)的32位操作系統(tǒng)采用的是兩級分頁模型

64位和PAE支持都采用了三級分頁模型



何克勤 2010-09-30 16:22 發(fā)表評論
]]>
Linux slab 分配器剖析http://www.aygfsteel.com/tinysun/archive/2010/09/30/333537.html何克勤何克勤Thu, 30 Sep 2010 05:46:00 GMThttp://www.aygfsteel.com/tinysun/archive/2010/09/30/333537.htmlhttp://www.aygfsteel.com/tinysun/comments/333537.htmlhttp://www.aygfsteel.com/tinysun/archive/2010/09/30/333537.html#Feedback0http://www.aygfsteel.com/tinysun/comments/commentRss/333537.htmlhttp://www.aygfsteel.com/tinysun/services/trackbacks/333537.html

何克勤 2010-09-30 13:46 發(fā)表評論
]]>
頻繁分配釋放內(nèi)存導(dǎo)致的性能問題的分析http://www.aygfsteel.com/tinysun/archive/2010/09/30/333536.html何克勤何克勤Thu, 30 Sep 2010 05:35:00 GMThttp://www.aygfsteel.com/tinysun/archive/2010/09/30/333536.htmlhttp://www.aygfsteel.com/tinysun/comments/333536.htmlhttp://www.aygfsteel.com/tinysun/archive/2010/09/30/333536.html#Feedback0http://www.aygfsteel.com/tinysun/comments/commentRss/333536.htmlhttp://www.aygfsteel.com/tinysun/services/trackbacks/333536.html現(xiàn)象
1 壓力測試過程中,發(fā)現(xiàn)被測對象性能不夠理想,具體表現(xiàn)為:
進程的系統(tǒng)態(tài)CPU消耗20,用戶態(tài)CPU消耗10,系統(tǒng)idle大約70
2 用ps -o majflt,minflt -C program命令查看,發(fā)現(xiàn)majflt每秒增量為0,而minflt每秒增量大于10000。

初步分析
majflt代表major fault,中文名叫大錯誤,minflt代表minor fault,中文名叫小錯誤。
這兩個數(shù)值表示一個進程自啟動以來所發(fā)生的缺頁中斷的次數(shù)。
當一個進程發(fā)生缺頁中斷的時候,進程會陷入內(nèi)核態(tài),執(zhí)行以下操作:
檢查要訪問的虛擬地址是否合法
查找/分配一個物理頁
填充物理頁內(nèi)容(讀取磁盤,或者直接置0,或者啥也不干)
建立映射關(guān)系(虛擬地址到物理地址)
重新執(zhí)行發(fā)生缺頁中斷的那條指令
如果第3步,需要讀取磁盤,那么這次缺頁中斷就是majflt,否則就是minflt。
此進程minflt如此之高,一秒10000多次,不得不懷疑它跟進程內(nèi)核態(tài)cpu消耗大有很大關(guān)系。

分析代碼
查看代碼,發(fā)現(xiàn)是這么寫的:一個請求來,用malloc分配2M內(nèi)存,請求結(jié)束后free這塊內(nèi)存。看日志,發(fā)現(xiàn)分配內(nèi)存語句耗時10us,平均一條請求處理耗時1000us 。 原因已找到!
雖然分配內(nèi)存語句的耗時在一條處理請求中耗時比重不大,但是這條語句嚴重影響了性能。要解釋清楚原因,需要先了解一下內(nèi)存分配的原理。

內(nèi)存分配的原理
從 操作系統(tǒng)角度來看,進程分配內(nèi)存有兩種方式,分別由兩個系統(tǒng)調(diào)用完成:brk和mmap(不考慮共享內(nèi)存)。brk是將數(shù)據(jù)段(.data)的最高地址指 針_edata往高地址推,mmap是在進程的虛擬地址空間中(一般是堆和棧中間)找一塊空閑的。這兩種方式分配的都是虛擬內(nèi)存,沒有分配物理內(nèi)存。在第 一次訪問已分配的虛擬地址空間的時候,發(fā)生缺頁中斷,操作系統(tǒng)負責(zé)分配物理內(nèi)存,然后建立虛擬內(nèi)存和物理內(nèi)存之間的映射關(guān)系。
在標準C庫中,提供了malloc/free函數(shù)分配釋放內(nèi)存,這兩個函數(shù)底層是由brk,mmap,munmap這些系統(tǒng)調(diào)用實現(xiàn)的。
下面以一個例子來說明內(nèi)存分配的原理:

1進程啟動的時候,其(虛擬)內(nèi)存空間的初始布局如圖1所示。其中,mmap內(nèi)存映射文件是在堆和棧的中間(例如libc-2.2.93.so,其它數(shù)據(jù)文件等),為了簡單起見,省略了內(nèi)存映射文件。_edata指針(glibc里面定義)指向數(shù)據(jù)段的最高地址。
2 進程調(diào)用A=malloc(30K)以后,內(nèi)存空間如圖2:malloc函數(shù)會調(diào)用brk系統(tǒng)調(diào)用,將_edata指針往高地址推30K,就完成虛擬內(nèi)存 分配。你可能會問:只要把_edata+30K就完成內(nèi)存分配了?事實是這樣的,_edata+30K只是完成虛擬地址的分配,A這塊內(nèi)存現(xiàn)在還是沒有物 理頁與之對應(yīng)的,等到進程第一次讀寫A這塊內(nèi)存的時候,發(fā)生缺頁中斷,這個時候,內(nèi)核才分配A這塊內(nèi)存對應(yīng)的物理頁。也就是說,如果用malloc分配了 A這塊內(nèi)容,然后從來不訪問它,那么,A對應(yīng)的物理頁是不會被分配的。
3進程調(diào)用B=malloc(40K)以后,內(nèi)存空間如圖3.

4進程調(diào)用C=malloc(200K)以后,內(nèi)存空間如圖4:默認情況下,malloc函數(shù)分配內(nèi)存,如果請求內(nèi)存大于128K(可由 M_MMAP_THRESHOLD選項調(diào)節(jié)),那就不是去推_edata指針了,而是利用mmap系統(tǒng)調(diào)用,從堆和棧的中間分配一塊虛擬內(nèi)存。這樣子做主 要是因為brk分配的內(nèi)存需要等到高地址內(nèi)存釋放以后才能釋放(例如,在B釋放之前,A是不可能釋放的),而mmap分配的內(nèi)存可以單獨釋放。當然,還有 其它的好處,也有壞處,再具體下去,有興趣的同學(xué)可以去看glibc里面malloc的代碼了。
5進程調(diào)用D=malloc(100K)以后,內(nèi)存空間如圖5.
6進程調(diào)用free(C)以后,C對應(yīng)的虛擬內(nèi)存和物理內(nèi)存一起釋放

4進程調(diào)用C=malloc(200K)以后,內(nèi)存空間如圖4:默認情況下,malloc函數(shù)分配內(nèi)存,如果請求內(nèi)存大于128K(可由 M_MMAP_THRESHOLD選項調(diào)節(jié)),那就不是去推_edata指針了,而是利用mmap系統(tǒng)調(diào)用,從堆和棧的中間分配一塊虛擬內(nèi)存。這樣子做主 要是因為brk分配的內(nèi)存需要等到高地址內(nèi)存釋放以后才能釋放(例如,在B釋放之前,A是不可能釋放的),而mmap分配的內(nèi)存可以單獨釋放。當然,還有 其它的好處,也有壞處,再具體下去,有興趣的同學(xué)可以去看glibc里面malloc的代碼了。
5進程調(diào)用D=malloc(100K)以后,內(nèi)存空間如圖5.
6進程調(diào)用free(C)以后,C對應(yīng)的虛擬內(nèi)存和物理內(nèi)存一起釋放

7進程調(diào)用free(B)以后,如圖7所示。B對應(yīng)的虛擬內(nèi)存和物理內(nèi)存都沒有釋放,因為只有一個_edata指針,如果往回推,那么D這塊內(nèi)存怎 么辦呢?當然,B這塊內(nèi)存,是可以重用的,如果這個時候再來一個40K的請求,那么malloc很可能就把B這塊內(nèi)存返回回去了。
8進程調(diào)用free(D)以后,如圖8所示。B和D連接起來,變成一塊140K的空閑內(nèi)存。
9默認情況下:當最高地址空間的空閑內(nèi)存超過128K(可由M_TRIM_THRESHOLD選項調(diào)節(jié))時,執(zhí)行內(nèi)存緊縮操作(trim)。在上一個步驟free的時候,發(fā)現(xiàn)最高地址空閑內(nèi)存超過128K,于是內(nèi)存緊縮,變成圖9所示。

真相大白
說 完內(nèi)存分配的原理,那么被測模塊在內(nèi)核態(tài)cpu消耗高的原因就很清楚了:每次請求來都malloc一塊2M的內(nèi)存,默認情況下,malloc調(diào)用mmap 分配內(nèi)存,請求結(jié)束的時候,調(diào)用munmap釋放內(nèi)存。假設(shè)每個請求需要6個物理頁,那么每個請求就會產(chǎn)生6個缺頁中斷,在2000的壓力下,每秒就產(chǎn)生 了10000多次缺頁中斷,這些缺頁中斷不需要讀取磁盤解決,所以叫做minflt;缺頁中斷在內(nèi)核態(tài)執(zhí)行,因此進程的內(nèi)核態(tài)cpu消耗很大。缺頁中斷分 散在整個請求的處理過程中,所以表現(xiàn)為分配語句耗時(10us)相對于整條請求的處理時間(1000us)比重很小。

解決辦法
將動態(tài)內(nèi)存改為靜態(tài)分配,或者啟動的時候,用malloc為每個線程分配,然后保存在threaddata里面。但是,由于這個模塊的特殊性,靜態(tài)分配,或者啟動時候分配都不可行。另外,Linux下默認棧的大小限制是10M,如果在棧上分配幾M的內(nèi)存,有風(fēng)險。
禁止malloc調(diào)用mmap分配內(nèi)存,禁止內(nèi)存緊縮。
在進程啟動時候,加入以下兩行代碼:
mallopt(M_MMAP_MAX, 0); // 禁止malloc調(diào)用mmap分配內(nèi)存
mallopt(M_TRIM_THRESHOLD, -1); // 禁止內(nèi)存緊縮
效果:加入這兩行代碼以后,用ps命令觀察,壓力穩(wěn)定以后,majlt和minflt都為0。進程的系統(tǒng)態(tài)cpu從20降到10。

小結(jié)
可以用命令ps -o majflt minflt -C program來查看進程的majflt, minflt的值,這兩個值都是累加值,從進程啟動開始累加。在對高性能要求的程序做壓力測試的時候,我們可以多關(guān)注一下這兩個值。
如果一個進程使用了mmap將很大的數(shù)據(jù)文件映射到進程的虛擬地址空間,我們需要重點關(guān)注majflt的值,因為相比minflt,majflt對于性能的損害是致命的,隨機讀一次磁盤的耗時數(shù)量級在幾個毫秒,而minflt只有在大量的時候才會對性能產(chǎn)生影響。



何克勤 2010-09-30 13:35 發(fā)表評論
]]>
系統(tǒng)管理中 bash shell 腳本常用方法總結(jié)http://www.aygfsteel.com/tinysun/archive/2010/09/30/333468.html何克勤何克勤Thu, 30 Sep 2010 02:54:00 GMThttp://www.aygfsteel.com/tinysun/archive/2010/09/30/333468.htmlhttp://www.aygfsteel.com/tinysun/comments/333468.htmlhttp://www.aygfsteel.com/tinysun/archive/2010/09/30/333468.html#Feedback0http://www.aygfsteel.com/tinysun/comments/commentRss/333468.htmlhttp://www.aygfsteel.com/tinysun/services/trackbacks/333468.html

在日常系統(tǒng)管理工作中,需要編寫腳本來完成特定的功能,編寫shell腳本是一個基本功了!
在編寫的過程中,掌握一些常用的技巧和語法就可以完成大部分功能了,也就是2/8原則.

1. 單引號和雙引號的區(qū)別

單引號與雙引號的最大不同在于雙引號仍然可以引用變量的內(nèi)容,但單引號內(nèi)僅是 普通字符 ,不會作變量的引用,直接輸出字符竄。請看如下例子:

  [root@linux ~]# name=HaHa
[root@linux ~]# echo $name
HaHa
[root@linux ~]# myname="$name is wow"
[root@linux ~]# echo $myname
HaHa is wow
[root@linux ~]# myname='$name is wow'
[root@linux ~]# echo $myname
$name is wow

從上面例子可以看出,使用了單引號的時候,那么$name只是普通字符,直接輸出而已!

2. 逐行讀取文件

  • 使用for循環(huán)來讀取文件
      for line in `cat file.txt`
    do
    echo $line
    done

注意:由于使用for來讀入文件里的行時,會自動把空格和換行符作為一樣分隔符,如果行里有空格的時候,輸出的結(jié)果會很亂,所以只適用于行連續(xù)不能有空格或者換行符的文件

  • 使用while循環(huán)讀取文件
      cat file.txt |while read line
    do
    echo $line
    done

    或者:

    while read line
    do
    echo $line
    done < file.txt

注意:由于使用while來讀入文件里的行時,會整行讀入,不會關(guān)注行的內(nèi)容(空格..),所以比for讀文件有更好的適用性,推薦使用while循環(huán)讀取文件

3. bash shell 腳本中常用隱含變量

$0 當前執(zhí)行的腳本或者命令名稱
$1-$9 代表參數(shù)的位置. 舉例 $1 代表第一個參數(shù).
$# 腳本調(diào)用的參數(shù)的個數(shù)
$@ 所有參數(shù)的內(nèi)容
$* 所有參數(shù)的內(nèi)容
$$ 當前運行腳本的進程號
$? 命令執(zhí)行后返回的狀態(tài)
$! 后臺運行的最后一個進程號

注意: $? 用于檢查上一個命令執(zhí)行是否正確(在Linux中,命令退出狀態(tài)為0表示該命令正確執(zhí)行,任何非0值表示命令出錯)
$$ 變量最常見的用途是用做暫存文件的名字以保證暫存文件不會重復(fù)。
$* 和 $@ 如果輸出是一樣的,但是在使用for循環(huán),在使用 雙引號(”")引用時 “$*” 會輸出成一個元素 而 “$@” 會按照每個參數(shù)是一個元素方式輸出

請看測試例子

  #cat test.sh
#!/bin/sh
echo '"$@" output.....'
for i in "$@"
do
echo $i
done
echo '"$*" output ....'
for i in "$*"
do
echo $i
done

輸出結(jié)果

  #sh test.sh a b c d
"$@" output.....
a
b
c
d
"$*" output ....
a b c d

從輸出結(jié)果可以看出 “$*” 輸出是一行 而 “$@” 輸出則是四行

4. 變量內(nèi)容的刪除與替換

我們在一些情況下,需要對變量中的字符竄進行查找刪除或者替換,就需要使用下表列出的方法

變量設(shè)定方式 說明
${變量#關(guān)鍵字} 若變量內(nèi)容從頭開始的資料符合‘關(guān)鍵字’,則將符合的最短資料刪除
${變量##關(guān)鍵字} 若變量內(nèi)容從頭開始的資料符合‘關(guān)鍵字’,則將符合的最長資料刪除
${變量%關(guān)鍵字} 若變量內(nèi)容從尾向前的資料符合‘關(guān)鍵字’,則將符合的最短資料刪除
${變量%%關(guān)鍵字} 若變量內(nèi)容從尾向前的資料符合‘關(guān)鍵字’,則將符合的最長資料刪除
${變量/舊字串/新字串} 若變量內(nèi)容符合‘舊字串’則‘第一個舊字串會被新字串取代
${變量//舊字串/新字串} 若變量內(nèi)容符合‘舊字串’則‘全部的舊字串會被新字串取代

舉例如下(刪除字符竄中的某個字符):

  [root@linux ~]# export test_str="/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin"
[root@linux ~]# echo ${test_str#/*kerberos/bin:}
/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin

5. 變量條件測試賦值

在某些時刻我們需要‘判斷’某個變量是否存在,若變量存在則將此變量值賦值給新的變量,若變量不存在則將其他值賦值給新的變量.

變量設(shè)定方式 str 未定義 str 為空字串 str 已賦值為非空字串
var=${str-expr} var=expr var= var=$str
var=${str:-expr} var=expr var=expr var=$str
var=${str+expr} var= var=expr var=expr
var=${str:+expr} var= var= var=expr
var=${str?expr} expr 輸出至 stderr var= var=$str
var=${str:?expr} expr 輸出至 stderr expr 輸出至 stderr var=$str
var=${str=expr} var=expr var= var=$str
var=${str:=expr} var=expr var=expr var=$str

舉例如下:

  [root@linux ~]# test_name=""
[root@linux ~]# test_name=${test_name-root}
[root@linux ~]# echo $test_name
<== 因為 test_name 被設(shè)定為空字符竄!所以當然還是保留為空字符竄!
[root@linux ~]# test_name=${test_name:-root}
[root@linux ~]# echo $test_name
root <== 加上‘:’后若變量內(nèi)容為空或者是未設(shè)定,都能夠以后面的內(nèi)容替換!

基本上這種變量的測試也能夠透過 shell script 內(nèi)的 if…then… 來處理,不過通過上述提及的簡單的方法來測試變量,是程序看起來更精簡一些!

6. shell 中分隔符 : 變量IFS 使用

shell腳本中,如果使用for循環(huán)一個字符竄的話,默認使用空格來分割字符竄. 還有前面所提到的 使用for循環(huán)逐行讀取文件內(nèi)容時候,文件行中如果有空格的話輸出的結(jié)果也會變亂. 這個時候 使用 IFS 變量來設(shè)置特定的字符竄分割符來,達到輸出正確的目的. 默認情況下 IFS 是使用 <space><tab><newline>, 空格 "t "n 來作為默認的分割符的.

我們將前面使用for逐行讀取文件的例子 改進下就可以輸出正確了,請看下面

  #!/bin/bash
IFS_old=$IFS #將原IFS值保存,以便用完后恢復(fù)
IFS=$’"n’ #更改IFS值為$’"n’
for line in `cat file.txt`
do
echo $line
done

file.txt 文件內(nèi)容如下

  [root@linux]$ cat file.txt
sdfsdfsdfsdf
ssssss ssssss ssssss sssss
sdfsdfsdfsdfsdf

執(zhí)行測試程序 輸出結(jié)果如下(正確輸出)

  [root@linux]$ sh test.sh
sdfsdfsdfsdf
ssssss ssssss ssssss sssss
sdfsdfsdfsdfsdf

如果未設(shè)置IFS變量,使用默認的IFS變量值 ,輸出結(jié)果如下

  [root@linux]$ sh test.sh
sdfsdfsdfsdf
ssssss
ssssss
ssssss
sssss
sdfsdfsdfsdfsdf

從以上測試程序輸出結(jié)果,可以根據(jù)自己的需求來設(shè)定 IFS變量,在舉一個例子如下:

  while IFS=: read userName passWord userID groupID geCos homeDir userShell
do
echo "$userName -> $homeDir"
done < /etc/passwd

7. shell 數(shù)組的使用

數(shù)組賦值方式:

  (1) array=(var1 var2 var3 ... varN)
(2) array=([0]=var1 [1]=var2 [2]=var3 ... [n]=varN)
(3) array[0]=var1
arrya[1]=var2
...
array[n]=varN

計算數(shù)組元素個數(shù)或者長度:

  (1) ${#array[@]}
(2) ${#array[*]}

了解了數(shù)組基礎(chǔ)語法,舉例說明,請看:

  #!/bin/bash
NAMESERVERS=("ns1.www.net." "ns2.www.net." "ns3.www.net.")
# 得到數(shù)組長度
tLen=${#NAMESERVERS[@]}

# 循環(huán)數(shù)組
for (( i=0; i<${tLen}; i++ ));
do
echo ${NAMESERVERS[$i]}
done

在看一個復(fù)雜一點的例子,將文件內(nèi)容讀取到數(shù)組中:

  #!/bin/bash

# 設(shè)置IFS將分割符 設(shè)置為 換行符("n)
OLDIFS=$IFS
IFS=$'"n'

# 讀取文件內(nèi)容到數(shù)組
fileArray=($(cat file.txt))

# restore it
IFS=$OLDIFS
tLen=${#fileArray[@]}

# 循環(huán)顯示文件內(nèi)容
for (( i=0; i<${tLen}; i++ ));
do
echo "${fileArray[$i]}"
done

8. 邏輯判斷 條件測試

  • 文件屬性的判斷
操作符 測試結(jié)果
-e filename 文件存在返回1, 否則返回0
-r filename 文件可讀返回1,否則返回0
-w filename 文件可寫返回1,否則返回0
-x filename 文件可執(zhí)行返回1,否則返回0
-o filename 文件屬于用戶本人返回1, 否則返回0
-z filename 文件長度為0返回1, 否則返回0
-f filename 文件為普通文件返回1, 否則返回0
-d filename 文件為目錄文件時返回1, 否則返回0

舉例如下,測試文件是否存在:

  #!/bin/bash
echo "checks the existence of the messages file."
echo -n "Checking..."
if [ -f /var/log/messages ];then
echo "/var/log/messages exists."
fi
echo
echo "...done."
  • 字符串比較
操作符 比較結(jié)果
str1 = str2 當兩個字串相等時為真
str1 != str2 當兩個字串不等時為真
-n str1 當字符串的長度大于0時為真
-z str1 當字符串的長度為0時為真
str 當字符串為非空時為真

舉例如下,比較字符串來測試用戶ID :

  if [ "$(whoami)" != 'root' ]; then
echo "You have no permission to run $0 as non-root user."
exit 1;
fi
  • 數(shù)值比較(整數(shù))
操作符 比較結(jié)果
num1 -eq num2 兩數(shù)相等為真
num1 -ne num2 兩數(shù)不等為真
num1 -gt num2 num1大于num2為真
num1 -ge num2 num1大于等于num2為真
num1 -lt num2 num1小于num2為真
num1 -le num2 num1小于等于num2為真

舉例如下:

  num=`wc -l work.txt`
if [ $num -gt 150 ];then
echo "you've worked hard enough for today."
echo
fi

如果要查看詳細的測試操作,可以查看man手冊 man test



何克勤 2010-09-30 10:54 發(fā)表評論
]]>
linux下which、whereis、locate、find 命令的區(qū)別http://www.aygfsteel.com/tinysun/archive/2010/09/30/333460.html何克勤何克勤Thu, 30 Sep 2010 02:17:00 GMThttp://www.aygfsteel.com/tinysun/archive/2010/09/30/333460.htmlhttp://www.aygfsteel.com/tinysun/comments/333460.htmlhttp://www.aygfsteel.com/tinysun/archive/2010/09/30/333460.html#Feedback1http://www.aygfsteel.com/tinysun/comments/commentRss/333460.htmlhttp://www.aygfsteel.com/tinysun/services/trackbacks/333460.html which       查看可執(zhí)行文件的位置
whereis    查看文件的位置
locate       配 合數(shù)據(jù)庫查看文件位置
find          實際搜尋硬盤查詢文件名稱

1、which
語法:
[root@redhat ~]# which 可執(zhí)行文件名稱
例如:
[root@redhat ~]# which passwd
/usr/bin/passwd
which是通過 PATH環(huán)境變量到該路徑內(nèi)查找可執(zhí)行文件,所以基本的功能是尋找可執(zhí)行文件

2、whereis
語法:
[root@redhat ~]# whereis [-bmsu] 文件或者目錄名稱
參數(shù)說 明:
-b : 只找二進制文件
-m: 只找在說明文件manual路徑下的文件
-s : 只找source源文件
-u : 沒有說明文檔的文件
例如:
[root@redhat ~]# whereis passwd
passwd: /usr/bin/passwd /etc/passwd /usr/share/man/man1/passwd.1.gz /usr/share/man/man5/passwd.5.gz
將和passwd文件相關(guān)的文件都查找出來

[root@redhat ~]# whereis -b passwd
passwd: /usr/bin/passwd /etc/passwd
只將二進制文件 查找出來

和find相比,whereis查找的速度非常快,這是因為linux系統(tǒng)會將 系統(tǒng)內(nèi)的所有文件都記錄在一個數(shù)據(jù)庫文件中,當使用whereis和下面即將介紹的locate時,會從數(shù)據(jù)庫中查找數(shù)據(jù),而不是像find命令那樣,通 過遍歷硬盤來查找,效率自然會很高。
但是該數(shù)據(jù)庫文件并不是實時更新,默認情況下時一星期更新一次,因此,我們在用whereis和locate 查找文件時,有時會找到已經(jīng)被刪除的數(shù)據(jù),或者剛剛建立文件,卻無法查找到,原因就是因為數(shù)據(jù)庫文件沒有被更新。

3、 locate
語法:
[root@redhat ~]# locate 文件或者目錄名稱
例 如:
[root@redhat ~]# locate passwd
/home/weblogic/bea/user_projects/domains/zhanggongzhe112/myserver/stage/_appsdir_DB_war/DB.war/jsp/as/user/passwd.jsp
/home/weblogic/bea/user_projects/domains/zhanggongzhe112/myserver/stage/_appsdir_admin_war/admin.war/jsp/platform/passwd.jsp
/lib/security/pam_unix_passwd.so
/lib/security/pam_passwdqc.so
/usr/include/rpcsvc/yppasswd.x
/usr/include/rpcsvc/yppasswd.h
/usr/lib/perl5/5.8.5/i386-linux-thread-multi/rpcsvc/yppasswd.ph
/usr/lib/kde3/kded_kpasswdserver.la
/usr/lib/kde3/kded_kpasswdserver.so
/usr/lib/ruby/1.8/webrick/httpauth/htpasswd.rb
/usr/bin/vncpasswd
/usr/bin/userpasswd
/usr/bin/yppasswd
…………

4、 find
語法:
[root@redhat ~]# find 路徑 參數(shù)
參 數(shù)說明:
時間查找參數(shù):
-atime n :將n*24小時內(nèi)存取過的的文件列出來
-ctime n :將n*24小時內(nèi)改變、新增的文件或者目錄列出來
-mtime n :將n*24小時內(nèi)修改過的文件或者目錄列出來
-newer file :把比file還要新的文件列出來
名稱查找參數(shù):
-gid n       :尋找群組ID為n的文件
-group name  :尋找群組名稱為name的文件
-uid n       :尋找擁有者ID為n的文件
-user name   :尋找用戶者名稱為name的文件
-name file   :尋找文件名為file的文件(可以使用通配符)
例 如:
[root@redhat ~]# find / -name zgz
/home/zgz
/home/zgz/zgz
/home/weblogic/bea/user_projects/domains/zgz
/home/oracle/product/10g/cfgtoollogs/dbca/zgz
/home/oracle/product/10g/cfgtoollogs/emca/zgz
/home/oracle/oradata/zgz

[root@redhat ~]# find / -name '*zgz*'
/home/zgz
/home/zgz/zgz1
/home/zgz/zgzdirzgz
/home/zgz/zgz
/home/zgz/zgzdir
/home/weblogic/bea/user_projects/domains/zgz
/home/weblogic/bea/user_projects/domains/zgz/zgz.log00006
/home/weblogic/bea/user_projects/domains/zgz/zgz.log00002
/home/weblogic/bea/user_projects/domains/zgz/zgz.log00004
/home/weblogic/bea/user_projects/domains/zgz/zgz.log
/home/weblogic/bea/user_projects/domains/zgz/zgz.log00008
/home/weblogic/bea/user_projects/domains/zgz/zgz.log00005

當我們用whereis和locate無法查找到我們需要的文件時,可以使用find,但是find是在硬盤上遍歷查 找,因此非常消耗硬盤的資源,而且效率也非常低,因此建議大家優(yōu)先使用whereis和locate。
locate 是在數(shù)據(jù)庫里查找,數(shù)據(jù)庫大至每天更新一次。
whereis 可以找到可執(zhí)行命令和man page
find 就是根據(jù)條件查找文件。
which 可以找到可執(zhí)行文件和別名(alias)

何克勤 2010-09-30 10:17 發(fā)表評論
]]>
bash快捷鍵http://www.aygfsteel.com/tinysun/archive/2010/09/28/333110.html何克勤何克勤Tue, 28 Sep 2010 01:57:00 GMThttp://www.aygfsteel.com/tinysun/archive/2010/09/28/333110.htmlhttp://www.aygfsteel.com/tinysun/comments/333110.htmlhttp://www.aygfsteel.com/tinysun/archive/2010/09/28/333110.html#Feedback0http://www.aygfsteel.com/tinysun/comments/commentRss/333110.htmlhttp://www.aygfsteel.com/tinysun/services/trackbacks/333110.htmlCtrl-A 相當于HOME鍵,用于將光標定位到本行最前面

Ctrl-E 相當于End鍵,即將光標移動到本行末尾

Ctrl-B 相當于左箭頭鍵,用于將光標向左移動一格

Ctrl-F 相當于右箭頭鍵,用于將光標向右移動一格

Ctrl-D 相當于Del鍵,即刪除光標所在處的字符

Ctrl-K 用于刪除從光標處開始到結(jié)尾處的所有字符

Ctrl-L 清屏,相當于clear命令

Ctrl-R 進入歷史命令查找狀態(tài),然后你輸入幾個關(guān)鍵字符,就可以找到你使用過的命令

Ctrl-U 用于刪除從光標開始到行首的所有字符。一般在密碼或命令輸入錯誤時常用

Ctrl-H 刪除光標左側(cè)的一個字符

Ctrl-W 用于刪除當前光標左側(cè)的一個單詞

Ctrl-P 相當于上箭頭鍵,即顯示上一個命令

Ctrl-N 相當于下箭頭鍵,即顯示下一個命令

Ctrl-T 用于顛倒光標所在處字符和前一個字符的位置。(目前不知道有什么作用,哪位朋友知道?)

Ctrl-J 相當于回車鍵



何克勤 2010-09-28 09:57 發(fā)表評論
]]>
內(nèi)存相關(guān)分享http://www.aygfsteel.com/tinysun/archive/2010/08/25/329890.html何克勤何克勤Wed, 25 Aug 2010 07:09:00 GMThttp://www.aygfsteel.com/tinysun/archive/2010/08/25/329890.htmlhttp://www.aygfsteel.com/tinysun/comments/329890.htmlhttp://www.aygfsteel.com/tinysun/archive/2010/08/25/329890.html#Feedback0http://www.aygfsteel.com/tinysun/comments/commentRss/329890.htmlhttp://www.aygfsteel.com/tinysun/services/trackbacks/329890.html一 linux內(nèi)存管理以及內(nèi)存碎片產(chǎn)生原因

                    
        最底層使用伙伴算法管理內(nèi)存頁面。系統(tǒng)將所有空閑內(nèi)存頁面分10個組,每個組中的內(nèi)存塊大小依次是1,2,4……512個內(nèi)存頁面,每組中的內(nèi)存塊大小相 同,并且以鏈表結(jié)構(gòu)保存。大小相同,并且內(nèi)存地址連續(xù)的兩個內(nèi)存塊稱為伙伴?;锇樗惴ǖ闹行乃枷刖褪菍⒊蔀榛锇榈目臻e內(nèi)存合并成一個更大的內(nèi)存塊。
        os中使用get_free_page獲取空閑頁面,如果找不到合適大小的空閑頁面,則從更大的組中找到空閑內(nèi)存塊,分配出去,并將剩余內(nèi)存分割,插入到 合適的組中。當歸還內(nèi)存時,啟動伙伴算法合并空閑內(nèi)存。如果不停的申請內(nèi)存,并且部分歸還,但歸還的內(nèi)存不能成為伙伴,長期運行后,所有內(nèi)存將被分割成不 相鄰的小塊,當再次申請大塊內(nèi)存時,則可能由于找不到足夠大的連續(xù)內(nèi)存塊而失敗,這種零散的不相鄰的小塊內(nèi)存稱之為內(nèi)存碎片。當然這只是理論上的說明,伙 伴算法本身就是為了解決內(nèi)存碎片問題。

二  malloc子系統(tǒng)內(nèi)存管理(dlmalloc)
        應(yīng)用層面的開發(fā)并不是直接調(diào)用sbrk/mmap之類的函數(shù),而是調(diào)用malloc/free等malloc子系統(tǒng)提供的函數(shù),linux上安裝的大多為 DougLea的dlmalloc或者其變形ptmalloc。下面以dlmalloc為例說明malloc工作的原理。
1 dlmalloc下名詞解釋:
   boundary tag: 邊界標記,每個空閑內(nèi)存塊均有頭部表識和尾部標識,尾部表識的作為是合并空閑內(nèi)存塊時更快。這部分空間屬于無法被應(yīng)用層面使用浪費的內(nèi)存空間。
   smallbins: 小內(nèi)存箱。dlmalloc將8,16,24……512大小的內(nèi)存分箱,相臨箱子中的內(nèi)存相差8字節(jié)。每個箱子中的內(nèi)存大小均相同,并且以雙向鏈表連接。
   treebins: 樹結(jié)構(gòu)箱。大于512字節(jié)的內(nèi)存不再是每8字節(jié)1箱,而是一個范圍段一箱。比如512~640, 640~896…..每個箱子的范圍段依次是128,256,512……。每箱中的結(jié)構(gòu)不再是雙向鏈表,而是樹形結(jié)構(gòu)。
   dv chunk:  當申請內(nèi)存而在對應(yīng)大小的箱中找不到大小合適的內(nèi)存,則從更大的箱中找一塊內(nèi)存,劃分出需要的內(nèi)存,剩余的內(nèi)存稱之為dv chunk.
   top chunk: 當dlmalloc中管理的內(nèi)存都找不到合適的內(nèi)存時,則調(diào)用sbrk從系統(tǒng)申請內(nèi)存,可以增長內(nèi)存方向的chunk稱為top chunk.
2 內(nèi)存分配算法
        從合適的箱子中尋找內(nèi)存塊–>從相臨的箱子中尋找內(nèi)存塊–>從dv chunk分配內(nèi)存–>從其他可行的箱子中分配內(nèi)存–>從top chunk中分配內(nèi)存–>調(diào)用sbrk/mmap申請內(nèi)存
3 內(nèi)存釋放算法
       臨近內(nèi)存合并–>如屬于top chunk,判斷top chunk>128k,是則歸還系統(tǒng)
                              –>不屬于chunk,則歸相應(yīng)的箱子

dlmalloc還有小內(nèi)存緩存等其他機制??梢钥闯鼋?jīng)過dlmalloc,頻繁調(diào)用malloc/free并不會產(chǎn)生內(nèi)存碎片,只要后續(xù)還有相同 的內(nèi)存大小的內(nèi)存被申請,仍舊會使用以前的合適內(nèi)存,除非大量調(diào)用malloc之后少量釋放free,并且新的malloc又大于以前free的內(nèi)存大 小,造成dlmalloc不停的從系統(tǒng)申請內(nèi)存,而free掉的小內(nèi)存因被使用的內(nèi)存割斷,而使top chunk<128k,不能歸還給系統(tǒng)。即便如此,占用的總內(nèi)存量也小于的確被使用的內(nèi)存量的2倍(使用的內(nèi)存和空閑的內(nèi)存交叉分割,并且空閑的內(nèi) 存總是小于使用的內(nèi)存大小)。因此可以說,在沒有內(nèi)存泄露的情況,常規(guī)頻繁調(diào)用malloc/free并不會產(chǎn)生內(nèi)存碎片。

何克勤 2010-08-25 15:09 發(fā)表評論
]]>
進程組及會話的概念理解http://www.aygfsteel.com/tinysun/archive/2010/08/06/328127.html何克勤何克勤Fri, 06 Aug 2010 06:09:00 GMThttp://www.aygfsteel.com/tinysun/archive/2010/08/06/328127.htmlhttp://www.aygfsteel.com/tinysun/comments/328127.htmlhttp://www.aygfsteel.com/tinysun/archive/2010/08/06/328127.html#Feedback0http://www.aygfsteel.com/tinysun/comments/commentRss/328127.htmlhttp://www.aygfsteel.com/tinysun/services/trackbacks/328127.html 將闡述Linux內(nèi)核中的如下幾個概念
1) 進程組
2) 會話
3) 控制終端
前面的概念來源于前人,我只是站在前人的肩膀上結(jié)合內(nèi)核中的實現(xiàn)加深概念理解。
1.概念:
a)進程組
Shell 上的一條命令行形成一個進程組
每個進程屬于一個進程組
每個進程組有一個領(lǐng)頭進程
進程組的生命周期到組中最后一個進程終止, 或加入其他進程組為止
getpgrp: 獲得進程組 id, 即領(lǐng)頭進程的 pid
setpgid: 加入進程組和建立新的進程組
前臺進程組和后臺進程組
===============================================================================
       #include
        int setpgid (pid_t pid, pid_t pgid);
        pid_t getpgid (pid_t pid);
        int setpgrp (void);
        pid_t getpgrp (void);
-------------------------------------------------------------------------------
    進程只能將自身和其子進程設(shè)置為進程組 id.
    某個子進程調(diào)用 exec 函數(shù)之后, 就不能再將該子進程的 id 作為進程組 id.
===============================================================================
b)會話
一次登錄形成一個會話
一個會話可包含多個進程組, 但只能有一個前臺進程組.
setsid 可建立一個新的會話
===============================================================================
       #include
       pid_t setsid(void);
-------------------------------------------------------------------------------
    如果調(diào)用進程不是進程組的領(lǐng)頭進程, 該函數(shù)才能建立新的會話.
    調(diào)用 setsid 之后, 進程成為新會話的領(lǐng)頭進程.
    進程成為新進程組的領(lǐng)頭進程.
    進程失去控制終端
===============================================================================
c)控制終端
會話的領(lǐng)頭進程打開一個終端之后, 該終端就成為該會話的控制終端 (SVR4/Linux)
與控制終端建立連接的會話領(lǐng)頭進程稱為控制進程 (session leader)
一個會話只能有一個控制終端
產(chǎn)生在控制終端上的輸入和信號將發(fā)送給會話的前臺進程組中的所有進程
終端上的連接斷開時 (比如網(wǎng)絡(luò)斷開或 Modem 斷開), 掛起信號將發(fā)送到控制進程(session leader)

何克勤 2010-08-06 14:09 發(fā)表評論
]]>
ulimit命令http://www.aygfsteel.com/tinysun/archive/2010/08/06/328122.html何克勤何克勤Fri, 06 Aug 2010 05:34:00 GMThttp://www.aygfsteel.com/tinysun/archive/2010/08/06/328122.htmlhttp://www.aygfsteel.com/tinysun/comments/328122.htmlhttp://www.aygfsteel.com/tinysun/archive/2010/08/06/328122.html#Feedback0http://www.aygfsteel.com/tinysun/comments/commentRss/328122.htmlhttp://www.aygfsteel.com/tinysun/services/trackbacks/328122.html
1,說明:
ulimit用于shell啟動進程所占用的資源.
2,類別:
shell內(nèi)建命令
3,語法格式:
ulimit [-acdfHlmnpsStvw] [size]

[john@localhost ~]$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 4096
max locked memory (kbytes, -l) 32
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 10240
cpu time (seconds, -t) unlimited
max user processes (-u) 4096
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited

4,參數(shù)介紹:
-H 設(shè)置硬件資源限制.
-S 設(shè)置軟件資源限制.
-a 顯示當前所有的資源限制.
-c size:設(shè)置core文件的最大值.單位:blocks
-d size:設(shè)置數(shù)據(jù)段的最大值.單位:kbytes
-f size:設(shè)置創(chuàng)建文件的最大值.單位:blocks
-l size:設(shè)置在內(nèi)存中鎖定進程的最大值.單位:kbytes
-m size:設(shè)置可以使用的常駐內(nèi)存的最大值.單位:kbytes
-n size:設(shè)置內(nèi)核可以同時打開的文件描述符的最大值.單位:n
-p size:設(shè)置管道緩沖區(qū)的最大值.單位:kbytes
-s size:設(shè)置堆棧的最大值.單位:kbytes
-t size:設(shè)置CPU使用時間的最大上限.單位:seconds
-v size:設(shè)置虛擬內(nèi)存的最大值.單位:kbytes 5,簡單實例:
5.舉例
在Linux下寫程序的時候,如果程序比較大,經(jīng)常會遇到“段錯誤”(segmentation fault)這樣的問題,這主要就是由于Linux系統(tǒng)初始的堆棧大小(stack size)太小的緣故,一般為10M。我一般把stack size設(shè)置成256M,這樣就沒有段錯誤了!命令為:
ulimit -s 262140
如果要系統(tǒng)自動記住這個配置,就編輯/etc/profile文件,在 “ulimit -S -c 0 > /dev/null 2>&1”行下,添加“ulimit -s 262140”,保存重啟系統(tǒng)就可以了!
1]在RH8的環(huán)境文件/etc/profile中,我們可以看到系統(tǒng)是如何配置ulimit的:
#grep ulimit /etc/profile
ulimit -S -c 0 > /dev/null 2>&1
這條語句設(shè)置了對軟件資源和對core文件大小的設(shè)置
2]如果我們想要對由shell創(chuàng)建的文件大小作些限制,如:
#ll h
-rw-r--r-- 1 lee lee 150062 7月 22 02:39 h
#ulimit -f 100 #設(shè)置創(chuàng)建文件的最大塊(一塊=512字節(jié))
#cat h>newh
File size limit exceeded
#ll newh
-rw-r--r-- 1 lee lee 51200 11月 8 11:47 newh
文件h的大小是150062字節(jié),而我們設(shè)定的創(chuàng)建文件的大小是512字節(jié)x100塊=51200字節(jié)
當然系統(tǒng)就會根據(jù)你的設(shè)置生成了51200字節(jié)的newh文件.
3]可以像實例1]一樣,把你要設(shè)置的ulimit放在/etc/profile這個環(huán)境文件中.
用途
設(shè)置或報告用戶資源極限。
語法
ulimit [ -H ] [ -S ] [ -a ] [ -c ] [ -d ] [ -f ] [ -m ] [ -n ] [ -s ] [ -t ] [ Limit ]
描述
ulimit 命令設(shè)置或報告用戶進程資源極限,如 /etc/security/limits 文件所定義。文件包含以下缺省值極限:
fsize = 2097151
core = 2097151
cpu = -1
data = 262144
rss = 65536
stack = 65536
nofiles = 2000
當新用戶添加到系統(tǒng)中時,這些值被作為缺省值使用。當向系統(tǒng)中添加用戶時,以上值通過 mkuser 命令設(shè)置,或通過 chuser 命令更改。
極限分為軟性或硬性。通過 ulimit 命令,用戶可將軟極限更改到硬極限的最大設(shè)置值。要更改資源硬極限,必須擁有 root 用戶權(quán)限。
很多系統(tǒng)不包括以上一種或數(shù)種極限。 特定資源的極限在指定 Limit 參數(shù)時設(shè)定。Limit 參數(shù)的值可以是每個資源中指定單元中的數(shù)字,或者為值 unlimited。要將特定的 ulimit 設(shè)置為 unlimited,可使用詞 unlimited。
注:在 /etc/security/limits 文件中設(shè)置缺省極限就是設(shè)置了系統(tǒng)寬度極限, 而不僅僅是創(chuàng)建用戶時用戶所需的極限。
省略 Limit 參數(shù)時,將會打印出當前資源極限。除非用戶指定 -H 標志,否則打印出軟極限。當用戶指定一個以上資源時,極限名稱和單元在值之前打印。如果未給予選項,則假定帶有了 -f 標志。
由于 ulimit 命令影響當前 shell 環(huán)境,所以它將作為 shell 常規(guī)內(nèi)置命令提供。如果在獨立的命令執(zhí)行環(huán)境中調(diào)用該命令,則不影響調(diào)用者環(huán)境的文件大小極限。以下示例中正是這種情況:
nohup ulimit -f 10000
env ulimit 10000
一旦通過進程減少了硬極限,若無 root 特權(quán)則無法增加,即使返回到原值也不可能。
關(guān)于用戶和系統(tǒng)資源極限的更多信息,請參見 AIX 5L Version 5.3 Technical Reference: Base Operating System and Extensions Volume 1 中的 getrlimit、setrlimit 或 vlimit 子例程。
標志
-a 列出所有當前資源極限。
-c 以 512 字節(jié)塊為單位,指定核心轉(zhuǎn)儲的大小。
-d 以 K 字節(jié)為單位指定數(shù)據(jù)區(qū)域的大小。
-f 使用 Limit 參數(shù)時設(shè)定文件大小極限(以塊計),或者在未指定參數(shù)時報告文件大小極限。缺省值為 -f 標志。
-H 指定設(shè)置某個給定資源的硬極限。如果用戶擁有 root 用戶權(quán)限,可以增大硬極限。任何用戶均可減少硬極限。
-m 以 K 字節(jié)為單位指定物理存儲器的大小。
-n 指定一個進程可以擁有的文件描述符的數(shù)量的極限。
-s 以 K 字節(jié)為單位指定堆棧的大小。
-S 指定為給定的資源設(shè)置軟極限。軟極限可增大到硬極限的值。如果 -H 和 -S 標志均未指定,極限適用于以上二者。
-t 指定每個進程所使用的秒數(shù)。
退出狀態(tài)
返回以下退出值:
0 成功完成。
>0 拒絕對更高的極限的請求,或發(fā)生錯誤。
示例
要將文件大小極限設(shè)置為 51,200 字節(jié),輸入:
ulimit -f 100


何克勤 2010-08-06 13:34 發(fā)表評論
]]>
write的奧秘 轉(zhuǎn)http://www.aygfsteel.com/tinysun/archive/2010/08/06/328099.html何克勤何克勤Fri, 06 Aug 2010 02:00:00 GMThttp://www.aygfsteel.com/tinysun/archive/2010/08/06/328099.htmlhttp://www.aygfsteel.com/tinysun/comments/328099.htmlhttp://www.aygfsteel.com/tinysun/archive/2010/08/06/328099.html#Feedback0http://www.aygfsteel.com/tinysun/comments/commentRss/328099.htmlhttp://www.aygfsteel.com/tinysun/services/trackbacks/328099.html來源:http://www.linuxforum.net/doc/write-coly.html

摘要:介紹了一個簡單的字符設(shè)備驅(qū)動程序,深入剖析了write函數(shù)的工作原理

     在Linux下我們在使用設(shè)備的時候,都會用到write這個函數(shù),通過這個函數(shù)我們可以象使
用文件那樣向設(shè)備傳送數(shù)據(jù)。可是為什么用戶使用write函數(shù)就可以把數(shù)據(jù)寫到設(shè)備里面
去,這個過程到底是怎么實現(xiàn)的呢?

這個奧秘就在于設(shè)備驅(qū)動程序的write實現(xiàn)中,這里我結(jié)合一些源代碼來解釋如何使得一
個簡簡單單的write函數(shù)能夠完成向設(shè)備里面寫數(shù)據(jù)的復(fù)雜過程。

這里的源代碼主要來自兩個地方。第一是oreilly出版的《Linux device driver》中的
實例,第二是Linux Kernel 2.2.14核心源代碼。我只列出了其中相關(guān)部分的內(nèi)容,如果
讀者有興趣,也可以查閱其它源代碼。不過我不是在講解如何編寫設(shè)備驅(qū)動程序,所以不
會對每一個細節(jié)都進行說明,再說有些地方我覺得自己還沒有吃透。

由于《Linux device driver》一書中的例子對于我們還是復(fù)雜了一些,我將其中的一個
例程簡化了一下。這個驅(qū)動程序支持這樣一個設(shè)備:核心空間中的一個長度為10的數(shù)組
kbuf[10]。我們可以通過用戶程序open它,read它,write它,close它。這個設(shè)備的名
字我稱為short_t。

現(xiàn)在言歸正傳。
對于一個設(shè)備,它可以在/dev下面存在一個對應(yīng)的邏輯設(shè)備節(jié)點,這個節(jié)點以文件的形式
存在,但它不是普通意義上的文件,它是設(shè)備文件,更確切的說,它是設(shè)備節(jié)點。這個節(jié)
點是通過mknod命令建立的,其中指定了主設(shè)備號和次設(shè)備號。主設(shè)備號表明了某一類設(shè)
備,一般對應(yīng)著確定的驅(qū)動程序;次設(shè)備號一般是區(qū)分是標明不同屬性,例如不同的使用
方法,不同的位置,不同的操作。這個設(shè)備號是從/proc/devices文件中獲得的,所以一
般是先有驅(qū)動程序在內(nèi)核中,才有設(shè)備節(jié)點在目錄中。這個設(shè)備號(特指主設(shè)備號)的主
要作用,就是聲明設(shè)備所使用的驅(qū)動程序。驅(qū)動程序和設(shè)備號是一一對應(yīng)的,當你打開一
個設(shè)備文件時,操作系統(tǒng)就已經(jīng)知道這個設(shè)備所對應(yīng)的驅(qū)動程序是哪一個了。這個"知道"
的過程后面就講。

我們再說說驅(qū)動程序的基本結(jié)構(gòu)吧。這里我只介紹動態(tài)模塊型驅(qū)動程序(就是我們使用
insmod加載到核心中并使用rmmod卸載的那種),因為我只熟悉這種結(jié)構(gòu)。
模塊化的驅(qū)動程序由兩個函數(shù)是固定的:int init_module(void) ;void
cleanup_module(void)。前者在insmod的時候執(zhí)行,后者在rmmod的時候執(zhí)行。
init_nodule在執(zhí)行的時候,進行一些驅(qū)動程序初始化的工作,其中最主要的工作有三
件:注冊設(shè)備;申請I/O端口地址范圍;申請中斷IRQ。這里和我們想知道的事情相關(guān)的只
有注冊設(shè)備。

下面是一個典型的init_module函數(shù):

int init_module(void){
int result = check_region(short_base,1);/* 察看端口地址*/
……
request_region(short_base,1,"short"); /* 申請端口地址*/
……
result = register_chrdev(short_major, "short", &short_fops); /* 注冊設(shè)備
*/
……
result = request_irq(short_irq, short_interrupt, SA_INTERRUPT, "short",
NULL); /* 申請IRQ */
……
return 0;
}/* init_module*/

上面這個函數(shù)我只保留了最重要的部分,其中最重要的函數(shù)是
result = register_chrdev(short_major, "short", &short_fops);
這是一個驅(qū)動程序的精髓所在!!當你執(zhí)行indmod命令時,這個函數(shù)可以完成三件大事:
第一,申請主設(shè)備號(short_major),或者指定,或者動態(tài)分配;第二,在內(nèi)核中注冊設(shè)
備的名字("short");第三,指定fops方法(&short_fops)。其中所指定的fops方法就是
我們對設(shè)備進行操作的方法(例如read,write,seek,dir,open,release等),如何實現(xiàn)
這些方法,是編寫設(shè)備驅(qū)動程序大部分工作量所在。

現(xiàn)在我們就要接觸關(guān)鍵部分了--如何實現(xiàn)fops方法。
我們都知道,每一個文件都有一個file的結(jié)構(gòu),在這個結(jié)構(gòu)中有一個file_operations的
結(jié)構(gòu)體,這個結(jié)構(gòu)體指明了能夠?qū)υ撐募M行的操作。

下面是一個典型的file_operations結(jié)構(gòu):
struct file_operations {
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned
long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *);
int (*fasync) (int, struct file *, int);
int (*check_media_change) (kdev_t dev);
int (*revalidate) (kdev_t dev);
int (*lock) (struct file *, int, struct file_lock *);
};

我們可以看到它實際上就是許多文件操作的函數(shù)指針,其中就有write,其它的我們就不
去管它了。這個write指針在實際的驅(qū)動程序中會以程序員所實現(xiàn)的函數(shù)名字出現(xiàn),它指
向程序員實現(xiàn)的設(shè)備write操作函數(shù)。下面就是一個實際的例子,這個write函數(shù)可以向核
心內(nèi)存的一個數(shù)組里輸入一個字符串。

int short_write (struct inode *inode, struct file *filp, const char *buf,
int count){
int retval = count;
extern unsigned char kbuf[10];

if(count>10)
count=10;
copy_from_user(kbuf, buf, count);
return retval;
}/* short_write */
設(shè)備short_t對應(yīng)的fops方法是這樣聲明的:
struct file_operations short_fops = {
NULL, /* short_lseek */
short_read,
short_write,
NULL, /* short_readdir */
NULL, /* short_poll */
NULL, /* short_ioctl */
NULL, /* short_mmap */
short_open,
short_release,
NULL, /* short_fsync */
NULL, /* short_fasync */
/* nothing more, fill with NULLs */
};

其中NULL的項目就是不提供這個功能。所以我們可以看出short_t設(shè)備只提供了
read,write,open,release功能。其中write功能我們在上面已經(jīng)實現(xiàn)了,具體的實現(xiàn)函
數(shù)起名為short_write。這些函數(shù)就是真正對設(shè)備進行操作的函數(shù),這就是驅(qū)動程序的一
大好處:不管你實現(xiàn)的時候是多么的復(fù)雜,但對用戶來看,就是那些常用的文件操作函數(shù)。

但是我們可以看到,驅(qū)動程序里的write函數(shù)有四個參數(shù),函數(shù)格式如下:
short_write (struct inode *inode, struct file *filp, const char *buf, int count)
而用戶程序中的write函數(shù)只有三個參數(shù),函數(shù)格式如下:
write(inf fd, char *buf, int count)
那他們兩個是怎么聯(lián)系在一起的呢?這就要靠操作系統(tǒng)核心中的函數(shù)sys_write了,下面
是Linux Kernel 2.2.14中sys_write中的源代碼:
asmlinkage ssize_t sys_write(unsigned int fd, const char * buf, size_t count)
{
ssize_t ret;
struct file * file;
struct inode * inode;
ssize_t (*write)(struct file *, const char *, size_t, loff_t *); /* 指向
驅(qū)動程序中的wirte函數(shù)的指針*/

lock_kernel();
ret = -EBADF;
file = fget(fd); /* 通過文件描述符得到文件指針 */
if (!file)
goto bad_file;
if (!(file->f_mode & FMODE_WRITE))
goto out;
inode = file->f_dentry->d_inode; /* 得到inode信息 */
ret = locks_verify_area(FLOCK_VERIFY_WRITE, inode, file, file->f_pos,
count);
if (ret)
goto out;
ret = -EINVAL;
if (!file->f_op || !(write = file->f_op->write)) /* 將函數(shù)開始時聲明的
write函數(shù)指針指向fops方法中對應(yīng)的write函數(shù) */
goto out;
down(&inode->i_sem);
ret = write(file, buf, count, &file->f_pos); /* 使用驅(qū)動程序中的write函數(shù)
將數(shù)據(jù)輸入設(shè)備,注意看,這里就是四個參數(shù)了 */
up(&inode->i_sem);
out:
fput(file);
bad_file:
unlock_kernel();
return ret;
}

我寫了一個簡單的程序來測試這個驅(qū)動程序,該程序源代碼節(jié)選如下(該省的我都省了):

main(){
int fd,count=0;
unsigned char buf[10];
fd=open("/dev/short_t",O_RDWR);
printf("input string:");
scanf("%s",buf);
count=strlen(buf);
if(count>10)
count=10;
count=write(fd,buf,count);
close(fd);
return 1;
}

現(xiàn)在我們就演示一下用戶使用write函數(shù)將數(shù)據(jù)寫到設(shè)備里面這個過程到底是怎么實現(xiàn)的:
1,insmod驅(qū)動程序。驅(qū)動程序申請設(shè)備名和主設(shè)備號,這些可以在/proc/devieces中獲得。
2,從/proc/devices中獲得主設(shè)備號,并使用mknod命令建立設(shè)備節(jié)點文件。這是通過主
設(shè)備號將設(shè)備節(jié)點文件和設(shè)備驅(qū)動程序聯(lián)系在一起。設(shè)備節(jié)點文件中的file屬性中指明了
驅(qū)動程序中fops方法實現(xiàn)的函數(shù)指針。
3,用戶程序使用open打開設(shè)備節(jié)點文件,這時操作系統(tǒng)內(nèi)核知道該驅(qū)動程序工作了,就
調(diào)用fops方法中的open函數(shù)進行相應(yīng)的工作。open方法一般返回的是文件標示符,實際
上并不是直接對它進行操作的,而是有操作系統(tǒng)的系統(tǒng)調(diào)用在背后工作。
4,當用戶使用write函數(shù)操作設(shè)備文件時,操作系統(tǒng)調(diào)用sys_write函數(shù),該函數(shù)首先通
過文件標示符得到設(shè)備節(jié)點文件對應(yīng)的inode指針和flip指針。inode指針中有設(shè)備號信
息,能夠告訴操作系統(tǒng)應(yīng)該使用哪一個設(shè)備驅(qū)動程序,flip指針中有fops信息,可以告訴
操作系統(tǒng)相應(yīng)的fops方法函數(shù)在那里可以找到。
5,然后這時sys_write才會調(diào)用驅(qū)動程序中的write方法來對設(shè)備進行寫的操作。
其中1-3都是在用戶空間進行的,4-5是在核心空間進行的。用戶的write函數(shù)和操作系統(tǒng)
的write函數(shù)通過系統(tǒng)調(diào)用sys_write聯(lián)系在了一起。
注意:
對于塊設(shè)備來說,還存在寫的模式的問題,這應(yīng)該是由GNU C庫來解決的,這里不予討
論,因為我沒有看過GNU C庫的源代碼。
另外,這是一個測試版的文章,請各位朋友們多提意見和建議,非常感謝!
http://blog.csdn.net/lphpc/category/170686.aspx



何克勤 2010-08-06 10:00 發(fā)表評論
]]>
UNIX環(huán)境高級編程(APUE) 總結(jié)http://www.aygfsteel.com/tinysun/archive/2010/07/29/327454.html何克勤何克勤Thu, 29 Jul 2010 08:26:00 GMThttp://www.aygfsteel.com/tinysun/archive/2010/07/29/327454.htmlhttp://www.aygfsteel.com/tinysun/comments/327454.htmlhttp://www.aygfsteel.com/tinysun/archive/2010/07/29/327454.html#Feedback0http://www.aygfsteel.com/tinysun/comments/commentRss/327454.htmlhttp://www.aygfsteel.com/tinysun/services/trackbacks/327454.html閱讀全文

何克勤 2010-07-29 16:26 發(fā)表評論
]]>
學(xué)會用core dump調(diào)試程序錯誤http://www.aygfsteel.com/tinysun/archive/2010/07/29/327453.html何克勤何克勤Thu, 29 Jul 2010 08:21:00 GMThttp://www.aygfsteel.com/tinysun/archive/2010/07/29/327453.htmlhttp://www.aygfsteel.com/tinysun/comments/327453.htmlhttp://www.aygfsteel.com/tinysun/archive/2010/07/29/327453.html#Feedback0http://www.aygfsteel.com/tinysun/comments/commentRss/327453.htmlhttp://www.aygfsteel.com/tinysun/services/trackbacks/327453.html      在使用半導(dǎo)體作為內(nèi)存的材料前,人類是利用線圈當作內(nèi)存的材料(發(fā)明者為王安),線圈就叫作core ,用線圈做的內(nèi)存就叫作“core memory”。(線圈的單詞應(yīng)該是coil,呵呵)如今,半導(dǎo)體工業(yè)澎勃發(fā)展,已經(jīng)沒有人用線圈當內(nèi)存了,不過,在許多情況下,人們還是把內(nèi)存叫作“core”。 所以注意了:這里的core不是核心,而是內(nèi)存。不過結(jié)合實際來看,好像也有點“內(nèi)核所占內(nèi)存”的意思。
      core dump又是什么東東? 我 們在開發(fā)(或使用)一個程序時,最怕的就是程序莫明其妙地掛掉。雖然系統(tǒng)沒事,但我們下次仍可能遇到相同的問題。于是,這時操作系統(tǒng)就會把程序掛掉時的 內(nèi)存內(nèi)容寫入一個叫做core的文件里(這個寫入的動作就叫dump,dump的英語意思是垃圾、傾倒。從這里來看,這些內(nèi)存的內(nèi)容是程序錯誤運行的結(jié) 果,所以算是垃圾,把他弄出來就好比從大的內(nèi)存池里“傾倒”。),以便于我們調(diào)試。這個過程,因此叫做core dump。

1. 在嵌入式系統(tǒng)中,有時core dump直接從串口打印出來,結(jié)合objdump查找ra和epa地址,運用棧回溯,可以找到程序出錯的地方。

2. 在一般Linux系統(tǒng)中,默認是不會產(chǎn)生core dump文件的,通過ulimit -c來查看core dump文件的大小,一般開始是0,可以設(shè)置core文件大小,ulimit -c 1024(kbytes單位)或者ulimit -c unlimited。

3. core dump文件輸出設(shè)置,一般默認是當前目錄,可以在/proc/sys/kernel中找到core-user-pid,通過

echo "1" > /proc/sys/kernel/core-user-pid使core文件名加上pid號,還可以用

mkdir -p /root/corefile

echo "/root/corefile/core-%e-%p-%t" > /proc/sys/kernel/core-pattern控制core文件保存位置和文件名格式。

以下是參數(shù)列表:
    %p - insert pid into filename 添加pid
    %u - insert current uid into filename 添加當前uid
    %g - insert current gid into filename 添加當前gid
    %s - insert signal that caused the coredump into the filename 添加導(dǎo)致產(chǎn)生core的信號
    %t - insert UNIX time that the coredump occurred into filename 添加core文件生成時的unix時間
    %h - insert hostname where the coredump happened into filename 添加主機名
    %e - insert coredumping executable name into filename 添加命令名

4. 用gdb查看core文件:
下面我們可以在發(fā)生運行時信號引起的錯誤時發(fā)生core dump了.編譯時加上-g
發(fā)生core dump之后, 用gdb進行查看core文件的內(nèi)容, 以定位文件中引發(fā)core dump的行.
gdb [exec file] [core file]
如:
gdb ./test test.core
在進入gdb后, 用bt命令查看backtrace以檢查發(fā)生程序運行到哪里, 來定位core dump的文件行.

5. 給個例子

test.c

void a()

{

   char *p = NULL;

   printf("%d\n", *p);

}

int main()

{

    a();

    return 0;

}

編譯 gcc -g -o test test.c

運行 ./test

報segmentation fault(core dump)

gdb ./test test.core如果生成的是test.core.



何克勤 2010-07-29 16:21 發(fā)表評論
]]>
C語言的反匯編代碼(BP,SP的關(guān)系) 轉(zhuǎn)http://www.aygfsteel.com/tinysun/archive/2010/07/29/327413.html何克勤何克勤Thu, 29 Jul 2010 03:41:00 GMThttp://www.aygfsteel.com/tinysun/archive/2010/07/29/327413.htmlhttp://www.aygfsteel.com/tinysun/comments/327413.htmlhttp://www.aygfsteel.com/tinysun/archive/2010/07/29/327413.html#Feedback0http://www.aygfsteel.com/tinysun/comments/commentRss/327413.htmlhttp://www.aygfsteel.com/tinysun/services/trackbacks/327413.html閱讀全文

何克勤 2010-07-29 11:41 發(fā)表評論
]]>
gcc編譯C++程序http://www.aygfsteel.com/tinysun/archive/2010/07/03/325117.html何克勤何克勤Sat, 03 Jul 2010 02:08:00 GMThttp://www.aygfsteel.com/tinysun/archive/2010/07/03/325117.htmlhttp://www.aygfsteel.com/tinysun/comments/325117.htmlhttp://www.aygfsteel.com/tinysun/archive/2010/07/03/325117.html#Feedback0http://www.aygfsteel.com/tinysun/comments/commentRss/325117.htmlhttp://www.aygfsteel.com/tinysun/services/trackbacks/325117.html單個源文件生成可執(zhí)行程序
下面是一個保存在文件 helloworld.cpp 中一個簡單的 C++ 程序的代碼:
/* helloworld.cpp */
#include <iostream>
int main(int argc,char *argv[])
{
    std::cout << "hello, world" << std::endl;
    return(0);
}
程序使用定義在頭文件 iostream 中的 cout,向標準輸出寫入一個簡單的字符串。該代碼可用以下命令編譯為可執(zhí)行文件:
 
$  g++ helloworld.cpp
編譯器 g++ 通過檢查命令行中指定的文件的后綴名可識別其為 C++ 源代碼文件。編譯器默認的動作:編譯源代碼文件生成對象文件(object file),鏈接對象文件和 libstdc++ 庫中的函數(shù)得到可執(zhí)行程序。然后刪除對象文件。由于命令行中未指定可執(zhí)行程序的文件名,編譯器采用默認的 a.out。程序可以這樣來運行:
$ ./a.out
hello, world
更普遍的做法是通過 -o 選項指定可執(zhí)行程序的文件名。下面的命令將產(chǎn)生名為 helloworld 的可執(zhí)行文件:
$ g++ helloworld.cpp -o helloworld
在命令行中輸入程序名可使之運行:
$ ./helloworld
hello, world
程序 g++ 是將 gcc 默認語言設(shè)為 C++ 的一個特殊的版本,鏈接時它自動使用 C++ 標準庫而不用 C 標準庫。通過遵循源碼的命名規(guī)范并指定對應(yīng)庫的名字,用 gcc 來編譯鏈接 C++ 程序是可行的,如下例所示:
$ gcc helloworld.cpp -lstdc++ -o helloworld
選項 -l (ell) 通過添加前綴 lib 和后綴 .a 將跟隨它的名字變換為庫的名字 libstdc++.a。而后它在標準庫路徑中查找該庫。gcc 的編譯過程和輸出文件與 g++ 是完全相同的。
 
在大多數(shù)系統(tǒng)中,GCC 安裝時會安裝一名為 c++ 的程序。如果被安裝,它和 g++ 是等同,如下例所示,用法也一致:
$ c++ helloworld.cpp -o helloworld
多個源文件生成可執(zhí)行程序
如果多于一個的源碼文件在 g++ 命令中指定,它們都將被編譯并被鏈接成一個單一的可執(zhí)行文件。下面是一個名為 speak.h 的頭文件;它包含一個僅含有一個函數(shù)的類的定義:
/* speak.h */
#include <iostream>
class Speak
{
    public:
        void sayHello(const char *);
};
下面列出的是文件 speak.cpp 的內(nèi)容:包含 sayHello() 函數(shù)的函數(shù)體:
/* speak.cpp */
#include "speak.h"
void Speak::sayHello(const char *str)
{
    std::cout << "Hello " << str << "\n";
}
文件 hellospeak.cpp 內(nèi)是一個使用 Speak 類的程序:
/* hellospeak.cpp */
#include "speak.h"
int main(int argc,char *argv[])
{
    Speak speak;
    speak.sayHello("world");
    return(0);
}
下面這條命令將上述兩個源碼文件編譯鏈接成一個單一的可執(zhí)行程序:
$ g++ hellospeak.cpp speak.cpp -o hellospeak
PS:這里說一下為什么在命令中沒有提到“speak.h“該文件(原因是:在“speak.cpp“中包含有”#include"speak.h"“這句代碼,它的意思是搜索系統(tǒng)頭文件目錄之前將先在當前目錄中搜索文件“speak.h“。而”speak.h“正在該目錄中,不用再在命令中指定了)。
源文件生成對象文件
選項 -c 用來告訴編譯器編譯源代碼但不要執(zhí)行鏈接,輸出結(jié)果為對象文件。文件默認名與源碼文件名相同,只是將其后綴變?yōu)?.o。例如,下面的命令將編譯源碼文件 hellospeak.cpp 并生成對象文件 hellospeak.o:
$ g++ -c hellospeak.cpp
命令 g++ 也能識別 .o 文件并將其作為輸入文件傳遞給鏈接器。下列命令將編譯源碼文件為對象文件并將其鏈接成單一的可執(zhí)行程序:
$ g++ -c hellospeak.cpp
$ g++ -c speak.cpp
$ g++ hellospeak.o speak.o -o hellospeak
選項 -o 不僅僅能用來命名可執(zhí)行文件。它也用來命名編譯器輸出的其他文件。例如:除了中間的對象文件有不同的名字外,下列命令生將生成和上面完全相同的可執(zhí)行文件:
$ g++ -c hellospeak.cpp -o hspk1.o
$ g++ -c speak.cpp -o hspk2.o
$ g++ hspk1.o hspk2.o -o hellospeak
編譯預(yù)處理
選項 -E 使 g++ 將源代碼用編譯預(yù)處理器處理后不再執(zhí)行其他動作。下面的命令預(yù)處理源碼文件 helloworld.cpp 并將結(jié)果顯示在標準輸出中:
$ g++ -E helloworld.cpp
本文前面所列出的 helloworld.cpp 的源代碼,僅僅有六行,而且該程序除了顯示一行文字外什么都不做,但是,預(yù)處理后的版本將超過 1200 行。這主要是因為頭文件 iostream 被包含進來,而且它又包含了其他的頭文件,除此之外,還有若干個處理輸入和輸出的類的定義。
預(yù)處理過的文件的 GCC 后綴為 .ii,它可以通過 -o 選項來生成,例如:
$ gcc -E helloworld.cpp -o helloworld.ii
生成匯編代碼
選項 -S 指示編譯器將程序編譯成匯編語言,輸出匯編語言代碼而后結(jié)束。下面的命令將由 C++ 源碼文件生成匯編語言文件 helloworld.s:
$ g++ -S helloworld.cpp
生成的匯編語言依賴于編譯器的目標平臺。
創(chuàng)建靜態(tài)庫
靜態(tài)庫是編譯器生成的一系列對象文件的集合。鏈接一個程序時用庫中的對象文件還是目錄中的對象文件都是一樣的。庫中的成員包括普通函數(shù),類定義,類的對象實例等等。靜態(tài)庫的另一個名字叫歸檔文件(archive),管理這種歸檔文件的工具叫 ar 。
在下面的例子中,我們先創(chuàng)建兩個對象模塊,然后用其生成靜態(tài)庫。
頭文件 say.h 包含函數(shù) sayHello() 的原型和類 Say 的定義:
/* say.h */
#include <iostream>
void sayhello(void);
class Say {
    private:
        char *string;
    public:
        Say(char *str)
        {
            string = str;
        }
        void sayThis(const char *str)
        {
            std::cout << str << " from a static library\n";
        }
        void sayString(void);
};
下面是文件 say.cpp 是我們要加入到靜態(tài)庫中的兩個對象文件之一的源碼。它包含 Say 類中 sayString() 函數(shù)的定義體;類 Say 的一個實例 librarysay 的聲明也包含在內(nèi):
/* say.cpp */
#include "say.h"
void Say::sayString()
{
    std::cout << string << "\n";
}
Say librarysay("Library instance of Say");源碼文件 syshello.cpp 是我們要加入到靜態(tài)庫中的第二個對象文件的源碼。它包含函數(shù) sayhello() 的定義:
/* sayhello.cpp */
#include "say.h"
void sayhello()
{
    std::cout << "hello from a static library\n";
}
下面的命令序列將源碼文件編譯成對象文件,命令 ar 將其存進庫中:
$ g++ -c sayhello.cpp
$ g++ -c say.cpp
$ ar -r libsay.a sayhello.o say.o
程序 ar 配合參數(shù) -r 創(chuàng)建一個新庫 libsay.a 并將命令行中列出的對象文件插入。采用這種方法,如果庫不存在的話,參數(shù) -r 將創(chuàng)建一個新的庫,而如果庫存在的話,將用新的模塊替換原來的模塊。
下面是主程序 saymain.cpp,它調(diào)用庫 libsay.a 中的代碼:
/* saymain.cpp */
#include "say.h"
int main(int argc,char *argv[])
{
    extern Say librarysay;
    Say localsay = Say("Local instance of Say");
    sayhello();
    librarysay.sayThis("howdy");
    librarysay.sayString();
    localsay.sayString();
    return(0);
}
該程序可以下面的命令來編譯和鏈接:
$ g++ saymain.cpp libsay.a -o saymain
程序運行時,產(chǎn)生以下輸出:
hello from a static library
howdy from a static library
Library instance of SayLocal instance of Say


本文來自CSDN博客,轉(zhuǎn)載請標明出處:http://blog.csdn.net/shanxiao528/archive/2010/05/11/5578743.aspx



何克勤 2010-07-03 10:08 發(fā)表評論
]]>
kernel study noteshttp://www.aygfsteel.com/tinysun/archive/2010/06/28/324680.html何克勤何克勤Mon, 28 Jun 2010 08:06:00 GMThttp://www.aygfsteel.com/tinysun/archive/2010/06/28/324680.htmlhttp://www.aygfsteel.com/tinysun/comments/324680.htmlhttp://www.aygfsteel.com/tinysun/archive/2010/06/28/324680.html#Feedback0http://www.aygfsteel.com/tinysun/comments/commentRss/324680.htmlhttp://www.aygfsteel.com/tinysun/services/trackbacks/324680.html管道
FIFO
信號 承載信息量少, 可靠信號 不可靠信號


共享內(nèi)存 最快的IPC之一
消息隊列
信號量 不同進程間 同一進程不同線程間的同步

Socket 不同機器上的進程間的通信


添加系統(tǒng)調(diào)用
添加系統(tǒng)模塊

內(nèi)存模型 分段 分頁
邏輯地址->線性地址->物理地址

所有的段寄存器 段內(nèi)偏移一樣

頁目錄 頁面 進程私有的
虛擬地址描述符表 虛擬內(nèi)存管理 分配回收

物理內(nèi)存管理


-----
進程


BSS
初始化的數(shù)據(jù)段
代碼段

內(nèi)核棧
控制塊

進程Entry:
進程的虛擬地址空間->分區(qū)->分頁
全局頁目錄pgd->pmd->page


線程同步
互斥鎖
pthread_mutex_t
pthread_mutex_initializer
pthread-mutex_int
pthread_mutex_lock
pthread_mutex_unlock

條件變量
pthread_cond_t
pthread_cond_init
ptread_con_wait
調(diào)用之前和調(diào)用之后都是上鎖的,一個條件變量關(guān)聯(lián)一個互斥鎖
函數(shù)內(nèi)部實現(xiàn)機制
解鎖
睡眠
上鎖
pthead_cond_destroy
pthread_cond_broadcast
pthead_cond_signalh



何克勤 2010-06-28 16:06 發(fā)表評論
]]>
線程控制--私有數(shù)據(jù)http://www.aygfsteel.com/tinysun/archive/2010/05/29/322210.html何克勤何克勤Sat, 29 May 2010 07:16:00 GMThttp://www.aygfsteel.com/tinysun/archive/2010/05/29/322210.htmlhttp://www.aygfsteel.com/tinysun/comments/322210.htmlhttp://www.aygfsteel.com/tinysun/archive/2010/05/29/322210.html#Feedback0http://www.aygfsteel.com/tinysun/comments/commentRss/322210.htmlhttp://www.aygfsteel.com/tinysun/services/trackbacks/322210.html     線程私有數(shù)據(jù)采用了一種被稱為一鍵多值的技術(shù),即一個鍵對應(yīng)多個數(shù)值。訪問數(shù)據(jù)時都是通過鍵值來訪問,好像是對一個變量進行訪問,其實是在訪問不同的數(shù)據(jù)。使用線程私有數(shù)據(jù)時,首先要為每個線程數(shù)據(jù)創(chuàng)建一個相關(guān)聯(lián)的鍵。在各個線程內(nèi)部,都使用這個公用的鍵來指代線程數(shù)據(jù),但是在不同的線程中,這個鍵代表的數(shù)據(jù)是不同的。操作線程私有數(shù)據(jù)的函數(shù)主要有4個:pthread_key_create(創(chuàng)建一個鍵),pthread_setspecific(為一個鍵設(shè)置線程私有數(shù)據(jù)),pthread_getspecific(從一個鍵讀取線程私有數(shù)據(jù)),pthread_key_delete(刪除一個鍵)。這幾個函數(shù)的聲明如下:
#include <pthread.h>
int pthread_key_create(pthread_key_t *key,void (*destr_function)(void *));
int pthread_setspecific(pthread_key_t key,const void *pointer));
void *pthread_getspecific(pthread_key_t key);
int pthread_key_delete(pthread_key_t key);
    pthread_key_create:從Linux的TSD池中分配一項,將其值賦給key供以后訪問使用,它的第一個參數(shù)key為指向鍵值的指針,第二個參數(shù)為一個函數(shù)指針,如果指針不為空,則在線程退出時將以key所關(guān)聯(lián)的數(shù)據(jù)為參數(shù)調(diào)用destr_function(),釋放分配的緩沖區(qū)。
    key一旦被創(chuàng)建,所有線程都可以訪問它,但各線程可以根據(jù)自己的需要往key中填入不同的值,這就相當于提供了一個同名而不同值的全局變量,一鍵多值。一鍵多值靠的是一個關(guān)鍵數(shù)據(jù)結(jié)構(gòu)數(shù)組,即TSD池其結(jié)構(gòu)如下:
static struct pthread_key_struct pthread_keys[PTHREAD_KEYS_MAX] ={{0,NULL}};
    創(chuàng)建一個TSD就相當于將結(jié)構(gòu)數(shù)組中的某一項設(shè)置為“in_use”,并將其索引返回給*key,然后設(shè)置destructor函數(shù)destr_function。
    pthread_setspecific:該函數(shù)將pointer的值(不是內(nèi)容)與key相關(guān)聯(lián)。用pthread_setspecific為一個鍵指定新的線程數(shù)據(jù)時,線程必須先釋放原有的線程數(shù)據(jù)用以回收空間。
    pthread_getspecific:通過該函數(shù)得到與key相關(guān)聯(lián)的數(shù)據(jù)。
    pthread_key_delete:該函數(shù)用來刪除一個鍵,鍵所占用的內(nèi)存將被釋放。需要注意的是,鍵占用的內(nèi)存被釋放,與該鍵關(guān)聯(lián)的線程數(shù)據(jù)所占用的內(nèi)存并不被釋放。因此,線程數(shù)據(jù)的釋放必須在釋放鍵之前完成。
    例8-4將實現(xiàn)如何創(chuàng)建和使用線程的私有數(shù)據(jù),具體代碼如下所示。
    例8-4
#include <stdio.h>
#include <string.h>
#include <pthread.h>

pthread_key_t key;

void * thread2(void *arg)
{
int tsd = 5;
printf("thread %d is running\n",pthread_self());
pthread_setspecific(key,(void *)tsd);
printf("thread %d returns %d\n",pthread_self(),pthread_getspecific(key));
}

void * thread1(void *arg)
{
int tsd = 0;
pthread_t thid2;

printf("thread %d is running\n",pthread_self());
pthread_setspecific(key,(void *)tsd);
pthread_create(&thid2,NULL,thread2,NULL);
sleep(2);
printf("thread %d return %d\n",pthread_self(),pthread_getspecific(key));
}

int main(void)
{
pthread_t thid1;
printf("main thread begins running\n");
pthread_key_create(&key,NULL);
pthread_create(&thid1,NULL,thread1,NULL);
sleep(5);
pthread_key_delete(key);
printf("main thread exit\n");
return 0;
}
    編譯并執(zhí)行,結(jié)果如下:
$ gcc -o 8-4 8-4.c -g -l pthread
$ ./8-4
main thread begins running
thread -1209746544 is running
thread -1218139248 is running
thread -1218139248 returns 5
thread -1209746544 return 0
main thread exit
    程序說明:程序中,主線程創(chuàng)建了線程thread1,線程thread1創(chuàng)建了線程thread2。兩個線程分別將tsd作為線程私有數(shù)據(jù)。從程序運行結(jié)果可以看出,兩個線程tsd的修改互不干擾,可以看出thread2先于thread1結(jié)束,線程在創(chuàng)建thread2后,睡眠3s等待thread2執(zhí)行完畢。主線程睡眠5s等待thread1結(jié)束??梢钥闯鰐hread2對tsd的修改并沒影響到thread1的tsd的取值。


何克勤 2010-05-29 15:16 發(fā)表評論
]]>
vi使用技巧(二):copy,paste,delete,塊編輯,redo/undohttp://www.aygfsteel.com/tinysun/archive/2010/05/12/320671.html何克勤何克勤Wed, 12 May 2010 02:34:00 GMThttp://www.aygfsteel.com/tinysun/archive/2010/05/12/320671.htmlhttp://www.aygfsteel.com/tinysun/comments/320671.htmlhttp://www.aygfsteel.com/tinysun/archive/2010/05/12/320671.html#Feedback0http://www.aygfsteel.com/tinysun/comments/commentRss/320671.htmlhttp://www.aygfsteel.com/tinysun/services/trackbacks/320671.html     yy : copy 光標所在的行
  nyy: copy n line
    yw: copy 光標所在的單詞
  nyw: copy 光標所在位置到其后的n 個單詞(未必是同一行)
    y$:  copy 光標所在位置到行尾($是行尾的標示)
  ny$:  copy 光標所在位置之后的n行(包括當前行,當前行=y$)
      p:  paste 在光標所在位置之右
      P: --------------------------------左
2. delete, 和copy 類似
    dd : delete current line
  ndd:  delete n line
    dw: delete current word
  ndw: delete n word
    d$ : delete to the end of line.
  nd$ : delete n line. (current line = d$)
       x: delete one character(無論是ascii 還是unicode)
     nx: delete n characters.
3. block edit
    在命令模式下,輸入v 進入塊編輯狀態(tài)
    a. 移動光標選定操作快
    b. c(cut), y(copy)
    c. p or P.
4. undo /redo
    u: undo
    U: 取消最近一行的改動
    crtl +r: redo
    e!: 放棄所有改動,重新編輯。

何克勤 2010-05-12 10:34 發(fā)表評論
]]>
VMware網(wǎng)絡(luò)設(shè)置(WindowsXP+虛擬Ret Hat Linux 9)http://www.aygfsteel.com/tinysun/archive/2010/05/04/320075.html何克勤何克勤Tue, 04 May 2010 14:14:00 GMThttp://www.aygfsteel.com/tinysun/archive/2010/05/04/320075.htmlhttp://www.aygfsteel.com/tinysun/comments/320075.htmlhttp://www.aygfsteel.com/tinysun/archive/2010/05/04/320075.html#Feedback0http://www.aygfsteel.com/tinysun/comments/commentRss/320075.htmlhttp://www.aygfsteel.com/tinysun/services/trackbacks/320075.html宿主機:Windows XP Professinoal SP2
VMware:Red Hat Linux 9
網(wǎng)絡(luò):ADSL局域網(wǎng)512M

所用軟件:
VMware-workstation-6.0.0-45731.exe
Red Hat Linux 9

安裝過程:
1.安裝VMware-workstation-6.0.0-45731.exe
2.開啟VMware.安裝Linux.
  安裝時可直接用硬盤iso文件.
  VMware=>VM=>Settings=>CD-RoM=>Use ISO image(選擇iso位置,安裝一張,在換另一個iso文件)
3.安裝VMware后,會在XP上自動安裝2個虛擬網(wǎng)卡:
  VMware Virtual Ethernet Adapter for VMnet1
  VMware Virtual Ethernet Adapter for VMnet8
  XP運行cmd=>ipconfig/all
  會查看到2個虛擬網(wǎng)卡及真實網(wǎng)卡的IP配置.
  因這2個虛擬網(wǎng)卡是裝在XP系統(tǒng)上的,所以XP上可以Ping通.
4.此外,虛擬機上的Linux也有1個虛擬網(wǎng)卡,它的配置文件為
  /etc/sysconfig/network-scrīpts/ifcfg-eth0
5.我們可以把Linux的網(wǎng)關(guān)設(shè)置為XP系統(tǒng)虛擬網(wǎng)卡的IP地址,
  這樣Linux就可以和XP通信了.
6.VMware=>VM=>Settings=>Ethemet=>Custom:Specific virtual network 選VMnet1(Host-only)
  將VMware Virtual Ethernet Adapter for VMnet1地址
  設(shè)置為: 192.168.0.1
          255.255.255.0
  修改ifcfg-eth0文件如下:
  DEVICE=eth0
  ōNBOOT=yes
  BOOTPROTO=none
  IPADDR=192.168.0.2
  NETMASK=255.255.255.0
  GATEWAY=192.168.0.1
  TYPE=Ethernet
  USERCTL=no
  PEERDNS=no
  NETWORK=192.168.0.0
  BROADCAST=192.168.0.255
  保存后更新下# service network restart

  也可以打開Linux開始=>系統(tǒng)設(shè)置=>網(wǎng)絡(luò),進行設(shè)置.
7.這時XP和Linux可以通信了.
  XP運行:ping 192.168.0.2  OK
8.此外SecureCRT是個不錯的終端訪問軟件.可以裝在XP上,訪問Linux.
9.如果想讓虛擬機的Linux也上網(wǎng)的話.
  可以在XP=>本地連接屬性=>高級=>允許其他網(wǎng)絡(luò)用戶通過此計算機的Internet連接來連接
  (在家庭網(wǎng)絡(luò)連接中選擇VMware Network Adapter VMnet1)
  linux 主DNS 192.168.0.1

  這樣就可以在虛擬Linux上網(wǎng)了. 



何克勤 2010-05-04 22:14 發(fā)表評論
]]>
使用異步 I/O 大大提高應(yīng)用程序的性能http://www.aygfsteel.com/tinysun/archive/2010/05/01/319860.html何克勤何克勤Sat, 01 May 2010 11:44:00 GMThttp://www.aygfsteel.com/tinysun/archive/2010/05/01/319860.htmlhttp://www.aygfsteel.com/tinysun/comments/319860.htmlhttp://www.aygfsteel.com/tinysun/archive/2010/05/01/319860.html#Feedback0http://www.aygfsteel.com/tinysun/comments/commentRss/319860.htmlhttp://www.aygfsteel.com/tinysun/services/trackbacks/319860.htmlLinux® 中最常用的輸入/輸出(I/O)模型是同步 I/O。在這個模型中,當請求發(fā)出之后,應(yīng)用程序就會阻塞,直到請求滿足為止。這是很好的一種解決方案,因為調(diào)用應(yīng)用程序在等待 I/O 請求完成時不需要使用任何中央處理單元(CPU)。但是在某些情況中,I/O 請求可能需要與其他進程產(chǎn)生交疊。可移植操作系統(tǒng)接口(POSIX)異步 I/O(AIO)應(yīng)用程序接口(API)就提供了這種功能。在本文中,我們將對這個 API 概要進行介紹,并來了解一下如何使用它。

AIO 簡介

Linux 異步 I/O 是 Linux 內(nèi)核中提供的一個相當新的增強。它是 2.6 版本內(nèi)核的一個標準特性,但是我們在 2.4 版本內(nèi)核的補丁中也可以找到它。AIO 背后的基本思想是允許進程發(fā)起很多 I/O 操作,而不用阻塞或等待任何操作完成。稍后或在接收到 I/O 操作完成的通知時,進程就可以檢索 I/O 操作的結(jié)果。

I/O 模型

在深入介紹 AIO API 之前,讓我們先來探索一下 Linux 上可以使用的不同 I/O 模型。這并不是一個詳盡的介紹,但是我們將試圖介紹最常用的一些模型來解釋它們與異步 I/O 之間的區(qū)別。圖 1 給出了同步和異步模型,以及阻塞和非阻塞的模型。


圖 1. 基本 Linux I/O 模型的簡單矩陣
基本 Linux I/O 模型的簡單矩陣

每個 I/O 模型都有自己的使用模式,它們對于特定的應(yīng)用程序都有自己的優(yōu)點。本節(jié)將簡要對其一一進行介紹。

同步阻塞 I/O

I/O 密集型與 CPU 密集型進程的比較

I/O 密集型進程所執(zhí)行的 I/O 操作比執(zhí)行的處理操作更多。CPU 密集型的進程所執(zhí)行的處理操作比 I/O 操作更多。Linux 2.6 的調(diào)度器實際上更加偏愛 I/O 密集型的進程,因為它們通常會發(fā)起一個 I/O 操作,然后進行阻塞,這就意味著其他工作都可以在兩者之間有效地交錯進行。

最常用的一個模型是同步阻塞 I/O 模型。在這個模型中,用戶空間的應(yīng)用程序執(zhí)行一個系統(tǒng)調(diào)用,這會導(dǎo)致應(yīng)用程序阻塞。這意味著應(yīng)用程序會一直阻塞,直到系統(tǒng)調(diào)用完成為止(數(shù)據(jù)傳輸完成或發(fā)生錯誤)。調(diào)用應(yīng)用程序處于一種不再消費 CPU 而只是簡單等待響應(yīng)的狀態(tài),因此從處理的角度來看,這是非常有效的。

圖 2 給出了傳統(tǒng)的阻塞 I/O 模型,這也是目前應(yīng)用程序中最為常用的一種模型。其行為非常容易理解,其用法對于典型的應(yīng)用程序來說都非常有效。在調(diào)用 read 系統(tǒng)調(diào)用時,應(yīng)用程序會阻塞并對內(nèi)核進行上下文切換。然后會觸發(fā)讀操作,當響應(yīng)返回時(從我們正在從中讀取的設(shè)備中返回),數(shù)據(jù)就被移動到用戶空間的緩沖區(qū)中。然后應(yīng)用程序就會解除阻塞(read 調(diào)用返回)。


圖 2. 同步阻塞 I/O 模型的典型流程
同步阻塞 I/O 模型的典型流程

從應(yīng)用程序的角度來說,read 調(diào)用會延續(xù)很長時間。實際上,在內(nèi)核執(zhí)行讀操作和其他工作時,應(yīng)用程序的確會被阻塞。

同步非阻塞 I/O

同步阻塞 I/O 的一種效率稍低的變種是同步非阻塞 I/O。在這種模型中,設(shè)備是以非阻塞的形式打開的。這意味著 I/O 操作不會立即完成,read 操作可能會返回一個錯誤代碼,說明這個命令不能立即滿足(EAGAINEWOULDBLOCK),如圖 3 所示。


圖 3. 同步非阻塞 I/O 模型的典型流程
同步非阻塞 I/O 模型的典型流程

非阻塞的實現(xiàn)是 I/O 命令可能并不會立即滿足,需要應(yīng)用程序調(diào)用許多次來等待操作完成。這可能效率不高,因為在很多情況下,當內(nèi)核執(zhí)行這個命令時,應(yīng)用程序必須要進行忙碌等待,直到數(shù)據(jù)可用為止,或者試圖執(zhí)行其他工作。正如圖 3 所示的一樣,這個方法可以引入 I/O 操作的延時,因為數(shù)據(jù)在內(nèi)核中變?yōu)榭捎玫接脩粽{(diào)用 read 返回數(shù)據(jù)之間存在一定的間隔,這會導(dǎo)致整體數(shù)據(jù)吞吐量的降低。

異步阻塞 I/O

另外一個阻塞解決方案是帶有阻塞通知的非阻塞 I/O。在這種模型中,配置的是非阻塞 I/O,然后使用阻塞 select 系統(tǒng)調(diào)用來確定一個 I/O 描述符何時有操作。使 select 調(diào)用非常有趣的是它可以用來為多個描述符提供通知,而不僅僅為一個描述符提供通知。對于每個提示符來說,我們可以請求這個描述符可以寫數(shù)據(jù)、有讀數(shù)據(jù)可用以及是否發(fā)生錯誤的通知。


圖 4. 異步阻塞 I/O 模型的典型流程 (select)
異步阻塞 I/O 模型的典型流程

select 調(diào)用的主要問題是它的效率不是非常高。盡管這是異步通知使用的一種方便模型,但是對于高性能的 I/O 操作來說不建議使用。

異步非阻塞 I/O(AIO)

最后,異步非阻塞 I/O 模型是一種處理與 I/O 重疊進行的模型。讀請求會立即返回,說明 read 請求已經(jīng)成功發(fā)起了。在后臺完成讀操作時,應(yīng)用程序然后會執(zhí)行其他處理操作。當 read 的響應(yīng)到達時,就會產(chǎn)生一個信號或執(zhí)行一個基于線程的回調(diào)函數(shù)來完成這次 I/O 處理過程。


圖 5. 異步非阻塞 I/O 模型的典型流程
異步非阻塞 I/O 模型的典型流程

在一個進程中為了執(zhí)行多個 I/O 請求而對計算操作和 I/O 處理進行重疊處理的能力利用了處理速度與 I/O 速度之間的差異。當一個或多個 I/O 請求掛起時,CPU 可以執(zhí)行其他任務(wù);或者更為常見的是,在發(fā)起其他 I/O 的同時對已經(jīng)完成的 I/O 進行操作。

下一節(jié)將深入介紹這種模型,探索這種模型使用的 API,然后展示幾個命令。





回頁首


異步 I/O 的動機

從前面 I/O 模型的分類中,我們可以看出 AIO 的動機。這種阻塞模型需要在 I/O 操作開始時阻塞應(yīng)用程序。這意味著不可能同時重疊進行處理和 I/O 操作。同步非阻塞模型允許處理和 I/O 操作重疊進行,但是這需要應(yīng)用程序根據(jù)重現(xiàn)的規(guī)則來檢查 I/O 操作的狀態(tài)。這樣就剩下異步非阻塞 I/O 了,它允許處理和 I/O 操作重疊進行,包括 I/O 操作完成的通知。

除了需要阻塞之外,select 函數(shù)所提供的功能(異步阻塞 I/O)與 AIO 類似。不過,它是對通知事件進行阻塞,而不是對 I/O 調(diào)用進行阻塞。





回頁首


Linux 上的 AIO 簡介

本節(jié)將探索 Linux 的異步 I/O 模型,從而幫助我們理解如何在應(yīng)用程序中使用這種技術(shù)。

在傳統(tǒng)的 I/O 模型中,有一個使用惟一句柄標識的 I/O 通道。在 UNIX® 中,這些句柄是文件描述符(這對等同于文件、管道、套接字等等)。在阻塞 I/O 中,我們發(fā)起了一次傳輸操作,當傳輸操作完成或發(fā)生錯誤時,系統(tǒng)調(diào)用就會返回。

Linux 上的 AIO

AIO 在 2.5 版本的內(nèi)核中首次出現(xiàn),現(xiàn)在已經(jīng)是 2.6 版本的產(chǎn)品內(nèi)核的一個標準特性了。

在異步非阻塞 I/O 中,我們可以同時發(fā)起多個傳輸操作。這需要每個傳輸操作都有惟一的上下文,這樣我們才能在它們完成時區(qū)分到底是哪個傳輸操作完成了。在 AIO 中,這是一個 aiocb(AIO I/O Control Block)結(jié)構(gòu)。這個結(jié)構(gòu)包含了有關(guān)傳輸?shù)乃行畔ⅲ閿?shù)據(jù)準備的用戶緩沖區(qū)。在產(chǎn)生 I/O (稱為完成)通知時,aiocb 結(jié)構(gòu)就被用來惟一標識所完成的 I/O 操作。這個 API 的展示顯示了如何使用它。





回頁首


AIO API

AIO 接口的 API 非常簡單,但是它為數(shù)據(jù)傳輸提供了必需的功能,并給出了兩個不同的通知模型。表 1 給出了 AIO 的接口函數(shù),本節(jié)稍后會更詳細進行介紹。


表 1. AIO 接口 API
API 函數(shù) 說明
aio_read 請求異步讀操作
aio_error 檢查異步請求的狀態(tài)
aio_return 獲得完成的異步請求的返回狀態(tài)
aio_write 請求異步寫操作
aio_suspend 掛起調(diào)用進程,直到一個或多個異步請求已經(jīng)完成(或失敗)
aio_cancel 取消異步 I/O 請求
lio_listio 發(fā)起一系列 I/O 操作

每個 API 函數(shù)都使用 aiocb 結(jié)構(gòu)開始或檢查。這個結(jié)構(gòu)有很多元素,但是清單 1 僅僅給出了需要(或可以)使用的元素。


清單 1. aiocb 結(jié)構(gòu)中相關(guān)的域
            struct aiocb {
            int aio_fildes;               // File Descriptor
            int aio_lio_opcode;           // Valid only for lio_listio (r/w/nop)
            volatile void *aio_buf;       // Data Buffer
            size_t aio_nbytes;            // Number of Bytes in Data Buffer
            struct sigevent aio_sigevent; // Notification Structure
            /* Internal fields */
            ...
            };
            

sigevent 結(jié)構(gòu)告訴 AIO 在 I/O 操作完成時應(yīng)該執(zhí)行什么操作。我們將在 AIO 的展示中對這個結(jié)構(gòu)進行探索。現(xiàn)在我們將展示各個 AIO 的 API 函數(shù)是如何工作的,以及我們應(yīng)該如何使用它們。

aio_read

aio_read 函數(shù)請求對一個有效的文件描述符進行異步讀操作。這個文件描述符可以表示一個文件、套接字甚至管道。aio_read 函數(shù)的原型如下:

int aio_read( struct aiocb *aiocbp );
            

aio_read 函數(shù)在請求進行排隊之后會立即返回。如果執(zhí)行成功,返回值就為 0;如果出現(xiàn)錯誤,返回值就為 -1,并設(shè)置 errno 的值。

要執(zhí)行讀操作,應(yīng)用程序必須對 aiocb 結(jié)構(gòu)進行初始化。下面這個簡短的例子就展示了如何填充 aiocb 請求結(jié)構(gòu),并使用 aio_read 來執(zhí)行異步讀請求(現(xiàn)在暫時忽略通知)操作。它還展示了 aio_error 的用法,不過我們將稍后再作解釋。


清單 2. 使用 aio_read 進行異步讀操作的例子
            #include <aio.h>
            ...
            int fd, ret;
            struct aiocb my_aiocb;
            fd = open( "file.txt", O_RDONLY );
            if (fd < 0) perror("open");
            /* Zero out the aiocb structure (recommended) */
            bzero( (char *)&my_aiocb, sizeof(struct aiocb) );
            /* Allocate a data buffer for the aiocb request */
            my_aiocb.aio_buf = malloc(BUFSIZE+1);
            if (!my_aiocb.aio_buf) perror("malloc");
            /* Initialize the necessary fields in the aiocb */
            my_aiocb.aio_fildes = fd;
            my_aiocb.aio_nbytes = BUFSIZE;
            my_aiocb.aio_offset = 0;
            ret = aio_read( &my_aiocb );
            if (ret < 0) perror("aio_read");
            while ( aio_error( &my_aiocb ) == EINPROGRESS ) ;
            if ((ret = aio_return( &my_iocb )) > 0) {
            /* got ret bytes on the read */
            } else {
            /* read failed, consult errno */
            }
            

在清單 2 中,在打開要從中讀取數(shù)據(jù)的文件之后,我們就清空了 aiocb 結(jié)構(gòu),然后分配一個數(shù)據(jù)緩沖區(qū)。并將對這個數(shù)據(jù)緩沖區(qū)的引用放到 aio_buf 中。然后,我們將 aio_nbytes 初始化成緩沖區(qū)的大小。并將 aio_offset 設(shè)置成 0(該文件中的第一個偏移量)。我們將 aio_fildes 設(shè)置為從中讀取數(shù)據(jù)的文件描述符。在設(shè)置這些域之后,就調(diào)用 aio_read 請求進行讀操作。我們?nèi)缓罂梢哉{(diào)用 aio_error 來確定 aio_read 的狀態(tài)。只要狀態(tài)是 EINPROGRESS,就一直忙碌等待,直到狀態(tài)發(fā)生變化為止。現(xiàn)在,請求可能成功,也可能失敗。

使用 AIO 接口來編譯程序

我們可以在 aio.h 頭文件中找到函數(shù)原型和其他需要的符號。在編譯使用這種接口的程序時,我們必須使用 POSIX 實時擴展庫(librt)。

注意使用這個 API 與標準的庫函數(shù)從文件中讀取內(nèi)容是非常相似的。除了 aio_read 的一些異步特性之外,另外一個區(qū)別是讀操作偏移量的設(shè)置。在傳統(tǒng)的 read 調(diào)用中,偏移量是在文件描述符上下文中進行維護的。對于每個讀操作來說,偏移量都需要進行更新,這樣后續(xù)的讀操作才能對下一塊數(shù)據(jù)進行尋址。對于異步 I/O 操作來說這是不可能的,因為我們可以同時執(zhí)行很多讀請求,因此必須為每個特定的讀請求都指定偏移量。

aio_error

aio_error 函數(shù)被用來確定請求的狀態(tài)。其原型如下:

int aio_error( struct aiocb *aiocbp );
            

這個函數(shù)可以返回以下內(nèi)容:

  • EINPROGRESS,說明請求尚未完成
  • ECANCELLED,說明請求被應(yīng)用程序取消了
  • -1,說明發(fā)生了錯誤,具體錯誤原因可以查閱 errno

aio_return

異步 I/O 和標準塊 I/O 之間的另外一個區(qū)別是我們不能立即訪問這個函數(shù)的返回狀態(tài),因為我們并沒有阻塞在 read 調(diào)用上。在標準的 read 調(diào)用中,返回狀態(tài)是在該函數(shù)返回時提供的。但是在異步 I/O 中,我們要使用 aio_return 函數(shù)。這個函數(shù)的原型如下:

ssize_t aio_return( struct aiocb *aiocbp );
            

只有在 aio_error 調(diào)用確定請求已經(jīng)完成(可能成功,也可能發(fā)生了錯誤)之后,才會調(diào)用這個函數(shù)。aio_return 的返回值就等價于同步情況中 readwrite 系統(tǒng)調(diào)用的返回值(所傳輸?shù)淖止?jié)數(shù),如果發(fā)生錯誤,返回值就為 -1)。

aio_write

aio_write 函數(shù)用來請求一個異步寫操作。其函數(shù)原型如下:

int aio_write( struct aiocb *aiocbp );
            

aio_write 函數(shù)會立即返回,說明請求已經(jīng)進行排隊(成功時返回值為 0,失敗時返回值為 -1,并相應(yīng)地設(shè)置 errno)。

這與 read 系統(tǒng)調(diào)用類似,但是有一點不一樣的行為需要注意。回想一下對于 read 調(diào)用來說,要使用的偏移量是非常重要的。然而,對于 write 來說,這個偏移量只有在沒有設(shè)置 O_APPEND 選項的文件上下文中才會非常重要。如果設(shè)置了 O_APPEND,那么這個偏移量就會被忽略,數(shù)據(jù)都會被附加到文件的末尾。否則,aio_offset 域就確定了數(shù)據(jù)在要寫入的文件中的偏移量。

aio_suspend

我們可以使用 aio_suspend 函數(shù)來掛起(或阻塞)調(diào)用進程,直到異步請求完成為止,此時會產(chǎn)生一個信號,或者發(fā)生其他超時操作。調(diào)用者提供了一個 aiocb 引用列表,其中任何一個完成都會導(dǎo)致 aio_suspend 返回。 aio_suspend 的函數(shù)原型如下:

int aio_suspend( const struct aiocb *const cblist[],
            int n, const struct timespec *timeout );
            

aio_suspend 的使用非常簡單。我們要提供一個 aiocb 引用列表。如果任何一個完成了,這個調(diào)用就會返回 0。否則就會返回 -1,說明發(fā)生了錯誤。請參看清單 3。


清單 3. 使用 aio_suspend 函數(shù)阻塞異步 I/O
            struct aioct *cblist[MAX_LIST]
            /* Clear the list. */
            bzero( (char *)cblist, sizeof(cblist) );
            /* Load one or more references into the list */
            cblist[0] = &my_aiocb;
            ret = aio_read( &my_aiocb );
            ret = aio_suspend( cblist, MAX_LIST, NULL );
            

注意,aio_suspend 的第二個參數(shù)是 cblist 中元素的個數(shù),而不是 aiocb 引用的個數(shù)。cblist 中任何 NULL 元素都會被 aio_suspend 忽略。

如果為 aio_suspend 提供了超時,而超時情況的確發(fā)生了,那么它就會返回 -1,errno 中會包含 EAGAIN

aio_cancel

aio_cancel 函數(shù)允許我們?nèi)∠麑δ硞€文件描述符執(zhí)行的一個或所有 I/O 請求。其原型如下:

int aio_cancel( int fd, struct aiocb *aiocbp );
            

要取消一個請求,我們需要提供文件描述符和 aiocb 引用。如果這個請求被成功取消了,那么這個函數(shù)就會返回 AIO_CANCELED。如果請求完成了,這個函數(shù)就會返回 AIO_NOTCANCELED。

要取消對某個給定文件描述符的所有請求,我們需要提供這個文件的描述符,以及一個對 aiocbpNULL 引用。如果所有的請求都取消了,這個函數(shù)就會返回 AIO_CANCELED;如果至少有一個請求沒有被取消,那么這個函數(shù)就會返回 AIO_NOT_CANCELED;如果沒有一個請求可以被取消,那么這個函數(shù)就會返回 AIO_ALLDONE。我們?nèi)缓罂梢允褂?aio_error 來驗證每個 AIO 請求。如果這個請求已經(jīng)被取消了,那么 aio_error 就會返回 -1,并且 errno 會被設(shè)置為 ECANCELED

lio_listio

最后,AIO 提供了一種方法使用 lio_listio API 函數(shù)同時發(fā)起多個傳輸。這個函數(shù)非常重要,因為這意味著我們可以在一個系統(tǒng)調(diào)用(一次內(nèi)核上下文切換)中啟動大量的 I/O 操作。從性能的角度來看,這非常重要,因此值得我們花點時間探索一下。lio_listio API 函數(shù)的原型如下:

int lio_listio( int mode, struct aiocb *list[], int nent,
            struct sigevent *sig );
            

mode 參數(shù)可以是 LIO_WAITLIO_NOWAIT。LIO_WAIT 會阻塞這個調(diào)用,直到所有的 I/O 都完成為止。在操作進行排隊之后,LIO_NOWAIT 就會返回。list 是一個 aiocb 引用的列表,最大元素的個數(shù)是由 nent 定義的。注意 list 的元素可以為 NULL,lio_listio 會將其忽略。sigevent 引用定義了在所有 I/O 操作都完成時產(chǎn)生信號的方法。

對于 lio_listio 的請求與傳統(tǒng)的 readwrite 請求在必須指定的操作方面稍有不同,如清單 4 所示。


清單 4. 使用 lio_listio 函數(shù)發(fā)起一系列請求
            struct aiocb aiocb1, aiocb2;
            struct aiocb *list[MAX_LIST];
            ...
            /* Prepare the first aiocb */
            aiocb1.aio_fildes = fd;
            aiocb1.aio_buf = malloc( BUFSIZE+1 );
            aiocb1.aio_nbytes = BUFSIZE;
            aiocb1.aio_offset = next_offset;
            aiocb1.aio_lio_opcode = LIO_READ;
            ...
            bzero( (char *)list, sizeof(list) );
            list[0] = &aiocb1;
            list[1] = &aiocb2;
            ret = lio_listio( LIO_WAIT, list, MAX_LIST, NULL );
            

對于讀操作來說,aio_lio_opcode 域的值為 LIO_READ。對于寫操作來說,我們要使用 LIO_WRITE,不過 LIO_NOP 對于不執(zhí)行操作來說也是有效的。





回頁首


AIO 通知

現(xiàn)在我們已經(jīng)看過了可用的 AIO 函數(shù),本節(jié)將深入介紹對異步通知可以使用的方法。我們將通過信號和函數(shù)回調(diào)來探索異步函數(shù)的通知機制。

使用信號進行異步通知

使用信號進行進程間通信(IPC)是 UNIX 中的一種傳統(tǒng)機制,AIO 也可以支持這種機制。在這種范例中,應(yīng)用程序需要定義信號處理程序,在產(chǎn)生指定的信號時就會調(diào)用這個處理程序。應(yīng)用程序然后配置一個異步請求將在請求完成時產(chǎn)生一個信號。作為信號上下文的一部分,特定的 aiocb 請求被提供用來記錄多個可能會出現(xiàn)的請求。清單 5 展示了這種通知方法。


清單 5. 使用信號作為 AIO 請求的通知
            void setup_io( ... )
            {
            int fd;
            struct sigaction sig_act;
            struct aiocb my_aiocb;
            ...
            /* Set up the signal handler */
            sigemptyset(&sig_act.sa_mask);
            sig_act.sa_flags = SA_SIGINFO;
            sig_act.sa_sigaction = aio_completion_handler;
            /* Set up the AIO request */
            bzero( (char *)&my_aiocb, sizeof(struct aiocb) );
            my_aiocb.aio_fildes = fd;
            my_aiocb.aio_buf = malloc(BUF_SIZE+1);
            my_aiocb.aio_nbytes = BUF_SIZE;
            my_aiocb.aio_offset = next_offset;
            /* Link the AIO request with the Signal Handler */
            my_aiocb.aio_sigevent.sigev_notify = SIGEV_SIGNAL;
            my_aiocb.aio_sigevent.sigev_signo = SIGIO;
            my_aiocb.aio_sigevent.sigev_value.sival_ptr = &my_aiocb;
            /* Map the Signal to the Signal Handler */
            ret = sigaction( SIGIO, &sig_act, NULL );
            ...
            ret = aio_read( &my_aiocb );
            }
            void aio_completion_handler( int signo, siginfo_t *info, void *context )
            {
            struct aiocb *req;
            /* Ensure it's our signal */
            if (info->si_signo == SIGIO) {
            req = (struct aiocb *)info->si_value.sival_ptr;
            /* Did the request complete? */
            if (aio_error( req ) == 0) {
            /* Request completed successfully, get the return status */
            ret = aio_return( req );
            }
            }
            return;
            }
            

在清單 5 中,我們在 aio_completion_handler 函數(shù)中設(shè)置信號處理程序來捕獲 SIGIO 信號。然后初始化 aio_sigevent 結(jié)構(gòu)產(chǎn)生 SIGIO 信號來進行通知(這是通過 sigev_notify 中的 SIGEV_SIGNAL 定義來指定的)。當讀操作完成時,信號處理程序就從該信號的 si_value 結(jié)構(gòu)中提取出 aiocb,并檢查錯誤狀態(tài)和返回狀態(tài)來確定 I/O 操作是否完成。

對于性能來說,這個處理程序也是通過請求下一次異步傳輸而繼續(xù)進行 I/O 操作的理想地方。采用這種方式,在一次數(shù)據(jù)傳輸完成時,我們就可以立即開始下一次數(shù)據(jù)傳輸操作。

使用回調(diào)函數(shù)進行異步通知

另外一種通知方式是系統(tǒng)回調(diào)函數(shù)。這種機制不會為通知而產(chǎn)生一個信號,而是會調(diào)用用戶空間的一個函數(shù)來實現(xiàn)通知功能。我們在 sigevent 結(jié)構(gòu)中設(shè)置了對 aiocb 的引用,從而可以惟一標識正在完成的特定請求。請參看清單 6。


清單 6. 對 AIO 請求使用線程回調(diào)通知
            void setup_io( ... )
            {
            int fd;
            struct aiocb my_aiocb;
            ...
            /* Set up the AIO request */
            bzero( (char *)&my_aiocb, sizeof(struct aiocb) );
            my_aiocb.aio_fildes = fd;
            my_aiocb.aio_buf = malloc(BUF_SIZE+1);
            my_aiocb.aio_nbytes = BUF_SIZE;
            my_aiocb.aio_offset = next_offset;
            /* Link the AIO request with a thread callback */
            my_aiocb.aio_sigevent.sigev_notify = SIGEV_THREAD;
            my_aiocb.aio_sigevent.notify_function = aio_completion_handler;
            my_aiocb.aio_sigevent.notify_attributes = NULL;
            my_aiocb.aio_sigevent.sigev_value.sival_ptr = &my_aiocb;
            ...
            ret = aio_read( &my_aiocb );
            }
            void aio_completion_handler( sigval_t sigval )
            {
            struct aiocb *req;
            req = (struct aiocb *)sigval.sival_ptr;
            /* Did the request complete? */
            if (aio_error( req ) == 0) {
            /* Request completed successfully, get the return status */
            ret = aio_return( req );
            }
            return;
            }
            

在清單 6 中,在創(chuàng)建自己的 aiocb 請求之后,我們使用 SIGEV_THREAD 請求了一個線程回調(diào)函數(shù)來作為通知方法。然后我們將指定特定的通知處理程序,并將要傳輸?shù)纳舷挛募虞d到處理程序中(在這種情況中,是個對 aiocb 請求自己的引用)。在這個處理程序中,我們簡單地引用到達的 sigval 指針并使用 AIO 函數(shù)來驗證請求已經(jīng)完成。





回頁首


對 AIO 進行系統(tǒng)優(yōu)化

proc 文件系統(tǒng)包含了兩個虛擬文件,它們可以用來對異步 I/O 的性能進行優(yōu)化:

  • /proc/sys/fs/aio-nr 文件提供了系統(tǒng)范圍異步 I/O 請求現(xiàn)在的數(shù)目。
  • /proc/sys/fs/aio-max-nr 文件是所允許的并發(fā)請求的最大個數(shù)。最大個數(shù)通常是 64KB,這對于大部分應(yīng)用程序來說都已經(jīng)足夠了。




回頁首


結(jié)束語

使用異步 I/O 可以幫助我們構(gòu)建 I/O 速度更快、效率更高的應(yīng)用程序。如果我們的應(yīng)用程序可以對處理和 I/O 操作重疊進行,那么 AIO 就可以幫助我們構(gòu)建可以更高效地使用可用 CPU 資源的應(yīng)用程序。盡管這種 I/O 模型與在大部分 Linux 應(yīng)用程序中使用的傳統(tǒng)阻塞模式都不同,但是異步通知模型在概念上來說卻非常簡單,可以簡化我們的設(shè)計。




何克勤 2010-05-01 19:44 發(fā)表評論
]]>
LINUX下的用戶和組管理http://www.aygfsteel.com/tinysun/archive/2010/05/01/319858.html何克勤何克勤Sat, 01 May 2010 11:20:00 GMThttp://www.aygfsteel.com/tinysun/archive/2010/05/01/319858.htmlhttp://www.aygfsteel.com/tinysun/comments/319858.htmlhttp://www.aygfsteel.com/tinysun/archive/2010/05/01/319858.html#Feedback0http://www.aygfsteel.com/tinysun/comments/commentRss/319858.htmlhttp://www.aygfsteel.com/tinysun/services/trackbacks/319858.html         1 超級用戶:用戶名為root,具有一切管理權(quán)限,他的UID為0,可以創(chuàng)建多個管理員的。
         2 普通用戶:在默認情況下,普通用戶的UID是介于500-60000之間的。
         3 偽用戶:這些用戶的存在是為了方便系統(tǒng)管理,滿足相應(yīng)的系統(tǒng)進程對文件屬主的要求。他不能夠登錄,它的ID值介于1--499之間。
 
LINUX的組:分為私有組和標準組
私有組:新創(chuàng)建一個用戶時,如果沒有指定所屬組,則系統(tǒng)會為用戶創(chuàng)建一個同名的私有組。
標準組:事先創(chuàng)建好的,新創(chuàng)建一個用戶時就可以指定他所屬的組,它可以容納多個用戶。
 
創(chuàng)建用戶命令:useradd 用戶名   然后再加密碼:passwd 用戶名
 
LINUX系統(tǒng)是多用戶,多任務(wù)操作系統(tǒng),但是當系統(tǒng)管理員需要備份時,就不允許其它用戶登錄進來。下面介紹一個方法來解決這個問題。
    可以在/etc目錄下創(chuàng)建一個名為nologin的文件,當其他用戶登錄時,系統(tǒng)只要發(fā)現(xiàn)此文件存在,就會禁止其他用戶登錄。若要再次恢復(fù)用戶登錄,則只要把nologin文件刪除即可,或重新啟動后,系統(tǒng)即會直接刪除這個文件,恢復(fù)用戶登錄。


何克勤 2010-05-01 19:20 發(fā)表評論
]]>
linux 網(wǎng)絡(luò)相關(guān)命令http://www.aygfsteel.com/tinysun/archive/2010/04/16/318563.html何克勤何克勤Fri, 16 Apr 2010 12:56:00 GMThttp://www.aygfsteel.com/tinysun/archive/2010/04/16/318563.htmlhttp://www.aygfsteel.com/tinysun/comments/318563.htmlhttp://www.aygfsteel.com/tinysun/archive/2010/04/16/318563.html#Feedback0http://www.aygfsteel.com/tinysun/comments/commentRss/318563.htmlhttp://www.aygfsteel.com/tinysun/services/trackbacks/318563.html
第5章:網(wǎng)絡(luò)應(yīng)用
  
  5.1 常用網(wǎng)絡(luò)命令
  在Red Hat Linux 7.1系統(tǒng)提供了與網(wǎng)絡(luò)相關(guān)的工具,掌握好這些工具是十分必要的:
  第一類:設(shè)置工具
  1.netconf:
  netconf是Red Hat Linux提供的Linuxconf的一部分,主要用于設(shè)置與網(wǎng)絡(luò)相關(guān)的參數(shù)。它可以在consle下運行(文本菜單),也可以在X-Window中運行(圖形界面)。在前面,我們介紹過了netconf的一些應(yīng)用,它的使用比較簡單,只要認識上面的英文就可以了,所以在此就不再多說。BTW,如果你設(shè)置好了X-Window的話,用用圖形界面的netconf,會更漂亮的喲。
  2.ifconfig
  ifconfig是Linux系統(tǒng)中最常用的一個用來顯示和設(shè)置網(wǎng)絡(luò)設(shè)備的工具。其中“if”是“interface”的縮寫。它可以用來設(shè)備網(wǎng)卡的狀態(tài),或是顯示當前的設(shè)置。
  下面我們簡單地說明常用的命令組合:
  1) 將第一塊網(wǎng)卡的IP地址設(shè)置為192.168.0.1:
bbs.bitsCN.com中國網(wǎng)管論壇

  ifconfig eth0 192.168.0.1 (格式:ifconfig 網(wǎng)絡(luò)設(shè)備名 IP地址)
  2) 暫時關(guān)閉或啟用網(wǎng)卡:
  關(guān)閉第一塊網(wǎng)卡:ifconfig eth0 down
  啟用第一塊網(wǎng)卡:ifconfig eth0 up
  3) 將第一塊網(wǎng)卡的子網(wǎng)掩碼設(shè)置為255.255.255.0:
  ifconfig eth0 netmask 255.255.255.0(格式:ifconfig 網(wǎng)絡(luò)設(shè)備名 netmask 子網(wǎng)掩碼)
  我們也可以同時設(shè)置IP地址和子網(wǎng)掩碼:
  ifconfig eth0 192.168.0.1 netmask 255.255.255.0
  4) 將第一塊網(wǎng)卡的廣播地址設(shè)置為192.168.0.255:
  ifconfig eth0 -broadcast 192.168.0.255
  5) 將第一塊網(wǎng)卡設(shè)置為不接收多播數(shù)據(jù)包:
  ifconifg eth0 allmulti
  如果要讓其接收,則使用命令:ifconfig eth0 -allmulti
  6) 查看第一塊網(wǎng)卡的狀態(tài):
  ifconfig eth0
  如果要查看所有的網(wǎng)卡狀態(tài),則直接使用不帶參數(shù)的ifconfig命令即可。
  ifconfig輸出的狀態(tài)信息是十分有用的,下面,我們就簡單說明一下:
  有幾個狀態(tài)比較重要:
  Ø UP/DOWN:網(wǎng)卡是否啟動了,如果是DOWN的話,那肯定無法用的; [bitsCN.Com]
  Ø RX packets中的errors包的數(shù)量如果過大說明網(wǎng)卡在接收時有問題;
  Ø TX packets中的errors包的數(shù)量如果過大說明網(wǎng)卡在發(fā)送時有問題;
  3.route
  route命令是用來查看和設(shè)置Linux系統(tǒng)的路由信息,以實現(xiàn)與其它網(wǎng)絡(luò)的通訊。要實現(xiàn)兩個不同的子網(wǎng)之間的網(wǎng)絡(luò)通訊,需要一臺連接兩個網(wǎng)絡(luò)路由器或者同時位于兩個網(wǎng)絡(luò)的網(wǎng)關(guān)來實現(xiàn)。
  在Linux系統(tǒng)中,我們通常設(shè)置路由是為了解決以下問題:該Linux機器在一個局域網(wǎng)中,局域網(wǎng)中有一個網(wǎng)關(guān),能夠讓你的機器訪問Internet,那么我們就需要將這臺機器的IP地址設(shè)置為Linux機器的默認路由。
  1) 增加一個默認路由:
  route add 0.0.0.0 gw 網(wǎng)關(guān)地址
  2) 刪除一個默認路由:
  route del 0.0.0.0 gw 網(wǎng)關(guān)地址
  3) 顯示出當前路由表
  route


  第二類:診斷工具
  1.ping
  ping是一個最常用的檢測是否能夠與遠端機器建立網(wǎng)絡(luò)通訊連接。它是通過Internet控制報文協(xié)議ICMP來實現(xiàn)的。而現(xiàn)在有些主機對ICMP進行過濾,在這種特殊的情況下,有可能使得一些主機Ping不通,但能夠建立網(wǎng)絡(luò)連接。這是一種特例,在此事先說明。 bitsCN#com中國網(wǎng)管聯(lián)盟
  同樣的,在此不羅列ping命令的所有可選參數(shù),而是通過實例來說明一些常用的組合,需要更詳細地了解的,可以通過www.linuxaid.com.cn網(wǎng)站在線培訓(xùn)的命令查詢工具獲得。
  1) 檢測與某機器的連接是否正常:
  ping 192.168.0.1
  ping www.linuxaid.com.cn
  也就是說,我們可以用IP地址或域名來指定機器。
  2) 指定ping回應(yīng)次數(shù)為4:
  在Linux下,如果你不指定回應(yīng)次數(shù),ping命令將一直不斷地向遠方機器發(fā)送ICMP信息。我們可以通過-c參數(shù)來限定:ping -c 4 192.168.0.1
  3) 通過特定的網(wǎng)卡進行ping:
  有時,我們需要檢測某塊網(wǎng)卡(系統(tǒng)中有多塊)能否ping通遠方機器。我們需要在執(zhí)行ping命令時指出:
  ping -I eth0 192.168.0.1
  2.traceroute
  如果你ping不通遠方的機器,想知道是在什么地方出的問題;或者你想知道你的信息到遠方機器都經(jīng)過了哪些路由器,可以使用traceroute命令。顧名思義:trace是跟蹤,route是路由,也就是跟蹤路由。
  使用這個命令很簡單:
  traceroute 遠程主機IP地址或域名
  這個命令的輸出類似:
  1 路由器(網(wǎng)關(guān))的IP地址 訪問所需時間1 訪問所需時間2 訪問所需時間3 www@bitscn@com
  2 路由器(網(wǎng)關(guān))的IP地址 訪問所需時間1 訪問所需時間2 訪問所需時間3
  ………
  1) 最前面的數(shù)字代表“經(jīng)過第幾站”;
  2) 路由器(網(wǎng)關(guān))的IP地址就是“該站”的IP地址;
  3) 訪問所需時間1、2、3是指訪問到這個路由器(網(wǎng)關(guān))需要的時間。
  
  3.netstat
  在Linux系統(tǒng)中,提供了一個功能十分強大的查看網(wǎng)絡(luò)狀態(tài)的工具:netstat。它可以讓您得知整個Linux系統(tǒng)的網(wǎng)絡(luò)情況。
  1)統(tǒng)計出各網(wǎng)絡(luò)設(shè)備傳送、接收數(shù)據(jù)包的情況:
  使用命令:netstat -i
  這個命令將輸出一張表,其中包括:
  Iface:網(wǎng)絡(luò)接口名 MTU:最大傳輸單元
  RX-OK:共成功接收多少個包 RX-ERR:接收的包中共有多少個錯誤包
  RX-DRP:接收時共丟失多少個包 RX-OVR:共接收了多少個碰撞包
  TX-OK:共成功發(fā)送多少個包 TX-ERR:發(fā)送的包中共有多少個錯誤包
  TX-DRP:發(fā)磅時共丟失多少個包 TX-OVR:共接收了多少個碰撞包

bitsCN.nET*中國網(wǎng)管博客


  2)顯示網(wǎng)絡(luò)的統(tǒng)計信息
  使用命令:netstat -s
  使用這個命令,將會以摘要的形式統(tǒng)計出IP、ICMP、TCP、UDP、TCPEXT形式的通信信息。
  3)顯示出TCP傳輸協(xié)議的網(wǎng)絡(luò)連接情況:
  使用命令:netstat -t
  這個命令的輸出也是一張表,其中包括:
  Local Address:本地地址,格式是IP地址:端口號
  Foreign Address:遠程地址,格式也是IP地址:端口號
  State:連接狀態(tài),包括LISTEN、ESTABLISHED、TIME_WAIT等。
  4)只顯示出使用UDP的網(wǎng)絡(luò)連接情況:
  使用命令:netstat -t
  輸出格式也是一樣的。
  5)顯示路由表:
  使用命令:netstat -r
  這個命令的輸出與route命令的輸出相同。
  
  5.2 網(wǎng)絡(luò)配置文件
  在Red Hat Linux 7.1中有一些用于存放網(wǎng)絡(luò)配置的文件:
  1./etc/hosts
  在該文件中存放的是一組IP地址與主機名的列表,如果在該列表中指出某臺主機的IP地址,那么訪問該主機時將無需進行DNS解析。 bbs.bitsCN.com中國網(wǎng)管論壇
  2./etc/host.conf
  該文件用來指定域名解析方法的順序,如:
  order hosts,bind
  它說明,首先通過/etc/hosts文件解析,如果在該文件中沒有相應(yīng)的主機名與IP地址的對應(yīng)關(guān)系,再通過域名服務(wù)器bind進行解析。
  3./etc/resolv.conf
  在該文件中存放域名服務(wù)器的IP地址。
  4./etc/protocols
  Red Hat Linux 7.1系統(tǒng)使用該文件辨別本主機使用的,并通過它完成協(xié)議和協(xié)議號之間的映射,用戶不應(yīng)修改該文件。
  5./etc/services
  該用戶用于定義現(xiàn)有的網(wǎng)絡(luò)服務(wù),用戶無需修改它,它通常由安裝網(wǎng)絡(luò)服務(wù)的程序來維護。該文件包括網(wǎng)絡(luò)服務(wù)名、網(wǎng)絡(luò)端口號和使用的協(xié)議類型,其中網(wǎng)絡(luò)端口號和使用的協(xié)議類型之間有一個斜杠分開,在設(shè)置行的最后還可以添加一些服務(wù)的別名。
  5./etc/xinetd.d目錄
  在Linux系統(tǒng)中有一個超級服務(wù)程序inetd,大部分的網(wǎng)絡(luò)服務(wù)都是由它啟動的,如chargen、echo、finger、talk、telnet、wu-ftpd等…,在7.0之間的版本它的設(shè)置是在/etc/inetd.conf中配置的,在Red Hat 7.0后,它就改成了一個xinetd.d目錄。 bbs.bitsCN.com
  在xinetd.d目錄中,每一個服務(wù)都有一個相應(yīng)的配置文件,我們以telnet為例,說明一下各個配置行的含義:
  service telnet
  {
  socket_type=stream
  wait=no
  user=root
  server=/usr/sbin/in.telnetd
  log_on_failure+=USERID
  disable=yes
  }
  第一行,說明該配置用來設(shè)置telnet服務(wù)。
  第二行,說明Socket連接類型是stream,也就是TCP
  第三行,是指不等待到啟動完成
  第四行,是指以root用戶啟動服務(wù)進程
  第五行,是指服務(wù)進程是/usr/sbin/in.telnetd
  第六行,是用于做一些出錯日志
  第七行,是指禁止遠方telnet,如果需要開放則將該配置改為:disable=no
  修改了xinetd的配置,需要重啟xinetd才能夠生效,有兩種方法可以實現(xiàn):
  1) 執(zhí)行如下命令:
  /etc/rc.d/init.d/xinetd restart
  2) 執(zhí)行如下命令:
  killall -HUP xinetd
  
  5.3 網(wǎng)絡(luò)服務(wù)訪問限制
  在Red Hat Linux 7.1中加強了網(wǎng)絡(luò)安全的防范,如果你安裝時安全等級不是在最低一級的話,那么本機之外的所有訪問都可能被拒絕。這是因為在Red Hat 7.1中做了一些默認的ipchains設(shè)置,這是Linux內(nèi)置的防火墻機制,它可以使用一些規(guī)則來允許或禁止某種訪問。

bbs.bitsCN.com


  它的規(guī)則存放在/etc/sysconfig/ipchains文件中,如果你想讓它暫時不生效,那你可以運行/etc/rc.d/init.d/ipchains stop,那么所有的規(guī)則都被取消,所有的網(wǎng)絡(luò)訪問都將被允許。
  你可以運行/etc/rc.d/init.d/ipchains status來獲知現(xiàn)在對網(wǎng)絡(luò)訪問的限制。關(guān)于這方面的知識,本文限于篇幅無法詳細介紹,有興趣的讀者可參考《Linux防火墻》一書。
  
  5.4 WEB服務(wù)器
  在Linux系統(tǒng)中最適合于做服務(wù)器的當數(shù)Apache,Red H


何克勤 2010-04-16 20:56 發(fā)表評論
]]>
頻繁分配釋放內(nèi)存導(dǎo)致的性能問題的分析[轉(zhuǎn)]http://www.aygfsteel.com/tinysun/archive/2010/03/28/316737.html何克勤何克勤Sun, 28 Mar 2010 02:48:00 GMThttp://www.aygfsteel.com/tinysun/archive/2010/03/28/316737.htmlhttp://www.aygfsteel.com/tinysun/comments/316737.htmlhttp://www.aygfsteel.com/tinysun/archive/2010/03/28/316737.html#Feedback0http://www.aygfsteel.com/tinysun/comments/commentRss/316737.htmlhttp://www.aygfsteel.com/tinysun/services/trackbacks/316737.html現(xiàn)象
1 壓力測試過程中,發(fā)現(xiàn)被測對象性能不夠理想,具體表現(xiàn)為:
進程的系統(tǒng)態(tài)CPU消耗20,用戶態(tài)CPU消耗10,系統(tǒng)idle大約70
2 用ps -o majflt,minflt -C program命令查看,發(fā)現(xiàn)majflt每秒增量為0,而minflt每秒增量大于10000。

初步分析
majflt代表major fault,中文名叫大錯誤,minflt代表minor fault,中文名叫小錯誤。
這兩個數(shù)值表示一個進程自啟動以來所發(fā)生的缺頁中斷的次數(shù)。
當一個進程發(fā)生缺頁中斷的時候,進程會陷入內(nèi)核態(tài),執(zhí)行以下操作:
檢查要訪問的虛擬地址是否合法
查找/分配一個物理頁
填充物理頁內(nèi)容(讀取磁盤,或者直接置0,或者啥也不干)
建立映射關(guān)系(虛擬地址到物理地址)
重新執(zhí)行發(fā)生缺頁中斷的那條指令
如果第3步,需要讀取磁盤,那么這次缺頁中斷就是majflt,否則就是minflt。
此進程minflt如此之高,一秒10000多次,不得不懷疑它跟進程內(nèi)核態(tài)cpu消耗大有很大關(guān)系。

分析代碼
查看代碼,發(fā)現(xiàn)是這么寫的:一個請求來,用malloc分配2M內(nèi)存,請求結(jié)束后free這塊內(nèi)存??慈罩荆l(fā)現(xiàn)分配內(nèi)存語句耗時10us,平均一條請求處理耗時1000us 。 原因已找到!
雖然分配內(nèi)存語句的耗時在一條處理請求中耗時比重不大,但是這條語句嚴重影響了性能。要解釋清楚原因,需要先了解一下內(nèi)存分配的原理。

內(nèi)存分配的原理
從操作系統(tǒng)角度來看,進程分配內(nèi)存有兩種方式,分別由兩個系統(tǒng)調(diào)用完成:brk和mmap(不考慮共享內(nèi)存)。brk是將數(shù)據(jù)段(.data)的最高地址指針_edata往高地址推,mmap是在進程的虛擬地址空間中(一般是堆和棧中間)找一塊空閑的。這兩種方式分配的都是虛擬內(nèi)存,沒有分配物理內(nèi)存。在第一次訪問已分配的虛擬地址空間的時候,發(fā)生缺頁中斷,操作系統(tǒng)負責(zé)分配物理內(nèi)存,然后建立虛擬內(nèi)存和物理內(nèi)存之間的映射關(guān)系。
在標準C庫中,提供了malloc/free函數(shù)分配釋放內(nèi)存,這兩個函數(shù)底層是由brk,mmap,munmap這些系統(tǒng)調(diào)用實現(xiàn)的。
下面以一個例子來說明內(nèi)存分配的原理:

1進程啟動的時候,其(虛擬)內(nèi)存空間的初始布局如圖1所示。其中,mmap內(nèi)存映射文件是在堆和棧的中間(例如libc-2.2.93.so,其它數(shù)據(jù)文件等),為了簡單起見,省略了內(nèi)存映射文件。_edata指針(glibc里面定義)指向數(shù)據(jù)段的最高地址。
2進程調(diào)用A=malloc(30K)以后,內(nèi)存空間如圖2:malloc函數(shù)會調(diào)用brk系統(tǒng)調(diào)用,將_edata指針往高地址推30K,就完成虛擬內(nèi)存分配。你可能會問:只要把_edata+30K就完成內(nèi)存分配了?事實是這樣的,_edata+30K只是完成虛擬地址的分配,A這塊內(nèi)存現(xiàn)在還是沒有物理頁與之對應(yīng)的,等到進程第一次讀寫A這塊內(nèi)存的時候,發(fā)生缺頁中斷,這個時候,內(nèi)核才分配A這塊內(nèi)存對應(yīng)的物理頁。也就是說,如果用malloc分配了A這塊內(nèi)容,然后從來不訪問它,那么,A對應(yīng)的物理頁是不會被分配的。
3進程調(diào)用B=malloc(40K)以后,內(nèi)存空間如圖3.


4進程調(diào)用C=malloc(200K)以后,內(nèi)存空間如圖4:默認情況下,malloc函數(shù)分配內(nèi)存,如果請求內(nèi)存大于128K(可由M_MMAP_THRESHOLD選項調(diào)節(jié)),那就不是去推_edata指針了,而是利用mmap系統(tǒng)調(diào)用,從堆和棧的中間分配一塊虛擬內(nèi)存。這樣子做主要是因為brk分配的內(nèi)存需要等到高地址內(nèi)存釋放以后才能釋放(例如,在B釋放之前,A是不可能釋放的),而mmap分配的內(nèi)存可以單獨釋放。當然,還有其它的好處,也有壞處,再具體下去,有興趣的同學(xué)可以去看glibc里面malloc的代碼了。
5進程調(diào)用D=malloc(100K)以后,內(nèi)存空間如圖5.
6進程調(diào)用free(C)以后,C對應(yīng)的虛擬內(nèi)存和物理內(nèi)存一起釋放


7進程調(diào)用free(B)以后,如圖7所示。B對應(yīng)的虛擬內(nèi)存和物理內(nèi)存都沒有釋放,因為只有一個_edata指針,如果往回推,那么D這塊內(nèi)存怎么辦呢?當然,B這塊內(nèi)存,是可以重用的,如果這個時候再來一個40K的請求,那么malloc很可能就把B這塊內(nèi)存返回回去了。
8進程調(diào)用free(D)以后,如圖8所示。B和D連接起來,變成一塊140K的空閑內(nèi)存。
9默認情況下:當最高地址空間的空閑內(nèi)存超過128K(可由M_TRIM_THRESHOLD選項調(diào)節(jié))時,執(zhí)行內(nèi)存緊縮操作(trim)。在上一個步驟free的時候,發(fā)現(xiàn)最高地址空閑內(nèi)存超過128K,于是內(nèi)存緊縮,變成圖9所示。

真相大白
說完內(nèi)存分配的原理,那么被測模塊在內(nèi)核態(tài)cpu消耗高的原因就很清楚了:每次請求來都malloc一塊2M的內(nèi)存,默認情況下,malloc調(diào)用mmap分配內(nèi)存,請求結(jié)束的時候,調(diào)用munmap釋放內(nèi)存。假設(shè)每個請求需要6個物理頁,那么每個請求就會產(chǎn)生6個缺頁中斷,在2000的壓力下,每秒就產(chǎn)生了10000多次缺頁中斷,這些缺頁中斷不需要讀取磁盤解決,所以叫做minflt;缺頁中斷在內(nèi)核態(tài)執(zhí)行,因此進程的內(nèi)核態(tài)cpu消耗很大。缺頁中斷分散在整個請求的處理過程中,所以表現(xiàn)為分配語句耗時(10us)相對于整條請求的處理時間(1000us)比重很小。

解決辦法
將動態(tài)內(nèi)存改為靜態(tài)分配,或者啟動的時候,用malloc為每個線程分配,然后保存在threaddata里面。但是,由于這個模塊的特殊性,靜態(tài)分配,或者啟動時候分配都不可行。另外,Linux下默認棧的大小限制是10M,如果在棧上分配幾M的內(nèi)存,有風(fēng)險。
禁止malloc調(diào)用mmap分配內(nèi)存,禁止內(nèi)存緊縮。
在進程啟動時候,加入以下兩行代碼:
mallopt(M_MMAP_MAX, 0); // 禁止malloc調(diào)用mmap分配內(nèi)存
mallopt(M_TRIM_THRESHOLD, -1); // 禁止內(nèi)存緊縮
效果:加入這兩行代碼以后,用ps命令觀察,壓力穩(wěn)定以后,majlt和minflt都為0。進程的系統(tǒng)態(tài)cpu從20降到10。

小結(jié)
可以用命令ps -o majflt minflt -C program來查看進程的majflt, minflt的值,這兩個值都是累加值,從進程啟動開始累加。在對高性能要求的程序做壓力測試的時候,我們可以多關(guān)注一下這兩個值。
如果一個進程使用了mmap將很大的數(shù)據(jù)文件映射到進程的虛擬地址空間,我們需要重點關(guān)注majflt的值,因為相比minflt,majflt對于性能的損害是致命的,隨機讀一次磁盤的耗時數(shù)量級在幾個毫秒,而minflt只有在大量的時候才會對性能產(chǎn)生影響。

何克勤 2010-03-28 10:48 發(fā)表評論
]]>
主站蜘蛛池模板: 湾仔区| 社旗县| 富源县| 安西县| 永吉县| 彰武县| 通化市| 大竹县| 上虞市| 商南县| 钟祥市| 芷江| 莱州市| 玉田县| 金山区| 惠州市| 嘉义市| 海淀区| 辛集市| 沽源县| 黄大仙区| 宁城县| 鹿泉市| 连平县| 定南县| 灌南县| 泰顺县| 柳河县| 泽州县| 伽师县| 马龙县| 右玉县| 衡阳县| 辽阳县| 丹棱县| 清苑县| 淮滨县| 射阳县| 郑州市| 个旧市| 石家庄市|