Linux下高階C程式設計(學習總結)

2020-10-21 13:00:37

Linux下高階C程式設計

第一章 unix/linux系統的基本概念
第二章 unix/linux系統下的程式設計基礎和開發方式
第三章 unix/linux系統下的記憶體管理
第四章 unix/linux系統下的檔案管理和目錄操作
第五章 unix/linux系統下的程序管理
第六章 unix/linux系統下的訊號處理
第七章 unix/linux系統下的程序間通訊
第八章 unix/linux系統下的網路程式設計
第九章 unix/linux系統下的多執行緒程式設計


文章目錄



前言

主要學習unix/linux系統下的API程式設計

一、linux系統的基本概念

1.linux系統簡介

2.gcc/cc基本使用

gcc/cc xxx.c 可以編譯連結C源程式生成一個可執行檔案 a.out
整個流程分4步:
	  (1) 預處理/預編譯 (包含標頭檔案的擴充套件,以及執行宏替換等,生成 .i 檔案)
	  (2) 編譯 (將高階語言程式翻譯成組合語言,得到組合檔案,生成 .s 檔案)
	  (3) 組合 (將組合語言翻譯成機器指令,得到目標檔案,生成 .o 檔案)
	  (4) 連結 (將目標檔案和標準庫連結,得到可執行檔案)

3.常見的編譯選項

-E    實現預處理的執行,預設將處理輸出到控制檯,可以通過 -o 選項指定輸出到 xxx.i 檔案中,預處理檔案中包含很多標頭檔案,型別別名,以及各種函數宣告等
-S    實現編譯處理,得到一個 .s 為字尾的組合檔案
-c    實現編譯處理,得到一個 .o 為字尾的目標檔案
-v    檢視編譯器的版本資訊
-std    指定執行的C標準
-wall   儘可能的產生警告資訊
-werror 將警告當做錯誤進行處理
-g    產生偵錯資訊,採用GDB進行單步偵錯
-O    進行具體的優化
-x    指定原始碼的具體程式語言

4.常用的C程式檔案字尾

.h   標頭檔案  包含結構體的定義,變數和函數的宣告
.c   原始檔  包含變數和函數的定義
.i   預處理檔案
.s   組合檔案
.o   目標檔案
.a   靜態庫檔案  對功能函數打包
.so  共用庫檔案  對功能函數打包

5.多檔案程式設計

預處理:
	#include  表示將指定檔案的內容包含進來
	#define   表示一個宏
	#undef    表示取消一個宏
	#if       表示如果
	#ifdef    表示如果定義
	#ifndef   表示如果沒有定義
	#else     表示否則
	#elif     表示否則如果
	#endif    表示結束判斷
常用指令:
	#line 整數n      表示修改程式碼的行數/指定行號,修改下一行行號為n
	#error 字串    表示產生一個錯誤資訊
	#warning 字串  表示產生一個警告
	#pragma GCC dependency 檔名  表示當前檔案依賴於指定的檔案,如果當前檔案的最後一次修改時間早於依賴的檔案,則產生警告資訊
	#pargma GCC poison 識別符號  表示將後面的標識設定成毒藥的意思,一旦使用識別符號,則產生錯誤或者警告資訊
	#pargma pack (整數n) 表示按照整數n的倍數進行對齊補齊,必須按照2的最小次方對齊補齊

6.預定義宏

__FILE__  表示獲取所在的檔名(使用 %s 列印輸出)
__BASE_FILE__  表示獲取正在編譯的原檔名(使用 %s 列印輸出)
__LINE__  表示獲取所在行號(使用 %d 列印輸出)
__FUNCTION__/__func__  表示獲取所在函數名稱(使用 %s 列印輸出)
__DATE__  獲取日期資訊(使用 %s 列印輸出)
__TIME__  獲取時間資訊(使用 %s 列印輸出)

7.環境變數的概念和使用

環境變數: 存放和程式設計環境/系統環境相關資訊的變數
PATH/path 就是一個環境變數,存放各種路徑,在 PATH/path 中存放路徑的程式不需要在增加路徑就可以直接執行
程式設計相關的環境變數:
	CPATH/C_INCLUDE_PATH  主要表示C語言中標頭檔案的所在路徑
	CPLUS_INCLUDE_PAHT  主要表示C++標頭檔案所在路徑
	LIBRARY_PATH  編譯連結時查詢靜態庫和共用庫的路徑
	LD_LIBRARY_PATH  執行時查詢共用庫的路徑
查詢標頭檔案的方式:
	(1) #include <>
		表示去系統預設的路徑中查詢指定的標頭檔案
	(2) #include ""
		表示去當前工作目錄中進行查詢
	(3) 設定環境變數 CPATH 進行查詢
		export CPATH=$CPATH:標頭檔案所在路徑
	(4) 使用編譯選項進行查詢(重點)
		gcc/cc  xxx.c  -l  標頭檔案所在的路徑

二、linux系統下的程式設計基礎和開發方式

1.庫檔案的概念和使用

(1)靜態庫
	靜態庫檔案在使用時,直接將呼叫的功能程式碼複製到目標檔案
	缺點:造成目標檔案變大,不利於修改和維護
	優點:不需要跳轉,所以效率比較高,不依賴於靜態庫檔案
(2)共用庫
	共用庫檔案在使用時,將所呼叫功能程式碼在共用庫檔案中的地址拷貝到目標檔案中
	缺點:需要跳轉,所以效率比較低,依賴於共用庫檔案的存在
	優點:目標檔案比較小,修改和維護比較方便

2.靜態庫的生成和呼叫

靜態庫檔案的生成步驟:
	(1) 編寫原始碼程式 xxx.c(例如 add.c)
	(2) 只編譯不連線,生成目標檔案 xxx.o(例如 add.o)
	(3) 使用 ar -r 命令生成靜態庫檔案
		ar -r  lib庫名.a  目標檔案1  目標檔案2  ...
		lib庫名.a  叫做靜態庫檔名
		例如:  ar -r  libadd.a  add.o
靜態庫檔案的呼叫步驟:
	(1) 編寫呼叫庫檔案的原始碼 xxx.c(例如 main.c)
	(2) 只編譯不連結生成目標檔案 xxx.o(例如 mian.o)
	(3) 連結測試程式和庫檔案,方法有三種:
		1) 直接連結
			gcc/cc  main.o  libadd.o
		2) 通過編譯器選項進行間接連結
			gcc/cc  main.o  -l  庫名  -L  庫檔案所在的路徑
		3) 設定環境變數 LIBRARY_PAHT 進行連結
			export LIBRARY_PATH=$LIBRARY_PAHT:.
			gcc/cc  main.o  -l  庫名

3.共用庫的生成和呼叫

共用庫檔案的生成步驟
	(1) 編寫源程式 xxx.c(例如 add.c)
	(2) 只編譯不連結,生成目標檔案 xxx.o(例如 add.o)
		gcc/cc  [-fpic]/*小模式選項,希望目標檔案越小越好*/  add.c
	(3) 生成共用庫檔案
		gcc/cc  -shared  xxx.o  -o  lib庫名.so
		例如: gcc/cc  -shared  xxx.o  -o  libadd.so  
共用庫檔案的呼叫步驟
	(1) 編寫呼叫庫檔案的源程式 xxx.c(例如: main.c)
	(2) 只編譯不連結,生成目標檔案 xxx.o(例如: main.o)
		gcc/cc  -c  main.c
	(3) 連結測試程式和庫檔案,方法有三種:
		1) 直接連結
			gcc/cc  main.o  libadd.so
		2) 通過編譯器選項進行間接連結
			gcc/cc  main.o  -l  庫名  -L  庫檔案所在的路徑
		3) 設定環境變數 LIBRARY_PATH 進行連結
			export  LIBRARY_PATH=$LIBRARY_PATH:.
			gcc/cc  main.o  -l  庫名

4.共用庫動態載入

(1)dlopen 函數
	開啟共用庫檔案
	函數: void * dlopen( const char * pathname, int mode);
	引數一: 字串形式的共用庫檔名
	引數二: 載入標誌
		RTLD_LAZY 延遲載入
		RTLD_NOW 立即載入
	返回值: 
		失敗返回: NULL
		成功返回: 該共用庫對應的控制程式碼/地址
(2)dlerror 函數
	排查顯示出錯的資訊
	函數: char * dlerror(void);
	返回值: 
		當動態連結庫操作函數執行失敗時,dlerror可以返回出錯資訊,返回值為NULL時表示操作函數執行成功
(3)dlsym 函數
	根據引數指定的控制程式碼和函數名,返回函數名所對應的記憶體地址
	函數: void * dlsym(void*handle,constchar*symbol);
	引數一: 共用庫控制程式碼,dlopen的返回值
	引數二: 字串型別的符號,也就是函數名
	返回值:
		函數的地址
(4)dlclose 函數
	關閉引數指定的共用庫控制程式碼,也就是關閉共用庫
	函數: int dlclose (void *handle);

5.C語言的錯誤處理

return  0;   表示程式正常結束
return  -1;   表示程式不正常結束
 
1.錯誤表示的一般規則(是否出錯)
	C語言中通過使用返回值來表示是否出錯,根據返回值來進行具體的錯誤處理
   	(1) 如果返回值型別是int型,並且返回的值不可能是負數時則使用返回值-1代表出錯,其他資料型別表示正常返回
   	(2) 如果返回值型別是int型,並且返回值可能是負數時,則需要使用指標取出返回的資料,返回值僅僅表示是否出錯,-1 表示出錯,0 表示正常返回
   	(3) 如果返回值型別是指標型別,則返回NULL代表出錯
   	(4) 如果不考慮是否出錯,返回值型別使用void即可
   	
2.錯誤編號和錯誤資訊(為什麼錯了)
	#include<errno.h> 標頭檔案
	errno 是一個全域性變數,當函數呼叫失敗時,會將具體的錯誤編號設定到errno中可以在通過error來獲取錯的原因
	
3.錯誤資訊
	(1) strerror函數  表示字串錯誤,這個函數主要用於將引數指定的錯誤編號翻譯成對應的錯誤資訊返回
	(2) perror函數  表示最後一個錯誤資訊大印出來,引數s不為空時原樣輸出,後面追加一個冒號和空格,再跟著錯誤資訊以及換行
	(3) printf函數  printf("%m");  列印錯誤資訊

注意:
判斷函數的呼叫是否成功,還是需要根據返回值進行判斷,而在確定已經呼叫失敗的情況下,這時可以通過errno來獲取錯誤的原因,也就是不能直接使用error來判斷函數的呼叫是否成功

6.環境表的概念和使用

1.環境表的概念
	環境表 是指環境變數的集合,而每個程序都擁有一張獨立的環境表,來儲存和當前程序相關的環境變數資訊
	環境表採用字元指標陣列來儲存所有的環境變數,使用全域性變數 char** environ 來記錄環境表的首地址,使用NULL來代表環境表的結束,所以存取環境表則需要藉助 environ 變數
	
2.環境表相關的處理

	(1) getenv 函數 
	函數: char *getenv(const char *name);
	表示根據引數指定的環境變數名去環境表中進行查詢,返回該環境變數所對應的環境值,查詢失敗則返回NULL

	(2) setenv 函數  
	函數: int setenv(const char *name, const char *value, int overwrite);
	第一引數 環境變數名 
	第二引數 環境變數值 
	第三引數 是否修改,如果引數指定的環境變數不存在則增加,如果存在並且overwrite 是非0,則修改環境變數值,是否環境變數不變

	(3) putenv 函數 
	函數: int putenv(char *string);
	表示按照引數的內容增加/修改環境變數,其中string的格式為: name=value, 如果不存在則新增,存在則修改

	(4) unsetenv 函數 
	函數: int unsetenv(const char *name);
	表示根據引數指定的環境變數去環境表中進行刪除

	(5) clearenv 函數 
	函數: int clearenv(void);
	表示清空整個環境表

3.主函數的原型
	int main(int argc,char* argv[],char* envp[]){}
	第一個引數:命令列引數的個數
	第二個引數:命令列引數的地址資訊
	第三個引數:環境表的首地址

三、linux系統下的記憶體管理

1.程式和程序的概念

	程式:表示存放在硬碟上的可執行檔案
	程序:表示在記憶體中正在執行的程式

2.程序中的記憶體區域劃分 (記憶體地址從小到大,其中堆和棧沒有明確的分割線,可以適當的調整)

	(1) 程式碼區       存放程式的功能程式碼的區域  例如:函數名
	(2) 唯讀常數區   主要存放字串常數和const修飾的全域性變數
	(3) 全域性區       主要存放已經初始化的全域性變數和static修飾的區域性變數
	(4) BSS段        主要存放沒有初始化的全域性變數和static修飾的區域性變數
	(5) 堆區         主要表示使用 malloc/calloc/realloc 等手動申請的動態記憶體空間,記憶體由程式設計師手動申請手動釋放
	(6) 棧區         主要存放區域性變數(包括函數的形參),const修飾的區域性變數,以及塊變數,該記憶體區域由作業系統自動管理

3.字串儲存形式之間的比較

	對於一個儲存常數字串的字元指標和字元陣列來說,字元指標可以改變指向,不可以改變指向的內容,而字元陣列可以改變指向的內容,不可以改變指向
	對於一個儲存常數字串的動態記憶體空間來說,其中指向該記憶體空間的指標,既可以改變指向,又可以改變內容

4.虛擬記憶體管理技術

	unix/Linux系統的記憶體都是採用虛擬記憶體管理技術來進行管理的,
	即:每個程序都有0~4G的記憶體地址 (虛擬的,並不是真實存在的),由作業系統負責把記憶體地址和真實的實體記憶體對映起來.
	因此,不同程序的記憶體地址看起來是一樣的,但是所對應的實體記憶體是不一樣的
	每個程序中0~4G的虛擬地址空間分為: 使用者空間和核心空間. 
	使用者空間指0~3G虛擬地址空間,而核心空間指3G~4G的虛擬地址空間. 
	使用者程式執行在使用者空間,核心空間只有系統核心才能存取,使用者程式不能直接存取核心空間,
	不過系統核心提供了一些系統函數負責從使用者空間切換到記憶體空間
	記憶體地址的基本單位是位元組,記憶體對映的基本單位是記憶體頁,目前主流的作業系統中一個記憶體頁是4kb (4096位元組)
	1Tb=1024Gb
	1Gb=1024Mb
	1Mb=1024Kb
	1Kb=1024byte (位元組) 1位元組= 8位元 
	1byte=8個bit (二進位制位)

5.段錯誤的由來

	(1) 使用scanf函數時缺少 &
	(2) 空指標/野指標的使用
	(3) 試圖使用一個沒有經過對映的虛擬地址可能引發段錯誤

	STL(標準模版庫)   - 申請/釋放動態記憶體
	new/delete       - 申請/釋放動態記憶體,c++語言中的運運算元
	malloc()/free()  - 申請/釋放動態記憶體,標c函數
	sbrk()/brk()     - 申請/釋放動態記憶體,UC函數
	mmap()/munmap()  - 建立/解除 到記憶體的對映

6.使用malloc 申請動態記憶體的特性

	(1) 使用malloc 申請記憶體的注意事項
		使用malloc申請動態記憶體時,可能還需要額外的12個位元組來儲存一些用於管理動態記憶體的資訊,比如記憶體的大小等
		malloc底層採用連結串列的形式去處理多個記憶體塊,也就是需要儲存有關 下一個記憶體塊/上一個記憶體塊 的資訊
		使用malloc申請的動態記憶體,千萬不要越界存取,因為極有可能破壞管理資訊,從而引發段錯誤
	(2) 使用malloc 申請記憶體的一般對映規則
		一般來說,使用malloc申請比較小的動態記憶體時,作業系統會一次性分配33個記憶體頁,從而提高效率

7.使用free 釋放動態記憶體的特性

	一般來說,使用malloc申請比較大的記憶體時,系統會預設分配34個記憶體頁,
	當申請的記憶體超過34個記憶體頁時,則系統會再次分配33個記憶體頁(也就是按照33個記憶體頁的整數倍進行配)
	使用free釋放動態記憶體時,釋放多少則在動態記憶體總數中減去多少,
	當所有的記憶體釋放完畢時,程序還保留33個記憶體頁備用,知道程序結束,主要是為了提高效率

8.記憶體處理的相關函數

	(1) getpagesize函數
		函數: int getpagesize(void);
		主要用於獲取系統中一個記憶體頁的大小,一般為4kb
		
	(2) sbrk 函數
		函數: void *sbrk(intptr_t increment);
		主要用於按照引數指定的大小來調整記憶體塊的大小,
		如果引數大於0表示申請記憶體,如果引數等於0表示獲取記憶體塊的當前位置,
		如果引數小於0表示釋放記憶體,成功返回之前記憶體塊的地址,失敗返回-1
		sbrk操作記憶體的一般規則:
		申請比較小的記憶體時,一般會預設分配1個記憶體頁,
		申請的記憶體超過1個記憶體頁時,會再次分配1個記憶體頁,
		釋放記憶體時,釋放完畢後剩餘的記憶體如果在一個記憶體頁內,則一次性釋放1個記憶體頁
		
	(3) brk 函數
		函數: int brk(void *addr);
		表示操作記憶體的末尾地址到引數指定的位置,
		如果引數指定的位置大於當前的末尾位置,則申請記憶體,如果引數指定的位置小於當前的末尾位置,則釋放記憶體
		
	(4) mmap 函數
		函數: void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
		第一個引數:建立對映的起始地址,給NULL 由系統核心選擇
		第二個引數:建立對映的大小 
		第三個引數:對映的許可權
        	PROT_EXEC  - 可執行
        	PROT_READ  - 可讀
       		PROT_WRITE - 可寫
        	PROT_NONE  - 不可存取
    	第四個引數: 對映的模式
        	MAP_SHARED    - 共用的
        	MAP_PRIVATE   - 私有的
        	MAP_ANONYMOUS - 對映到實體記憶體
    	第五個引數:檔案描述符,對映實體記憶體給0即可
    	第六個引數:檔案偏移量,對映實體記憶體給0即可
    	返回值 :成功返回對映的地址,失敗返回MAP_FAILED (-1)
    	函數功能:建立檔案/裝置  到記憶體對映
 
	(5) munmap函數
		函數: int munmap(void *addr, size_t length);
		第一個引數:對映的地址
		第二個引數:對映的大小
		函數功能:刪除指定的對映

四、linux系統下的檔案管理和目錄操作

1.基本概念

	linux/unix系統中,幾乎所有的一切都可以統稱檔案,因此,對於檔案的操作幾乎適合所有的裝置等等,目錄也可以看作檔案處理
	/dev/null				空裝置
	echo  字串				表示原樣輸出字串內容
    echo 字串>檔名   	表示寫入字串到檔案
    echo 字串>/dev/null	表示丟棄結果資訊
    cat  /dev/null>檔名	表示清空檔案
    /dev/tty				輸入輸出裝置,一般預設為終端(瞭解)
    echo 字串>/dev/tty		表示輸出到輸入輸出裝置
    cat  /dev/tty			表示讀取/列印輸入輸出裝置內容

2.檔案的相關處理常式

	(1) open 函數
		函數: int open(const char *pathname, int flags, mode_t mode);
         第一個引數:字串形式的路徑和檔名
         第二個引數:操作標誌 必須包含以下存取許可權中的一個:
         	O_RDONLY - 唯讀
         	O_WRONLY - 只寫
         	O_RDWR   - 可讀可寫
         還可以按位元或上以下標誌中的一個:
         	O_CREAT   - 檔案存在則開啟,不存在則建立
         	O_EXCL    - 與O_CREAT搭配使用,如果存在則建立失敗
         	O_TRUNC   - 檔案存在,是個規則檔案,開啟方式有寫許可權,則清空檔案
         	O_APPEND  - 追加
         第三個引數:操作模式用於指定建立的新檔案許可權,如:0644   查詢許可權  ls -l 檔名
         返回值: 成功返回一個新的檔案描述符,失敗返回-1,檔案描述符就是一個非負整數,用於代表一個開啟的檔案
    (2) close 函數
    	函數: int close(int fd);
    	引數: 檔案描述符  
    (3) read 函數
    	函數: ssize_t read(int fd, void *buf, size_t count);
    	第一個引數:檔案描述符 (資料從哪裡讀取)
    	第二個引數:緩衝區的首地址 (資料存到哪裡去)
    	第三個引數:讀取的資料大小
    	返回值:成功返回讀取到的資料大小,失敗返回-1
    (4) write 函數
    	函數: ssize_t write(int fd, const void *buf, size_t nbyte);
    	第一個引數:檔案描述
    	第二個引數:偏移量
    	第三個引數:從什麼地方開始偏移
        	SEEK_SET  -  檔案的起始位置
            SEEK_CUR  -  檔案的當前位置
            SEEK_END  -  檔案的尾部位置
        返回值: 成功返回當前位置距離檔案頭的偏移量,失敗返回-1

3.檔案描述符

	檔案描述符本質就是一個整數,可以代表一個開啟檔案.
	但是檔案的資訊並不是儲存在檔案描述符中,而是存在檔案表等結構中,使用open函數開啟一個檔案時,會把檔案愛的資訊放入檔案表等結構中,
	但是處於安全和效率等因素的考慮,檔案表等結構不適合直接操作,而是給檔案表對應一個編號,使用編號進行操作,這個編號就是檔案描述符
	系統還會管理檔案描述符,在每個程序中都會有一張描述符總表,當有新的檔案描述符需要時,會去總表中查詢未使用的最小值並且返回.
	檔案描述符本質就是非負整數,也就是從0開始,一直OPEN_MAX(在linux系統中一般是255),其中0 1 2被系統佔用,分別代表標準輸入,標準輸出以及標準錯誤
	
	注意:
		開啟不同的檔案時,對應的問價表和v節點表資訊都不同,而多次開啟相同檔案時,v節點表資訊相同,檔案表資訊不同close()函數的工作方式:
	先把檔案描述符與檔案表的對應關係解除,不一定會刪除檔案表,只有當檔案表沒有與其他描述符對應時才會刪除檔案表(一個檔案表可以對應多個描述符),close()函數也不會修改描述符的整數值,但是會讓一個描述符無法代表一個檔案 

4.檔案的非讀寫函數

	(1) dup 函數
		函數: int dup(int oldfd);
		表示根據引數指定的檔案描述符進行拷貝,成功返回新的檔案描述符,失敗返回-1
		
	(2) dup2 函數
		函數:int dup2(int oldfd, int newfd);
		表示將引數newfd作為引數oldfd的拷貝,如果有必要,則先關閉newfd,成功返回新描述符,也就是newfd,失敗返回-1
		
	(3) fcntl 函數  
	根據檔案描述符對檔案執行的操作:
    	a. 複製檔案描述符
        b. 設定/獲取檔案描述符的標誌(瞭解)
        c. 設定/獲取檔案狀態的標誌(瞭解)
        d. 實現檔案鎖的功能(掌握)
	函數: int fcntl(int fd, int cmd, ... /* arg */ );
		第一個引數:檔案描述符
		第二個引數: 操作的命令
        	F_DUPFD - 複製檔案描述符的功能,尋找最小的有效的大於等於第三個引數arg的描述符作為新的描述符,與dup2()函數所不同的是,不會強制關閉已經被佔用的描述符       fcntl(fd,F_DUPFD,5);
        	F_GETFD/F_SETFD  -  獲取/設定檔案描述符的標誌
        	F_GETFL/F_SETFL  -  獲取/設定檔案狀態的標誌
       		F_SETLK/F_SETLKW/F_GETLK - 加鎖/解鎖/測試鎖是否存在
		第三個引數:可變長引數,引數是否需要,主要取決於引數cmd
		返回值:
			F_DUPFD  - 成功返回新的檔案描述
			F_GETFD  - 成功返回檔案描述符的標誌值
			F_GETFL  - 成功返回檔案狀態的標誌值
			其他操作成功返回0,所有的操作失敗返回-1

	(4)使用fcntl 函數實現檔案鎖功能
		當使用多個程序同時讀寫檔案時,可能會引發檔案的混亂,如果左右的程序都是讀檔案,則可以同時進行,但是隻要有一個進程序執行寫操作,則多個程序應該序列操作而不是並行,使用檔案鎖實現上面的想法,檔案鎖就是讀寫鎖,一把讀鎖和一把寫鎖,其中讀鎖是一把共用鎖,允許其他程序加讀鎖,不允許加寫鎖,寫鎖是一把互斥鎖,不允許其他程序加讀鎖和寫鎖
		使用檔案鎖的功能時:
			第二個引數的取值
				F_SETLK/F_SETLKW/F_GETLK  - 加鎖/解鎖/測試鎖是否存在 
			第三個引數取值:指定以下結構體型別的指標(一般使用的時候用 &取結構體的地址)
		struct flock 
		{         ...
		    short l_type;    /* Type of lock: F_RDLCK, F_WRLCK, F_UNLCK */ /*鎖的型別*/
		    short l_whence;  /* How to interpret l_start:SEEK_SET, SEEK_CUR, SEEK_END */  /*表示從什麼地發開始*/
		    off_t l_start;   /* Starting offset for lock *//*偏移量*/
		    off_t l_len;     /* Number of bytes to lock *//*鎖定的位元組數*/
		    pid_t l_pid;     /* PID of process blocking our lock(F_GETLK only) *//*加鎖的程序號 , 給 -1  */
		               ...
		 };

		1)F_SETLK 的使用    主要用於加鎖/解鎖 的功能	
	 		測試結果:加完讀鎖之後,還是可以向檔案寫入資料的,結果說明鎖是獨立於檔案的,並沒有真正鎖定對檔案的讀寫操作,也就是說鎖只能鎖定其他的鎖換句話說,如果加了一把鎖,可以導第二個加鎖失敗(兩個讀鎖除外)
		如何使用檔案鎖來控制是否可以對檔案進行讀寫操作。
	  	實現方案:在執行讀操作之前嘗試加讀鎖,在執行寫操作之前嘗試加寫鎖,根據能不能枷鎖成功來決定是否進行讀寫操作即可。      
	    	釋放檔案鎖:
	    	a.程序結束,自動釋放所有檔案鎖
	    	b.將檔案鎖的型別設定為F_UNLCK,使用fcntl 重新設定來實現解鎖的效果
		2)F_SETLKW 的使用   功能與F_SETLK類似,所不同的是,加不上鎖會等待,直到鎖能加上為止
		3)F_GETLK 的使用   測試一下引數鎖能否加上,如果能加上,則不會去加鎖,而是將鎖的型別改成F_UNLCK;如果不能加上,則見將檔案中已經存在的鎖的資訊通過引數鎖帶出來,並且將l_pid設定為真正給檔案枷鎖的程序號,所以可以使用l_pid判斷鎖是否能加上

	(5)stat/fstat函數
		函數: int stat(const char *pathname, struct stat *statbuf);
		函數: int fstat(int fd, struct stat *statbuf);
		函數功能:獲取指定檔案的狀態資訊
		第一個引數:檔案的路徑/檔案描述符
		第二個引數:結構體指標,結構體變數的地址
		  struct stat 
		   {
		    ...
		     mode_t  st_mode;  /*檔案的型別和許可權*/
		     off_t   st_size;  /*檔案的大小*/
		     time_t  st_mtine; /*檔案的最後一次修改時間*/
		    ...
		   };
	   
		  擴充套件:
		  #include<time.h>
		  char *ctime(const time_t *timep);     =>主要用於將整數型別的時間轉換為字串形式的時間 
		  struct  tm *localtime(const time_t *time);    =>主要用於將整數形式的時間轉換為結構體的指標型別
		  結構體的型別:
		  struct tm {
		               int tm_sec;         /* seconds 秒*/
		               int tm_min;         /* minutes 分鐘*/
		               int tm_hour;        /* hours 小時*/
		               int tm_mday;        /* day of the month 日*/
		               int tm_mon;         /* month 月    +1     */
		               int tm_year;        /* year 年     +1900  */
		               int tm_wday;        /* day of the week 星期幾*/
		               int tm_yday;        /* day in the year 年中的第幾天  +1*/
		               int tm_isdst;       /* daylight saving time  夏令時 */
		           }; 
	           
	  (6)access函數 
		函數:int access(const char *pathname, int mode);  
		函數功能:主要用於檔案是否存在以及對應的許可權
    	第一個引數:檔案的路徑和檔名
    	第二個引數:操作模式
              F_OK  判斷檔案是否存在
              R_OK  判斷檔案是否可讀
              W_OK 判斷檔案是否可寫
              X_OK  判斷檔案是否可執行 

	(7)chmod 函數 
     	函數功能:主要用於將引數指定的檔案許可權修改為引數二指定的值
 	
	(8)truncate 函數  
    	函數功能: 主要用於將引數一指定的檔案大小擷取到引數二指定的大小,如果檔案變小則多餘資料丟失,如果檔案變大則擴充套件出來的區域用"\0"填充
	
	(9)umask 函數   
	    函數功能: 主要用於設定建立檔案時需要遮蔽的許可權,返回之前遮蔽的許可權

	(10)其他處理常式:
	    link()    主要用於建立硬連結
	    unlink()  主要用於刪除硬連結 
	    rename()  主要用於重新命名
	    remove()  主要用於刪除指定的檔案

5.目錄管理

	常用的函數
        (1) opendir 函數   
        	函數: DIR *opendir(const char *name);
        	函數功能: 主要用於開啟引數指定目錄,並且返回該目錄所在地址。
        (2) readdir 函數    
        	函數: struct dirent *readdir(DIR *dirp);
        	函數功能: 主要用與讀取引數指定的目錄中內容,返回結構體指標。
            struct dirent {
             	ino_t    d_ino;       /* inode number i節點的編號*/
             	off_t     d_off;       /* offset to the next dirent 距離下一個子項的偏移量*/
             	unsigned short d_reclen;    /* length of this record 記錄的長度*/
             	unsigned char  d_type;     /* type of file; not supportedby all file system types 檔案的一個型別*/
             	char    d_name[256];     /* filename 檔名*/
          	};
          	
       (3) closedir 函數  
           函數功能: 主要用於關閉引數指定的目錄。
       
       (4) 其他函數:
        	mkdir() 建立一個目錄
        	rmdir() 刪除一個目錄
            chdir() 切換所在的目錄
            getcwd () 獲取當前程式所在的工作目錄

五、linux系統下的程序管理

1. 基本概念

     程序 表示在記憶體中執行的程式
     程式 表示在磁碟上執行的可執行檔案

2. 基本命令

	ps  表示察看當前終端上啟動的程序(程序的快照)
	ps 命令的執行結果如下:
		PID - 程序號(重點)
		TTY - 終端的編號
		TIME - 消耗cpu的時間
		CMD - 命令的名稱和引數   
		          
	ps - aux          表示顯示所有包含其他使用者的程序 
	ps - aux | more    表示將ps - aux 的結果進行分屏顯示 
	ps - aus |more 執行的結果如下:
		USER   程序的屬主(熟悉)
		PID    程序編號(掌握)
		%CPU   佔用CPU的百分比
		%MEM   佔用記憶體的百分比
		VSZ    虛擬記憶體的大小
		RSS    實體記憶體的大小
		TTY    終端編號
		STAT   程序的狀態資訊(熟悉)
		START  程序的啟動時間
		TIME   消耗CPU的時間
		CMD    命令的路徑和名稱以及引數(掌握)

	ps - ef 表示以全格式的方式顯示當前所有的程序
	ps - ef | more 分屏顯示命令的執行結果
	ps - ef | more的執行結果:
		PPID - 父程序的程序號 
	常見的程序狀態:
		S 休眠狀態
		s 程序的領導者(擁有子程序)
		Z 殭屍程序
		R 正在執行的程序
		O 可執行的程序
		T 掛起狀態
		< 優先順序高
		N 優先順序低
		...
	目前主流的作業系統都支援多程序,如果程序A啟動了程序B,那麼程序A叫做程序B的父程序,而程序B叫做A的子程序 
	程序0是系統內部的程序,負責啟動程序1(init),也會啟動程序2,而且其他所有的程序都是 程序1/程序2,直接/間接 地啟動起來

3.各種ID的獲取

	PID   - 程序號,是程序的唯一標識,作業系統採用延遲重用的策略來管理程序號,保證在每一個時刻PID都唯一
	PPID  - 父程序號,與程序號的使用策略相同
	關於ID 的函數:
		getpid()  - 獲取程序號
		getppid() - 獲取副程序號
		getuid()  - 獲取使用者ID
		getgid()  - 獲取使用者組ID

4.程序相關函數

	建立:
		(1) fork函數  
			函數功能:主要用於以複製正在呼叫程序的方式去建立一個新的程序,新程序叫做子程序原來的程序叫做父程序,成功時父程序返回子程序的ID,子程序返回0,失敗時返回-1 
        fork 建立子程序的程式碼執行方式是:
			a.  fork 之前的程式碼,父程序執行一次
			b.  fork 之後的程式碼,父子程序各自執行一次
			c.  fork 函數的返回值,父子程序各自返回一次
        注意事項:fork 建立成功子程序之後,父子程序各自獨立,並沒有明確的先後執行順序
        父子程序的關係:
			a. 父程序啟動了子程序,父程序同時啟動,如果子程序先結束,則父程序負責幫助回收子程序的資源
			b.如果父程序先結束,子程序會變成孤兒程序,子程序變更父程序(重新認定父程序,一般選擇 init(1) 程序作為父程序), init程序也叫做孤兒院
			c.如果子程序先結束,但是父程序由於各種原因沒有回收子程序的資源,子程序變成殭屍程序
        父子程序中記憶體資源的關係:
        	使用fork建立的子程序,會複製父程序中除了程式碼區之外的其他記憶體區域,程式碼區和父程序共用  
    	
    擴充套件:
        a.如何建立出4個程序?
			fork();
			fork();
			4個程序: 1個父程序,2個子程序,1個孫子程序
		b.如何建立出3個程序?
			pid_t pid = fork();
			if(0 !=  pid)  //父程序
			{
				fork();
			} 
			3個程序:1個父程序 + 2 個子程序
		c. 俗稱 fork 炸彈
			while(1)
			{
				fork();
			}
			
		程序的一個終止:
			正常終止程序的方式:
				a. 在main函數中執行了return0;
				b. 呼叫exit()函數終止程序 
				c. 呼叫_exit()/_Exit()函數
				d. 最後一個執行緒返回
				e. 最後一個執行緒呼叫了pthrread_exit()函數
			非正常終止的方式:
				a. 採用訊號終止程序
				b. 最後一個執行緒被其他執行緒取消
			
	退出:
		(2) _exit/_Exit 函數(uc的函數/標c的函數)  
			函數功能:這兩個函數都用於立即終止正在呼叫的程序,引數作為返回值返回給父程序,來代表程序的退出狀態,可以使用wait 系列函數獲取推出狀態
			
		_exit 函數  
			函數功能:主要用於引起正常程序的終止,引數status中的低8位元作為退出狀態資訊返回給父程序,該函數在終止程序期間會呼叫atexit()/on_exit()函數註冊過的函數
		
		atexit 函數  
			函數功能:主要用於按照引數指定的函數進行註冊,註冊過的函數會在正常程序終止時被呼叫
		
	等待:
		(3) wait 函數   
			函數功能:主要用於掛起正在執行的程序進入組塞狀態,直到有一個子程序終止,引數用於獲取終止程序的退出狀態,成功返回終止程序的程序號,失敗返回-1
		WIFEXITED(*status) 判斷是否正常終止
		WEXITSTATUS(*status) 獲取程序退出狀態的資訊
		
		(4) waitpid 函數    
			函數功能:掛起當前正在執行的程序,直到指定程序的狀態發生改變為止
			第一個引數:程序號 
             	< -1 表示等待程序的組ID為pid絕對值的任意一個子程序(瞭解)
                -1   表示等待任意一個子程序
                0    表示等待和呼叫程序在同一個行程群組的任意一個子程序(瞭解)
                > 0  表示等待程序號為pid的程序
            第二個引數: 指標引數,獲取程序的推出狀態資訊
            第三個引數: 選項 預設給0即可
            返回值: 成功返回狀態發生改變的程序號,失敗返回-1
            
		(5) 其他處理常式
			vfork 函數  
				函數功能:功能與fork函數基本一樣,所不同的是不會拷貝父程序的記憶體區域,而是直接佔用,導致父程序進入阻塞狀態,直到子程序終止,或者呼叫exec系列函數跳出為止,對於子程序的終止建議使用_exit()函數
			exec系列函數: 
			execl 函數   函數功能:主要用於實現跳轉的功能 
				第一個引數:執行的檔案路徑及檔名
				第二個引數:執行的選項,一般給可執行檔案的執行方式
				第三個引數:可變長引數
		注意:
		vfork 函數本身沒有太大的實際意義,一般與exec系列函數搭配使用。
		fork 函數也可以和exec系列函數搭配使用,但是基本不會這樣用。
		system 函數   
			函數功能:  主要用於執行指定的shell命令,以及shell指令碼

		shell 指令碼編寫流程:
			a.  vi xxx.sh  檔案
			b.  編寫shell指令到檔案中
			c.  給xxx.sh 檔案增加執行許可權   chmod a+x shell.sh
			d.  執行xxx.sh檔案 

中斷的概念:

	中斷指暫時停止當前程式的執行,轉而去執行新的程式,或者處理出現的意外情況
	中斷分為兩種:軟體中斷 和 硬體中斷

六、linux系統下的訊號處理

1.訊號的基本概念和分類

	(1)概念:
		訊號本質上就是軟體中斷,訊號既可以作為程序間通訊的一種機制,更重要的是,訊號總是中斷一個程序的正常執行,而訊號更多的用於處理一些非正常情況
		
	(2)特點:
		a. 訊號是非同步的,程序並不知道訊號什麼時候會到來
		b. 程序即可以傳送訊號,也可以處理訊號
		c. 每個訊號都有一個名字,這些名字以SIG開頭
		
    (3)命令和分類:
		使用命令: kill -l 用於察看當前作業系統所支援的訊號
		
	掌握訊號:
		訊號2   SIGINT   使用ctrl + c 產生此訊號
		訊號3   SIGQUIT  使用ctrl + \ 產生此訊號
		訊號9   SIGKILL   使用kill -9 程序號傳送此訊號(該訊號不能被使用者捕捉)
		一般來說, 在linux系統中訊號是從1~ 64, 不保證連續, 其中1~31之間的訊號, 叫做不可靠訊號, 也就是訊號不支援排隊, 可能會丟失. 其中34~64 之間的訊號叫做可靠訊號, 也就是訊號支援排隊, 不會丟失.
		不可靠訊號有叫做非實時訊號,可靠訊號又叫做事實訊號

2.訊號的處理方式

	(1)預設處理,絕大多數訊號的處理方式都是終止程序
	(2)忽略處理
	(3)自定義處理

3.訊號的處理常式

	(1)signal 函數    
		函數的功能:  設定指定訊號的指定處理方式
			#include <signal.h>
			typedef void (*sighandler_t)(int);   
				=> 給函數指標其個別名 叫做 sighandler_t
				=> typedef void (*p) (int) sighandler_t;  
			sighandler_t signal(int signum, sighandler_t handler);
				展開後是這樣 =>  
			void (*)(int) signal(int signum,void(*)(int)handler);     
				繼續展開後是這樣 =>  
			void (*)(int) signal(int signum,void(*hander)(int)); 
				最後展開後是這樣 =>  
			void (*signal(int signum,void(*hander)(int)))(int);

			首先 signal 是一個函數,具有兩個引數的函數,第一個引數是int型別,第二個引數是函數指標型別
			函數的返回值型別也是函數指標型別
			函數指標型別是一個
			指向具有int型別引數和void作為返回值型別的函數的指標

	    函數功能解析:
			第一個引數:訊號值/訊號名稱(處理那個訊號)
			第二個引數:訊號的處理方式(如何進行處理)
				SIG_IGN  忽略處理
				SIG_DFL  預設處理
				自定義函數地址   自定義處理
			返回值: 成功返回之前的處理方式,失敗返回SIG_ERR 
 
		父子程序對訊號的處理方式:
			對於fork函數建立的父子程序來說,子程序完全按照父程序的對訊號的處理方式,也就是父程序忽略則子程序忽略,父程序自定義處理,則子程序自定義處理,父程序預設處理,則子程序預設處理

	採用系統函數傳送訊號
	(2) alarm 函數
		函數功能:主要用於設定引數指定的秒數之後傳送SIGALRM,如果引數為0表示沒有設定新的鬧鐘.成功返回上一個鬧鐘沒有來得及走的秒數,如果之前沒有鬧鐘,則返回0

		訊號集
        訊號集:訊號的集合用於儲存多個訊號
        在linux系統中,訊號的數值範圍:1~64,採用最省記憶體的方式來表示所有的訊號,採用什麼樣的資料資料型別呢?

		訊號集的資料型別是:
			sigset_t 型別 底層還是採用每一個二進位制代表一個訊號的方式儲存多個訊號 
			查詢sigset_t 的步驟  cc -E 02set.c -o 02set.i  
		
			typedef struct
			{
				unsigned long int __val[(1024 / (8 * sizeof (unsigned long int)))];
			} __sigset_t; 

		訊號集的基本操作:  
			sigemptyset()   清空訊號集
			sigfillset()    填滿訊號集
			sigaddset()     增加訊號到訊號集      
			sigdelset()     刪除訊號幾中的指定訊號
			sigismember()   判斷訊號是否存在
			
		訊號的遮蔽
			sigprocmask 函數    
				函數功能: 主要用於檢查和改變要遮蔽的訊號集
				第一個引數: 用什麼養的方式遮蔽
					SIG_BLOCK: ABC+CDE =>ABCDE 
					SIG_UNBLOCK: ABC-CDE =>AB   
					SIG_SETMASK: ABC  CDE  =>CDE 
				第二個引數: 新的訊號集
				第三個引數: 舊的訊號集 用於帶出之前遮蔽的訊號集

			sigpending 函數  
				函數的功能: 主要用於獲取在訊號遮蔽的期間來過的訊號,通過引數將來過的訊號帶進去

			sigaction 函數 => signal函數的增強版  
				函數功能: 主要用於設定/改變訊號的處理方式
					第一個引數: 訊號值/訊號的名稱 (設定哪個訊號)
					(訊號SIGKILL和SIGSTOP不能使用)
					第二個引數: 針對訊號的新處理方式 
				
				struct sigaction {
					void (*sa_handler)(int);
					=> 函數指標用於設定訊號的處理方式,
					=> 與signal函數中第二個引數相同, SIG_IGN  SIG_DFL 函數名
									
					void (*sa_sigaction)(int, siginfo_t *, void *);
					=> 函數指標,作為第二種處理訊號的方式
					=> 是否使用該處理方式,依賴於sa_flags的值
 
					sigset_t  sa_mask;
					=> 用於設定在訊號處理常式執行期間需要遮蔽的訊號
					=> 自動遮蔽與正在處理的訊號相同的訊號
             
					int sa_flags;
					=> 處理標誌
					=> 寫入 SA_SIGINFO 表示採用第二個函數指標處理訊號
					=> 寫入 SA_NODEFER 表示解除對相同訊號的遮蔽
					=> 寫入 SA_RESETHAND 表示自定義處理訊號之後恢復預設處理方式

					void (*sa_restorer)(void);
					=>  保留成員,暫時不使用
				};
				
				第三個引數: 舊的處理方式(用於帶出針對訊號之前的處理方式)
				SIGKILL和SIGSTOP兩個訊號是不能自定義處理的,只能預設處理
				
				其中第二個函數指標的第二個引數型別如下:
				siginfo_t {
					int      si_signo;       /* Signal number */
					int      si_errno;       /* An errno value */
					int      si_code;        /* Signal code */
					int      si_trapno;      /* Trap number that caused  hardware-generated signal(unused on most architectures) */
					pid_t    si_pid;         /* Sending process ID *//傳送訊號的程序ID           
					uid_t    si_uid;         /* Real user ID of sending process */
					int      si_status;       /* Exit value or signal */
					clock_t  si_utime;        /* User time consumed */
					clock_t  si_stime;        /* System time consumed */
					sigval_t  si_value;        /* Signal value *//傳送訊號時的附加資料
					int      si_int;          /* POSIX.1b signal */
					void    *si_ptr;         /* POSIX.1b signal */
					int      si_overrun;     /* Timer overrun count; POSIX.1b timers */
					int      si_timerid;      /* Timer ID; POSIX.1b timers */
					void    *si_addr;        /* Memory location which caused fault */
					long     si_band;        /* Band event (was int in glibc 2.3.2 and earlier) */
					int      si_fd;          /* File descriptor */
					short    si_addr_lsb;     /* Least significant bit of address (since kernel 2.6.32) */
				} 

		sigqueue 函數          
			函數功能: 表示指向的程序傳送指定的訊號和附加資料
			第一個引數: 程序號(給誰發訊號)
			第二個引數: 訊號值(傳送什麼樣的訊號) 
			第三個引數: 聯合型別,附加資料
			union sigval {
				int   sival_int; //整數
				void *sival_ptr; //地址 
			};

4.計時器

	在UNIX/linux系統中,作業系統會為每一個程序維護三種計時器:
	真實計時器,虛擬計時器,實用計時器;
	一般採用真實計時器進行工作,真實計時器採用傳送SIGALRM訊號進行工作的
	
	#include <sys/time.h>
	int getitimer(int which, struct itimerval *curr_value);
	int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
	
	第一個引數: 計時器的型別
		ITIMER_REAL     真實計時器,主要描述程序執行的真實時間,通過產生SIGALRM訊號工作
		ITIMER_VIRTUAL  虛擬計時器,主要描述程序在使用者空間消耗的時間,通過產生SIGVTALRM訊號工作
		ITIMER_PROF     實用計時器,主要描述程序在使用者空間和核心空間共同消耗的時間,通過產生SIGPROF訊號工作
	
	第二個引數: 計時器的新值 
        struct itimerval 
        {
         	struct timeval it_interval;  /* next value */     間隔時間
         	struct timeval it_value;  /* current value */  啟動時間
        };
        struct timeval 
        {
         	long tv_sec;    /* seconds */       秒數
         	long tv_usec;   /* microseconds */  微秒(到秒為 10^6)
        };
        
	第三個引數: 用於獲取計時器的舊值
	函數功能: 用於獲取/設定計時器的數值

七、linux系統下的程序間通訊

1.概念:

	兩個/多個程序間的資料的交換,叫做程序間的通訊

2.程序間通訊的方式

	(1)檔案
	(2)訊號
	(3)管道
	(4)共用記憶體
	(5)訊息佇列(重點)
	(6)號誌集
	(7)網路 (重點)
	...
    其中 (4) (5) (6) 統稱為XSI IPC通訊方式
	(X/open system interface Inter-process commucation)

3.使用訊息佇列實現程序間通訊

	(1)概念:
		使用不同的程序將傳送的資料打包成具體個格式的訊息,然後將訊息放入到訊息中,使用其他程序從訊息中取出訊息,從而實現程序間的通訊
		
	(2) 使用訊息佇列通訊的流程
		a.獲取key值 使用ftok函數
		b.建立/獲取訊息佇列,使用msgget函數
		c.傳送訊息/接受訊息,使用msgsnd和msgrcv函數
		d.如果不再使用訊息佇列,使用msgctl函數刪除訊息佇列
    
    (3) 相關函數解析
		ftok 函數  
			函數功能: 根據路徑和專案ID 來生成一個key_t型別的key值,生成的key提供給後續函數使用
			第一個引數: 字串形式的路徑要求存在並且可以存取
			第二個引數: 專案ID 要求非0
			注意: 使用相同的路徑和相同的專案ID 會生成相同的key值
			
		msgget 函數  
			函數功能: 主要用於建立/獲取訊息佇列,返回佇列的ID
			第一個引數:key值,ftok的返回值
			第二個引數:標誌
				IPC_CREAT  - 建立
				IPC_WXCL   - 與IPC_CREAT搭配使用,如果存在則建立失敗
				0          - 獲取已存在的訊息佇列
			注意: 如果需要建立一個新的訊息佇列時,那麼需要在引數msgflg中指定新佇列的許可權,如0644
			
		msgsnd函數 
			函數功能:主要用於將指定的訊息傳送到指定的訊息佇列中
			第一個引數: 訊息佇列的ID msgget函數的返回值
			第二個引數: 訊息的首地址(訊息從哪裡來) 
				struct msgbuf {
					long mtype;       /* 訊息的型別, must be > 0 */
					char mtext[1];     /* 訊息的資料內容,可以是其他的結構 */
				};
			第三個引數: 訊息的大小 主要是指訊息內容的大小,不包括訊息的型別
			第四個引數: 傳送訊息的標誌,直接給0即可
			
		msgrcv 函數  
			函數功能: 從指定的訊息佇列中接受訊息
			第一個引數: 訊息佇列的ID msgget函數的返回值
			第二個引數: 訊息的首地址(將接受的訊息存到哪裡去) 
			第三個引數: 訊息的大小
			第四個引數: 訊息的型別
				0  表示讀取訊息佇列中的第一條訊息
				>0 表示讀取訊息佇列中第一個型別為msgtyp的訊息
				<0 表示讀取訊息佇列中訊息型別 <=msgtyp 絕對值的型別最小的訊息
			第五個引數: 訊息的標誌,給0即可
			返回值: 成功返回實際讀取資料的大小,失敗返回-1
			
		msgctl 函數 
			函數功能: 表示對指定的訊息佇列執行指定的操作
			第一個引數: 訊息佇列的ID   msgget函數的返回值
			第二個引數: 具體的命令
				IPC_RMID  刪除訊息佇列,此時第三個引數給NULL即可
			第三個引數: 結構體指標 
			相關的命令
				使用 ipcs -q      命令 表示察看當前系統中存在的訊息佇列
				使用 ipcrm -q ID  命令 表示刪除指定的訊息佇列 

4.使用管道實現程序間通訊

	(1)概念和分類 
		概念: 本質上是以檔案作為媒介來實現程序間的通訊,該檔案比較特殊,叫做管道檔案   
		管道分為: 有名管道 和 無名管道  
		有名管道: 一般採用命令建立管道檔案,實現任意兩個程序之間的通訊
		無名管道: 一般採用系統函數來建立管道檔案,用於父子程序之間的通訊
		
	(2)使用有名管道進行程序間的通訊
		如: touch 檔名(建立普通檔案) 
		
		使用  mkfifo 管道檔名.pipe => 建立管道檔案 
			例子: mkfifo a.pipe =>建立管道檔案a.pipe
			
		使用 echo hello > a.pipe =>寫入資料到管道檔案中 
			例子: cat a.pipe => 讀取管道檔案的資料
			
		注意: 管道檔案本身並不會儲存傳遞的資料
		
	(3)使用無名管道實現程序間的通訊
		pipe 函數 建立無名管道  
			函數功能: 主要用於建立管道檔案,利用引數返回兩個檔案描述符,其中pipefd[0]關聯管道的讀端,pipefd[1] 關聯管道的寫端.
			注意: 管道是比較古老的通訊方式,現在很少使用(瞭解)

5.使用共用記憶體實現程序間通訊

	(1)概念:
		本質上是由核心維護的一塊記憶體區域,共用在兩個/多個程序之間,然後兩個/多個程序分別對該記憶體區域進行讀寫操作,從而實現通訊,是最快的IPC的通訊方式
		
	(2)共用記憶體通訊的基本流程
		a.獲取key值 使用ftok函數
		b.建立/獲取共用記憶體,從而得到ID,使用 shmget 函數
		c.掛接共用記憶體,使用 shmat 函數
		d.使用共用記憶體,程序讀寫操作等
		e.脫接共用記憶體,使用 shmdt 函數
		f.如果不再使用,使用 shmctl 函數刪除共用記憶體
		
	(3)相關函數解析
		shmget 函數     
			函數功能: 主要用於建立/獲取一個共用記憶體,得到ID
			第一個引數: key值,ftok函數的返回值
			第二個引數: 共用記憶體的大小  
			第三個引數: 操作的標誌
				IPC_CREAT - 建立
				IPC_EXCL  -  與IPC_CREAT 搭配使用,存在則建立失敗
				0         -  獲取已經存在的共用記憶體
            注意: 當建立新的共用記憶體時,需要指定許可權
        
        shmat 函數
          	函數功能: 主要用於將指定的共用記憶體掛接到正在執行的程序的地址空間中,成功返回共用記憶體的地址,失敗返回-1.
			第一個引數: 共用記憶體的ID,也就是 shmget 的返回值
			第二個引數: 共用記憶體的掛接地址,給NULL由系統選擇
			第三個引數: 掛接標誌,直接給 0 即可
			
		shmdt 函數     
			函數功能: 表示按照引數指定的共用記憶體的地址進行脫節,引數傳遞shmat函數的返回值即可
		
		shmctl 函數     
			函數功能: 主要用於對指定的共用記憶體執行指定的操作
			第一個引數:共用記憶體的ID,也就是 shmget 的返回值
			第二個引數:執行的操作命令
				IPC_RMID  刪除共用記憶體 
			第三個引數:給NULL即可
			相關的基本命令    
				ipcs -m   察看系統中已經存在的共用記憶體
				ipcrm -m  共用記憶體ID 刪除指定的共用記憶體

6.使用號誌集實現程序間通訊

	(1)號誌和號誌集概念
		號誌概念:
			號誌本質就是一個計數器,用於控制同時存取共用資源的程序數/執行緒數,也就是說解決有限資源的分配問題.
		號誌集概念:
			號誌集就是表示號誌的集合,也就是多個計數器的集合,用於控制同時存取多種共用資源的程序數/執行緒數.
		
	(2)號誌工作方式
		a.先把號誌進行初始化,初始化為最大值
		b.如果有程序,申請到共用資源,那麼號誌的值減1
		c.當號誌的值為0時,終止程序對共用資源的申請,讓申請共用資源的程序進入阻塞狀態
		d.如果有程序釋放資源,那麼號誌的值加1
		e.當號誌的值大於0時,阻塞的程序可以繼續搶佔資源,搶佔不到資源的程序繼續進入阻塞狀態等等
		
	(3)使用號誌集通訊流程
		a.獲取key值          使用ftok函數
		b.建立/獲取號誌集   使用semget函數
		c.初始化號誌集      使用semctl函數
		d.操作號誌集        使用semop函數
		e.如果不再使用則刪除   使用semctl函數
		
	(4)相關函數解析
		semget函數     
			函數功能: 建立/獲取號誌集的ID
			第一個引數:key值 ,ftok函數的返回值
			第二個引數:號誌集的大小,也就是號誌的個數
			第三個引數:操作的標誌
				IPC_CREAT   建立
				IPC_EXECL   與IPC_CREAT 搭配使用,存在則建立失敗
				0           獲取已經存在的號誌集
			注意: 當建立新的號誌集是,需要指定許可權,如0644
		
		semctl函數     
			函數功能: 主要用於對號誌集實現各種控制操作 
			第一個引數: 號誌集的ID 使用semget函數的返回值
			第二個引數: 號誌集的下標,從0開始
			第三個引數: 具體的操作命令
				IPC_RMID 刪除號誌集,引數semnum被忽略,不需要第四個引數
				SETVAL  使用第四個引數的值給號誌集中下標為semnum的號誌進行初始化
			第四個引數: 可變長引數,是否需要取決於cmd
			
			semop 函數     
				函數功能: 對號誌集中指定的號誌進行操作
				第一個引數: 號誌集的ID,semget函數的返回值
				第二個引數: 結構體指標
					結構體引數包含:
					unsigned short sem_num;  /*   號誌的下標 */
					short  sem_op;   /* 號誌的操作,正數增加,負數減少 */
					short  sem_flg;  /* 操作的標誌,給0即可 */
				第三個引數: 表示第二個引數所指定的結構體陣列的元素的個數
				基本的命令
					ipcs -s 察看系統中存在的號誌集
					ipcrm -s 號誌集的ID ,刪除指定的號誌集
					ipcs -a 察看系統中所有的IPC結構 

八、linux系統下的網路程式設計

1. 網路的基本常識

	七層網路協定
		由ISO 將網路從邏輯上劃分為七層:
		應用層    主要將資料交給應用程式
		表示層    主要將資料按照統一的格式進行封裝   應用層 TELNET  FTP WWW
		對談層    主要控制對話的開始和結束等
		傳輸層    主要使用具體的傳輸協定檢查和傳輸資料   TCP和UDP
		網路層	  主要進行路徑的選擇和傳遞資料   IP和路由
		資料鏈路層 主要將資料轉換為高低電平訊號   網路卡驅動
		物理層     主要藉助網路卡驅動,交換機裝置等傳輸

	常見的協定
		A.TCP協定  - 傳輸控制協定,一種面向連線的協定,類似打電話
		B.UDP協定  - 使用者資料包協定,一種非面向連線的協定,類似傳簡訊
		C.IP協定   - 網際網路協定,是TCP/UDP的底層協定   IPX 協定
	
	IP 地址
		IP 地址    - 是網際網路中唯一的地址標識,本質上來講,就是一個32位元的整數,使用(IPV4)目前也有IPV6(128位元二進位制)
		日常生活中採用點分十進位制表示法來描述一個具體的IP地址,也就是說,將每一個位元組,計算出一個十進位制整數,多個十進位制之間採用小數點分隔
		
	IP 地址主要分為兩個部分:網路地址+主機地址
	IP 地址分以下4類
		A類: 0 +7 位網路地址 +  24位元主機地址
		B類: 10 + 14 位網路地址 + 16位元主機地址
		C類: 110 + 21 位網路地址 +  8位元主機地址
		D類: 1110 + 28 位多播地址
		
	檢視IP的命令
		Window 系統下         ipconfig    ipconfig/all
		Unix/linux作業系統    ifconfig    /sbin/ifconfig
	
	子網掩碼
		一般與IP地址搭配使用,主要用於指定一個IP地址中具體的網路地址和主機地址,也就是說判斷兩個IP地址是否在同一個子網中0~255
		如: IP地址 172.40.5.210 子網掩碼 255.255.255.0 
		把IP和子網掩碼按位元與 &
        172.40.5  - 網路地址
        210       - 主機地址
        
	實體地址
		實體地址又叫做Mac地址,本質上就是硬體網路卡的地址
		主要用於通過繫結Mac地址來實現上網裝置的控制
		
	埠號
		IP地址     主要用於定位具體的某一臺主機
		埠號     主要用於定位該主機中具體的某一個程序
		埠號的資料型別: unsigned short型別 ,取值範圍是:0 ~ 65535 其中 0 ~1024 之間的埠由系統佔用,自己指定埠號從 1025 以上開始使用
		
	位元組序
		如: 0x12345678 
		小端系統: 低位記憶體地址存放低位資料的系統叫做小端系統 
		小端  地址從小到大 0x78 0x56 0x34 0x12
		大端系統:低位記憶體地址存放高位資料的系統叫做高位系統
		大端  地址從小到大 0x12 0x34 0x56 0x78      
		
	網路位元組序
		主要描述資料在網路中傳遞時的位元組順序,而網路位元組序本質上就是大端位元組序
		
	主機位元組序
		主要描述資料根據在本地系統中存放的位元組順序,
		當傳送時,由主機位元組序轉化為網路位元組序時候傳送,
		當接收時,由網路位元組序轉化為主機位元組序.

2.基於socket的一對一通訊模型

	(1)基本概念 
		socket 英文中是插座的意思
		socket 網路通訊的載體,使用socket可以實現了兩個程序/兩個主機之間的通訊
		
	(2)通訊的模型
		伺服器端:
			a.建立socket            使用socket函數
			b.準備通訊地址           使用結構體型別
			c.繫結socket和通訊地址   使用bind函數
			d.進行讀寫操作           使用read/write函數
			e.關閉socket            使用close函數
		使用者端: 
			a.建立socket            使用socket函數
			b.準備通訊地址           使用結構體型別
			c.連線socket和通訊地址   使用connect函數
			d.進行讀寫操作           使用read/write函數
			e.關閉socket            使用clode函數
			
	(3)相關函數解析
	socket 函數    
		函數功能: 主要用於建立一個通訊點,成功返回一個描述符
		第一個引數: 表示 域/協定族,決定本地通訊還是網路通訊
			Name                Purpose                          Man page
			AF_UNIX, AF_LOCAL   Local communication            unix(7)  本地通訊
			AF_INET             IPv4 Internet protocols           ip(7)   基於IPV4的網路通訊
			AF_INET6            IPv6 Internet protocols           ipv6(7) 基於IPV6的網路通訊 
			AF_IPX              IPX - Novell protocols
			AF_NETLINK          Kernel user interface device       netlink(7)
			AF_X25              ITU-T X.25 / ISO-8208 protocol     x25(7)
			AF_AX25             Amateur radio AX.25 protocol
			AF_ATMPVC           Access to raw ATM PVCs
			AF_APPLETALK        Appletalk                        ddp(7)
			AF_PACKET           Low level packet interface           packet(7)

		第二個引數: 型別,決定採用何種協定進行通訊
			SOCK_STREAM  資料流的方式,基於TCP的通訊協定
			SOCK_DGRAM   資料包的方式,基於UDP的通訊協定
			
		第三個引數:特殊協定的意思,給0即可

	通訊地址的資料型別
		a. 通用的通訊地址
			struct sockaddr
			{
				sa_family_t   sa_family;    //協定族
				char          sa_data[14];  //資料內容
			}
		注意: 給通訊地址通常用於函數的引數型別,用於各種通訊地址之間的轉換
		
       b. 本地通訊的結構體型別 
			#include<sys/un.h>
			struct sockaddr_un
			{            
				sa_family_t  sun_family; //地址族,和socket引數一致即可
				char         sun_path[]; //socket檔案的路徑和檔名
			};   
			
       c. 網路通訊的結構體型別
			#include<netinet/in.h>      
			struct  sockaddr_in
			{
				sa_family_t      sin_family;    // AF_INET
				in_port_t        sin_port;     // 埠號 
				struct in_addr   sin_addr;      // IP地址
			}; 
			struct  in_addr 
			{
				in_addr_t  s_addr;
			};
			
	bind 函數  函數功能: 主要用與繫結指定的socket和通訊地址
		第一個引數:socket的描述符,就是socket函數的返回值
		第二個引數:結構體型別的指標(&通訊地址)
		第三個引數:通訊地址的大小  

	connect 函數  函數功能:主要用於socket和通訊地址之間的連線
		第一個引數:socket的描述符,就是socket函數的返回值
		第二個引數:結構體型別的指標(&通訊地址)
		第三個引數:通訊地址的大小

	位元組序的轉換函數
		#include <arpa/inet.h>
		uint32_t htonl(uint32_t hostlong);   主要將unsigned int 的主機位元組序轉換為網路位元組序 
		uint16_t htons(uint16_t hostshort);  主要將unsigned short的主機位元組序轉換位網路位元組序
		uint32_t ntohl(uint32_t netlong);    主要將unsigned int的網路位元組序轉換為主機位元組序                                         
		uint16_t ntohs(uint16_t netshort);   主要將unsigned short的網路位元組序轉換為主機位元組序

	IP地址的轉換函數
		#include <sys/socket.h>
		#include <netinet/in.h>
		#include <arpa/inet.h>
		in_addr_t inet_addr(const char *cp);
		函數功能:主要將字串型別的IP地址轉換為整數

3.基於TCP的協定的通訊模型

	(1)建立通訊模型
		伺服器:
			a建立socket ,使用socket函數    
			b準備通訊地址,使用結構體型別
			c繫結socket和通訊地址,使用bind函數
			d進行監聽,使用listen函數
			e響應使用者端的連線請求,使用accept函數
			f進行通訊,使用read/write函數
			g關閉socket使用close函數
		使用者端:
			a 建立socket    使用socket函數
			b準備通訊地址
			c連結socket    使用sonnect函數
			d進行通訊
			e關閉socket    使用close函數
	(2)函數解析
		listen 函數   
			函數功能: 主要用於監聽指定socket上的連線個數
			第一個引數:socket描述符,socket函數的返回值
			第二個引數: 排隊等待被響應的最大連線數
		accept 函數   
			函數功能: 主要用於響應使用者端的連線請求
			第一個引數:socket描述符,socket函數的返回值
			第二個引數: 結構體指標,用於儲存使用者端的通訊地址
			第三個引數:指標,通訊地址的大小
			返回值: 成功返回用於通訊的描述符,失敗返回-1 
        socket 函數返回的描述符主要用於監聽使用
     	inet_ntoa 函數  
     		函數功能: 主要將結構體型別的IP地址轉換為字串型別

4.基於UDP協定的通訊模型

	(1)建立通訊模型
	伺服器:
		a建立socket           使用socket函數
		b準備通訊地址          使用結構體型別
		c繫結socket和通訊地址  使用bind函數
		d進行通訊              使用sendto()/recvfrom()/send()/recv()函數
		e關閉socket函數        使用close函數
	使用者端: 
		a建立socket           使用socket函數
		b準備通訊地址          伺服器的地址
		c進行通訊             使用sendto()/recvfrom()/send()/recv()函數
		d關閉socket           使用close函數
		
	(2)函數解析
		傳送訊息的函數   
			函數功能: 主要用於向指定的地址傳送指定的資料
			ssize_t send(int sockfd, const void *buf, size_t len, int flags);
			ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
			第一個引數:socket描述符,socket函數的返回值
			第二個引數:被傳送資料的首地址
			第三個引數:傳送資料的大小
			第四個引數:傳送標誌,該0即可
			第五個引數:目標地址
			第六個引數:目標地址的大小
			返回值:成功返回實際傳送資料的大小,失敗返回-1
			
		接收訊息的函數   
			函數功能:表示接受指定資料並提供來電顯示功能
			ssize_t recv(int sockfd, void *buf, size_t len, int flags);
			ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
			第一個引數:socket描述福
			第二個引數:首地址(把接受到的資料存到哪裡去)
			第三個參書:接收的的資料大小
			第四個引數:接收的標誌,給0即可
			第五個引數:儲存傳送方的通訊地址(資料從哪裡來)
			第六個引數:傳送方通訊地址的大小
			返回值: 成功返回接受到的資料大小,失敗-1

TCP協定和UDP協定的比較

	TCP協定
		(1)概念: TCP 傳輸控制協定,面向連線的協定,傳遞的是資料流.
			建立連線=> 傳遞資料=> 斷開連線    
		(2)優點: 可以重發一切資料,可一保證資料的完整性和準確性,接收方可以通知傳送方控制資料量
		(3)缺點: 伺服器壓力比較大,資源佔用率比較高       
	UDP協定
		(1)概念: UDP 使用者資料包協定,非面向連線的協定,傳送資料包
		(2)優點: 不需要全程保持連線,伺服器壓力比較小,資源佔用率比較低
		(3)缺點: 不會重發錯誤資料,不保證資料的準確性和完整性,接受方不會通知傳送方進行資料量的控制 

九、linux系統下的多執行緒程式設計

1.基本概念

	目前主流的作業系統支援多程序,而在每一個程序的內部,可以支援多執行緒,也就是說執行緒是程序中的程式流
	程序是重量級單位,每個程序都需要獨立的記憶體空間,啟動新的程序對資源的是很大的消耗,而執行緒是輕量級單位,執行緒共用所在的程序的記憶體空間,但是每個執行緒都有一塊很小的獨立棧區.

2.執行緒的相關函數

	pthread_create 函數   
		函數功能: 主要用於曾在執行的程序中啟動新的執行緒 
		第一個引數:用於儲存新執行緒的ID
		第二個引數: 執行緒的屬性,給NULL即可,表示預設屬性
		第三個引數: 函數指標,表示新執行緒執行的處理常式
		第四個引數: 作為第三個引數函數指標的引數
		返回值: 成功返回0,失敗直接返回錯誤編號
		
	注意:
		a. 編譯連結時,記得加上選項 -pthread
		b. 當啟動新執行緒之後,子執行緒和主執行緒各自獨立執行,每個執行緒內部的程式碼按照次序執行,也就是多執行緒之間相互獨立,又互相影響,主執行緒結束,導致程序結束,而程序結束導致程序中所有的子執行緒隨之結束

	pthread_self 函數  
		函數功能: 主要用於獲取正在執行的執行緒ID,通常都是成功的,通過返回值返回獲取到的ID

	pthread_join函數   
		函數功能: 主要用於等待一個執行緒的結束,並且獲取退出碼
		第一個引數: 執行緒的ID
		第二個引數: 二級指標,用於獲取執行緒的退出碼
		注意: 該函數根據引數thread指定的執行緒進行等待,將目標執行緒終止時的退出狀態資訊拷貝到 *retval 這個引數指定的位置上
		
	pthread_detach函數
		函數的功能: 主要將引數指定的執行緒標記為分離狀態,對於分離狀態的執行緒來說,當該執行緒終止後,會自動將資源釋放給系統,不需要其他執行緒的加入/等待,也就是說分離的執行緒無法被其他執行緒使用prhread_join進行等待
		
		建議: 對於新啟動的執行緒來說,要麼使用pthread_detach設定為分離狀態,要麼使用pthread_join設定為可加入狀態
      
	pthread_exit 函數   
		函數的功能: 主要用於終止正在執行的執行緒,通過引數retval來帶出執行緒的退出狀態資訊,在同一個程序中的其他執行緒可以通過呼叫pthread_join函數來獲取退出狀態資訊
		
	pthread_cancel 函數(執行緒的取消函數)   
		函數功能: 主要用於對引數指定的執行緒傳送取消的請求,目標執行緒是否會被取消以及何時被取消,主要依賴兩個屬性:setcancelstate 和setcanceltype
		
	pthread_setcancelstate 函數    
		函數功能: 主要用於設定新的取消狀態,返回之前的取消狀態
		第一個引數: 新的取消狀態
		PTHREAD_CANCEL_ENABLE   允許被取消(預設狀態)
		PTHREAD_CANCEL_DISABLE   不允許被取消
		第二個引數: 獲取之前的取消狀態,不想獲取給NULL即可
		
	pthread_setcanceltype 函數   
		函數功能: 主要用於設定新的取消型別,獲取之前取消的型別
		第一個引數:設定新的取消型別
			PTHREAD_CANCEL_DEFERRED  延遲取消(預設的取消型別)
			PTHREAD_CANCEL_ASYNCHRONOUS   立即取消 
		第二個引數:獲取舊的取銷型別,不獲取則給NULL即可

3.執行緒的同步問題

	(1)基本概念:
		多執行緒共用程序中的資源,多個執行緒同時存取相同的共用資源時,需要相互的協調以避免出現資料的不一致和混亂問題,而執行緒之間的協調和通訊叫做執行緒的同步問題
	(2)執行緒同步的思想: 
		多執行緒存取共用資源時,應該進行序列,而不是並行
	(3)執行緒同步的解決方法: 
		執行緒中提供了互斥量(互斥鎖)的機制來實現執行緒的同步
	(4)互斥量的使用步驟
		a.定義互斥量
			pthread_mutex_t mutext;
		b.初始化互斥量
			pthread_mutex_init(&mutex,0);
		c.使用互斥量進行加鎖
			pthread_mutex_lock(&mutext);   
		d.使用共用資源
		e.使用互斥量進行解鎖
			pthjread_mutex_unlock(&mutext);
		f.如果不再使用,則銷燬互斥量
			pthread_mutex_destroy(&mutext);

4.使用號誌 實現執行緒的同步問題

	(1)基本概念 
		號誌 - 本質就是一個計數器,用於控制同時存取共用資源的程序數/執行緒數  
		當號誌的值是1時,效果等同於互斥量.
		
	(2)號誌的使用流程
		a. 定義號誌 
			sem_t sem;
		b. 初始化號誌
			sem_init(&sem,0,最大值);//值 0 表示控制控制執行緒的個數
		c. 獲取一個號誌(減1)
			sem_wait(&sem);
		d. 使用共用資源
		e. 釋放一個號誌
			sem_post(&sem);
		f. 如果不再使用,則銷燬號誌
			sem_destroy(&sem);

5.使用條件變數實現執行緒的同步問題

	生產者和消費者模型(重點掌握)   

總結

以上是linux下的c開發的基礎知識,使用的是linux系統下的API函數介面,熟練使用這些介面,可以實現相應的簡單功能.