下面是CPPUnit的一個簡單例子.
class?SimpleCalcTest?:?public?CPPUNIT_NS::TestFixture
{
????CPPUNIT_TEST_SUITE(?SimpleCalcTest?);
????CPPUNIT_TEST(?testAdd?);
????CPPUNIT_TEST(?testSub?);
????CPPUNIT_TEST(?testMul);
????CPPUNIT_TEST(?testDiv?);
????CPPUNIT_TEST_SUITE_END();
private?:
????SimpleCalculator?*?sc;
public:
????virtual?void?setUp()
????{
????????sc?=?new?SimpleCalculator();
????}
????virtual?void?tearDown()
????{
????????delete?sc;
????}
????void?testAdd(){
????????CPPUNIT_ASSERT_EQUAL(?sc->add(5,6),?11);
????}
????void?testSub(){
????????CPPUNIT_ASSERT_EQUAL(?sc->sub(5,6),?-1?);
????}
????void?testMul(){
????????CPPUNIT_ASSERT_EQUAL(?sc->mul(5,6),?30?);
????}
????void?testDiv(){
????????CPPUNIT_ASSERT_EQUAL(?sc->div(12,6),?2?);
????}
};
//?把這個TestSuite注冊到名字為"alltest"的TestSuite中,?如果沒有定義會自動定義
//?也可以CPPUNIT_TEST_SUITE_REGISTRATION(?MathTest?);注冊到全局的一個未命名的TestSuite中.
CPPUNIT_TEST_SUITE_REGISTRATION(?SimpleCalcTest,?"alltest"?);
int?main()
{
????CPPUNIT_NS::TestResult?r;
????CPPUNIT_NS::TestResultCollector?result;
????r.addListener(?&result?);
????
????//?從注冊的TestSuite中獲取特定的TestSuite,?沒有參數獲取全局的未命名的TestSuite.
????CPPUNIT_NS::TestFactoryRegistry::getRegistry("alltest").makeTest()->run(?&r?);
????CPPUNIT_NS::TextOutputter?out(?&result,?std::cout?);
????out.write();
????return?0;
}
從上面的代碼可以看到, 使用CPPUnit 主要是兩個步驟:
1. 創建TestSuite
首先從CPPUNIT_NS::TestFixture 生成一個子類, 然后用宏 CPPUNIT_TEST_SUITE, CPPUNIT_TEST, CPPUNIT_TEST_SUITE_END 來定義要測試的各個小單元, 并且實現CPPUNIT_TEST 中定義的類函數; 在每個類函數中使用 CPPUNIT_ASSERT, CPPUNIT_ASSERT_MESSAGE, CPPUNIT_FAIL, CPPUNIT_ASSERT_EQUAL, CPPUNIT_ASSERT_EQUAL_MESSAGE, CPPUNIT_ASSERT_DOUBLES_EQUAL 等來對結果進行斷言.
然后通過宏 CPPUNIT_TEST_SUITE_REGISTRATION 將測試類注冊到TestSuite中.
2. Main
在main程序中對TestSuite 進行測試.
CPPUnit的更詳細的資料可以查看:
IBM 的
便利的開發工具 CppUnit 快速使用指南? 比較詳細的介紹了CppUnit
VCKBase的
CppUnit測試框架入門 詳細的介紹了VC6下MFC Dialog下的CPPUnit的使用
CSDN 的
如何使用CppUnit做單元測試 介紹了VC6的MFC 下的CPPUnit的使用, 和VCKBase的實現稍微有點差別
Meng Yan ( 孟巖 ) 的文章
CPPUnit Lite 對CPPUnit的使用和宏進行了簡單的分析
介紹了如何在Linux訪問MDB數據庫, 感覺非常有用:)
You will need the following:
Linux ( I’m running RedHat 9.0)
PHP
Apache
UnixODBC
MDBTools
INSTRUCTIONS
1) Download the UnixODBC RPM, found here. I installed unixODBC version 2.2.5.1.
rpm -ivh unixODBC-2-2.5-1.i386.rpm
2) Download the MDBTools rpm, found here. I installed mdbtools version 0.5.1. Read limitations!
rpm -ivh mdvtools-0.5-1.i386.rpm
3) Download the MDBTools ODBC driver rpm. Again I installed version 0.5-1.i386.rpm. Read limitations!
rpm -ivh mdbtools-odbc-0.5-1.i386.rpm
4) Add the mdbtools driver to your unixODBC config.
Create a new text file. NON-LINUX user: Beware do not do this on windows as you might get werid new lines, use vi.
[MDBToolsODBC]
Description = MDB Tools ODBC drivers
Driver = /usr/lib/libmdbodbc.so.0
Setup =
FileUsage = 1
CPTimeout =
CPReuse =
NOTE: The driver may be in /usr/local/lib/libmdbodbc.so.0. This happens if you build from source and use the make install command. The RPM puts it in /usr/lib.
Now install the driver using the file you created. This is called a template file.
odbcinst -i -d -f template.file
5) Define the datasource name. This is done in the /etc/odbc.ini file. So pull up this file in vi or another text editor and add the following lines.
[Dogs]
Description = Microsoft Access Database of Dogs
Driver = MDBToolsODBC
Database = /var/data/my_dog_db.mdb
Servername = localhost
UserName =
Password =
port = 5432
That’s it you should now have an odbc connection available. I will demonstrate using php, this assumes that your php is compiled with UnixODBC support, the version that ships with Redhat 9 does if yours does not then you can learn how here.
So I will write a quick php script to query my dogs database and print out the names and weights of my dogs.
$myDB = odbc_connect(”Dogs”,”",”");
$query = “select name, weight from dog_list”;
$result = odbc_exec($myDB, $query);
while (odbc_fetch_row($result)) {
print “Name = ” . odbc_result($result,”name”);
print “
Weight = ” . odbc_result($result,”weight”);
}
If you get a php error that says odbc_connect is not a function then see if you have php-odbc installed. Do rpm -qa php-odbc. If you see php-odbc returned then you have it if not install it., the rpm is available on the redhat discs.
Limitations:
- As of the time of writing this entry MDBTools did not support write access to the mdb files. This was fine for my purposes as I was reading data in and sticking it into a mysql database.
- There is a bug in MDBTools v0.5 which does not allow you to query tables or columns with an underscore. This was a bug I hit early on, but it has been fixed in new version 0.6 but that has not been released as of the time of writing this article. So I recompiled the 0.5 source code with the fix from the 0.6 CVS repository. I have bundled it into a 0.5 release and have the two rpms mentioned above here:
mdbtools-0.5-1.i386.rpm
mdbtools-odbc-0.5-1.i386.rpm
I would check the official download site before using my hacked version as I’m sure this bug will be fixed in 0.6 (plus rumor has write access will be present as well).
對Web進行壓力測試有很多工具, 比如Microsoft的application center test (ACT), 還有Mercury 的 LoadRunner, Apache的ab(Apache benchmark), 作為開源軟件的Siege 等。
LoadRunner是一個商業軟件,其功能非常的強大,可以自定義HTTP的頭, 訪問的URL, 以及各種訪問并發規則等.
apache的ab做重復壓力測試不錯,但是每次只能測試一個鏈接.
Siege(英文意思是圍攻)設計用于WEB開發這評估應用在壓力下的承受能力:可以根據配置對一個WEB站點進行多用戶的并發訪問,記錄每個用戶所有請求過程的相應時間,并在一定數量的并發訪問下重復進行。
Siege可以從http://www.joedog.org/siege/獲得, 它包含了一組壓力測試工具:
siege (1)
----------
使用樣例:
創建任務列表文件:www.xxx.com.url
http://www.xxx.com/a.html
http://www.xxx.com/b.html
....
siege -c 20 -r 2 -f www.chedong.com.url
參數說明:
-c 20 并發20個用戶
-r 2 重復循環2次
-f www.xxx.com.url 任務列表:URL列表
輸出樣例:
** Siege 2.59
** Preparing 20 concurrent users for battle. 這次“戰斗”準備了20個并發用戶
The server is now under siege.. done. 服務在“圍攻”測試中:
Transactions: 40 hits 完成40次處理
Availability: 100.00 % 成功率
Elapsed time: 7.67 secs 總共用時
Data transferred: 877340 bytes 共數據傳輸:877340字節
Response time: 1.65 secs 相應用時1.65秒:顯示網絡連接的速度
Transaction rate: 5.22 trans/sec 平均每秒完成5.22次處理:表示服務器后臺處理的速度
Throughput: 114385.92 bytes/sec 平均每秒傳送數據:114385.92字節
Concurrency: 8.59 最高并發數 8.59
Successful transactions: 40 成功處理次數
Failed transactions: 0 失敗處理次數
注意:由于速度很快,可能會達不到并發速度很高就已經完成。Response time顯示的是測試機器和被測試服務器之間網絡鏈接狀況。Transaction rate則表示服務器端任務處理的完成速度。
為了方便增量壓力測試,siege還包含了一些輔助工具:
bombardment (1)
---------------
用于按照增量用戶壓力測試:
bombardment urlfile.txt 5 3 4 1
初始化URL列表:urlfile.txt
初始化為:5個用戶
每次增加:3個用戶
運行:4次
每個客戶端之間的延遲為:1秒
siege2csv.pl (1)
----------------
siege2csv.pl將bombardment的輸出變成CSV格式:
Time Data Transferred Response Time Transaction Rate Throughput Concurrency Code 200 (note that this is horribly broken.)
242 60.22 603064 0.02 4.02 10014.35 0.08
605 59.98 1507660 0.01 10.09 25136.05 0.12
938 59.98 2337496 0.02 15.64 38971.26 0.26
1157 60 2883244 0.04 19.28 48054.07 0.78
參考:
開源測試工具:http://www.opensourcetesting.org/performance.php
Windows的默認GUI shell是資源管理器explorer.exe,bblean可以把windows裝點成Xwindow的樣子,還有四個虛擬桌面。
bblean的下載:http://bb4win.sourceforge.net/bblean/,屬于綠色軟件,解壓縮即可。其主運行程序是blackbox.exe,桌面上點擊右鍵即可看到設置菜單。設置和插件的修改都很簡單,都是用配置文件設置的,用純文本編輯器打開擴展名為.rc的文件就可以看到各種參數設置。修改以后重新啟動blackbox.exe。在bblean工具條和插件顯示工具條上點擊鼠標右鍵或者ctrl+鼠標右鍵,能夠看到設置選項。
bblean能夠很輕松的設置各種樣式顯示效果,比如longhorn/vista才會有的alpha透明效果,一些插件包括hue值也可以調節,一直覺得Xwindow比ms window好看,確實如此。
bblean可以通過各種插件,支持不同的功能。插件下載列表:http://xoblite.net/plugins.html,推薦iconbox、calendar,放在桌面上比較實用。
在使用上做簡單的說明:
1. 綠色軟件且體積小(139k),解壓即可,無須安裝,雙擊blackbox.exe啟動。
2. 在桌面上點擊右鍵,所有的操作全在這個彈出菜單里了。
3. 彈出菜單以后,左鍵點擊下級菜單標題可以拖動將其擺在桌面上的任何地方。
4. 對任務欄以及任何插件,Ctrl+右鍵可以對它們進行配置。
5. 選擇你喜歡的樣式:
?? 右鍵菜單>Styles
6. 使用清晰的字體:
?? 右鍵菜音>BlackBox>Conifguration>Graphics>Global Font Override? 選中
7. 還可以讓bblean代替explorer成為默認的窗口管理器:
?? 右鍵菜單>BlackBox>Install>Install
8. 退出bblean返回windows:
?? 右鍵菜單>BlackBox>Quit
9. 如果bblean異常問題。別慌,不要忘記windows下的Ctrl+Alt+Del。
比較好的網站:
BBLean的快捷鍵的介紹
http://bb4win.sourceforge.net/bblean/docs/bblean_inc1.htm
http://bb.nlc.no
http://box.crackmonkey.us
http://themes.freshmeat.net/browse/920
http://bb4win.sourceforge.net/latest/Latest
http://www.desktopian.org/bb/plugins.html
http://xoblite.net/plugins.html
Diff 在Linux下非常有用, 最近在對定向蜘蛛的研究中, 發現網頁的信息大部分在Diff部分, 所以考慮了Diff的原理.
1. Unix Diff
Linux Diff 的基本原理在文 [ Dynamic Programming | http://www.sbc.su.se/~pjk/molbioinfo2001/dynprog/dynamic.html ] 中介紹的很詳細了.如果一個文件有n行, 則需要對一個n*n的矩陣進行計算以得到最佳匹配.
2. 貪心算法
在[ Windiff原理初探 | http://www.2maomao.com/blog/how-windiff-works-continued-1/ ]中看到一個貪心算法, 感覺在某種情況下還是比較適合的,但是有些情況還是存在一些問題.
貪心算法的基本思路是: 從問題的某一個初始解出發逐步逼近給定的目標,以盡可能快的地求得更好的解。當達到某算法中的某一步不能再繼續前進時,算法停止。
首先還是簡化問題:每個行根據內容映射到一個整型值, 這就將整個問題簡化為整形數組的比較,姑且稱之為Anew和Aold。
程序處理流程簡述:
while (還沒到頭)
{
?? while (還可以繼續找,還可以更貪心)
?? {
????? 找到下一個匹配的(依次對ANew的元素,查找其在AOld中的位置)
????? if (找的到)
???????? 計算其貪心值,如果當前更貪則用這個匹配做當前最佳匹配
????? else
???????? break;
?? }
}
輸出剩余的未匹配節點。
其中“還可以更貪心”是如何判定的呢?
首先定義左臂(leftHand,Anew里面新匹配位置距上一次被采用的匹配的距離),右臂(rightHand,Aold里面新匹配位置距上一次被采用的匹配的距離)。
要找一個目標,在這里我給定的目標是左右平衡情況下的最近匹配。比如一個匹配左臂1,右臂10,另一個匹配是左臂3,右臂3,這時候傾向于選擇后一個匹配。
為了公式化和便于計算,我采用一個簡單的具有這個邏輯的函數:leftHand*leftHand + rightHand*rightHand的值(貪心值)最小。
定義了這個目標以后,你會發現只要左臂過長,lefthand自身的平方超過上個候選匹配的貪心值,則可以停止往下計算了。
然后這個循環繼續下去,直到找到所有的匹配,對每兩個匹配之間,如果有內容,則表示有Add/Delete/Modify發生,根據左臂右臂是否為0可以明顯區分。
舉個例子:
Anew Aold
1???? 1
2???? 1
3???? 3
2???? 2
4???? 4
首次匹配找到1<-->1,匹配立即停止,因為1*1 + 1*1 = 2,2*2 > 2,所以沒有比較進行下去了.
然后往下找到2<-->2,這時候左臂等于1,右臂等于3,(注意臂長是相對上一次被采用的匹配的),1*1 + 3*3 = 10,當前貪心值是10;然后往下找到3<-->3,左臂為2,右臂為2,2*2 + 2*2 = 8,這個匹配優于上一個匹配;然后繼續往下找到2<-->2(左邊第二個2),左臂3,右臂3,3*3本身的平方已經超過目前的貪心值8,沒有必要再繼續往下匹配,這一輪匹配查詢結束。
這里可以看出采用平方和做貪心算式的好處,很快可以收斂,而且符合“左右平衡”以及“最近匹配”。
后面2和4的分析略去。
但是這個算法存在一個問題,它的算法只針對單行最優, 而無法實現多行的最優, 比如
Anew Aold
a??? a
b??? a
c??? b
像上面的兩個文件, Anew:1 匹配 Aold:1, 但是應該使 Anew:1 匹配 Aold:2, 這樣子才可以使Anew中序列ab 與 Aold的序列ab匹配.
3. 下一步工作
?* 調整貪心值計算函數
?* 貪心算法 + 部分動態規劃
安裝
----
總共需要下面的文件
1. Eclipse的CDT 插件
?? http://www.eclipse.org/cdt/
2. MinGW, 主要提供編譯C/C++文件的GCC、GDB 和 Make
?? http://www.mingw.org/download.shtml
安裝CDT插件和MinGW,然后修改Windows的環境變量(設MinGW安裝在D:\MinGW)
"PATH" = "D:\MinGW\bin;%PATH%"
"LIBRARY_PATH" = "D:\MinGW\lib"
"C_INCLUDE_PATH" = "D:\MinGW\include"
"CPLUS_INCLUDE_PATH" = "D:\MinGW\include\c++\3.2.3;D:\MinGW\include\c++\3.2.3\mingw32;D:\MinGW\include\c++\3.2.3\backward;D:\MinGW\include"
Eclipse 具體使用
------------
1. 新建項目
?? 打開eclipse,new->project->Standard Make C++ Project
2. 配置Make命令
?? 在Project->Properties->C/C++ make project
?? build command 的“make”改為“mingw32-make”,再按“應用” “確定”
3. 新建源代碼文件和Makefile文件
4. 配置生成目標
window->show view ->make targets? add maketargets? 按“ok”,再雙擊這個maketargets 它就自動幫你make 生成exe
5. 在Project->Properties->C/C++ Make Project->Binary Parser 把ELF Parser改成PE Windows Parser
6. 大功告成? Run->Run as->C Local Application
有一種寫優先讀寫鎖,有如下特點:
1)多個讀者可以同時進行讀
2)寫者必須互斥(只允許一個寫者寫,也不能讀者寫者同時進行)
3)寫者優先于讀者(一旦有寫者,則后續讀者必須等待,喚醒時優先考慮寫者)
在Solaris 中直接提供了讀寫鎖, 但是在Linux 中只提供了線程的讀寫鎖, 這里記錄了一些讀寫鎖的資料.
1.Solaris .vs. Linux Posix 庫函數
Solaris 庫(lib 線程) | Linux POSIX 庫(libp 線程) | 操作 |
sema_destroy() | sem_destroy() | 銷毀信號狀態。 |
sema_init() | sem_init() | 初始化信號。 |
sema_post() | sem_post() | 增加信號。 |
sema_wait() | sem_wait() | 阻止信號計數。 |
sema_trywait() | sem_trywait() | 減少信號計數。 |
mutex_destroy() | pthread_mutex_destroy() | 銷毀或禁用與互斥對象相關的狀態。 |
mutex_init() | pthread_mutex_init() | 初始化互斥變量。 |
mutex_lock() | pthread_mutex_lock() | 鎖定互斥對象和塊,直到互斥對象被釋放。 |
mutex_unlock() | pthread_mutex_unlock() | 釋放互斥對象。 |
cond_broadcast() | pthread_cond_broadcast() | 解除對等待條件變量的所有線程的阻塞。 |
cond_destroy() | pthread_cond_destroy() | 銷毀與條件變量相關的任何狀態。 |
cond_init() | pthread_cond_init() | 初始化條件變量。 |
cond_signal() | pthread_cond_signal() | 解除等待條件變量的下一個線程的阻塞。 |
cond_wait() | pthread_cond_wait() | 阻止條件變量,并在最后釋放它。 |
rwlock_init() | pthread_rwlock_init() | 初始化讀/寫鎖。 |
rwlock_destroy() | pthread_rwlock_destroy() | 鎖定讀/寫鎖。 |
rw_rdlock() | pthread_rwlock_rdlock() | 讀取讀/寫鎖上的鎖。 |
rw_wrlock() | pthread_rwlock_wrlock() | 寫讀/寫鎖上的鎖。 |
rw_unlock() | pthread_rwlock_unlock() | 解除讀/寫鎖。 |
rw_tryrdlock() | pthread_rwlock_tryrdlock() | 讀取非阻塞讀/寫鎖上的鎖。 |
rw_trywrlock() | pthread_rwlock_trywrlock() | 寫非阻塞讀/寫鎖上的鎖。 |
2.使用mutex 來實現
設置三個互斥信號量:
rwmutex?? ??? ?用于寫者與其他讀者/寫者互斥的訪問共享數據
rmutex?? ??? ?用于讀者互斥的訪問讀者計數器readcount
nrmutex?? ??? ?用于寫者等待已進入讀者退出,所有讀者退出前互斥寫操作
var?? rwmutex,rmutex,nrmutex:semaphore:=1,1,1; ?
int?? readcount=0;
cobegin
?? ?reader begin
?? ??? ?P(rwmutex);
?? ??? ?P(rmutex);
?? ??? ?readcount++;
?? ??? ?if (readcount == 1) P(nrmutex);? //有讀者進入,互斥寫操作
?? ??? ?V(rmutex);
?? ??? ?V(rwmutex);? //及時釋放讀寫互斥信號量,允許其它讀、寫進程申請資源讀數據;
?? ??? ?
?? ??? ?P(rmutex);
?? ??? ?readcount--;
?? ??? ?if(readcount == 0) V(nrmutex);? //所有讀者退出,允許寫更新
?? ??? ?V(rmutex);
?? ?End
?? ?
?? ?writer begin
?? ??? ?P(rwmutex);??? //互斥后續其它讀者、寫者
?? ??? ?P(nrmutex);??? //如有讀者正在讀,等待所有讀者讀完
?? ??? ?寫更新;
?? ??? ?V(nrmutex);??? //允許后續新的第一個讀者進入后互斥寫操作 ?
?? ??? ?V(rwmutex);??? //允許后續新讀者及其它寫者
?? ?End ?
coend??
3. 利用pthread_cond_* & pthread_mutex_* 實現rw_lock
#include?<pthread.h>
#include?<cstdlib>
#include?<ctime>
#include?<iostream>
using?namespace?std;
class?RWLock?{
private?:
????pthread_mutex_t?cnt_mutex;
????pthread_cond_t?rw_cond;
????int?rd_cnt,?wr_cnt;
????RWLock(const?RWLock&);
????RWLock&?operator=?(const?RWLock&);
public?:
????RWLock():?rd_cnt(0),wr_cnt(0)
????????{
????????????pthread_mutex_init(&cnt_mutex,?NULL);
????????????pthread_cond_init(&rw_cond,?NULL);
????????}
????void?get_shared_lock()
????????{
????????????pthread_mutex_lock(&cnt_mutex);
????????????while?(wr_cnt?>0)
????????????????{
????????????????????pthread_cond_wait(&rw_cond,&cnt_mutex);
????????????????}
????????????rd_cnt++;
????????????pthread_mutex_unlock(&cnt_mutex);
????????}
????void?release_shared_lock()
????????{
????????????pthread_mutex_lock(&cnt_mutex);
????????????rd_cnt--;
????????????if?(0?==?rd_cnt)
????????????????{
????????????????????pthread_cond_signal(&rw_cond);
????????????????}
????????????pthread_mutex_unlock(&cnt_mutex);
????????}
????void?get_exclusive_lock()
????????{
????????????pthread_mutex_lock(&cnt_mutex);
????????????while?(rd_cnt+wr_cnt>0)
????????????????{
????????????????????pthread_cond_wait(&rw_cond,&cnt_mutex);
????????????????}
????????????wr_cnt++;
????????????pthread_mutex_unlock(&cnt_mutex);
????????}
????void?release_exclusive_lock()
????????{
????????????pthread_mutex_lock(&cnt_mutex);
????????????wr_cnt--;
????????????pthread_cond_broadcast(&rw_cond);
????????????pthread_mutex_unlock(&cnt_mutex);
????????}
????~RWLock()
????????{
????????????pthread_mutex_destroy(&cnt_mutex);
????????????pthread_cond_destroy(&rw_cond);
????????}
};
class?Test
{
private?:????
????RWLock?lock;
????
????static?void*?shared_task_handler(void*?arg)
????????{
????????????Test*?testptr?=?static_cast<Test*>(arg);
????????????testptr->lock.get_shared_lock();
????????????//do?the?shared?task?here
????????????testptr->lock.release_shared_lock();
????????}
????static?void?*?exclusive_task_handler(void?*?arg)
????????{
????????????Test*?testptr?=?static_cast<Test*>(arg);
????????????testptr->lock.get_exclusive_lock();
????????????//do?the?exclusive?task?here
????????????testptr->lock.release_exclusive_lock();
????????}
public?:
????typedef?void*?(*ThreadFunc)?(void*);
????void?start()
????????{
????????????srand(time(NULL));
????????????const?int?THREADS_NO=rand()%100;
????????????pthread_t*?threads?=?new?pthread_t[THREADS_NO];
????????????for(int?i=0;?i<THREADS_NO;?i++)
????????????????{
????????????????????ThreadFunc?tmpfunc?=?rand()%2??shared_task_handler?:?exclusive_task_handler;
????????????????????if?(pthread_create(threads+i,NULL,tmpfunc,this))
????????????????????????{
????????????????????????????cerr?<<?"pthread_create?fails"?<<?endl;
????????????????????????????exit(1);
????????????????????????}
????????????????}
????????????for(int?i=0;?i<THREADS_NO;?i++)
????????????????{
????????????????????pthread_join(threads[i],NULL);
????????????????}
????????????delete[]?threads;
????????}
};
int?main()
{
????Test?tmptest;
????tmptest.start();
}
--------------------------
參考:
Solaris 執行緒與同步機制之實例Posix線程編程指南
Linux 系統下的多線程遵循POSIX線程接口,稱為pthread。編寫Linux下的多線程程序,需要使用頭文件pthread.h,連接時需要使用庫 libpthread.a。
1. 線程的創建和使用
線程的創建是用下面的幾個函數來實現的.
int
?pthread_create(pthread_t?
*
thread,pthread_attr_t?
*
attr,?
void
?
*
(
*
start_routine)(
void
?
*
),
void
?
*
arg);
void
?pthread_exit(
void
?
*
retval);
int
?pthread_join(pthread?
*
thread,
void
?
**
thread_return);
pthread_create創建一個線程,thread是用來表明創建線程的ID,attr指出線程創建時候的屬性,我們用NULL來表明使用缺省屬性.start_routine函數指針是線程創建成功后開始執行的函數,arg是這個函數的唯一一個參數.表明傳遞給start_routine 的參數.
pthread_exit函數和exit函數類似用來退出線程.這個函數結束線程,釋放函數的資源,并在最后阻塞,直到其他線程使用
pthread_join函數等待它.然后將*retval的值傳遞給**thread_return.由于這個函數釋放所以的函數資源,所以 retval不能夠指向函數的局部變量.
pthread_join和wait調用一樣用來等待指定的線程.
下面展示一個最簡單的多線程程序。
#include?
<
stdio.h
>
#include?
<
pthread.h
>
void
?thread(
void
)
{
?
int
?i;
?
for
(i
=
0
;i
<
3
;i
++
)
?printf(
"
This?is?a?pthread.\n
"
);
}
int
?main(
void
)
{
?pthread_t?id;
?
int
?i,ret;
?ret
=
pthread_create(
&
id,NULL,(
void
?
*
)?thread,NULL);
?
if
(ret
!=
0
)
?{
??printf?(
"
Create?pthread?error!\n
"
);
??exit?(
1
);
?}
?
for
(i
=
0
;i
<
3
;i
++
)
??printf(
"
This?is?the?main?process.\n
"
);
?pthread_join(id,NULL);
?
return
?(
0
);
}
2. 修改線程的屬性
上面用pthread_create函數創建了一個線程,在這個線程中,我們使用了默認參數,即將該函數的第二個參數設為NULL。屬性結構為pthread_attr_t,它同樣在頭文件/usr/include/pthread.h中定義。屬性值不能直接設置,須使用相關函數進行操作,初始化的函數為pthread_attr_init,這個函數必須在pthread_create函數之前調用。屬性對象主要包括是否綁定、是否分離、堆棧地址、堆棧大小、優先級。默認的屬性為非綁定、非分離、缺省1M的堆棧、與父進程同樣級別的優先級。
關于線程的綁定,牽涉到另外一個概念:輕進程(LWP:Light Weight Process)。輕進程可以理解為內核線程,它位于用戶層和系統層之間。系統對線程資源的分配、對線程的控制是通過輕進程來實現的,一個輕進程可以控制一個或多個線程。默認狀況下,啟動多少輕進程、哪些輕進程來控制哪些線程是由系統來控制的,這種狀況即稱為非綁定的。綁定狀況下,則顧名思義,即某個線程固定的"綁"在一個輕進程之上。被綁定的線程具有較高的響應速度,這是因為CPU時間片的調度是面向輕進程的,綁定的線程可以保證在需要的時候它總有一個輕進程可用。通過設置被綁定的輕進程的優先級和調度級可以使得綁定的線程滿足諸如實時反應之類的要求。
設置線程綁定狀態的函數為pthread_attr_setscope,它有兩個參數,第一個是指向屬性結構的指針,第二個是綁定類型,它有兩個取值:PTHREAD_SCOPE_SYSTEM(綁定的)和PTHREAD_SCOPE_PROCESS(非綁定的)。下面的代碼即創建了一個綁定的線程。
#include?
<
pthread.h
>
pthread_attr_t?attr;
pthread_t?tid;
/*
初始化屬性值,均設為默認值
*/
pthread_attr_init(
&
attr);
pthread_attr_setscope(
&
attr,?PTHREAD_SCOPE_SYSTEM);
pthread_create(
&
tid,?
&
attr,?(
void
?
*
)?my_function,?NULL);
線程的分離狀態決定一個線程以什么樣的方式來終止自己。在上面的例子中,我們采用了線程的默認屬性,即為非分離狀態,這種情況下,原有的線程等待創建的線程結束。只有當pthread_join()函數返回時,創建的線程才算終止,才能釋放自己占用的系統資源。而分離線程不是這樣子的,它沒有被其他的線程所等待,自己運行結束了,線程也就終止了,馬上釋放系統資源。程序員應該根據自己的需要,選擇適當的分離狀態。設置線程分離狀態的函數為 pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。第二個參數可選為PTHREAD_CREATE_DETACHED(分離線程)和 PTHREAD_CREATE_JOINABLE(非分離線程)。這里要注意的一點是,如果設置一個線程為分離線程,而這個線程運行又非常快,它很可能在 pthread_create函數返回之前就終止了,它終止以后就可能將線程號和系統資源移交給其他的線程使用,這樣調用pthread_create的線程就得到了錯誤的線程號。要避免這種情況可以采取一定的同步措施,最簡單的方法之一是可以在被創建的線程里調用 pthread_cond_timewait函數,讓這個線程等待一會兒,留出足夠的時間讓函數pthread_create返回。設置一段等待時間,是在多線程編程里常用的方法。但是注意不要使用諸如wait()之類的函數,它們是使整個進程睡眠,并不能解決線程同步的問題。
另外一個可能常用的屬性是線程的優先級,它存放在結構sched_param中。用函數pthread_attr_getschedparam和函數 pthread_attr_setschedparam進行存放,一般說來,我們總是先取優先級,對取得的值修改后再存放回去。下面即是一段簡單的例子。
#include?
<
pthread.h
>
#include?
<
sched.h
>
pthread_attr_t?attr;
pthread_t?tid;
sched_param?param;
int
?newprio
=
20
;
pthread_attr_init(
&
attr);
pthread_attr_getschedparam(
&
attr,?
&
param);
param.sched_priority
=
newprio;
pthread_attr_setschedparam(
&
attr,?
&
param);
pthread_create(
&
tid,?
&
attr,?(
void
?
*
)myfunction,?myarg);
[參考]
http://fanqiang.chinaunix.net/a4/b2/20010508/113838.html
http://fanqiang.chinaunix.net/a4/b8/20010811/0905001105.html
在Linux中存在下面幾種進程間通信方式:
1.POSIX無名信號量
2.System V信號量
3.System V消息隊列
4.System V共享內存
5.管道(FIFO)
--------------------------------------------------------------------------------
1。POSIX無名信號量
如果你學習過操作系統,那么肯定熟悉PV操作了.PV操作是原子操作.也就是操作是不可以中斷的,在一定的時間內,只能夠有一個進程的代碼在CPU上面執行.在系統當中,有時候為了順利的使用和保護共享資源,大家提出了信號的概念. 假設我們要使用一臺打印機, 如果在同一時刻有兩個進程在向打印機輸出,那么最終的結果會是什么呢.為了處理這種情況,POSIX標準提出了有名信號量和無名信號量的概念,由于 Linux只實現了無名信號量,我們在這里就只是介紹無名信號量了. 信號量的使用主要是用來保護共享資源,使的資源在一個時刻只有一個進程所擁有.為此我們可以使用一個信號燈.當信號燈的值為某個值的時候,就表明此時資源不可以使用.否則就表>示可以使用. 為了提供效率,系統提供了下面幾個函數?
POSIX的無名信號量的函數有以下幾個:
int
?sem_init(sem_t?
*
sem,
int
?pshared,unsigned?
int
?value);
int
?sem_destroy(sem_t?
*
sem);
int
?sem_wait(sem_t?
*
sem);
int
?sem_trywait(sem_t?
*
sem);
int
?sem_post(sem_t?
*
sem);
int
?sem_getvalue(sem_t?
*
sem);
sem_init創建一個信號燈,并初始化其值為value.pshared決定了信號量能否在幾個進程間共享.由于目前Linux還沒有實現進程間共享信號燈,所以這個值只能夠取0.
sem_destroy是用來刪除信號燈的.
sem_wait調用將阻塞進程,直到信號燈的值大于0.這個函數返回的時候自動的將信號燈的值的件一.
sem_post和sem_wait相反,是將信號燈的內容加一同時發出信號喚醒等待的進程..
sem_trywait和sem_wait相同,不過不阻塞的,當信號燈的值為0的時候返回EAGAIN,表示以后重試.
sem_getvalue得到信號燈的值.
由于Linux不支持,我們沒有辦法用源程序解釋了.
2。System V信號量
信號燈的主要用途是保護臨界資源(在一個時刻只被一個進程所擁有). System V信號量的函數主要有下面幾個.
key_t?ftok(
char
?
*
pathname,
char
?proj);
int
?semget(key_t?key,
int
?nsems,
int
?semflg);
int
?semctl(
int
?semid,
int
?semnum,
int
?cmd,union?semun?arg);
int
?semop(
int
?semid,
struct
?sembuf?
*
spos,
int
?nspos);
struct
?sembuf{
short
?sem_num;?
/*
?使用那一個信號?
*/
short
?sem_op;?
/*
?進行什么操作?
*/
short
?sem_flg;?
/*
?操作的標志?
*/
};
ftok函數是根據pathname和proj來創建一個關鍵字.
semget創建一個信號量.成功時返回信號的ID,key是一個關鍵字,可以是用ftok創建的也可以是IPC_PRIVATE表明由系統選用一個關鍵字. nsems表明我們創建的信號個數.semflg是創建的權限標志,和我們創建一個文件的標志相同.
semctl對信號量進行一系列的控制.semid是要操作的信號標志,semnum是信號的個數,cmd是操作的命令.經常用的兩個值是:SETVAL(設置信號量的值)和IPC_RMID(刪除信號燈).arg是一個給cmd的參數.
semop是對信號進行操作的函數.semid是信號標志,spos是一個操作數組表明要進行什么操作,nspos表明數組的個數. 如果 sem_op大于0,那么操作將sem_op加入到信號量的值中,并喚醒等待信號增加的進程. 如果為0,當信號量的值是0的時候,函數返回,否則阻塞直到信號量的值為0. 如果小于0,函數判斷信號量的值加上這個負值.如果結果為0喚醒等待信號量為0的進程,如果小與0函數阻塞.如果大于0,那么從信號量里面減去這個值并返回.
下面我們一以一個實例來說明這幾個函數的使用方法.
#define
?PERMS?S_IRUSR|S_IWUSR
void
?init_semaphore_struct(
struct
?sembuf?
*
sem,
int
?semnum,
int
?semop,
int
?semflg)
{
?
/*
?初始話信號燈結構?
*/
?sem
->
sem_num
=
semnum;
?sem
->
sem_op
=
semop;
?sem
->
sem_flg
=
semflg;
}
int
?del_semaphore(
int
?semid)
{
?
/*
?信號燈并不隨程序的結束而被刪除,如果我們沒刪除的話(將1改為0)
?可以用ipcs命令查看到信號燈,用ipcrm可以刪除信號燈的
?
*/
?
#if
?1
?
return
?semctl(semid,
0
,IPC_RMID);
?
#endif
}
int
?main(
int
?argc,
char
?
**
argv)
{
?
char
?buffer[MAX_CANON],
*
c;
?
int
?i,n;
?
int
?semid,semop_ret,status;
?pid_t?childpid;
?
struct
?sembuf?semwait,semsignal;
?
if
((argc
!=
2
)
||
((n
=
atoi(argv[
1
]))
<
1
))
?{
??fprintf(stderr,
"
Usage:%s?number\n\a
"
,argv[
0
]);
??exit(
1
);
?}
?
?
/*
?使用IPC_PRIVATE?表示由系統選擇一個關鍵字來創建??
*/
?
/*
?創建以后信號燈的初始值為0?
*/
?
if
((semid
=
semget(IPC_PRIVATE,
1
,PERMS))
==-
1
)
?{
??fprintf(stderr,
"
[%d]:Acess?Semaphore?Error:%s\n\a
"
,
??getpid(),strerror(errno));
??exit(
1
);
?}
?
/*
?semwait是要求資源的操作(-1)?
*/
?init_semaphore_struct(
&
semwait,
0
,
-
1
,
0
);
?
/*
?semsignal是釋放資源的操作(+1)?
*/
?init_semaphore_struct(
&
semsignal,
0
,
1
,
0
);
?
/*
?開始的時候有一個系統資源(一個標準錯誤輸出)?
*/
?
if
(semop(semid,
&
semsignal,
1
)
==-
1
)
?{
??fprintf(stderr,
"
[%d]:Increment?Semaphore?Error:%s\n\a
"
,?getpid(),?strerror(errno));
??
if
(del_semaphore(semid)
==-
1
)
???fprintf(stderr,
"
[%d]:Destroy?Semaphore?Error:%s\n\a
"
,?getpid(),?strerror(errno));
??exit(
1
);
?}
?
/*
?創建一個進程鏈?
*/
?
for
(i
=
0
;i
<
n;i
++
)
??
if
(childpid
=
fork())?
break
;
?sprintf(buffer,
"
[i=%d]-->[Process=%d]-->[Parent=%d]-->[Child=%d]\n
"
,?i,getpid(),getppid(),childpid);
?c
=
buffer;
?
/*
?這里要求資源,進入原子操作?
*/
?
while
(((semop_ret
=
semop(semid,
&
semwait,
1
))
==-
1
)
&&
(errno
==
EINTR));
?
if
(semop_ret
==-
1
)
?{
??fprintf(stderr,
"
[%d]:Decrement?Semaphore?Error:%s\n\a
"
,
??getpid(),strerror(errno));
?}
?
else
?{
??
while
(
*
c
!=
'
\0
'
)fputc(
*
c
++
,stderr);
??
/*
?原子操作完成,趕快釋放資源?
*/
??
while
(((semop_ret
=
semop(semid,
&
semsignal,
1
))
==-
1
)
&&
(errno
==
EINTR));
??
if
(semop_ret
==-
1
)
???fprintf(stderr,
"
[%d]:Increment?Semaphore?Error:%s\n\a
"
,?getpid(),strerror(errno));
?}
?
/*
?不能夠在其他進程反問信號燈的時候,我們刪除了信號燈?
*/
?
while
((wait(
&
status)
==-
1
)
&&
(errno
==
EINTR));
?
/*
?信號燈只能夠被刪除一次的?
*/
?
if
(i
==
1
)
??
if
(del_semaphore(semid)
==-
1
)
?fprintf(stderr,
"
[%d]:Destroy?Semaphore?Error:%s\n\a
"
,?getpid(),strerror(errno));
?exit(
0
);
}
3。SystemV消息隊列
為了便于進程之間通信,我們可以使用管道通信 SystemV也提供了一些函數來實現進程的通信.這就是消息隊列.
int
?msgget(key_t?key,
int
?msgflg);
int
?msgsnd(
int
?msgid,
struct
?msgbuf?
*
msgp,
int
?msgsz,
int
?msgflg);
int
?msgrcv(
int
?msgid,
struct
?msgbuf?
*
msgp,
int
?msgsz,
long
?msgtype,
int
?msgflg);
int
?msgctl(Int?msgid,
int
?cmd,
struct
?msqid_ds?
*
buf);
struct
?msgbuf?{
long
?msgtype;???
/*
?消息類型?
*/
.?
/*
?其他數據類型?
*/
}
msgget函數和semget一樣,返回一個消息隊列的標志.
msgctl和semctl是對消息進行控制.
msgsnd和msgrcv函數是用來進行消息通訊的.msgid是接受或者發送的消息隊列標志. msgp是接受或者發送的內容.msgsz是消息的大小. 結構msgbuf包含的內容是至少有一個為msgtype.其他的成分是用戶定義的.對于發送函數msgflg指出緩沖區用完時候的操作.接受函數指出無消息時候的處理.一般為 0. 接收函數msgtype指出接收消息時候的操作.
如果msgtype=0,接收消息隊列的第一個消息.大于0接收隊列中消息類型等于這個值的第一個消息.小于0接收消息隊列中小于或者等于 msgtype絕對值的所有消息中的最小一個消息. 我們以一個實例來解釋進程通信.下面這個程序有server和client組成.先運行服務端后運行客戶端.
服務端 server.c
#define
???MSG_FILE?"server.c"
#define
???BUFFER?255
#define
???PERM?S_IRUSR|S_IWUSR
struct
?msgtype?{
long
?mtype;
char
?buffer[BUFFER
+
1
];
};
int
?main()
{
????
struct
?msgtype?msg;
????key_t?key;
????
int
?msgid;
????
if
((key
=
ftok(MSG_FILE,
'
a
'
))
==-
1
)
????{
????????fprintf(stderr,
"
Creat?Key?Error:%s\a\n
"
,strerror(errno));
????????exit(
1
);
????}
????
if
((msgid
=
msgget(key,PERM
|
IPC_CREAT
|
IPC_EXCL))
==-
1
)
????{
????????fprintf(stderr,
"
Creat?Message??Error:%s\a\n
"
,strerror(errno));
????????exit(
1
);
????}
????
while
(
1
)
????{
????????msgrcv(msgid,
&
msg,
sizeof
(
struct
?msgtype),
1
,
0
);
????????fprintf(stderr,
"
Server?Receive:%s\n
"
,msg.buffer);
????????msg.mtype
=
2
;
????????msgsnd(msgid,
&
msg,
sizeof
(
struct
?msgtype),
0
);
????}
????exit(
0
);
}
--------------------------------------------------------------------------------
客戶端(client.c)
#define
???MSG_FILE?"server.c"
#define
???BUFFER?255
#define
???PERM?S_IRUSR|S_IWUSR
struct
?msgtype?{
long
?mtype;
char
?buffer[BUFFER
+
1
];
};
int
?main(
int
?argc,
char
?
**
argv)
{
?
struct
?msgtype?msg;
?key_t?key;
?
int
?msgid;
?
if
(argc
!=
2
)
?{
??fprintf(stderr,
"
Usage:%s?string\n\a
"
,argv[
0
]);
??exit(
1
);
?}
?
if
((key
=
ftok(MSG_FILE,
'
a
'
))
==-
1
)
?{
??fprintf(stderr,
"
Creat?Key?Error:%s\a\n
"
,strerror(errno));
??exit(
1
);
?}
?
if
((msgid
=
msgget(key,PERM))
==-
1
)
?{
??fprintf(stderr,
"
Creat?Message??Error:%s\a\n
"
,strerror(errno));
??exit(
1
);
?}
?msg.mtype
=
1
;
?strncpy(msg.buffer,argv[
1
],BUFFER);
?msgsnd(msgid,
&
msg,
sizeof
(
struct
?msgtype),
0
);?
?memset(
&
msg,
'
\0
'
,
sizeof
(
struct
?msgtype));
?msgrcv(msgid,
&
msg,
sizeof
(
struct
?msgtype),
2
,
0
);
?fprintf(stderr,
"
Client?receive:%s\n
"
,msg.buffer);
?exit(
0
);
}??
注意服務端創建的消息隊列最后沒有刪除,我們要使用ipcrm命令來刪除的.
4。SystemV共享內存
還有一個進程通信的方法是使用共享內存.SystemV提供了以下幾個函數以實現共享內存.
int
?shmget(key_t?key,
int
?size,
int
?shmflg);
void
?
*
shmat(
int
?shmid,
const
?
void
?
*
shmaddr,
int
?shmflg);
int
?shmdt(
const
?
void
?
*
shmaddr);
int
?shmctl(
int
?shmid,
int
?cmd,
struct
?shmid_ds?
*
buf);
shmget和shmctl沒有什么好解釋的.size是共享內存的大小.
shmat是用來連接共享內存的.shmdt是用來斷開共享內存的.
shmaddr,shmflg我們只要用0代替就可以了.在使用一個共享內存之前我們調用 shmat得到共享內存的開始地址,使用結束以后我們使用shmdt斷開這個內存.?
#define
?PERM?S_IRUSR|S_IWUSR
int
?main(
int
?argc,
char
?
**
argv)
{
?
?
int
?shmid;
?
char
?
*
p_addr,
*
c_addr;
?
if
(argc
!=
2
)
?{
??fprintf(stderr,
"
Usage:%s\n\a
"
,argv[
0
]);
??exit(
1
);
?}
?
if
((shmid
=
shmget(IPC_PRIVATE,
1024
,PERM))
==-
1
)
?{
??fprintf(stderr,
"
Create?Share?Memory?Error:%s\n\a
"
,strerror(errno));
??exit(
1
);
?}
?
if
(fork())
?{
??p_addr
=
shmat(shmid,
0
,
0
);
??memset(p_addr,
'
\0
'
,
1024
);
??strncpy(p_addr,argv[
1
],
1024
);
??exit(
0
);
?}
?
else
?{
??c_addr
=
shmat(shmid,
0
,
0
);
??printf(
"
Client?get?%s
"
,c_addr);
??exit(
0
);
?}?
}?
這個程序是父進程將參數寫入到共享內存,然后子進程把內容讀出來.最后我們要使用ipcrm釋放資源的.先用ipcs找出ID然后用ipcrm shm ID刪除.?
5、管道(FIFO)
管道有無名管道和有名管道兩種,無名管道一般在父子進程中使用。
無名管道的使用方法一般是:
#include?<unistd.h>
int?pipe(int?filedes[2]);
filedes[0]用于讀出數據,讀取時必須關閉寫入端,即close(filedes[1]);
filedes[1]用于寫入數據,寫入時必須關閉讀取端,即close(filedes[0])。
無名管道的使用方法是:
#include?<sys/types.h>
#include?<sys/stat.h>
int?mkfifo(const?char?*pathname,?mode_t?mode);
讀寫管道與讀寫文件的操作相同。
利用mmap實現的一個文件拷貝例子
/*
?*?gcc?-Wall?-O3?-o?copy_mmap?copy_mmap.c
?
*/
#include?
<
stdio.h
>
#include?
<
stdlib.h
>
#include?
<
string
.h
>
??
/*
?for?memcpy?
*/
#include?
<
strings.h
>
#include?
<
sys
/
mman.h
>
#include?
<
sys
/
types.h
>
#include?
<
sys
/
stat.h
>
#include?
<
fcntl.h
>
#include?
<
unistd.h
>
#define
?PERMS?0600
int
?main?(?
int
?argc,?
char
?
*
?argv[]?)
{
????
int
??????????src,?dst;
????
void
????????
*
sm,?
*
dm;?
????
struct
?stat??statbuf;
????
if
?(?argc?
!=
?
3
?)
????{
????????fprintf(?stderr,?
"
?Usage:?%s?<source>?<target>\n
"
,?argv[
0
]?);
????????exit(?EXIT_FAILURE?);
????}
????
if
?(?(?src?
=
?open(?argv[
1
],?O_RDONLY?)?)?
<
?
0
?)
????{
????????perror(?
"
open?source
"
?);
????????exit(?EXIT_FAILURE?);
????}
????
/*
?為了完成復制,必須包含讀打開,否則mmap()失敗?
*/
????
if
?(?(?dst?
=
?open(?argv[
2
],?O_RDWR?
|
?O_CREAT?
|
?O_TRUNC,?PERMS?)?)?
<
?
0
?)
????{
????????perror(?
"
open?target
"
?);
????????exit(?EXIT_FAILURE?);
????}
????
if
?(?fstat(?src,?
&
statbuf?)?
<
?
0
?)
????{
????????perror(?
"
fstat?source
"
?);
????????exit(?EXIT_FAILURE?);
????}
????
/*
?????*?參看前面man手冊中的說明,mmap()不能用于擴展文件長度。所以這里必須事
?????*?先擴大目標文件長度,準備一個空架子等待復制。
?????
*/
????
if
?(?lseek(?dst,?statbuf.st_size?
-
?
1
,?SEEK_SET?)?
<
?
0
?)
????{
????????perror(?
"
lseek?target
"
?);
????????exit(?EXIT_FAILURE?);?
????}?
????
if
?(?write(?dst,?
&
statbuf,?
1
?)?
!=
?
1
?)
????{
????????perror(?
"
write?target
"
?);
????????exit(?EXIT_FAILURE?);
????}?
????
????
/*
?讀的時候指定?MAP_PRIVATE?即可?
*/
????sm?
=
?mmap(?
0
,?(?size_t?)statbuf.st_size,?PROT_READ,
???????????????MAP_PRIVATE?
|
?MAP_NORESERVE,?src,?
0
?);
????
if
?(?MAP_FAILED?
==
?sm?)
????{
????????perror(?
"
mmap?source
"
?);
????????exit(?EXIT_FAILURE?);
????}
????
/*
?這里必須指定?MAP_SHARED?才可能真正改變靜態文件?
*/
????dm?
=
?mmap(?
0
,?(?size_t?)statbuf.st_size,?PROT_WRITE,
???????????????MAP_SHARED,?dst,?
0
?);
????
if
?(?MAP_FAILED?
==
?dm?)
????{
????????perror(?
"
mmap?target
"
?);
????????exit(?EXIT_FAILURE?);
????}
????memcpy(?dm,?sm,?(?size_t?)statbuf.st_size?);
????
/*
?????*?可以不要這行代碼
?????*
?????*?msync(?dm,?(?size_t?)statbuf.st_size,?MS_SYNC?);
?????
*/
????
return
(?EXIT_SUCCESS?);
}?
mmap()好處是處理大文件時速度明顯快于標準文件I/O,無論讀寫,都少了一次用戶空間與內核空間之間的復制過程。操作內存還便于設計、優化算法。
文件I/O操作/proc/self/mem不存在頁邊界對齊的問題,但至少Linux的mmap()的最后一個形參offset并未強制要求頁邊界對齊,如果提供的值未對齊,系統自動向上舍入到頁邊界上。malloc()分配得到的地址不見得對齊在頁邊界上。
???????
/proc/self/mem和/dev/kmem不同。root用戶打開/dev/kmem就可以在用戶空間訪問到內核空間的數據,包括偏移0處的數
據,系統提供了這樣的支持。顯然代碼段經過/proc/self/mem可寫映射后已經可寫,無須mprotect()介入。
參考:
Linux環境進程間通信(五): 共享內存(上) 對mmap的介紹很詳細