Linux/Windows下 C/C++開發的差異zz

總結在前
1語言特性的差異
1.1字節順序的差異 Windows 低位在前 Unix 高位在前
差異帶來的問題,體現在以下幾個方面: Ø 網絡通信時 Ø  網絡通信時
解決方法:
1. 數字轉換成字符傳進行交互 
2. 協商一個統一的字節順序,根據自己平臺的字節順序還原數據 
3. 採用其他標準的編碼方式,如ASN1編碼
備註:32位系統和64位系統的差異也會出現這樣的問題,長整型(long)分 別用32位和64位表示,這樣,在不同系統之間交互的時候必然會出現 整型數據表示方式不同的問題。目前大多數Windows系統都是32位的系統,而 Unix中很多都是64位的。
1.2變量的作用域差異
1.3全局對象的初始化
             CMyObject g_Object; 
windows環境,默認調用構造函數但是在某些系統中(SCO Unix)不調用,可以通過顯式創建對象的方法解決,如下:
               CMyObject* g_pObject = new CMyObject; 
       如果不想使用指針的方式引用該對象,我們可以用如下方法
               CMyObject& g_Object = *(new CMyObject); 
1.4 語法檢查的差異    if(NULL == pVar)       在SCO Unix環境下報錯

2    操作系統特性的差異:   打開文件句柄數的限制、Socket等待隊列的限制、進程和線程堆棧大小的限制
 2.1 文件描述符的限制:     文件描述符最初是Unix下的一個概念
以Solaris爲例,默認情況下每個進程可以打開的文件描述符爲 1024個,系統的硬限制是8192(具體的值跟版本有關)。在Unix系統下使用ulimit命令來獲得系統的這些限制參 數。一般情況下,這都是夠用的,但是有一個例外,在32爲的Solaris程序中,使用標準輸入輸出函數(stdio)進行文件的操作,最大的文件描述符不能超過256。比如說用fopen打開文件,除去系統佔用的3個文件描述符(stdin、stdout和stderr),程序中只能再同時打開253個 文件。如果使用open函數來打開文件,就沒有這個限制,但是就不能夠使用stdio中的那些函數進行操作了,是程序的通用性和靈活性有所降低。這是因爲 在stdio的FILE結構中,用一個unsigned char來表示文件描述符,所以只能表示0~255。
 
在網絡程序的開發中,每一個網絡連接也都佔用一個文件描述符,如果程序打開了很多Socket連接(典型的例子就是使用了連接池技術),那麼程序運行的時候可能用fopen打不開文件。
 
解決這個問題,可以採用一下幾種方法: 
1. 升級爲64位系統或採用採用64位方式編譯程序 
2. 使用sys/io.h中的函數操作文件 
3. 採用文件池技術,預留一部分文件描述符(3~255之間的),使用freopen函數來重用這些描述符。
 至於採用哪種方法或者是否考慮系統中處理這個問題,就要視具體的情況而定了,那些不受這個限制影響的程序,可以不考慮這個問題。
 
1.2.2 進程和線程的限制
一般的操作系統對每個進程和線程可以使用的資源數都有限制,比如一個進程可以創建的線程數,一個進程可以打開的文件描述符的數量,進程和線程棧大小的限制和默認值等。
 
針對這些問題,首先要分析和考慮你的系統是一個什麼樣的規模,會不會收到這些限制的影響,如果需求大於系統的限制,可以通過適當的調整系統參數來解決,如果還不能解決,就得考慮採用多進程的方式來解決。
 
對於進程和線程的棧空間大小的限制,主要是線程棧空間的問題。一般的系統都有默認的線程棧空間大小,而且不同操作系統的默認值可能不同。在通常情況下,這些對程序沒有影響,但是當程序的層次結構比較複雜,使用了過多的本地變量,這個限制可能就會對程序產生影響,導致棧空間溢出,這是一個比較嚴重的問題。不能通過調整系統參數來解決這個問題,但是可以通過相應的函數,在程序裏面指定創建線程的棧空間的大小。但是具體該調整的數值應該適可而止,而不是越大越好。 因爲線程的棧空間過大的時候,就會影響到可創建線程的數量,雖然遠沒有達到系統多線程數的限制,但卻可能因爲系統資源佔用過多導致分配內存失敗。

文件描述符最初是Unix下的一個概念
1.2    操作系統特性的差異
不同的操作系統中都存在一些系統的限制,如打開文件句柄數的限制、Socket等待隊列的限制、進程和線程堆棧大小的限制等,因此在開發的過程中,必須考慮 到這些限制因素對程序的影響。當然,有些限制參數可以適當的調整,這就需要在發佈程序的時候加以聲明。另外,操作系統的容錯性也對程序有影響。下面分別進 行討論。
 
1.2.1 文件描述符的限制
文件描述符最初是Unix下的一個概念,在Unix系統中,用文件描述符來表示文件、打開的socket連接等,跟Windows下HANDLE的概念類 似。文件描述符是一種系統資源,系統對每個進程可以分配的文件描述符數量都有限制。以Solaris爲例,默認情況下每個進程可以打開的文件描述符爲 1024個,系統的硬限制是8192(具體的值跟版本有關),也就是說可以調整到8192。在Unix系統下使用ulimit命令來獲得系統的這些限制參 數。一般情況下,這都是夠用的,但是有一個例外,在32爲的Solaris程序中,使用標準輸入輸出函數(stdio)進行文件的操作,最大的文件描述符不能超過256。比如說用fopen打開文件,除去系統佔用的3個文件描述符(stdin、stdout和stderr),程序中只能再同時打開253個 文件。如果使用open函數來打開文件,就沒有這個限制,但是就不能夠使用stdio中的那些函數進行操作了,是程序的通用性和靈活性有所降低。這是因爲 在stdio的FILE結構中,用一個unsigned char來表示文件描述符,所以只能表示0~255。
 
在網絡程序的開發中,每一個網絡連接也都佔用一個文件描述符,如果程序打開了很多Socket連接(典型的例子就是使用了連接池技術),那麼程序運行的時候可能用fopen打不開文件。
 
解決這個問題,可以採用一下幾種方法: 
1. 升級爲64位系統或採用採用64位方式編譯程序 
2. 使用sys/io.h中的函數操作文件 
3. 採用文件池技術,預留一部分文件描述符(3~255之間的),使用freopen函數來重用這些描述符。
 至於採用哪種方法或者是否考慮系統中處理這個問題,就要視具體的情況而定了,那些不受這個限制影響的程序,可以不考慮這個問題。
 
1.2.2 進程和線程的限制
一般的操作系統對每個進程和線程可以使用的資源數都有限制,比如一個進程可以創建的線程數,一個進程可以打開的文件描述符的數量,進程和線程棧大小的限制和默認值等。
 
針對這些問題,首先要分析和考慮你的系統是一個什麼樣的規模,會不會收到這些限制的影響,如果需求大於系統的限制,可以通過適當的調整系統參數來解決,如果還不能解決,就得考慮採用多進程的方式來解決。
 
對於進程和線程的棧空間大小的限制,主要是線程棧空間的問題。一般的系統都有默認的線程棧空間大小,而且不同操作系統的默認值可能不同。在通常情況下,這些對程序沒有影響,但是當程序的層次結構比較複雜,使用了過多的本地變量,這個限制可能就會對程序產生影響,導致棧空間溢出,這是一個比較嚴重的問題。不能通過調整系統參數來解決這個問題,但是可以通過相應的函數,在程序裏面指定創建線程的棧空間的大小。但是具體該調整的數值應該適可而止,而不是越大越好。 因爲線程的棧空間過大的時候,就會影響到可創建線程的數量,雖然遠沒有達到系統多線程數的限制,但卻可能因爲系統資源佔用過多導致分配內存失敗。

文件描述符最初是Unix下的一個概念
,以Solaris爲例,默認情況下每個進程可以打開的文件描述符爲 1024個,系統的硬限制是8192(具體的值跟版本有關),也就是說可以調整到8192。在Unix系統下使用ulimit命令來獲得系統的這些限制參 數。一般情況下,這都是夠用的,但是有一個例外,在32爲的Solaris程序中,使用標準輸入輸出函數(stdio)進行文件的操作,最大的文件描述符不能超過256。比如說用fopen打開文件,除去系統佔用的3個文件描述符(stdin、stdout和stderr),程序中只能再同時打開253個 文件。如果使用open函數來打開文件,就沒有這個限制,但是就不能夠使用stdio中的那些函數進行操作了,是程序的通用性和靈活性有所降低。這是因爲 在stdio的FILE結構中,用一個unsigned char來表示文件描述符,所以只能表示0~255。
 
在網絡程序的開發中,每一個網絡連接也都佔用一個文件描述符,如果程序打開了很多Socket連接(典型的例子就是使用了連接池技術),那麼程序運行的時候可能用fopen打不開文件。
 
解決這個問題,可以採用一下幾種方法: 
1. 升級爲64位系統或採用採用64位方式編譯程序 
2. 使用sys/io.h中的函數操作文件 
3. 採用文件池技術,預留一部分文件描述符(3~255之間的),使用freopen函數來重用這些描述符。
 至於採用哪種方法或者是否考慮系統中處理這個問題,就要視具體的情況而定了,那些不受這個限制影響的程序,可以不考慮這個問題。
 
2.2 進程和線程的限制
一般的操作系統對每個進程和線程可以使用的資源數都有限制,比如一個進程可以創建的線程數,一個進程可以打開的文件描述符的數量,進程和線程棧大小的限制和默認值等。
 
針對這些問題,首先要分析和考慮你的系統是一個什麼樣的規模,會不會收到這些限制的影響,如果需求大於系統的限制,可以通過適當的調整系統參數來解決,如果還不能解決,就得考慮採用多進程的方式來解決。
 
對於進程和線程的棧空間大小的限制,主要是線程棧空間的問題。一般的系統都有默認的線程棧空間大小,而且不同操作系統的默認值可能不同。在通常情況下,這些對程序沒有影響,但是當程序的層次結構比較複雜,使用了過多的本地變量,這個限制可能就會對程序產生影響,導致棧空間溢出,這是一個比較嚴重的問題。不能通過調整系統參數來解決這個問題,但是可以通過相應的函數,在程序裏面指定創建線程的棧空間的大小。但是具體該調整的數值應該適可而止,而不是越大越好。 因爲線程的棧空間過大的時候,就會影響到可創建線程的數量,雖然遠沒有達到系統多線程數的限制,但卻可能因爲系統資源佔用過多導致分配內存失敗。


1. 平臺差異簡介
Windows 和Unix是當前兩大主流操作系統平臺,基於C/C++的開發人員經常會面臨這兩個平臺之間的移植的問題。Unix作爲一個開發式的系統,其下有出現了很 多個分支,包括Sun的Solaris、IBM的AIX、HP Unix、SCO Unix、Free BSD、蘋果的MAC OS以及開源的Linux等。對於這些Unix的分支操作系統,其實現又有很大的差別,因此開發人員又要針對這些不同的系統進行移植。本文的目的就是介紹 一下Windows平臺和Unix平臺之間的差別,並簡單介紹一下不同Unix分支操作系統之間的差別,在移植開發過程中的一些注意事項,同時簡要介紹一 下Unix下開發的一般流程和常用的開發調試工具。
 
關於平臺之間的差異,主要是Windows平臺和Unix平臺之間的差異,這裏着重介紹一下這兩個平臺在C/C++開發中存在的差異,其間會穿插介紹一些Unix不同分支之間的差異。
 
1.1語言特性的差異
       語言特性的差異,指的是不同操作系統平臺中,實現C++/C時的一些細微的差異,忽略這些差異可能會帶來一些特別隱蔽的錯誤。而且可能是致命的錯誤。所 以,瞭解語言特性的差異,對於在Unix移植來說非常重要。如果考慮系統多多個平臺支持,就必須瞭解在不同平臺下語言特性的差異,從開發一開始就把這些因 素考慮進去,這樣才能最低限度的降低移植的過程中工作量。
 
1.1.1字節順序的差異
       字節順序指的主要是整型變量在內存中的存儲方式。在計算機中,數據都是以二進制方式存儲的,包括在內存和硬盤中。而計算機又以8位二進制作爲一個存儲單元。在32位系統中,一個整型的存儲需要四個存儲單元。也就是說要把一個32位的整數分割成位四段分別進行存儲,而每一個存儲單元的位置就是字節順序的差異。 爲了清楚的表示每段存儲的先後位置,我們用16進制來表示一段的值,下表列出了在Unix系統和Windows系統中整數20000在內存中的情況。
 
十六進制表示       Windows內存表示 Unix內存表示

0x00004E20       20 4E 00 00 00 00 4E 20


如表中所示,Windows中存儲方式和該整數的16進製表示是相反,是一種低位在前高位在後的存儲順序。而Unix下的存儲順序和正常的16進製表示的順序相同,稱爲高位在前低位在後的順序。這種差異帶來的問題,主要體現在以下幾個方面:
 
Ø         網絡通信時
當Windows 和Unix之間發生網絡數據傳輸,傳輸一個整型數據(如一個數據包的長度)的時候,如果不經處理直接把內存中的數據傳輸過去,那麼在對方看來完全是另一個 數據,這樣就會造成問題。如Windows下面發送過去一個20000(0x00004E20),在Unix下面收到的數據就會被理解成 541982720(0x204E0000),這簡直是天壤之別。
 
Ø         文件存儲和讀取時 
跟網絡傳輸類似,如果在Windows下面把某個整數寫到了文件中,然後在Unix下面打開這個文件讀取該數據,就會出現跟上面類似的問題。
 
       這個問題主要體現在不同平臺之間互操作時,解決這個問題的方法就是交互的雙方採用一種相同的數據編碼標準,就是數據在傳輸和存儲的時候採用什麼方法進行編碼,具體的解決方法有一下幾種: 
1. 數字轉換成字符傳進行交互 
2. 協商一個統一的字節順序,根據自己平臺的字節順序還原數據 
3. 採用其他標準的編碼方式,如ASN1編碼
  
跟這個問題類似,32位系統和64位系統的差異也會出現這樣的問題,解決方法跟這個問題的解決方法相同。在32位系統和64位系統中,長整型(long)分 別用32位和64位表示,這樣,在不同系統之間交互的時候必然會出現整型數據表示方式不同的問題。目前大多數Windows系統都是32位的系統,而 Unix中很多都是64位的,尤其是大型的服務器,所以這個問題必須引起重視。
 
1.1.2變量的作用域差異
在不同的系統下,由於編譯器的不同,對變量作用域的實現機制也有所不同,這裏以Windows下的VC和Solaris下的CC這兩個編譯器爲例做一個簡單的比較說明。
 在C++的開發過程中,我們經常會有這樣的用法:
        for(int i=0;i<num;i++) 
       {      …       }
 這 是一種最常用的for循環的用法,因爲其中i主要使用來控制循環,所以一般沒有必要拿出來單獨進行聲明,只是放在for語句中一起聲明。就是這種聲明方法,在Windows下和Solaris下有了不同的理解,i 的作用域不 同。我們先把作用域進行劃分,如下:
 
我 們劃分出 I 和 II 兩個作用域,其中作用域 II 包含在作用域 I 當中。在Windows下,變量 i 的作用域是I的整個範圍,而Solaris下的i的作用域只 是II的範圍。其實標準的C++語法應該是Solaris的做法,但是微軟在實現的時候沒有按照這個標準實現,這就引發了我們討論的這個問題。由於這個差 異,就引發了一些微妙而隱蔽的問題。先看一下下面兩端代碼。
 A: 標準的C++語法應該是Solaris的做法
       for(int i=0;i<num;i++) 
       {      …       }
       for(i=0;i<num;i++) 
       {    …       }
 B Windows下
       for(int i=0;i<num;i++) 
       {  …  }
        for(int i=0;i<num;i++)
       {   …  }
  
1.1.3全局對象的初始化
       在C++中,初始化對象的時候系統會自動調用構造函數,因此我們習慣在構造函數中做一些初始化的工作。其中,有些對象是靜態分配的全局對象(就是在任何函數體外聲明的對象,如: 
             CMyObject g_Object; 
       通常情況下,程序啓動的時候,系統都會自動調用這個對象的構造函數對這個全局對象進行初始化,但是在某些系統中(SCO Unix),就不想我們期望的那樣,也許這是編譯器實現的一個bug,但是我們也不能忽視這個問題的存在。對於這種問題,我們可以通過顯式創建對象的方法 解決,如下:
               CMyObject* g_pObject = new CMyObject; 
       這樣,系統在啓動的時候,就會執行new CMyObject來爲對象分配空間,同時執行調用對象的構造函數來初始化對象。如果不想使用指針的方式引用該對象(爲了安全因素,不想某個函數在程序運行期間把這個指針置空),那麼我們可以採用另一種方法,如下:
               CMyObject& g_Object = *(new CMyObject); 
       這樣也可以達到對像創建和初始化的工作。雖然對於我們分配的這個對象沒有進行釋放操作,但是全局只有這麼一次,所以不用擔心內存泄漏問題。程序運行結束的時候,操作系統會自動釋放掉程序所申請的所有內存,當然也包含這個對象。
 
1.1.4 語法檢查的差異
不同操作系統下面有不同的編譯器的實現,不同的編譯器對語法要求的程度不同。在Windows下可以正常編譯的代碼,在Unix下就可能出現語法錯誤。1.1.2中就是一個典型的例子。另外,還有一些其他方面的語法檢查的差異。
 
C 是一種很靈活的語言,語法很自由,但是不同的平臺下這自由的程度也不同。Windows VC、Solaris CC和Linux gcc實現的都不錯,但是有些其他的系統實現的就不是這麼靈活,很多寫法在他們下面都行不通。具體的記不太清了,在AIX和SCO Unix下面碰到很多這種情況。所以只能在移植的過程中逐漸的發現和改正。但是隻要保證採用標準的書寫規範,應該可以更少的產生這種錯誤。
 
有這樣一段代碼: 
       if(NULL == pVar) 
       {    …       } 
這是在大多數平臺下面很好的一種習慣,可以避免哪種把==寫成=的錯誤,在編譯期間就能發現。但是在SCO Unix下面,這種寫法就會引發編譯器的一個警告。這個例子能簡單的說明一下不同編譯器之間存在的差別。
 
1.2    操作系統特性的差異
不同的操作系統中都存在一些系統的限制,如打開文件句柄數的限制、Socket等待隊列的限制、進程和線程堆棧大小的限制等,因此在開發的過程中,必須考慮 到這些限制因素對程序的影響。當然,有些限制參數可以適當的調整,這就需要在發佈程序的時候加以聲明。另外,操作系統的容錯性也對程序有影響。下面分別進 行討論。
 
1.2.1 文件描述符的限制
文件描述符最初是Unix下的一個概念,在Unix系統中,用文件描述符來表示文件、打開的socket連接等,跟Windows下HANDLE的概念類 似。文件描述符是一種系統資源,系統對每個進程可以分配的文件描述符數量都有限制。以Solaris爲例,默認情況下每個進程可以打開的文件描述符爲 1024個,系統的硬限制是8192(具體的值跟版本有關),也就是說可以調整到8192。在Unix系統下使用ulimit命令來獲得系統的這些限制參 數。一般情況下,這都是夠用的,但是有一個例外,在32爲的Solaris程序中,使用標準輸入輸出函數(stdio)進行文件的操作,最大的文件描述符不能超過256。比如說用fopen打開文件,除去系統佔用的3個文件描述符(stdin、stdout和stderr),程序中只能再同時打開253個 文件。如果使用open函數來打開文件,就沒有這個限制,但是就不能夠使用stdio中的那些函數進行操作了,是程序的通用性和靈活性有所降低。這是因爲 在stdio的FILE結構中,用一個unsigned char來表示文件描述符,所以只能表示0~255。
 
在網絡程序的開發中,每一個網絡連接也都佔用一個文件描述符,如果程序打開了很多Socket連接(典型的例子就是使用了連接池技術),那麼程序運行的時候可能用fopen打不開文件。
 
解決這個問題,可以採用一下幾種方法: 
1. 升級爲64位系統或採用採用64位方式編譯程序 
2. 使用sys/io.h中的函數操作文件 
3. 採用文件池技術,預留一部分文件描述符(3~255之間的),使用freopen函數來重用這些描述符。
 至於採用哪種方法或者是否考慮系統中處理這個問題,就要視具體的情況而定了,那些不受這個限制影響的程序,可以不考慮這個問題。
 
1.2.2 進程和線程的限制
一般的操作系統對每個進程和線程可以使用的資源數都有限制,比如一個進程可以創建的線程數,一個進程可以打開的文件描述符的數量,進程和線程棧大小的限制和默認值等。
 
針對這些問題,首先要分析和考慮你的系統是一個什麼樣的規模,會不會收到這些限制的影響,如果需求大於系統的限制,可以通過適當的調整系統參數來解決,如果還不能解決,就得考慮採用多進程的方式來解決。
 
對於進程和線程的棧空間大小的限制,主要是線程棧空間的問題。一般的系統都有默認的線程棧空間大小,而且不同操作系統的默認值可能不同。在通常情況下,這些對程序沒有影響,但是當程序的層次結構比較複雜,使用了過多的本地變量,這個限制可能就會對程序產生影響,導致棧空間溢出,這是一個比較嚴重的問題。不能通過調整系統參數來解決這個問題,但是可以通過相應的函數,在程序裏面指定創建線程的棧空間的大小。但是具體該調整的數值應該適可而止,而不是越大越好。 因爲線程的棧空間過大的時候,就會影響到可創建線程的數量,雖然遠沒有達到系統多線程數的限制,但卻可能因爲系統資源佔用過多導致分配內存失敗。
 
Linux的線程是通過進程實現的,實際上是假的線程。如果程序只在Linux下運行,就可以考慮直接使用多進程技術來代替多線程,因爲在Linux下多線程並不能帶來多線程相對於多進程的優勢。
 
1.2.3 網絡通信能力的限制
對於網絡編程來說,性能是最主要的因素。系統爲了提高網絡通信的性能,提供了很多輔助的技術,其中等待隊列就是其中之一。
 
對 於程序來說,在一個時間點只能處理一個網絡連接請求,而如果同時來了多個網絡連接請求的話,就會有很多請求失敗。爲了解決這個問題,操作系統提供了等待隊 列技術,就是處理不上的連接請求先放到系統的等待隊列中,這樣就可以提高網絡連接的成功率。等待隊列的創建也需要消耗系統資源,因此係統對等待隊列的大小 都有限制,程序中可以通過函數設定等待隊列的大小,但是不能超過系統的硬性限制。下面列出了幾個操作系統的最大等待隊列的大小:
 
操作系統
 最大等待隊列
 
Windows 2000 Server
 200
 
Windows XP Home
 5
 
Solaris E250
 128
  
上 表中只簡單列出了幾個操作系統的等待隊列參數,其他系統暫位列出,如果有興趣可以自己作個簡單的程序測試一下。所以這個問題就跟具體的系統環境有關了,不 過我們可以在系統連接池的基礎上再做一些工作,採用連接緩衝池技術。就是接受到網絡連接請求以後,提交給出去線程處理的之前,先放到一個緩衝池中。這樣可 以接受更多的連接請求等待處理,能一定程度的提高系統的連接成功率。不過,跟系統的等待隊列不同,這是通過軟件方式實現的等待隊列,而系統提供的連接池是 從操作系統級來解決問題的,更接近硬件層次,所以效率肯定會不同。面對這類問題,首先還是得以調整系統連接池的大小,然後再採用其他輔助手段。
 
1.2.4 容錯性的影響
採 用C/C++開發程序,緩衝區溢出的錯誤非常普遍,但是系統運行程序的時候,對待運行期出現的這些錯誤的處理能力都不相同。總的來說,Windows系統 的容錯性最強,尤其是Debug版的程序,系統都加入了一些保護機制,能夠保證出現一些小的錯誤以後,程序仍能夠正常運行。Unix平臺下面要求的就嚴格 一些,有些系統更是容不得一點沙子,有一點錯誤就會出現宕機現象,這些跟操作系統的內存分配機制有關。Windows平臺的程序分配內存的時候,一般都會 多分出一些字節用於對齊,如果緩衝區溢出的不是太多,就不回對內存中其他變量的值造成影響,因此程序也能夠正常運行。但是這種保護機制會帶來更多的系統開 銷。這就是Windows程序移植到Unix下面穩定性降低的主要原因之一,也是爲什麼Windows系統會消耗那麼多系統資源的原因。
 
要解決這類問題,就要進行更嚴格的測試和代碼檢查。同時,藉助相關的測試工具,找出系統中隱藏的潛在的問題,不能放過任何一個可能產生的錯誤,尤其是編譯過程中發現的警告信息。當然,這些工作都應該再移植前做的很充分,在移植後更應該加大測試的力度。
 
1.3    圖形用戶界面
Windows 和Unix 圖形模型差異極大,這點是Unix和Windows程序開發最大的差別。UNIX 使用X Window 系統GUI,而Windows 使用GDI。雖然在概念上類似,但是X API 和GDI API 之間沒有簡單的對應。在Windows下面可以通過MFC等類庫很方便的開發出圖形用戶界面的程序,而Unix下相對來說就麻煩了些,缺少哪種所見即所得 的好的開發工具。Unix下的GUI程序開發,是一個比較複雜的過程,這裏就不在詳細介紹。如果要進行Unix下面GUI程序的開發的話,可以單獨去查找 相關的文檔。
  
1.4    併發處理
併發處理包括多進程和多線程的概念,Windows和Unix的併發處理差別也比較大,但是基本上都能找到一組對應的函數來實現類似的功能。
 
在Windows 下: 創建進程和線程可以通過調用Windows的API來完成,或者通過調用MFC提供的併發處理類庫來實現。
在Unix下面:創建進程通常使用fork函 數。
這跟Windows下面的多進程概念有所不同,相當於在當前位置給當前進程創建一個副本;而Windows下的創建進程大都是創建一個新的進程
Unix下的多線程操作,通過一組線程函數來完成,通常我們使用POSIX 的PTHREAD線程庫來創建線程,但是在不同的Unix分支系統中,都包含又自己的本地線程庫。如在Solaris下面的本地線程庫,是一組以thr_ 開頭的函數,而POSIX的線程函數一般都已pthread_開頭。雖然有兩種不同的線程庫供我們選擇,但在某一個特定的系統下,他們的實現實質都是一樣 的,而且基本上都能夠找到對應的函數。爲了程序的可移植性,建議採用POSIX的線程庫。這是大多數Unix系統都支持的線程庫,但是不同系統下實現的功 能可能有所差別,可能只是實現了這個函數庫的一個子集。
 
在有些Unix系統下,沒有實現線程庫,如SCO Unix,系統只提供多進程的開發方式。但是,如果爲了實現程序代碼的統一性,我們可以採用第三方提供的線程庫。這裏有一個叫FSU-threads的線 程庫供我們選擇。這個線程庫中實現了POSIX中定義的線程函數,而且是開源的,可以支持SunOS 4.1.x, Solaris 2.x, SCO UNIX, FreeBSD, Linux等系統。除此之外,還有ZThreads線程庫等。
 
在Windows 的線程庫中,實現了互斥(Mutex)、事件(Event)、信號量(Semaphore)等同步對象,用於實現線程之間的同步。(還有臨界區 add by jia)
在Unix下面,線程同 步主要使用互斥(mutex)和條件變量(cond),其中條件變量可以實現事件和信號量的功能。另外, POSIX還定義了一套信號量函數,跟線程函數不同,是一組以sem_開頭的函數(POSIX 1003.1b semaphores)。但是這套函數就不想POSIX線程函數支持的那麼廣泛了,比如在AIX上就不支持POSIX的信號量函數,不過AIX系統下有另 一組函數來實現信號量的功能(SystemV semaphores)。在很多Unix系統中,同時支持POSIX的信號量和SystemV的信號量,在Solaris下面還有一套自己的本地函數來實 現信號量。下面分別列出Unix系統中的用於併發處理的主要的函數。
 
進程
 
       fork        創建進程
 
POSIX線程庫
 
pthread_create                            創建一個信的線程 
pthread_attr_init                  初始化一個線程屬性對象 
pthread_attr_destroy            釋放一個線程屬性對象 
pthread_exit                       終止執行調用的線程 
pthread_join                        把當前調用線程掛起,直到目標線程結束 
pthread_setschedparam               設置線程的調度策略和優先級 
pthread_getschedparam               獲得線程的調度策略和優先級 
pthread_sigmask                  改變/檢查調用線程的信號掩碼 
pthread_kill                         發送信號到另一個線程 
pthread_self                        返回當前線程的ID 
pthead_mutex_init                初始化一個互斥量 
pthread_mutexattr_init          初始化互斥量的屬性對象 
pthread_mutex_lock                    給一個互斥量加鎖,如果互斥量已經被別的線程鎖定,調用線程掛起,直到別的線程釋放 
pthread_mutex_unlock         釋放互斥量(解鎖) 
ptherad_mutex_destroy               銷燬一個互斥量 
pthread_cond_init                初始化一個條件變量 
pthread_condattr_init           初始化一個條件變量的屬性對象 
pthread_cond_wait              阻塞在一個條件變量上 
pthread_cond_signal            解除下一個線程在條件變量的阻塞 
pthread_cond_boradcast       解除所有線程在這個條件變量上的阻塞 
pthread_cond_destroy          銷燬一個條件變量 
pthread_cancel                    請求結束一個線程
 
Solaris本地線程庫
 
thr_create                           創建一個新線程 
thr_exit                               終止調用線程 
thr_join                               把當前調用線程掛起,直到目標線程結束 
thr_yield                             用當前線程創建出另一個線程 
thr_suspend                        掛起一個指定的線程 
thr_continue                        恢復一個被掛起的線程 
thr_setprio                          修改線程的優先級 
thr_getprio                          獲得線程的優先級 
thr_sigsetmask                    改變/檢查調用線程的信號掩碼 
thr_kill                                發送信號到另一個線程 
thr_self                               返回當前線程的ID 
thr_main                             標記爲主線程 
thr_mutex_init                     初始化一個互斥量 
thr_mutex_lock                          給一個互斥量加鎖,如果互斥量已經被別的線程鎖定,調用線程掛起,直到別的線程釋放 
thr_mutex_unlock                釋放互斥量(解鎖) 
thr_mutex_destroy               銷燬一個條互斥量 
thr_cond_init                       初始化一個條件變量 
thr_cond_wait                     阻塞在一個條件變量上 
thr_cond_signal                    解除下一個線程在條件變量的阻塞 
thr_cond_boradcast             解除所有線程在這個條件變量上的阻塞 
thr_cond_destroy                銷燬任何狀態的條件變量 
rwlock_init                         初始化一個讀寫鎖 
rw_rdlock                           獲得一個讀寫鎖的讀鎖定 
rw_wrlock                          獲得一個讀寫鎖的寫鎖定 
rw_unlock                          解鎖一個讀寫鎖
 
POSIX信號量
 
sem_init                              初始化一個信號量 
sem_destroy                        銷燬一個信號量 
sem_wait                                    等待獲得一個信號量,獲得後信號量的值減1,如果當前信號量值位0,當前線程阻塞,支持有別的線程釋放信號量
 sem_trywait                         嘗試獲得一個信號量,獲得後信號量的值減1,如果當前信號量值位0,返回失敗 
sem_post                             釋放一個信號量 
sem_getvalue                       獲得指定信號量的值
 
       System V信號量
 
javascript:if(confirm('http://personal.xfol.com/~rezaie/api/ipc/man.cgi@semctl%20%20""n""nThis%20file%20was%20not%20retrieved%20by%20Teleport%20Pro,%20because%20the%20server%20reports%20that%20this%20file%20cannot%20be%20found.%20%20""n""nDo%20you%20want%20to%20open%20it%20from%20the%20server?'))window.location='http://personal.xfol.com/%7Erezaie/api/ipc/man.cgi@semctl'>semctl                                 對信號量進行一系列的控制
 
javascript:if(confirm('http://personal.xfol.com/~rezaie/api/ipc/man.cgi@semget%20%20""n""nThis%20file%20was%20not%20retrieved%20by%20Teleport%20Pro,%20because%20the%20server%20reports%20that%20this%20file%20cannot%20be%20found.%20%20""n""nDo%20you%20want%20to%20open%20it%20from%20the%20server?'))window.location='http://personal.xfol.com/%7Erezaie/api/ipc/man.cgi@semget'>semget                                創建一個信號量,成功時返回信號的ID
 
javascript:if(confirm('http://personal.xfol.com/~rezaie/api/ipc/man.cgi@semop%20%20""n""nThis%20file%20was%20not%20retrieved%20by%20Teleport%20Pro,%20because%20the%20server%20reports%20that%20this%20file%20cannot%20be%20found.%20%20""n""nDo%20you%20want%20to%20open%20it%20from%20the%20server?'))window.location='http://personal.xfol.com/%7Erezaie/api/ipc/man.cgi@semop'>semop                                 對信號進行操作
 
       Solaris的本地信號量,更接近於操作系統中我們學到的PV操作的信號燈
 
sema_init                             初始化一個信號燈(信號量) 
sema_destroy                       銷燬一個信號燈 
sema_p                               執行信號燈的P操作 
sema_p_sig                                 跟sema_p類似,當阻塞再這個函數的時候,如果線程收到一個信號,函數退出 
sema_tryp                           嘗試執行信號燈的P操作 
sema_v                               執行信號燈的V操作
  
       爲了方便使用,我在開發的過程中已經把上面常用的函數都封裝成了類,兼容Windows和各種常見的Unix系統,而且網上還有很多這方面的代碼資源可用。如果感興趣的話可以向我索要。
 
1.5    網絡通信
Socket (中文譯名:套接字)最初在Unix上出現,並很快成爲Unix上最流行的網絡編程接口之一。後來,微軟將它引入到Windows中並得到實現,於是從 Windows 95、WinNT4開始,系統就內置了Winsock1.1,後來到了Windows98、Windows2000,它內置的Winsock DLL更新爲Winsock2.2。
 
Windows 下的Socket函數大體上和Unix下的Socket函數差不多,函數名稱很參數用法都類似,只有一些細微的差別,某些參數的意義不同,而且對於 Socket的屬性控制也不太一樣。Windows下面還對Socket函數進行了封裝,有一系列相關類可用使用,簡化網絡編程的複雜性。Unix本身沒 有這些類庫,但是我們也已經積累了很多這方面的經驗和資源。我們有一組現成的類對Windows和Unix下的Socket函數進行了封裝,上層只需要簡 單的調用即可,不用關心底層的差別。而且,這套類庫也可以同時支持多種平臺,可移植性非常好。
 
關於Socket開發就簡單說這些,想深入瞭解的話請參考介紹Socket編程的一些相關資料。
1.2    操作系統特性的差異
不同的操作系統中都存在一些系統的限制,如打開文件句柄數的限制、Socket等待隊列的限制、進程和線程堆棧大小的限制等,因此在開發的過程中,必須考慮 到這些限制因素對程序的影響。當然,有些限制參數可以適當的調整,這就需要在發佈程序的時候加以聲明。另外,操作系統的容錯性也對程序有影響。下面分別進 行討論。
 
1.2.1 文件描述符的限制
文件描述符最初是Unix下的一個概念,在Unix系統中,用文件描述符來表示文件、打開的socket連接等,跟Windows下HANDLE的概念類 似。文件描述符是一種系統資源,系統對每個進程可以分配的文件描述符數量都有限制。以Solaris爲例,默認情況下每個進程可以打開的文件描述符爲 1024個,系統的硬限制是8192(具體的值跟版本有關),也就是說可以調整到8192。在Unix系統下使用ulimit命令來獲得系統的這些限制參 數。一般情況下,這都是夠用的,但是有一個例外,在32爲的Solaris程序中,使用標準輸入輸出函數(stdio)進行文件的操作,最大的文件描述符不能超過256。比如說用fopen打開文件,除去系統佔用的3個文件描述符(stdin、stdout和stderr),程序中只能再同時打開253個 文件。如果使用open函數來打開文件,就沒有這個限制,但是就不能夠使用stdio中的那些函數進行操作了,是程序的通用性和靈活性有所降低。這是因爲 在stdio的FILE結構中,用一個unsigned char來表示文件描述符,所以只能表示0~255。
 
在網絡程序的開發中,每一個網絡連接也都佔用一個文件描述符,如果程序打開了很多Socket連接(典型的例子就是使用了連接池技術),那麼程序運行的時候可能用fopen打不開文件。
 
解決這個問題,可以採用一下幾種方法: 
1. 升級爲64位系統或採用採用64位方式編譯程序 
2. 使用sys/io.h中的函數操作文件 
3. 採用文件池技術,預留一部分文件描述符(3~255之間的),使用freopen函數來重用這些描述符。
 至於採用哪種方法或者是否考慮系統中處理這個問題,就要視具體的情況而定了,那些不受這個限制影響的程序,可以不考慮這個問題。
 
1.2.2 進程和線程的限制
一般的操作系統對每個進程和線程可以使用的資源數都有限制,比如一個進程可以創建的線程數,一個進程可以打開的文件描述符的數量,進程和線程棧大小的限制和默認值等。
 
針對這些問題,首先要分析和考慮你的系統是一個什麼樣的規模,會不會收到這些限制的影響,如果需求大於系統的限制,可以通過適當的調整系統參數來解決,如果還不能解決,就得考慮採用多進程的方式來解決。
 
對於進程和線程的棧空間大小的限制,主要是線程棧空間的問題。一般的系統都有默認的線程棧空間大小,而且不同操作系統的默認值可能不同。在通常情況下,這些對程序沒有影響,但是當程序的層次結構比較複雜,使用了過多的本地變量,這個限制可能就會對程序產生影響,導致棧空間溢出,這是一個比較嚴重的問題。不能通過調整系統參數來解決這個問題,但是可以通過相應的函數,在程序裏面指定創建線程的棧空間的大小。但是具體該調整的數值應該適可而止,而不是越大越好。 因爲線程的棧空間過大的時候,就會影響到可創建線程的數量,雖然遠沒有達到系統多線程數的限制,但卻可能因爲系統資源佔用過多導致分配內存失敗。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章