linux程序設計學習筆記(7-15)

七、數據管理

內存管理

malloc,free,realloc和windows都一樣,都是ANSI C。

實際上,應用程序並沒有直接訪問到物理內存,也可以通過malloc獲得比實際內存大得多的內存空間,因爲系統會使用交換空間(swap space ,可以理解爲windows的虛擬內存),如果申請的內存大於物理內存和交換空間,那麼系統將提前終止這個進程。
如果在申請的內存裏進程指針操作,當移動比如++出了這段分配的內存範圍之外,系統會自動報警。
如果向一個空指針裏複製數據,會發出越界報警。

分配的內存結構定義
struct mem_control_block {
int is_available;
int size;
};

詳細的內容可以看這篇文章《內存管理內幕》
http://www-128.ibm.com/developerworks/cn/linux/l-memory/


文件鎖定

使用O_CREAT|O_EXCL的特點可以實現鎖文件的效果,書上是用來做進程之間的協調。
但我覺得用來給一個程序只啓動一個實例比較實用,但是效果不好,因爲採用刪除文件的做法,一旦遇到上一個進程異常退出,後面起新進程就會比較麻煩。
所以還是用
fd=open(filename,O_WRONLY|O_CREAT, 0644);
if(flock(fd,LOCK_EX|LOCK_NB)){}
可以更好實現一個程序只有一個實例

fcntl和lockf使用不同的底層實現,他們不能同時工作,所以不能混合使用它們。
fcntl可以鎖定文件的部分區域。
int fcntl(int fd,int cmd,struct flock * lock);
fcntl()用來操作文件描述詞的一些特性。參數fd代表欲設置的文件描述詞,參數cmd代表欲操作的指令,參數lock是鎖信息,裏面規定了鎖類型和具體的文件區域。
F_GETLK 取得文件鎖定的狀態。
F_SETLK 設置文件鎖定的狀態。此時flcok 結構的l_type 值必須是F_RDLCK、F_WRLCK或F_UNLCK。如果無法建立鎖定,則返回-1,錯誤代碼爲EACCES 或EAGAIN。
F_SETLKW F_SETLK 作用相同,但是無法建立鎖定時,此調用會一直等到鎖定動作成功爲止。若在等待鎖定的過程中被信號中斷時,會立即返回-1,錯誤代碼爲EINTR。
flcok 結構的ll_type 有三種狀態:
F_RDLCK 建立一個供讀取用的鎖定,也就是共享鎖,多個進程可以對同一區域共享鎖,只要設置了某一區域的共享鎖,就再也不能設置獨佔鎖了,值爲0。
F_WRLCK 建立一個供寫入用的鎖定,也就是獨佔鎖,一旦設置後,這個區域就不能再設置任何鎖了,值爲1。
F_UNLCK 刪除之前建立的鎖定,也就是解鎖。
其他參數見P223,主要是設置具體位置還有加鎖的進程號。 
返回值 成功則返回0,若有錯誤則返回-1,錯誤原因存於errno.

注意對文件區域加鎖後,一個用read和write調用,而不要使用fread和fwrite ,因爲fread和fwrite 有自己的緩衝區。

另外一個函數lockf類似,不過注意一點:這2個函數都是建議性的鎖,並不會真正的阻止我們從文件中讀取或是向文件中寫數據。對鎖的檢測是應用程序的責任。fcntl和lockf的鎖不一樣,混合用的話會互不相認。

八、mysql

一些mysql數據庫的操作可以看《零散的一些東西》。

如果出錯獲得使用函數mysql_error(MYSQL m_connection)返回的出錯碼,mysql_errno(MYSQL m_connection)返回字符串也就是出錯信息。
具體對應可以errmsg.h mysqld_error.h 。具體可以看這裏《MySQL 錯誤代碼以及出錯信息對照大全》
http://www.e-gov.org.cn/xuexiyuandi/cunchu/200703/51606.html

注意mysql_affected_rows是返回這次mysql_query操作改變的多少行,而不是where匹配的多少行,其實就是我們在直接操作mysql後顯示的那個影響多少行。如果是delete所有數據,那麼返回的是0 。

mysql_field_count可以返回這次查詢獲得了多少列,這樣在sqlrow[]這個數組裏使用就不會越界了。

注意mysql_store_result和mysql_use_result的區別:
1、 mysql_use_result()的結果必須“一次性用完”,也就是說用它得到一個result後,必須反覆用mysql_fetch_row()讀取其結果直至該函數返回null爲止,否則如果你再次進行mysql查詢,會得到“Commands out of sync; you can't run this command now”的錯誤。而mysql_store_result()得到result是存下來的,你無需把全部行結果讀完,就可以進行另外的查詢。比如你進行一個查詢,得到一系列記錄,再根據這些結果,用一個循環再進行數據庫查詢,就只能用mysql_store_result()。
2、mysql_store_result查詢的結果使用使用mysql_num_rows獲得的結果是總行數。但是mysql_use_result查詢的結果使用使用mysql_num_rows獲得的結果是當前行行號。(這點差點讓我頭大,當時一直奇怪怎麼得不到總行數)
3、遠程訪問數據庫佔用的內存不一樣。mysql_store_result不佔用服務端內存的,但是客戶端的內存是急劇上升的,待執行完mysql_free_result後客戶端內存被釋放。mysql_use_result不佔用客戶端內存,對服務器端內存佔用也不大,但是使用mysql_use_result後服務器端內存增加的很明顯。

其本上都很簡單,在mysql_query裏直接使用mysql語句就行了。

九、開發工具

我目前用kdevelop,略。

關於kdevelop,裏面很多設置都很奇怪,比如給工程增加已存在的文件,需要在很隱蔽的右邊欄裏的automake裏選擇添加文件,剛一開始,我要添加已有的文件,只好新建一個同名空文件,然後覆蓋掉。

具體的設置,可以看這篇文章:
http://blog.csdn.net/linux2linux/archive/2007/12/07/1923231.aspx
不過這工具有個麻煩,調試的時候帶不就去參數,只有運行時設置的參數才能帶到程序裏去,真鬱悶。

十、調試

我目前用kdevelop,略。
像VC一樣很方便,裏面也可以使用gbd的命令,kdevelop裏面沒有專門看內存的工具,所以只好用gbd命令了,參數是x\數字sh 0x111111 數字一般是1,顯示到\0的字符串,如果這個區域裏可能有0,那就要把數字加大了。

十一、進程和信號

每一個進程都用自己的唯一數字編號,成爲PID,代碼中獲得是getpid ,命令行裏獲得是ps aux
PID取值範圍是2到32768,新進程選擇比最大的PID大1的數字做自己的PID,超過最大值重新從2開始。
數字1是init專用的,設置後臺進程(不是後臺運行,帶&參數運行的程序,當斷開連接後,將停止運行)也就是殭屍進程,就需要掛在init上,一般設置的方法是,父進程開啓一個子進程,然後殺死父進程,這樣子進程就掛在init上了。

execX是一系列替換函數,實際作用就是把新應用程序的代碼替換當前的這條語句。
(與system不同,system是用阻塞的方式調用一個外部命令,其實就是先啓動一個shell,再使用shell調用應用程序,除非加參數&否則不運行完是不會返回的。說白了就是和腳本編程一樣。)
另外我發現execX系列使用不能執行python的程序呀(當然python是解釋性語言,可能中間流程哪裏就不對了)

fork是複製是自己生成一個子進程,也就是說使用fork又對同樣程序啓動了一個新進程。
區別是,父進程調用的fork返回子進程號,子進程的fork這一步,返回的是0 。這也就是我們常用的,返回值是0的,說明自己是子進程,返回值非0的,說明自己的父進程。

wait這個函數可以使得父進程暫停,直到子進程結束,返回已停止的子進程的PID。參見P391的例子。

注意!就算子進程先退出,主進程再進行wait,也可以在wait的返回值裏得到子進程的進程號,同樣使用WIFEXITED也可以得到子進程的exit返回數值。
原因很簡單,子進程在父進程還沒有退出時,雖然不運行了,但是並沒有真正退出,它仍然存在系統中,直到父進程正常退出或父進程調用wait。此時的子進程也可以被稱作殭屍進程,不過這種殭屍進程沒什麼用。
如果子進程進入殭屍狀態,而父進程異常退出,則子進程將init作爲自己的父進程,直到被init發現並釋放。

還有一個命令waitpid也有類似的效果,而且它可以制定子進程號並設置有各種處理行爲。週期性檢查子進程是否結束可以用waitpid(child_pid, (int *) 0, WNOHANG); 子進程沒有結束返回0,已經結束返回pid,失敗返回-1 ,錯誤信息放在errno裏。

書裏說進程編寫比線程簡單,編寫多個互相協作的進程比線程容易,可能我是從windows轉移過來的,完全沒有這個感覺,我還是喜歡用線程,不知道繼續學習下去是不是會改變。

信號:感覺有點像windows裏的消息,可以給特定進程發送信號,還可以發送自定義信號(自定義信號數值從32到64)並引發自定義函數。不過它會打斷sleep、在主線也就是main裏執行函數,哪怕你是把sign調用寫在了子線程裏面,而且最主要的是,自定義函數還沒有處理完這個消息,再發過來的新消息都會被丟棄掉,比windows的消息差遠了,而且不能帶參數,而且是在主線程裏執行,天知道會不會有什麼問題。

1、信號可以打斷main裏的sleep,無論睡眠多少秒。
2、就算alarm和signal在子線程裏定義和調用,一切處理還是在main即主線程來處理的,子線程裏的sleep不會被中斷。
3、.SIG_IGN 忽略這個信號,SIG_DFL 恢復對這個信號的默認處理。
4、linux的大多數信號是提供給內核的,僅有少數幾種信號可以在信號間發送,但是32到64在定義中是空的,可以留給用戶自己使用。
5、利用kill可以給制定進程發送信號量,這裏可以發送自定義的信號量,在另外一個進程裏對這個信號量給於自定義函數。
6、pause()會令目前的進程暫停(進入睡眠狀態),直到被信號(signal)所中斷。
7、注意,如果是在2個進程中傳遞,那麼效果是非阻塞的POSTMESSAGE消息。另外消息不被保存,也就是說如果自定義函數還沒有處理完這個消息,再發過來的新消息都會被丟棄掉,kill不會返回-1,會返回0,但是實際編碼中,後面的信號沒有被接受。
8、killall可以給所以同一個應用程序啓動的進程發信號。
9、如果發送信號時,進程處於select等待,那麼將返回-1 。
10、如果進程沒有處理這個信號,無論是系統的信號還是用戶自定義的信號,都會立刻殺死進程。如果ctrl+z變成後臺處理,則會無視這些信號。但如果是加&啓動,還是會被這些未定義信號殺死。

具體關於信號的一些代碼,可以去看《linux函數整理及雜項》

十二、線程

記得以前上大學的時候,我們用的《操作系統》一書裏還說linux是沒有線程的,線程是windows的概念。時代發展的真快。
不過如果說linux沒有線程,也不是完全沒有道理,至少linux裏需要專門的線程庫才能使用到線程(pthread.h -lpthread)。
正因爲一直在改進,所以本書的代碼有幾個函數在我們的CS5上還有問題,不過只是一丁點無關大局,也可以看出linux線程在逐漸的規範。

pthread_exit(void* retval) 編程發現,這裏不能給常量比如字符串,和書上不一致,但是可用null,另外如果2個子線程用同一個全局變量(注意不能給局部變量,子線程退出,堆棧就清空了),並且2個線程在調用pthread_exit時都用它並設置成不同的值,那麼肯定衝突,即在2個pthread_join裏獲得的結果2個顯示的都是同一個值,即使它們的退出時間不一致。
如果子線程沒有退出,主線程先走完,或主線程調用pthread_exit,整個進程直接退出,子線程也自動結束。
pthread_join是等待制定的子線程結束,否則一直阻塞。

使用信號量和互斥量同步,名字不一樣,但是功能和windows上差不多。
信號量同步sem_post是以原子操作給某個信號量+1,原子操作,就是不能被更高等級中斷搶奪優先的操作,只有它執行完畢了,其他高優先級的才能運行。而其他操作可能被更高優先級的中斷,比如信號可以中斷sleep等。
sem_wait是以原子操作給信號量-1,如果如果發現當前值已經是0了,那麼就不會進行減操作,而是進入等待狀態,直到有sem_post給信號量加1,如果當前值非0,那麼-1後繼續運行。
在2個線程同步的時候,往往需要2個信號量,一個開一個關。

pthread_mutex_lock第一次運行的時候不會被鎖住,相當於sem_init給的初始值是1的情況。當第二個線程調用pthread_mutex_lock的時候,就阻塞住了,然後等待有其他線程pthread_mutex_unlock。
實際編碼的時候發現,原來stdin的標準輸入的緩存區是可以存儲多次輸入的,例子P420,如果sleep的時間改長,連續輸入,每次gets獲取的值都是正確的。

如果對2個線程進行3次加鎖,那麼就會死鎖,可以通過修改pthread_mutex_init第二個參數來改變,比如返回錯誤或者遞歸加鎖,平時只傳空就可以了,感覺在與其他人進行不透明的接口合作的時候纔會用到,因爲那些人會在給他們的回調函數裏調用其他接口,然後造成死鎖,以前我處理的方法是,推薦合作方用postmessage來處理,但如果用遞歸加鎖的方法來的話,其實還是挺麻煩的。

其實還有種可能引起死鎖,比如A,B 2個方法入口都有同一個鎖,那麼A方法調用B方法的時候就可能引起死鎖。可以使用pthread_mutexattr_settype來設置返回錯誤(PTHREAD_MUTEX_ERRORCHECK)或者遞歸加鎖(PTHREAD_MUTEX_RECURSIVE)。

線程的屬性可以通過pthread_attr_xxx設置,不過感覺沒什麼需要動的,唯一需要的估計就是stacksize,不過默認值很大,而且有些不支持,所以感覺用處不大。

pthread_cancel可以停止制定的線程,包括停止自己。就算子線程內不設置具體類型,也一樣可以用這個函數。

十三、進程間通信:管道

管道是用來在進程之間交互數據的。

個人感覺linux裏面通過進程名字找進程號挺麻煩的,可能是在windows上慣壞了,也可能是我沒有找到這樣的庫吧。很多時候,只用用popen來執行一些shell命令,然後獲得輸出的內容。最後,也是最麻煩的一步——整理字符串。雖然獲得對獲得的FILE指針用操作文件的函數處理,但是關閉的時候用pclose 。

簡單的管道就是pipe調用了,將讀寫放在一個2元素的數組中,標號1是寫入用的,標號2是讀取用的,遵守先進先出法的原則。然後一個進程write一個進程read(如果沒有數據就會阻塞住,如果文件描述關閉了,就會返回-1)
注意可以3個或3個進程以上使用這個pipe調用,不過某個進程讀取一個數據後,其他的就只能讀取下一個數據了。
但是這個主要是用在父進程和子進程之間的,在不相干的進程之間使用挺麻煩的,除非傳遞文件描述符作爲參數給新進程。


命名管道是一種特殊的文件(當然linux裏一切都是文件),在命令行裏可以使用mkfifo,在c裏也是一樣的名字。
不能用O_RDWR打開命名管道,因爲一般都是單項傳輸的,如果要雙向,建議開2個命名管道。
可以用open打開文件可以用3個參數4中組合,O_RDONLY,O_WRONLY,O_NONBLOCK
O_RDONLY只讀,如果沒有另一個進程進行寫打開就會阻塞。
O_RDONLY|O_NONBLOCK必然成功並立即返回。
O_WRONLY只寫,如果沒有另一個進程進行讀打開就會阻塞。
O_WRONLY|O_NONBLOCK如果沒有進程讀打開則返回-1

十四:信號量、共享內存、消息隊列

主要是作用於進程之間的協作的,哎,沒一個比windows的自定義消息好用,關鍵我到現在還沒有找到,不建立任何管道或socket或任何其他處理,就可以通過進程號直接通知其他進程處理任務的,信號雖然可以,但是不能帶參數,而且處理函數沒結束再發的會被丟棄。

信號量用來協調進程與線程,我覺得多進程裏如果要讀寫文件用文件鎖就夠了,主要用來協調共享內存吧。
注意如果沒有用semctl把val值設置爲1,那麼默認爲0 。
P操作就是先對val值減1,如果小於0,就阻塞,知道有人進行V操作,其實就是加1 。

共享內存允許2個不想幹的程序訪問同一個邏輯內存,傳遞起來比較簡單,但是沒有同步機制,需要使用其他方法來同步,比如用信號或信號量。

消息隊列是比較有趣的,首先是異步消息,獨立與發送和接受進程而存在,可以傳遞數據,先進先出,可以多個進程添加消息,多個進程讀取消息,不過一個進程讀取後,這個消息就從消息隊列裏消失了。
另外在發送的時候不用自己申請內存,也不用擔心數據被修改,系統會自動複製一份數據放在自己的隊列裏。

這3種方法都用一個常數key來標記,以便多個進程可以公用同一個信號量、共享內存或信息隊列。

十五:套接字:

以前一直不明白socket的第一個參數有什麼用,原來除了網絡通信,也可以用AF_UNIX來進行本地進程間的通信。
用一個字符串做標示後,就可以對它進行讀寫操作了,感覺沒有通道好用。

至於AF_INET和windows裏的沒有區別。

socket多連接下簡單的處理就是針對每個連接開一個進程,資源佔用高。或者使用select機制。

select如果遇到超時返回0,如果出錯返回-1,其他值是socket相應。
第一個參數是需要測試的套接字數目,最好是最大的socket值+1 。
關於超時時間,最好每個循環再重新設置一次,不同系統可能不同處理,會自動修改超時時間這個參數爲剩餘時間,可能會影響邏輯性。

如果要設置非阻塞,可以用fcntl設置。
int setnonblock(int sockId)
{
int f_old; 
if(sockId < 0)
{
   return -1;
}
f_old = fcntl(sockId, F_GETFL, 0);
if(-1 == f_old) return sockId;
f_old |= O_NONBLOCK;

return (fcntl(sockId, F_SETFL, f_old));}


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