Linux系統內核筆記

一、課程介紹
UNIX/Linux環境C語言,藉助學習操作系統的接口的方法來學習、理解操作系統的
運行機制以及一些網絡協議
C/C++、數據結構和算法 與平臺無關,重點是算法邏輯
Uinx/Linux/Android/IOS 平臺相關,系統接口
嵌入式/驅動/移植 硬件相關,硬件接口

	環境介紹
	內存管理
	文件操作
	文件管理
	信號處理
	進程管理
	進程通信
	網絡通信
	線程管理
	線程同步

二、UNIX操作系統
丹尼斯.裏奇、肯.湯姆遜於1971年左右在美國貝爾實驗室,使用C語言開發了這款操作
系統。
系統的特點是多用戶、多任務,支持多種處理器架構高安全性、高可靠性、高穩定性。

	既可以構建大型關鍵業務系統的商用服務器,也可以構建面向移動終端的、手持設備
等相關的嵌入式應用。
	
	三大衍生版本
		System V:銀行、電信在使用的服務器系統
		Berkley:MacOS iOS帶界面的
		Hybrid:Minix、Linux

三、Linux操作系統
類Uinx系統,免費開源,它指的是系統的內核,凡是使用這種內核的操作系統都叫作
Linux系統(發行板),嚴格意義上講Linux指的是內核,隸屬於GNU工程。
手機、平板電腦、路由器、視頻遊戲控制平臺、PC、大型計算機、超級計算機。
標誌是一隻企鵝,因爲企鵝是南極的標誌性動物,根據國際公約南極爲全人類共同所
有,所以Linux使用企鵝作爲標誌,也表明:開源的Linux爲全人類共用所有,任何公司或
個人無權將其私有

	Minix操作系統是一個微型的類UNIX系統、免費開源,而Linux之父就是在參照這款操作
,才寫出第一個版本的Linux內核代碼
	GNU工程:是自由軟件基金會所創立的一個開源組織,基本原則就是共享,主旨是發展出
一個有別於商業UNIX的免費且完整的類UNIX系統--GNU Not UNIX。目前Linux內核由它進行維
護,所以Linux也叫GNU Linux
	GPL通用公共許可證:允許對某些成果及派生成果重用、修改、複製,對所有人都是自
由的,但不能聲明做了原始工作,或聲明由他人所作。
	POXIX標準:統一的系統編程接口規範,它規定了操作系統以接口形式提供的功能的名字
、參數、返回值,它保障了應用程序源碼級的可移植性,而Linux完全遵循了這個標準
版本管理:
	早期版本:0.01、0.02、.....、0.09、1.0
	舊計劃:A.B.C
		A 主版本號
		B 次版本號
		C 補丁序號
	新計劃:A.B.C.D.E
		D 構建次數
		E 描述信息

特點:
	多用戶、多任務
	遵循GNU/GPL具有開放性
	設備獨立性
	豐富的網絡功能
	可靠的系統安全
	良好的可移植性
發行板:
	Ubuntu
	Fedora
	Debian
	Redhat
	CentOS

四、GNU編譯器
1、支持衆多編程語言、平臺
2、構建過程(C代碼是如何變成可執行文件的)
預處理:把程序員所編譯的C代碼翻譯成標準的C代碼
彙編:把預處理後的C代碼翻譯成彙編代碼
編譯:把彙編代碼翻譯成二進制指令
鏈接:把若干個目標文件合併成一個可執行文件

3、查看版本 gcc -v
4、文件後綴	
	.h     頭文件
	.gch   頭文件的編譯結果,一般不要保留
	.c	   源文件
	.i	   預處理文件
	.s	   彙編文件
	.o	   目標文件
	.a	   靜態庫文件
	.so	   共享庫文件
5、參數
	-E	   預處理
	-S	   彙編
	-c	   編譯(只生成目標文件)
	-o	   指定編譯結果的名字
	-Wall  產生儘可能多的警告
	-Werror  把警告當作錯誤處理
	-x	   指定編譯的語言
	-g	   生成調試信息
	-On	   優化等級
	-D	   編譯時定義宏
	-l	   鏈接里加庫
	-I	   指定頭文件的查找路徑,配置環境變量
			1、打開 vim ~/.bashrc
			2、在文件末尾,添加一行 export C_INCLUDE_PATH=$C_INTCLUDE_PATH:NEW_PATH
			3、重新加載配置文件 source ~/.bashrc
				注意:如果要刪除環境變量需要在~/.bashrc文件中刪除環境變量後,退出終端
			  重新打開
				
				考題1:#include <> / #include ""
				考題2:頭文件中可以編寫哪些內容?
				考題3:頭文件的作用是什麼?
					1、說明對應的.c文件的內容有哪些(聲明函數、全局變量)
					2、定義結構、聯合、枚舉、宏
					3、類型重定義
					雖然函數可以隱式聲明,但並不一定準確,而且非常有可能造成嚴重錯誤

6、預處理指令
	#include		文件包含,區分""和<>的區別
	#define			定義宏常量或函數
		#			把標識符轉換成字符串
		##			合併標識符
	#undef
	#line			指定當前行的行號
	#if
	#ifndef
	#ifdef
	#elif
	#endif
	#error			在編譯期間產生錯誤
	#warning		在編譯期間產生警告
	#pragma
		#pragma GCC dependency	用於監控文件,防止所依賴的文件,修改後而不知道
		#pragma GCC poison	用域禁用某些標識符
		#pragma pack(n)	設置結構、聯合的補齊和對齊字節數
						n的值必須比默認的要小
						對齊邊界必須式 2 的較小此次方


編譯時頭文件找不到怎麼辦?

五、庫
庫就是目標文件的集合,我們把不需要升級更新維護的代碼打包合併在一起方便使用
也可以對源代碼進行保密。
靜態庫:靜態庫在使用時是把被調用的代碼複製到調用模塊中,然後在執行程序時,
靜態庫就不需要了
靜態庫的執行速度塊,但佔用空間大,當庫中的內容發生變化時,需要重新編譯出新
的程序,因此不能輕易修改庫的內容,

	而共享庫只是在調用模塊中嵌入調用代碼的在庫的相對位置的地址,當執行程序時,
共享庫的程序會一起加載到內存中,當執行到調用共享中代碼的指令時跳轉到共共享中
執行,執行完畢後在跳轉回來

	佔用空間小,方便更新(共享庫發生變化後,程序不需要再次編譯),相對靜態庫
執行效率低

	靜態庫的擴展名.a,共享庫(動態庫)的擴展名爲.so

六、靜態庫
1、創建靜態庫
編寫源代碼:vi .c/.h
編譯源代碼:gcc -c xxx.c -> xxx.o
打包生成靜態庫:ar -r libxxx.a x1.o x2.o …
ar命令的一些參數:
-r 把目標文件添加到靜態庫中,已經存在的更新
-q 將目標文件追加到靜態庫的末尾
-d 從靜態庫中刪除目標文件
-t 顯示靜態庫中有哪些目標文件
-x 把靜態庫拆分成目標文件

2、調用靜態庫
	直接調用:調用者要和庫在同一路徑下
		gcc main.c libxxx.a
	設置環境變量:設置方法於C_INCLUDE_PATH類似
		1、打開 vim ~/.bashrc 文件
		2、在文件末尾添加一行
			export LIBRARY_PATH=$LIBRAY_PATH:庫文件的路徑
		3、重新加載配置文件  source ~/.bashrc
		4、編譯時要指定庫名
			gcc  main.c  -lmath
	設置編譯參數:-L路徑
		gcc  main.c  -L路徑  -lmath
3、運行
	在編譯時已經把函數的二進制複製到可執行文件中了,在執行時不再需要靜態庫文件

七、共享庫
1、創建共享庫
編譯源代碼:vi .c/.h
編譯出位置無關目標文件:
gcc -c -fpic xxx.c -> xxx.o
鏈接生成共享庫:
gcc -shared x1.o x2.o x3.o … -o libxxx.so
2、調用共享庫
直接調用:調用者要和庫在同一路徑下
gcc main.c libxxx.so
設置環境變量:設置方法於C_INCLUDE_PATH類似
1、打開 vim ~/.bashrc 文件
2、在文件末尾添加一行
export LIBRARY_PATH=$LIBRAY_PATH:庫文件的路徑
3、重新加載配置文件 source ~/.bashrc
4、編譯時要指定庫名
gcc main.c -lmath
設置編譯參數:-L路徑
gcc main.c -L路徑 -lmath
3、運行
在使用共享庫時,調用者只是記錄了代碼在庫的位置,因此在執行時需要共享庫同時
被加載。
操作系統會根據LD_LIBRARY_PATH環境變量的設置來加載共享庫。

八、動態加載共享庫
#include <dlfcn.h>

1、加載共享庫
	void *dlopen(const char*filename, int flag);
	filename:共享庫的庫名,或路徑
	flag:
		RYLD_LAZY	使用時才加載
		RTLD_NOW	立即加載
	返回值:共享庫的句柄(類似文件指針)
	
2、獲取標識符地址
	void *dlsym(void *handle, const char *symbol);
	handle:共享庫的句柄
	symbol:標識符的名字
	返回值:標識符在共享庫中的位置(地址,可以解引用,或跳轉過去)。
	
3、卸載共享庫
	int dlclose(void *handle);
	handle:共享庫的句柄
	返回值:成功返回0,失敗返回-1
	
4、獲取錯誤信息
	char *dlerror(void);
	返回值:會把在使用共享庫的過程中出現的錯誤,以字符串形式返回

九、輔助工具
nm:查看目標文件、可執行文件、靜態庫、共享庫的中的符號列表
ldd:查看可執行程序所依賴的共享庫有哪些
strip:減肥,去除掉目標文件、可執行文件、靜態庫和共享庫中的符號列表、調試信息。
objdump -S 顯示二進制模塊的反彙編信息

/***內存管理/

一、錯誤處理
1、通過函數返回值表示錯誤
返回值合法表示成功,非法表示失敗
返回有效指針表示成功,空指針(NULL/0xffffffff)表示失敗
返回0表示成功,-1表示失敗
永遠成功

練習1:str_len 求字符串的長度,若指針爲空則報錯
練習2:str_cpy(char* dest,size_t dlen,char* src) 字符串拷貝函數,考慮目標的溢
	出問題,如果目標位置無效或超出則報錯
練習3:intmin 求兩個整數的最小值,二者相等,則報錯
練習4:intagv 求兩個整數的平均值,該函數永遠成功

2、通過errno表示錯誤
	errno是一個全局變量,他的聲明在errno.h文件中,它的值隨時可能發生變化
	可以將他轉換成有意義的字符串,strerror(errno) <=> perror("msg")
	注意:在函數執行成功的情況下,不會修改errno的值
		因此不能以errno的值不等於0就判斷函數執行出錯了
		通常會和函數的返回值配合,通過返回值判斷是否出錯,而通過perror查詢
	出了什麼類型的錯誤

二、環境變量
以字符串形式存在的,絕大多數記錄的是路徑信息,它表示了當前操作系統的資
源配置,環境設置等相關信息。
1、環境變量表
每個程序運行時操作系統都會把所有的環境變量記錄到一張表中,傳給程序
通過main函數參數獲取 int main(int argc,char* argv,char* environ[]);
通過聲明爲全局變量獲取 extern char** environ;

	2、環境變量函數
		char* getenv(const char* name);
		功能:根據環境變量名,獲取環境變量的值
		
		int putenv(char* string);
		功能:以name=value形式設置環境變量,如果環境變量存在則更新,不存在則添加
			成功返回0,失敗返回-1
		
		int setenv(const char* name,const char *value, int overwrite);
		功能:設置name環境變量的值爲value,如果name存在且overwrite不爲零則更新,
		否則不變
		
		int unsetenv(const char *name);
		功能:從環境變量表中刪除name
		
		int clearenv(void);
		功能:清空環境變量表
		操作系統記錄的環境變量的數據記錄一塊特殊的存儲空間,而在程序自己添加的
	環境變量需要自己準備存儲空間
		注意:對於環境變量的修改,只能影響自己,不能影響別人

	練習五:從文件中讀取一個程序的配置信息
		ServerIP = 192.168.0.1
		Port = 8899
		MaxSize = 100
		ContinueSec = 3
		LogPath = /zhizhen/temp
		DataDath = /zhizhen/data/
	練習六:給LIBRARY_PATH添加一個路徑(/home/zhizhen/lib)

三、內存管理
自動分配/釋放內存(auto_ptr) STL 調用標準C++中的new/delete
new/delete 構造/析構 C++ 調用標準C中的malloc/free
malloc/free 標準C 調用POSIX
brk/sbrk POSIX 調用Linux系統接口
mmap/munmap Linux 調用內核
Kmalloc/vmalloc 內核 調用驅動
get_free_page 驅動 …

四、進程映像
程序是保存在磁盤上的可執行文件,加載到內存中被操作系統調用執行的程序叫進程
(一個程序可以被同時執行多次形成身份不同的進程)
進程在內存空間中的分佈情況叫進程映像,從低地址到高地址依次排列的是:

	代碼段/只讀段:
			二進制指令、字符串字面值、具有const屬性且被初始化過的全局、靜態變量
	數據段:被初始化過的全局變量和靜態變量
	BSS段:	沒有被初始化過的全局變量和靜態變量,進程一但加載成功就會把這段內存
			清理爲零
	堆:	動態的分配、管理,需要程序員手動操作
	棧:	非靜態的局部變量,包括函數的參數、返回值
			從高地址項低地址使用和堆內存之間存在一段空隙,
	命令行參數及環境變量表:命令行參數、環境變量
	
	練習1:在一個程序中打印各段內存一個地址,然後於操作系統中內存分配情況表
			然後一一對應內存的分配情況
			getpid()	可以獲取進程的編號
			cat/proc/xxx/maps
			size 程序名	查看text data bss各段的大小

五、虛擬內存
每個進程都有各自獨立的4G字節的虛擬地址空間,我們在編程時使用的永遠都是
這4G的虛擬地址空間中的地址,永遠無法直接訪問物理地址。

	操作系統不讓程序直接訪問物理內存而只能只用虛擬地址空間一方面爲了操作系統
自身的安全,另一方面可以讓程序使用到比物理內存更大的地址空間(把硬盤上的特殊
文件與虛擬地址空間進行映射)

	4G的虛擬地址空間被分爲兩個部分:
		0~3G	用戶空間
		3G~4G	內核空間
		注意:用戶空間的代碼不能直接訪問內核空間的代碼和數據,但可以通過系統
	調用(不是函數,但以函數形式調用)進入到內核空間間接與內核交換數據

	如果使用了沒有映射過的虛擬內存地址,或者訪問沒有權限的虛擬內存地址,就會
產生段錯誤(非法內存訪問)
	
	一個進程對應一個用戶空間,進程一但切換,用戶空間也會發生變化,內核空間有
操作系統管理,它不會隨着進程的切換而發生變化,內核空間由內核所管理的一張獨立
且唯一的init mm表進行內存映射,而用戶空間的表是每個進程一張

	注意:每個進程的內存空間完全獨立,不同的進程之間交換虛擬內存地址沒有任何
意義,所以進程之間不能直接進行通信,需要由內核中轉、協調。

虛擬內存到物理內存的映射以頁爲單位(一頁等於4K=4096字節)。

六、內存管理API
他們可以進行映射內存的取消映射(系統級的內存管理)

	void *sbrk(intptr_t increment);
	返回值:未分配前的內存首地址,以字節爲單位
	increment:
			0	獲取未分配前的內存首地址(也就是已經分配尾地址)
			>0	增加內存空間
			<0	釋放內存空間
			
	int brk(void* addr);
	功能:設置未分配內存的首地址
	返回值:成功返回0,失敗返回-1
		
	他們可以進行映射內存的取消映射(系統級的內存管理),他們背後維護着一個指針,
該指針記錄的是未分配的內存的首地址(當前堆內存的最後一個字節的下一個位置)。
	他們可以進行映射內存的取消映射(系統級的內存管理),但爲了方便起見,sbrk一
般用於分配內存,brk用於釋放內存。
	注意:sbrk/brk分配的釋放的都是使用權,真正的映射工作由其他系統調用完成(mmap/
munmap)
	
	練習1:計算1000以內的素數,存在到堆內存中,不要浪費內存
	練習2:使用sbrk和brk實現順序棧
	
	#include <sys/mman.h>
	void* mmap(void *addr,size_t length,int prot,int flags,int fd,off_t offset);
	功能:把虛擬內存地址於物理內存或文件建立映射關係
	addr:要映射的虛擬內存地址,如果爲NULL操作系統會自動選擇一個虛擬地址與物理
內存映射
	length:要映射的字節數
	prot:權限
	flags:映射標誌
	fd:文件描述符(與內存映射沒有關係)
	offset:文件映射偏移值
	返回值:映射成功後的虛擬內存地址,如果出錯返回值爲0xffffffff
	
	int munmap(void* addr,size_t length);
	功能:取消映射
	addr:需要取消映射的內存首地址
	length:需要映射的字節數
	返回值:成功返回0,失敗返回-1

/Linux文件操作**/

一、文件描述符
UNIX/Linux系統絕大部分功能都是通過系統調用實現,比如:open/close…
UNIX/Linux把系統調用都封裝成了C函數的形式,但它們並不是標準C的一部分
標準庫中的函數絕大部分時間都工作在用戶態,但部分時間也需要切換到內核
(進行了系統調用),比如:fread/fwrite/malloc/free
我們自己所編寫的代碼也可以直接調用系統接口進入內核態(進行系統調用),
比如:brk/sbrk/mmap/munmap

	系統調用的功能代碼存在於內存中,接口定義在C庫中,該接口通過系統中斷實現
調用,而不是普通函數進行跳轉
	注意:從用戶態切換到內核態或從內核態返回到用戶態都會消耗時間
	time a.out
	real	0m0.137s	總執行時間 = 用戶態 + 內核態 + 切換消耗
	user	0m0.092s	用戶態執行時間
	sys		0m0.040s	內核態執行時間
	
	strace 程序 可以跟蹤系統調用

二、一切皆文件
在UNIX/Linux系統下,幾乎所有資源都是有文件形式提供了,所以在UNIX/Linux系統
下一切皆文件,把操作系統的服務、設備抽像成簡單的文件,提供一套簡單同一的接口,
這樣程序就可以像訪問磁盤上的文件一樣訪問串口、終端、打印機、網絡等功能。
大多數情況下只需要 open/read/write/ioctl/close 就可以實現對各種設備的輸入、
輸出、設備、控制等。
UNIX/Linux下幾乎任何對象都可以當作特殊類型的文件,可以以文件的形式訪問
目標文件 裏面記錄的是一些文件信息,相關條目
設備文件 在系統的/dev目錄下存儲了所有的設備文件
stderr
stdin
stdout
普通文件
鏈接文件
管道文件
socket文件

三、文件相關係統調用
open 打開或創建文件
creat 創建文件
close 關閉文件
read 讀文件
write 寫文件
lseek 設置文件讀寫位置
unlink 刪除鏈接
remove 刪除文件

四、文件描述符
文件描述符是一個非負整數,表示一個打開的文件,由系統調用open/creat/socket返回值
爲什麼使用文件描述符而不像標準庫使用文件指針?
因爲記錄文件相關信息的結構存儲在內核中,爲了不暴露內存的地址,因此文件結構指針
不能直接給用戶操作,內核中記錄一張表,其中一列是文件描述符對應一列文件結構指針,
文件描述符就相當於獲取文件結構指針的下標,
內核中已經由三個已經打開的文件描述符,它們的宏定義在unistd.h:
stdin 0 STDIN_FILENO
stdout 1 STDOUT_FILENO
stderr 2 STDERR_FILENO

五、open/creat/close
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
功能:打開文件
pathname:文件的路徑
flags:打開的權限
	O_RDONLY		只讀
	O_WRONLY		只寫
	O_RDWR			讀寫
	O_CREAT			文件不存在則創建
	O_EXCL			如果文件存在,則創建失敗
	O_NOCTTY		當打開的是終端設備文件,不要把該文件當做主控終端
	O_TRUNC			清空
	O_APPEND		追加

返回值

int open(const char *pathname, int flags, mode_t mode);
功能:創建文件
pathname:文件的路徑
flags:打開的權限
	O_CREAT			文件不存在則創建
	O_EXCL			如果文件存在,則創建失敗
mode:設置文件的權限
	S_IRWXU  00700 user (file  owner)  has  read,write and execute permission
    S_IRUSR  00400 user has read permission
    S_IWUSR  00200 user has write permission
    S_IXUSR  00100 user has execute permission
	
    S_IRWXG  00070 group has read, write and exe‐cute permission
	S_IRGRP  00040 group has read permission
    S_IWGRP  00020 group has write permission
    S_IXGRP  00010 group has execute permission
	
    S_IRWXO  00007 others have  read,  write  and execute permission
    S_IROTH  00004 others have read permission
    S_IWOTH  00002 others have write permission
    S_IXOTH  00001 others have execute permission

六、read/write
#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);
功能:從文件中讀取數據到內存
fd:文件描述符,open函數的返回值
buf:數據的存儲位置
count:讀取的字節數
返回值:成功讀取到的字節數

ssize_t write(int fd,const void *buf,size_t count);
功能:把數據寫入到文件
fd:文件描述符,open函數的返回值
buf:要寫入的數據內存首地址
count:要寫入的字節數
返回值:成功寫入的字節數

注意:如果把結構體以文本形式寫入到文件,需要把結構體轉換成字符串(編碼)

七、lseek
#include <sys/types.h>
#include <unistd.h>

off_t lseek(int fd,off_t offset,int whence);
功能:設置文件位置指針
返回值:文件位置指針所在的位置,功能類似ftell

練習1:實現一個Linux系統下(使用系統調用來完成)的計算文件大小的函數
練習2:實現帶覆蓋檢查的cp命令

八、dup/dup2
#include <unistd.h>

int dup(int oldfd)
功能:複製文件描述符,操作系統會從未使用的文件描述符中選擇一個返回
oldfd:被複制的文件描述符

int dup2(int oldfd,int newfd)
功能:複製指定的文件描述符,如果newfd已經被使用,則先關閉,再複製

九、標準IO與系統IO比較
練習3:分別使用標準IO與系統IO寫入1000000個整數到文件,比較哪一種方法更快,
爲什麼?
因爲標準IO使用了緩衝技術,當數據寫入時並沒有立即把數據交給內核,而是先放
在緩衝區中,當緩衝區滿時,會一次性把緩衝區中的數據交給內核寫到文件中,這樣就
減少內核態和用戶態的切換次數,而系統IO每寫一次數據就要進入一次內核態,這樣就浪
費了大量時間進行內核態與用戶態的切換,因此用時更長
如果爲系統IO,設置更大的緩衝區,它會比標準IO更快

/文件管理**/

一、sync/fsync/fdatasync
#include <unistd.h>

	1、	硬盤上一般都有一些緩衝區以此來提高數據的寫入效率,操作系統寫入數據其實
只是寫入緩衝區,直到緩衝區滿,才排隊寫入硬盤中
	2、	這種操作降低了寫入的次數,但是提高了數據寫入的延時,導致緩衝區的數據
與磁盤中的內容不同步

void sync(void)
功能:把所有緩衝區中的數據全部同步到磁盤
注意:只是將數據同步到磁盤的命令,並不等待執行完畢才返回,而是命令發佈後立即返回

int fsync(int fd);
功能:指定fd文件的緩衝區數據同步到磁盤
注意:指定fd文件的緩衝區數據同步到磁盤,只針對一個文件,數據同步到磁盤後才返回

int fdatasync(int fd);
功能:指定fd文件的緩衝區數據同步到磁盤,但僅是文件的數據並不同步文件屬性

二、fcntl
#include <unistd.h>
#include <fcntl.h>

int fcntl(int fd,int cmd,…/*arg*/)
cmd:操作指令,不同的操作指令決定後續參數的個數和類型
注意:這是個變長參數的函數
  
int fcntl(int fd,int cmd,long newfd)
cmd:F_DUPFD
功能:複製文件描述符,與fd操作同一個文件
返回值:如果newfd沒有使用則返回newfd,如果newfd已經被佔用,則返回一個不小於
newfd的文件描述符

練習1:利用fcntl,實現dup和dup2的功能

int fcntl(int fd,int cmd,void/long)
功能:設置或獲取文件描述符標誌
cmd:
	F_GETFD  void
	F_SETFD  long
	目前只能設置FD_CLOEXEC標誌位
返回值:0新進程保持打開狀態,新進程中關閉文件描述符

int fcntl(int fd,int cmd,void/long)
功能:獲取文件狀態標誌(此文件打開的權限以及打開的方式)
cmd:
	F_GETFL  void
		O_CREAT,O_EXCL,O_NOCTTY,O_TRUNC 不能獲取到
		返回值:帶有文件狀態標誌的int類型變量,需要與個標誌相與得到
	F_SETFL  long
		僅能設置的有
		O_APPEND,O_ASYNC,O_DIRECT,O_NOATIME,O_NONBLOCK
		返回值:成功返回0,失敗返回-1

int fcntl(int fd,int cmd,struct* flock);
功能:爲文件加鎖,能鎖整個文件,或鎖一部分內容
cmd:
	F_GETLK		獲取鎖的信息
	F_SEKLK   	設置鎖
	F_SETLKW	測試鎖
注意:加鎖並不能讓其它進程打不開,而是使用者都要遵守鎖的約定,確保文件不混亂(勸誡鎖)


struct flock 
{      
	short l_type;    /* 鎖的類型 */
	short l_whence;  /* 基礎位置 */ 
	off_t l_start;   /* 鎖的偏移值 */
	off_t l_len;     /* 鎖的長度 */
	pid_t l_pid;     /* 加鎖的進程號 */   
};

讀鎖 與 讀鎖 成功
讀鎖 與 寫鎖 失敗
寫鎖 與 寫鎖 失敗

三、stat/fstat/lstat
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

功能:用來獲取文件屬性,返回值:成功返回0,失敗返回-1
int stat(const char* path,struct stat *buf);
path:需要文件路徑
int fstat(int fd,struct stat *buf);
fd:需要打開後的文件描述符
int lstat(const char *path,struct stat *buf);
stat/fstat會跟蹤鏈接目標,而lstat不跟蹤鏈接目標

struct stat {
    dev_t     st_dev;     /* 設備ID */
    ino_t     st_ino;     /* 結點號 */
    mode_t    st_mode;    /* 文件類型和權限 */
    nlink_t   st_nlink;   /* 硬鏈拉數 */
    uid_t     st_uid;     /* 用戶ID */
    gid_t     st_gid;     /* 組ID */
    dev_t     st_rdev;    /* 特殊設備ID */
    off_t     st_size;    /* 文件的總字節數 */
    blksize_t st_blksize; /* IO塊數 */
    blkcnt_t  st_blocks;  /* 佔用塊(512字節)數 */
    time_t    st_atime;   /* 最後訪問時間 */
    time_t    st_mtime;   /* 最後修改時間 */
    time_t    st_ctime;   /* 最後屬性修改時間 */
};

S_ISREG(m)  測試是否是標準文件
S_ISDIR(m)  目錄
S_ISCHR(m) 	字符設備文件
S_ISBLK(m)  塊設備文件
S_ISFIFO(m) 管道設備文件
S_ISLNK(m)  軟鏈接文件
S_ISSOCK(m) socket文件


S_IFMT     0170000   獲取文件類型出錯
S_IFSOCK   0140000   socket文件
S_IFLNK    0120000   軟鏈接文件
S_IFREG    0100000   標準文件
S_IFBLK    0060000   塊設備文件
S_IFDIR    0040000   目錄
S_IFCHR    0020000   字符設備文件
S_IFIFO    0010000   管道文件
S_ISUID    0004000   set UID bit
S_ISGID    0002000   set-group-ID bit (see below)
S_ISVTX    0001000   sticky bit (see below)

S_IRWXU    00700     屬主的讀寫執行權限
S_IRUSR    00400     屬主的讀				'r'
S_IWUSR    00200     屬主的寫				'w'
S_IXUSR    00100     屬主的執行				'x'

S_IRWXG    00070     屬組的讀寫執行權限
S_IRGRP    00040     屬組的讀
S_IWGRP    00020     屬組的寫
S_IXGRP    00010     屬組的執行

S_IRWXO    00007     其它用戶的讀寫執行權限
S_IROTH    00004     其它用戶的讀
S_IWOTH    00002     其它用戶的寫
S_IXOTH    00001     其它用戶的執行

練習1:使用stat函數,獲取文件的屬性,顯示出文件的類型和權限,參考ls
	-l 的第一列內容

#include <time.h>

struct tm *localtime(const time_t *timep);
功能:使用一個記錄秒數據的變量,獲取當前時間

練習:獲取文件的最後訪問時間,最後修改時間,最後文件屬性修改時間

四、access
int access(const char *pathname,int mode);
功能:測試當前用戶對文件的訪問權限,或者文件是否存在
pathname:文件路徑
mode:
F_OK 是否存在
R_OK 是否有讀權限
W_OK 是否有寫權限
X_OK 手否有執行權限
返回值:0表示有,-1表示沒有

五、umask
mode_t umask(mode_t mask);
功能:設置並獲取權限屏蔽碼,功能與umask命令一樣,一但設置成功,新創建文件就不會
具有mask中的權限
返回值:舊的權限屏蔽碼
注意:該權限屏蔽碼支隊當前進程有效,進程結束後,就會變成默認的。

六、chmod/fchmod

功能:修改文件的權限,返回值:成功返回0,失敗返回-1
int chmod(const char* path,mode_t mode);
int fchmod(int fd,mode_t mode);
注意:它們不受權限屏蔽碼的干擾

七、truncate/ftruncate
功能:修改文件的大小,如果文件修改成功返回0,失敗返回-1
int truncate(const char *path,off_t length);
int ftruncate(int fd,off_t length);

八、link/unlink/remove/rename
返回值:成功返回0,失敗返回-1

int link(const char *oldpath,const char *newpath);
功能:創建硬鏈接文件,硬鏈接指向的是文件的內容,因此當鏈接目標被刪除後,依然
	可以訪問文件的內容
int unlink(const char *pathname);
功能:刪除硬鏈接文件
注意:普通文件就是硬鏈接數量爲1的文件,當把一個問價的硬鏈接數刪除到0個時,這個
	文件就被刪除了
int remove(const char *pathname);
功能:刪除文件,該函數時標準庫中的刪除文件,底層調用了unlink系統調用

int rename(const char *oldpath,const char *newpath);
功能:文件重命名

九、symlink/readlink
int symlink(const char *oldpath,const char *newpath);
功能:創建軟連接(目錄文件只能創建軟鏈接)
oldpath:鏈接目標
newpath:鏈接文件
返回值:成功返回0,失敗返回-1

ssize_t readlink(const char *path,char *buf,size_t bufsiz);
功能:讀取軟鏈接文件的內容而非鏈接目標(open打開軟鏈接文件是打開的是目標文件)
path:鏈接文件的路徑
buf:讀取數據的存儲位置
bufsiz:讀取多少個字節
返回值:成功讀取到的字節數

十、mkdir/rmdir
返回值:成功返回0,失敗返回-1

int mkdir(const char *pathname,mode_t mode);
功能:創建目錄,目錄一定要有執行權限,否則無法進入

int rmdir(const char *pathname);
功能:刪除空目錄(只能刪除空目錄)

十一、chdir/fchdir/getcwd
char *getcwd(char *buf,size_t size);
功能:獲取當前進程的工作目錄,工作目錄是指當不加路徑信息時,所創建/打開時
從那個目錄下查找,工作目錄默認是程序所在的目錄

	int chdir(const char *path);
	功能:修改進程的工作目錄
	返回值:成功返回0,失敗返回-1
	
	int fchdir(int fd);
	功能:修改進程的工作目錄
	fd:被open打開的目錄文件的fd
	返回值:成功返回0,失敗返回-1

十二、opendir/fdopendir/closedir/readdir/rewinddir/telldir/seekdir
#include <dirent.h>

DIR *opendir(const char *name);
功能:打開一個目錄流
返回值:目錄流(鏈表)

DIR *fdopendir(int fd);
功能:使用文件描述符獲取目錄流,fd必須是目錄文件的

struct dirent *readdir(DIR *dirp);
功能:從目錄流中讀取一個文件結點信息

struct dirent {
    ino_t          d_ino;       /* i結點號 */
    off_t          d_off;       /* 下一個文件結點信息的偏移量 */
    unsigned short d_reclen;    /* 當前結點信息的長度 */
    unsigned char  d_type;      /* 文件類型 */
    char           d_name[256]; /* 文件的名字 */
 };
 
DT_BLK      This is a block device.		//塊設備文件	‘b'
DT_CHR      This is a character device.	//字符設備文件	'c'
DT_DIR      This is a directory.		//目錄			'd'
DT_FIFO     This is a named pipe (FIFO).//管道文件		'q'
DT_LNK      This is a symbolic link.	//鏈接文件		'l'
DT_REG      This is a regular file.		//標準文件		'-'
DT_SOCK     This is a UNIX domain socket.//socket文件	's'
DT_UNKNOWN  The file type is unknown.	//				'?'
 
void rewinddir(DIR *dirp);
功能:把目錄流的位置指針調整到開頭

long telldir(DIR *dirp);
功能:獲取當前目錄流的位置指針在第幾個文件結點

void seekdir(DIR *dirp,long offset);
功能:調整當前目錄流的位置指針
offset:根據開頭位置指針,進行偏移

作業1:實現ls -l的功能,要求真假難辨
struct passwd* getpwuid
功能:根據用戶ID獲取用戶ID
struct group *getgrgid(gid_t gid);
功能:根據組ID獲取組名

struct passwd {
    char   *pw_name;       /* username */
    char   *pw_passwd;     /* user password */
    uid_t   pw_uid;        /* user ID */
    gid_t   pw_gid;        /* group ID */
    char   *pw_gecos;      /* user information */
    char   *pw_dir;        /* home directory */
    char   *pw_shell;      /* shell program */
};

作業2:實現rm -rf的功能(刪除非空目錄)。

/信號處理*/

一、信號的基本概念
1、中斷:中止(注意不是終止),當前正在執行的任務,轉而執行其它任務(可能
返回也可能不返回),中斷分爲硬件中斷(硬件設備產生的中斷)和軟件中斷(其它程序
產生的中斷)。
2、信號:是一種軟件中斷,提供了一種異步執行任務的機制,
3、常見的信號
SIGINT(2) ctrl+c 產生的信號
SIGQUIT(3) ctrl+\ 產生的信號
SIGABRT(6) 調用adort函數,產生此信號
SIGFPE(8) 浮點數例外,除以0、浮點溢出等
SIGKILL(9) 不能被捕獲或忽略。常用於殺死進程
SIGSEGV(11) 段錯誤信號 非法訪問內存產生的信號
SIGTSTP(20) Ctrl+Z 生產的信號
SIGCHLD(17) 子進程狀態改變信號
注意:在終端中執行 kill -l 可以顯示出所有信號

	4、不可靠信號和可靠信號
		建立在早期機制上的信號被稱爲不可靠信號,SIGHUP(1)~SIGSYS(31)
		不支持排隊可能會丟失,同一個信號產生多次,進程可能只接受到一次
	5、可靠信號
		採用新的機制產生的信號,SIGRTMIN(34)~SIGRTMAX(64)
		支持排隊,不會丟失
	6、信號的來源
		硬件產生:除0、非法內存訪問
			這些異常是硬件(驅動)檢查到,並通知內核,然後內核在向引發這些異常
		的進程發送相應信號
		
		軟件產生:通過kill/raise/alarm/setitmer/sigqueue函數產生
		
	7、信號的處理
		1、忽略
		2、終止進程
		3、終止進程併產生core文件
		4、捕獲信號並處理

二、信號的捕獲
#include <signal.h>

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler);
功能:信號處理註冊函數
signum:信號的編號,1~31 也可以是宏
handler:函數指針
	SIG_IGN		忽略該信號
	SIG_DFL		默認處理
	函數指針	
	注意:在某些UNIX系統上,signal註冊的函數只執行一次,執行完後就恢復成默認處理方
式,如果長期使用該函數處理信號,可以在函數結束前在註冊一次
	SIGSTP 可以被捕獲,但不能被處理
	SIGKILL/SIGSTOP	既不能被捕獲,也不能被處理
	SIGSTOP	信號會讓進程暫停,當再次收到SIGCONT信號時會繼續執行
	普通用戶只能給自己的進程發送信號,而root可以給任何進程發送信號
	
練習1:實現一個“死不掉的進程”,當收到信號後,給出信號產生的原因

三、發送信號
1、鍵盤
Ctrl+c SIGINT(2)
Ctrl+\ SIGQUIT(3)
Ctrl+z SIGTSTP(20)
2、錯誤
除零 SIGFPE(8)
非法訪問內存 SIGSEGV(11)
3、命令
kill -signum pid
ps -aux 查看所有進程
4、函數
#include <sys/types.h>
#include <signal.h>

	int kill(pid_t pid, int sig);
	功能:向指定的進程發送信號
	pid:進程id
		pid > 0 向進程號爲pid的進程發送信號
		pid = 0 向同組一進程組的進程發送信號
		pid = -1 向所有(有權力發送信號的)進程發送信號
		pid < -1 向進程號爲abs(pid)的進程組發送信號
	sig:信號的編號
		sig值爲0時,kill不會發送信號,但會進行錯誤檢查(檢查進程號或進程組id號
	是否存在)
	
	int raise(int sig);
	功能:向當前進程發送信號

四、休眠

int pause(void);
功能:一但執行進程就會進入無限的休眠(暫停),直到遇到信號。
	先執行信號處理函數纔會從休眠中醒過來,返回-1

unsigned int sleep(unsigned int seconds);
功能:休眠指定的秒數,當有信號來臨時會提前醒來,提前醒來會返回剩餘的秒數,或者
	睡夠了,返回0

五、鬧鐘
unsigned int alarm(unsigned int seconds);
功能:告訴內核在seconds秒之後,向當前進程發送SIGALRM信號
注意:如果之前設定的時間還沒到,則會重新設置(覆蓋),並返回之前設置的剩餘秒數

六、信號集與信號屏蔽
信號集:信號的集合,有128位二進制組成,每一位代表一個信號
int sigemptyset(sigset_t *set);
功能:清空信號集,把所有位設置位0

int sigfillset(sigset_t *set);
功能:填滿信號集,把所有位設置位1

int sigaddset(sigset_t *set, int signum);
功能:往信號集中添加一個信號

int sigdelset(sigset_t *set, int signum);
功能:從信號集中刪除一個信號

int sigismember(const sigset_t *set, int signum);
功能:判斷信號集中是否有signum信號
返回值:存在返回1,不存在返回-1

	信號屏蔽:當做一些特殊操作時會希望,有些信號來而有些信號不要來,而與設置
信號忽略不同的,信號屏蔽只是暫時不來,而可以獲取當這段時間發生哪些信號
	每個進程都有一個信號掩碼(信號集),其中包括了需要屏蔽的信號,可以通過
sigprocmask函數,檢查、修改進程的掩碼

	int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
	功能:檢查、修改進程的信號掩碼
	how:
		SIG_BLOCK
			設置當前信號掩碼與set的並集爲新的信號掩碼,添加
		SIG_UNBLOCK
			新的信號掩碼是當前掩碼與set補集的交集,刪除
		SIG_SETMASK
			把set當作新的信號掩碼,重新設置
	set:可以爲空,則獲取信號掩碼
	oldset:舊的信號屏蔽掩碼
	
	int sigpending(sigset_t *set);	
	功能:獲取信號屏蔽期間發生的信號,當信號屏蔽解除後就沒了
	
	注意:在信號屏蔽期間發生的信號,無論多少次(不可靠信號),只捕獲一次

練習:學生管理系統,在保存數據和加載數據時屏蔽Ctrl+c和Ctrl+\,等數據加載、保存
完成後在處理信號

七、帶附加信息的信號捕獲
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact);
功能:向內核註冊信號處理函數
signum:信號編碼
act:信號處理方式
oldact:獲取到此信號舊的處理方式可以爲NULL
struct sigaction {
void (*sa_handler)(int); //簡單的信號處理函數指針
void (*sa_sigaction)(int, siginfo_t *, void *); //可以帶附加信息的信號處理函數指針
sigset_t sa_mask; //當執行信號處理函數時需要屏蔽的信號
int sa_flags;

			SA_RESETHAND	表示信號只處理一次,然後就恢復默認處理方式
			SA_RESTART		系統調用如果被signum信號中斷,自行重啓
			SA_NOCLDSTOP	當子進程暫停時,不用通知父進程		
			SA_NODEFER		當執行信號處理函數時,不屏蔽正常的處理的信號
			SA_SIGINFO		使用第二個函數指針處理信號
           
		void     (*sa_restorer)(void);		//保留暫時不用
};
       
int sigqueue(pid_t pid, int sig, const union sigval value);
功能:信號發送函數,與kill不同的是可以附加一些額外數據
pid:目標進程號
sig:要發送信號
value:聯合,成員可以是整數或指針

八、計時器
#include <sys/time.h>

1、系統爲每個進程維護三個計時器
	ITIMER_REAL		真實計時器,程序運行的實際所用時間
	ITIMER_VIRTUAL	虛擬計時器,程序運行在用戶態所消耗的時間
	ITIMER_PROF		實用計時器,程序在用戶態在內核態所消耗的時間
	
	實際時間(真實計時器)= 用戶時間(虛擬)+ 內核時間 + 睡眠時間
		
int getitimer(int which, struct itimerval *curr_value);
功能:獲取當前進程的定時器
which:選擇使用哪些計時器
curr_value:
	struct timeval it_interval;		每一次觸發時鐘信號所需的時間
	struct timeval it_value;		第一次觸發時鐘信號所需的時間

int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);
功能:給當前進程設置定時器,與alarm的區別是更精確,可以選擇那個時間段計算
which:
	ITIMER_REAL		真實計時器,程序運行的實際所用時間
	ITIMER_VIRTUAL	虛擬計時器,程序運行在用戶態所消耗的時間
	ITIMER_PROF		使用計時器,程序在用戶態在內核態所消耗的時間
value:	

由於文件讀寫時爲了提高效率,增加了緩衝區,所以當進行寫操作時,數據中並沒有
立即寫入文件,而暫時存儲緩衝區中,只有達到某些條件時才寫入文件
	1、由於寫入狀態切換到讀取狀態
	2、遇到\n符
	3、緩衝區滿4k
	4、手動刷新fflush(FILE*)
	5、文件關閉

/進程管理******/

一、基本概念
1、進程與程序
程序:存儲在磁盤上的文件,包含可執行指令和數據的靜態指令
進程:運行中的程序(一個程序可以執行多次,加載出多個進程)
進程就是處於活動狀態的計算機程序
2、進程的分類:
交互進程:有輸入、輸出,用戶可以根據自己的情況輸入數據,得到想要的結果(一般進程)
批處理進程:由腳本加載執行的程序(Linux下的shell,windows下的bat)
守護進程:總是活躍的、後臺運行,一般由系統開機時加載執行或root用戶手動加載執行
3、查看進程
簡單方式:ps,顯示當前用戶有終端控制權的進程信息
列表形式:ps -aux,以列表形式顯示詳細信息
a 所有用戶終端控制進程
x 所有用戶無終端控制的進程
u 詳細方式顯示

4、進程的詳細信息列表
	USER	進程的屬主
	PID		進程id
	%CPU	CPU使用率
	%MEM	內存使用率
	VSZ		佔用虛擬內存的大小
	RSS		佔用物理內存的大小
	TTY		有終端控制的顯示終端的次設備號,如果無終端控制權顯示'?'
	STAT	進程的狀態
				O 就緒態,等待被系統調度
				R 運行態,Linux系統沒有就緒態,就緒態也用R表示
				S 休眠態,可以被系統中斷(信號)喚醒轉入運行態
				T 暫停態,是被SIGSTOP信號暫停的,當收到SIGCONT信號時
					才能再轉入運行態
				Z 殭屍態,已經結束停止運行,但父進程還沒回收
				< 高優先級進程
				N 低優先級進程
				l 多線程化的進程
				+ 在前臺進程組中的進程
				s 會話首進程
	START TIME	進程開始時間
	COMMAND		進程的可執行文件名

5、父進程與子進程,孤兒進程與殭屍進程
	一個進程A可以創建出另一個進程B,創建者叫父進程,被創建進程叫子進程,父進程啓動
後,在操作系統的調用下父進程同時執行(同步)。

	如果子進程先於父進程結束,會向父進程發送SIGCHLD信號,父進程收到信號後,就應該去
回收子進程的相關資源,但在默認情況下父進程忽略該信號。
	也就是說,當子進程結束後,父進程沒有回收子進程的資源,那麼子進程就變成了殭屍進程
	如果父進程先於子進程結束,子進程就會變成了孤兒進程,同時被孤兒院收養(init),然後
就變成init的子進程

二、進程表示符
#include <sys/types.h>
#include <unistd.h>

	操作系統會爲每個進程分配一個唯一的標識符,採用無符號整數表示,即進程ID
	進程ID在任何時候後市唯一的,但是可以重用,當一進程結束,新創建的進程纔可以使
用它的進程ID(延時重用)。
	pid_t getpid(void);
	功能:獲取進程ID
	pid_t getppid(void);
	功能:獲取父進程ID
	uid_t getuid(void);
	功能:獲取當前進程用戶ID
	gid_t getgid(void);
	功能:獲取當前進程的組ID
	
	int setpgid(pid_t pid, pid_t pgid);
	設置進程pid的進程組ID
	pid_t getpgid(pid_t pid);
	獲取pid進程的進程組ID

三、fork
#include <unistd.h>

pid_t fork(void);
功能:創建一個新進程
返回值:一次調用兩次返回,失敗返回-1(當進程數超出系統限制進程創建就會失敗);

1、兩次返回分別時進程ID和0,父進程會拿到子進程的ID,子進程返回0,藉此可以分辨出
2、通過fork創建的子程序就是父進程的副本(拷貝)
	子進程會獲取父進程數據段、堆、棧、IO流(共享文件指針和文件描述符)、緩衝區的拷貝,
	與父進程共享代碼段
3、子進程會繼承父進程的信號處理方式
4、fork函數調用,父子進程各自執行,誰先返回不一定,但可以使用一些手法來確定誰先執行
	練習1:實現一程序來驗證:子進程會獲取父進程數據段、堆、棧、IO流(共享文件指針和文
		件描述符)、緩衝區的拷貝,與父進程共享代碼段
	練習2:爲一個父進程創建5個子進程,一共6個進程
5、殭屍進程與孤兒進程的實現。

四、進程的正常退出
1、從main函數中return。
2、調用標準庫中的exit函數
void exit(int status);
功能:調用者立即結束該進程
status:退出狀態碼,可以在父進程中獲取到,子進程留給父進程的遺言
退出前做的事情:
1)先調用事先註冊的函數(atexit/on_exit)
int atexit(void (*function)(void));
功能:註冊一個函數,當進程通過exit函數結束時調用
function:函數指針,無返回值無參數

			int on_exit(void (*function)(int , void *), void *arg);
			功能:註冊一個函數,當進程通過exit函數開始結束時調用
			function:函數指針,無返回值,參數1爲exit函數的參數,參數二,爲on_exit函數
					的第二個參數
			arg:當function函數被調用時傳遞給它的第二參數
			
		2)沖刷所有處在未關閉狀態的標準IO流
		3)返回一整數(EXIT_SUCCESS/EXIT_FAILURE)給操作系統
		4)該函數不會返回,它的功能實現藉助了_exit/_Exit

3、調用_exit/_Exit函數退出
	#include <unistd.h>
	void _exit(int status);
	#include <stdlib.h>
	void _Exit(int status);		//調用系統的_exit
	功能:調用的進程會結束,沒有返回值。
	status:會被父進程獲取到(低八位,一個字節)
	1)進程結束前會關閉所有處於打開狀態的文件描述符
	2)把所有子進程託付給孤兒院(init)
	3)向它的父進程發送SIGCHLD信號
	注意:exit函數也會執行以上操作,因此它底層調用了_exit/_Exit
	
4、進程的最後一個線程執行最後一條語句
5、進程的最後一個線程調用了pthread_exit函數

五、進程的異常退出
1、調用了abort函數,該函數會產生SIGABRT信號
2、進程接收到一些信號(無法捕獲處理、或無法捕獲處理)
3、進程的最後一線程收到“取消”請求,並做出響應,相當於線程收到了結束“信號”

六、wait/waitpid
#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status);
功能:等待所有子進程結束,並獲取到最終的狀態碼,只要有一個進程結束就立即返回

1、應該時父進程收到子進程發送來的SIGCHLD信號時,調用wait函數回收子進程的資源
	並獲取結束狀態
2、如果所有子進程都在運行,則wait阻塞
3、如果已有殭屍進程,wait也會立即返回,回收資源獲取結束狀態
4、如果沒有子進程,則返回失敗-1

pid_t waitpid(pid_t pid, int *status, int options);
功能:等待指定的進程結束,並獲取到最終的狀態碼
pid:	
	=-1	等待任一子進程結束,此時於wait等價
	> 0	等到進程號爲pid的進程結束,此時只等待一個進程結束
	= 0	等待同組的任一子進程結束,此時等待的時整個進程組
	<-1	等待的時進程級id是pid的絕對值中的任一子進程結束,此時等待的時整個進程組
	
options:
	WNOHANG			非阻塞模式,如果沒有子進程結束則立即退出
	WUNTRACED		如果子進程處理暫停,則返回它的狀態
	WCONTINUED		如果子進程從暫停轉位繼續,則返回它的狀態
	
1、wait函數只能孤獨的等待子進程的結束,而waitpid可以有更多的選擇	
2、waitpid不光可以等待子進程,也可以等待同組進程。
3、waitpid可以阻塞也可以不阻塞
4、也可以監控子進程的暫停或結束狀態

七、vfork
#include <sys/types.h>
#include <unistd.h>

pid_t vfork(void);
功能:與fork的功能基本一致
區別:通過vfork創建的進程不復制父進程的地址空間(數據段、堆、棧、IO流
(共享文件指針和文件描述符)、緩衝區的拷貝),必須通過excl系列函數加載自
的可執行程序
	注意:當執行vfork時,子進程先返回,此時它佔用了父進程的地址空間,當子進程
成功創建後(通過excl加載可執行程序),父進程纔會返回

八、execl
功能:加載子進程的可執行文件。

int execl(const char *path, const char *arg, ...);
path:可執行文件的路徑
arg:第一個main函數的參數,最後一次必須以NULL結尾

int execlp(const char *file, const char *arg, ...);
file:可執行文件的名字,會從PATH環境變量的路徑中查找可執行文件並執行
arg:第一個main函數的參數,最後一次必須以NULL結尾

int execle(const char *path, const char *arg,..., char * const envp[]);
path:可執行文件的路徑
envp:父進程的環境變量表,傳遞給子進程

int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);

九、system
int system(const char *command);
功能:執行系統命令的,也可以加載可執行程序
相當於創建了一個子進程,但子進程不結束,該函數不返回,父子進程不會同時進行

該函數的實現應該調用了:vfork、exec、wait等函數

作業1:實現system函數的功能

十、進程組
進程組:是由一個或多個進程的集合,每個進程除了有一個進程ID還有一個進程組ID
進程組中的進程歸屬同一個作業控制(負責完成同一個任務)
同一進程組的進程,會統一接受到終端的信號,由fork創建的子進程默認就加入了父進程
的進程組
每個進程組都有一個組長,組長的進程ID就是組ID
int setpgid(pid_t pid, pid_t pgid);
功能:設置進程pid進程的進程組ID,就相當於加入pgid進程組,pgid就是它的組長

	pid_t getpgid(pid_t pid);
	功能;獲取pid進程的進程ID

/進程通信***/

一、基本概念
什麼進程間通信(IPC):指兩個或多個進程之間交換數據的過程叫進程間通信
進程之間爲什麼需要通信?
當需要多個進程協同工作高效率完成任務時,因爲每個進程都是獨立的個體(資源單位)
,進程之間就需要進行通信。
進程間通信方式:
1、簡單進程間通信:命令行參數,環境變量表、信號、文件
2、傳統進程間通信:管道
3、XSI進程間通信:共享內存、消息隊列、信號量
4、網絡進程間通信:socket

二、傳統的進程間通信-管道
管道是UNIX系統最古老的進程間通信方式(基本不在使用),歷史上的管道通常是半雙工
(只允許單向數據流動),現在的系統大都可以全雙工,數據可以雙向流動
1、有名管道(創建實體文件) tmp
命令:mkfifo
函數:mkfifo
int mkfifo(const char *pathname, mode_t mode);
功能:創建管道文件
pathname:文件路徑
mode:權限
返回值:成功返回0,失敗返回-1

			編程模型:
				進程A						進程B
				創建管道(mkfifo)			....
				打開管道(open)			打開管道
				讀/寫管道(read/write)		讀/寫數據
				關閉管道(close)			關閉管道
				刪除管道(unlink)			....
			
	2、無名管道(用於通過fork創建的父子
		int pipe(int pipefd[2]);
		功能:創建無名管道
		pipefd:用來存儲內核返回的文件描述符
			pipefd[0];用於讀操作
			pipdfd[1]:用於寫操作
	
	練習1:使用有名管道進行通信,管道創建者讀,對方寫
	練習2:使用無名管道進程通信,父進程讀,子進程寫

三、XSI進程間通信
X/open組織爲UNIX系統設計一套進程間通信機制,有共享內存、消息隊列、信號量
1、IPC標識
內核會爲每個XSI的進程間通信對象維護一個IPC對象(XSI對象)
該對象通過一個非負整數來引用(類似於文件描述符)
與文件描述符不同的是,每用一個IPC對象標識符就持續+1,達到最大值時再從零開始
IPC標識需要程序員自己創建(類似於創建文件)
2、IPC鍵值
#include <sys/types.h>
#include <sys/ipc.h>

	創建IPC鍵值的依據(類似創建文件的文件名),也是一個非負整數
	1、自定義(不建議,可能會衝突)
	2、自動生成(項目路徑,項目編號)
	key_t ftok(const char *pathname, int proj_id);
	注意:項目路徑一定要是有效路徑,生成IPC鍵值依靠的是路徑而不是字符串
3、IPC對象的創建用到的宏
	IPC_PRIVATE	創建IPC對象時永遠創建成功
	IPC_CREAT	對象存在則獲取,不存在則創建
	IPC_EXCL	如果對象已經創建,則創建失敗
4、IPC對象銷燬/控制用到的宏
	IPC_STAT	獲取IPC對象的屬性
	IPC_SET		設置IPC對象的屬性
	IPC_RMID	刪除IPC對象

四、共享內存
#include <sys/ipc.h>
#include <sys/shm.h>

	共享內存就是內核中開闢一塊由IPC對象管理內存,進程A和進程B都用自己的虛擬
地址與它映射,這樣就共享了同一塊內存,然後就可以通信了
	特點:
		1、不需要複製信息,是最快的一種進程間通信機制
		2、需要需要同步問題(必須藉助其它的機制,如信號)
	編程模型:
	進程A:							進程B:
	生成IPC鍵值			ftok		生成IPC鍵			ftok
	創建共享內存		shmget		獲取共享內存		shmget
	映射共享內存		shmat		映射共享內存		shmat
	使用共享內存		*ptr		使用共享內存		*ptr
	取消映射共享內存	shmdt		取消映射			shmdt
	刪除共享內存		shmctl		.....

int shmget(key_t key, size_t size, int shmflg);
	功能:創建/獲取共享內存
	key:IPC鍵,由ftok函數生成
	size:共享內存的大小,最好時4096的整數倍,獲取共享內存時,此值無效
	shmflg:
		0				獲取共享內存
		IPC_CREAT		創建
		IPC_EXCL		如果存在則創建失敗
	返回值:成功返回共享內存標識(IPC標識),失敗返回-1
	
void *shmat(int shmid, const void *shmaddr, int shmflg);
	功能:映射共享內存
	shmid:共享內存標識符,shmget函數的返回值
	shmaddr:進程提供的虛擬地址,與內核中的內存映射用的,也可以時NULL
			(內核會自動選擇一個地址映射)
	shmflg:
		SHM_RDONLY	只讀權限
		SHM_RND		當shmaddr不爲空時自動選擇一個地址映射
	返回值:映射成功後的虛擬地址,失敗返回 (void*)-1
	
int shmdt(const void *shmaddr);
	功能:取消虛擬地址與共享內存的映射
	shmaddr:被映射過的虛擬地址

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
	功能:刪除共享內存,獲取/設置共享內存
	shmid:共享內存標識符
	cmd:
		IPC_STAT	獲取共享內存的屬性
		IPC_SET		設置共享內存的屬性
		IPC_RMID	刪除IPC共享內存
		
	struct shmid_ds {
           struct ipc_perm shm_perm;    /* 內存所有者及權限 */
           size_t          shm_segsz;   /* 內存的大小 */
           time_t          shm_atime;   /* 最後映射時間 */
           time_t          shm_dtime;   /* 最後取消映射時間 */
           time_t          shm_ctime;   /* 最後修改時間 */
           pid_t           shm_cpid;    /* 創建者進程ID */
           pid_t           shm_lpid;    /* 最後映射/取消映射的進程ID */
           shmatt_t        shm_nattch;  /* 映射的次數 */
           ...
       };	

	struct ipc_perm {
           key_t          __key;    /* IPC鍵值 */
           uid_t          uid;      /* 有效用戶ID */
           gid_t          gid;      /* 有效組ID */
           uid_t          cuid;     /* 創建者的用戶ID */
           gid_t          cgid;     /* 創建者的組ID */
           unsigned short mode;     /* 權限 */
           unsigned short __seq;    /* 對象id */
       };

五、消息隊列
消息隊列就是由內核負責管理的一個管道,可以按順序發送消息包
(消息類型+消息內容),可以全雙工工作,可以不按消息的順序接收

int msgget(key_t key, int msgflg);
功能:創建/獲取消息隊列
key:IPC鍵值,由ftok函數自動生成
msgflg:
	0			獲取消息隊列
	IPC_CREAT	創建消息隊列
	IPC_EXCL	如果存在則創建失敗
返回值:消息隊列標識	

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:向消息隊列發送消息
msqid:消息度列標識,msgget函數返回值
msgp:結構指針
	struct msgbuf {
        long mtype;       /* 消息類型 */
        char mtext[1];    /* 消息內容 */
    };
msgsz:消息的長度,不包括消息類型,sizeof(msgbuf)-4
msgflg:
	0	阻塞,當消息隊列滿時,等待
	1	不阻塞,當消息隊列滿時,不等待
	------------------------------------
返回值:成功發送返回0,失敗返回-1
	
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
功能:從消息隊列中按類型獲取消息
msqid:消息度列標識,msgget函數返回值
msgp:結構指針
	struct msgbuf {
        long mtype;       /* 消息類型 */
        char mtext[1];    /* 消息內容 */
    };
msgsz:要接收的消息的長度,可以長一些
msgtyp:要接收的消息類型
	0	接受任意類型的消息(接受隊列中第一個消息)
	>0	只接收msgtyp類型的消息
	<0	接收消息隊列中小於等於msgtyp絕對值的消息,取最小的那個
msgflg:
	0	阻塞,消息隊列中是否有對應類型的消息
	1	不阻塞,當消息隊列滿時,不等待
	------------------------------------
	MSG_NOERROR:
		消息類型正確,而消息的實際長度大於msgsz,則不接收消息並返回-1
		如果msgflg帶MSG_NOERROR標誌,則把多餘的消息截取,成功接收
	IPC_NOWAIT:
		如果消息隊列沒有要接收的消息,則不等待,返回-1
	MSG_EXCEPT:
		接收消息隊列中第一個消息不是msgtyp的消息,編譯時添加-D_GNU_SOURCE參數
		
	返回值:成功接收到消息的字節數
	
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:刪除消息隊列,設置或獲取消息隊列屬性
msqid:消息度列標識,msgget函數返回值
cmd:
	IPC_STAT	獲取消息隊列的屬性
	IPC_SET		設置消息隊列的屬性
	IPC_RMID	刪除IPC消息隊列
	
	struct msqid_ds {
           struct ipc_perm msg_perm;     /* 權限 */
           time_t          msg_stime;    /* 最後一個消息發送時間 */
           time_t          msg_rtime;    /* 最後一個消息接收時間 */
           time_t          msg_ctime;    /* 最後一次修改時間 */
           unsigned long   __msg_cbytes; /* 消息隊列中的字節數 */
           msgqnum_t       msg_qnum;     /* 消息隊列中消息的個數 */
           msglen_t        msg_qbytes;   /* 消息隊列中容納的最大字節數 */
           pid_t           msg_lspid;    /* 最後一個發送消息的進程 */
           pid_t           msg_lrpid;    /* 最後一個接收消息的進程 */
       };
	 
	struct ipc_perm {
		   key_t          __key;    /* IPC鍵值 */
           uid_t          uid;      /* 有效用戶ID */
           gid_t          gid;      /* 有效組ID */
           uid_t          cuid;     /* 創建者的用戶ID */
           gid_t          cgid;     /* 創建者的組ID */
           unsigned short mode;     /* 權限 */
           unsigned short __seq;    /* 對象id */
       };
	
返回值:成功返回0,失敗返回-1

六、信號量
內核維護的計數器,用於多進程之間共享資源
例如:有個變量n表示資源的數量,當有進程想要獨佔一個資源時,n的值要減1,如果n的值
等於0(不夠減多個),則進程阻塞,直到n的值可以減再被喚醒,當資源使用完畢後n的值要加1
(可能加多個)

int semget(key_t key, int nsems, int semflg);
	功能:創建/獲取信號量
	key:IPC鍵值
	nsems:信號量的數量
	semflg:
		0				獲取信號量
		IPC_CREAT		創建信號量(已存在則獲取,不存在則創建)
		IPC_EXCL		如果已經存在則創建失敗
	返回值:信號量標識
	
int semop(int semid, struct sembuf *sops, unsigned nsops);
	功能:操作信號量(對信號進行加/減操作)
	semid:信號量標識,semget的返回值
	sops:結構體數組
	nsops:數組的長度
	
	struct sembuf{
		unsigned short sem_num;  /* 信號量的下標 */
		short          sem_op;   /* 操作 */
		short          sem_flg;  /* 標記 */
			IPC_NOWAIT	當信號量不夠減時,不阻塞
			SEM_NUDO	當進程結束時,信號量的值自動歸還
	}
	
int semtimedop(int semid, struct sembuf *sops, unsigned nsops,struct timespec *timeout);
	功能:帶時間限制的操作信號量
	
	
		struct timespec{
			_time_t tv_sec;		秒
			long int tv_nsec;	納秒
		}
		
int semctl(int semid, int semnum, int cmd, ...);
	功能:初始化信號量,刪除信號量,獲取、設置信號量的屬性
	cmd:
		IPC_STAT	獲取信號量的屬性
		IPC_SET		設置信號量的屬性
		IPC_RMID	刪除信號量
		IPC_INFO	獲取信號量的信息
		SEM_INFO	設置信號量的信息
		GETALL		獲取所有信號量的值
		GETNCNT		獲取信號量的數量
		GETVAL		獲取某個信號量的值
		SETALL		設置所有信號量的值
		SETVAL		設置某個信號量的值
	union semun {
           int              val;    /* Value for SETVAL */
           struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
           unsigned short  *array;  /* Array for GETALL, SETALL */
           struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                       (Linux-specific) */
       };
	
	//
	struct ipc_perm {
           key_t          __key; /* Key supplied to semget(2) */
           uid_t          uid;   /* Effective UID of owner */
           gid_t          gid;   /* Effective GID of owner */
           uid_t          cuid;  /* Effective UID of creator */
           gid_t          cgid;  /* Effective GID of creator */
           unsigned short mode;  /* Permissions */
           unsigned short __seq; /* Sequence number */
       };

編程模型:
	進程A								進程B
	創建信號量	semget					獲取信號量
	初始化信號量的值	semctl			......
	加減信號量	semop					加減信號量
	刪除信號量	semctl					......
注意:信號量是用來計數的,一定要有資源對應	

七、IPC命令
顯示IPC對象
ipcs -m
ipcs -q
ipcs -s
ipcs -a
刪除IPC對象
ipcrm -m ID
ipcrm -q ID
ipcrm -s ID

/網絡通信**/

一、計算機網絡
1、什麼是計算機網絡
計算機網絡是指將地理位置不同的具有獨立功能的多臺計算機及其外部設備,通過通信
線路連接起來,在網絡操作系統,網絡管理軟件及網絡通信協議的管理和協調下,實現資源
共享和信息傳遞的計算機系統。

2、計算機網絡的功能
	數據通信
	資源共享
	提高系統的可靠性
	分佈式網絡處理和負載均衡

3、計算機網絡的組成
	通信子網:網卡、線纜、集線器、中繼器、交換機、路由器
	資源子網:網絡中的計算機、打印機、電話等一些可以提供服務的設備
	計算機網絡軟件:
		協議軟件:它規定了計算機之間通信的規則,TCP/IP協議簇
		網絡通信軟件:網絡中實習計算機與設備之間通信的軟件(網卡驅動)
		網絡管理軟件:防火牆,SELinux
		網絡應用軟件:瀏覽器、迅雷
		網絡操作系統:可以提供網路服務的計算機操作系統,windows server2008、UNIX、Linux

4、計算機網絡的分類
	按網絡的作用範圍劃分:局域網、城域網、廣域網
	按網絡技術劃分:廣播式網絡、點到點網絡
	按傳輸介質劃分:有線網、無線網、微波通信、衛星通信

5、計算機網絡的拓撲結構
	星型
	樹型
	總線型
	環型
	網狀型

6、計算機網絡的發展過程
	1、以計算機爲中心的聯機系統
	2、分組交換網絡的誕生
	3、網絡體系結構與協議標準化
		20世紀80年代ISO組織提出開放式系統互聯參考模型(OSI參考模型)
		但是由於這個模型照顧到了各方的利益,所以太過龐大,因此至今都沒有成熟的產品,
	目前我們所使用的是一系列協議的集合,簡稱TCP/IP簇,通常也叫TCP/IP模型
		目前所有的計算機系統都按照這份協議進行通信,所有不同的操作系統之間才能進行
	網絡通信
		C/C++/Java/python->Windows->socket->TCP/IP <-> TCP/IP->socket->Linux->C/C++/Java/python
	4、高速計算機網絡(5G)

7、網卡
	它負責將數據發送到網絡上,也負責從網絡中獲取數據,每個網卡上都都一個獨一無二MAC地址
8、OSI參考模型與TCP/IP參考模型
	網絡協議是爲網絡數據交換而制定的規則、約束、標準,一個功能完備的計算機網絡需要制定一套
複雜的協議集,目前的網絡協議是按層次結構組織的,網絡層次結構模型與各層協議的集合稱爲網絡體系結構
	OSI從上到下:物理層、數據鏈路層、網絡層、傳輸層、會話層、表示層、應用層
	TCP/IP目前只實現了四層,從下到上:
		物理層:負責通信網絡收發數據包
		網絡層:選擇、流量控制、網絡擁塞,IP協議是該層的核心
		傳輸層:機器之間建立用於會話的端到端連接(用於數據傳輸),該層的核心是TCP/UDP
		應用層:主要爲用戶提供針對性的服務,這一層的代表協議有:HTTP,SMTP,FTP,SNMP,TELNET
		
		OSI						TCP
		------------------------------
		物理層					
		數據鏈路層				物理層
		------------------------------
		網絡層					網絡層
		------------------------------
		傳輸層					傳輸層
		------------------------------
		會話層
		表示層					應用層
		應用層
		------------------------------
				
9、IP地址	ipv4
	在計算機網絡中每一臺計算機都必須有一個唯一的標識(MAC地址不容易記憶),它就是IP地址
目前在計算機以.分十進制表示(4個不超過255的整數),但在程序中它就是4字節的整數
	IP地址的分類:
		A類:第一個二進制位必須是0
			0.0.0.0~127.255.255.255
		B類:前兩位的二進制必須是10
			128.0.0.0~191.255.255.255
		C類:前三位二進制必須是110
			192.0.0.0~223.255.255.255
		D類:前四位二進制必須是1110
			224.0.0.0~239.255.255.255
		E類:前四位二進制必須是1111
			240.0.0.0~255.255.255.255
10、公有IP和私有IP
	公有IP:在網絡服務提供商登記過的叫公有IP
	私有IP:由一些公司或組織自己分配的,不能再網絡中公開直接訪問的
11、子網掩碼
	由四個不超過255的整數,.分十進制表示
	網絡地址 = ip地址 按位& 子網掩碼
	只有在同一子網內的IP地址才能直接進行通信,否則必須經過路由器。
12、網關地址
	負責子網出口的計算機,一般由路由器擔任(路由器就是一臺有路由功能的計算機)
13、端口號
	ip地址能決定我們與哪一臺計算機通信,而端口號決定了我們與計算機上的哪個進程通信
	1~1024基本上已經被操作系統預定完了,因此我們在編程時一般要使用1024以上
		http:80	ftp:21	ssh:22	telnet:23

二、網絡通信的基本概念
1、TCP和UDP的區別
TCP:傳輸控制協議,面向連接的服務(打電話),安全、可靠(三次握手、響應+重傳、四次回收)
速度相對較慢,一般應用在對安全性、完整性有嚴格要求的場景:FTP、SMTP、HTTP

		三次握手:
			A要知道,A能到B,B也能到A
			B也要知道,A能到B,B也能到A
		四次揮手:
			目的是保證關閉前發送完所有數據包(應用層已經交給底層了,但底層還沒完全發送出去)
		
	UDP:用戶數據報文協議,面向無連接的服務(發短信),不保證安全、可靠,但大多數情況下是可靠的。
		相對較快,流媒體(在線的視頻、音頻)

2、消息流
	應用層->表示層->會話層->網絡層->傳輸層->數據鏈路層->物理層->物理層->數據鏈路層->傳輸層->
->網絡層->會話層->表示層->應用層
3、消息包
	當socket收到一個要送的數據時,先發數據進行拆分成Bit流,然後再組成(防丟失)數據包(可能會丟包)

三、套接字
socket是一種接口機制,可以讓程序無論使用什麼端口、協議,都可以從socket進出數據,它負責了
進程與協議之間的連接。

	1、編程模式:
		點對點(p2p):一對一的通信
		客戶機/服務器(C/S):一對多通信

	2、函數
		#include <sys/types.h>
		#include <sys/socket.h>
		int socket(int domain, int type, int protocol);
		功能:創建socket描述符,可以把socket當作文件來看待,發送數據就是寫文件,接收數據就是
			讀文件
		domain:地址類型
				AF_UNIX/AF_LOCAL/AF_FILE 本地通信(進程間通信)
				AF_INET	基本32位IP地址通信,IPv4
				AF_INET6 基於128位IP地址通信,IPv6
		type:通信協議
			SOCK_STREAM		數據流協議,TCP
			SOCK_DGRAM		數據報協議,UDP
		protocol:特別通信協議,給0即可
		返回值:socket描述符,類似文件描述符
		
	3、通信地址
		注意:函數接口定義的是sockaddr,而實際提供的是sockaddr_un或sockaddr_in
		struct sockaddr {
           sa_family_t sa_family;
           char        sa_data[14];
		}
		
		struct sockaddr_un {
			_SOCKADDR_COMMON (sun_);	//地址類型	參看domain
			char sun_path[108];			//socket文件的路徑
		}
		
		struct sockaddr_in {
			_SOCKADDR_COMMON (sin_);
			in_port_t sin_port;			//端口號  大端字節序
			struct in_addr sin_addr;	//ip地址  大端4字節整數
		}
		
		struct in_addr
		{
			in_addr_t s_addr;
		}
	
	4、綁定
		socket描述符與物理通信載體(網卡或文件)綁定在一起
		int bind(int sockfd, const struct sockaddr *addr,
            socklen_t addrlen);
		sockfd:socket描述符,socket函數的返回值
		addr:通信地址結構體,實際給的是sockaddr_un或sockaddr_in,需要強制類型轉換
		addrlen:通信地址結構體類型的字節數,使用sizeof計算
	
	5、連接
		int connect(int sockfd, const struct sockaddr *addr,
               socklen_t addrlen);
		sockfd:socket描述符
		addr:通信目標地址
		addrlen:通信地址結構體類型的字節數,使用sizeof計算
		返回值:在不同的編程模型下返回值意義不同,在本地通信
			成功返回0,失敗返回-1
	
	6、數據接收與發送:read/write
		ssize_t recv(int sockfd, void *buf, size_t len, int flags);
		ssize_t send(int sockfd, const void *buf, size_t len, int flags);
		recv/send與read/write功能一樣,flags多了是否阻塞的功能(0阻塞,1不阻塞)。
	
	7、關閉套接字:close
		如果是網絡通信,端口號並不會立即回收,大概會佔用3分鐘左右
	
	8、字節序轉換
		#include <arpa/inet.h>
		uint32_t htonl(uint32_t hostlong);
		功能:把32位本機字節序轉換成32位的網絡字節序
		uint16_t htons(uint16_t hostshort);
		功能:把16位本機字節序轉換成16位的網絡字節序
		uint32_t ntohl(uint32_t netlong);
		功能:把32位網絡字節序轉換成32位的本機字節序
		uint16_t ntohs(uint16_t netshort);
		功能:把16位網絡字節序轉換成16位的本機字節序
	
	9、ip地址轉換
		#include <sys/socket.h>
		#include <netinet/in.h>
		#include <arpa/inet.h>
		
		int inet_aton(const char *cp, struct in_addr *inp);
		功能:把點分十進制的ip地址(字符串)轉換成32位的無符號整數,使用指針獲取
		
		in_addr_t inet_addr(const char *cp);
		功能:把點分十進制的ip地址(字符串)轉換成32位的無符號整數,使用返回值直接返回
		
		char *inet_ntoa(struct in_addr in);
		功能:32位無符號整數表示的ip地址,轉換成點分十進制的ip地址(字符串)
	
	10、本地通信編程模型
		進程A							進程B
		創建套接字(AF_LOCAL)			創建套接字(AF_LOCAL)
		準備地址(sockaddr_un)			準備地址(sockaddr_un)
		綁定(自己的socket/地址)			連接(connect,連接進程A的地址)
		接收數據						發送數據
		關閉套接字						關閉套接字

四、基於TCP協議的C/S模型
int listen(int sockfd, int backlog);
功能:設置等待連接的最大數量
sockfd:被監聽的socket描述符
backlog:等待連接的最大數量(排隊的數量)

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:等待連接sockfd連接
addr:獲取連接的地址
addrlen:設置連接地址結構的長度
返回值:專門用於通信的描述符

編程模型:
	Server									Client
	創建socket套接字						創建socket套接字
	準備地址(sockaddr_in,本機地址)			準備地址(服務器地址)
	綁定(bind)								......
	監聽(listen)							......
	等待連接(accept)						連接(connect)
	接收請求(read/recv)						發送請求(write/send)
	響應請求(write/send)					接收響應(read/recv)
	關閉(close)								關閉(close)

五、與阿里ESC服務器進行網絡通信
1、開啓端口號,詳細操作
2、使用FlashFXP上傳代碼
3、使用putty遠程登陸ESC服務器,修改代碼、編譯、執行

六、基本UDP協議的C/S模型
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
功能:UDP協議專用的數據發送函數
sockfd:套接字描述符
buf:待發送的緩衝區首地址
len:待發送的數據字節數
flag:0阻塞,0不阻塞
dest_addr:目標計算機地址
addrlen:地址結構體的字節數
返回值:成功發送的字節數

	ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                    struct sockaddr *src_addr, socklen_t *addrlen);
	功能:UDP協議專用的數據接收函數
	sockfd:套接字描述符
	buf:數據存儲位置
	len:最大接收字節數
	flag:0阻塞,1不阻塞
	src_addr:獲取發送者的地址
	addrlen:設置地址結構體的字節數
	返回值:成功接收的字節數
	
	編程模型:
		Server								Client
		創建套接字(socket)					創建套接字(socket)
		準備地址(本機地址sockaddr_in)		準備地址(目標機地址sockaddr_in)
		綁定(bind(sockfd+addr))				.....
		接收請求(revbfrom)					發送請求(sendto)
		響應請求(sendto)					接收響應(recvfrom)
		關閉套接字(close)					關閉套接字(close)
		
		注意:從服務器到客戶端返回的路線是UDP協議自己設計的。
		
	練習1:獲取當前計算機的ip地址和子網掩碼。
			ifconfig > ip.conf
	練習2:根據當前ip地址和子網掩碼,遍歷出所有同一局域網內的ip地址
	
	作業1:指針銀行升級爲網絡版
	作業2:實現網絡聊天室
	作業3:如何實現網絡傳輸文件

七、windows下的網絡的編程
一般的軟件都Linux/UNIX系統作爲服務器,而windows系統作爲客戶端,windows下的socket
編程的接口與Linux的基本一致,函數都聲明在winsock2.h頭文件中
int WSAStartup(WORD,LPWSADATA);
功能:初始化網絡庫
WORD:設置網絡庫的版本
MAKEWORD(1,2),第一位主版本號,第二位副版本號
LPWSADATA:WSADATA數據結構的指針,用來獲取網絡的相關信息

	SOCKET socket(int domain, int type, int protocol);
	功能:創建socket
	
	closesocket(sockfd);
	功能:關閉socket
	
	int WSACleanup(void);
	功能:卸載網絡庫
	
	注意:編譯時添加  -lws2_32

練習1:實現windows下的TCP的C/S通信
注意:在windows的write/read(只能用於文件讀寫)與send/recv
(正版網絡通信)是有區別的
練習2:實現windows下的UDP的C/S通信

/線程管理****/

一、線程基本概念
1、線程就是進程中的執行路線,即進程內部的控制序列,或者說是進程的子任務(進程就是
正在運行的程序,它是一個資源單位)
2、線程就是輕量級的,沒有自己獨立的內存資源,使用的是進程的的代碼段、數據段、bss段
、堆(注意沒有棧)、環境變量表、命令行參數、文件描述符、信號處理函數、工作目錄、用戶ID、
組ID等資源
3、線程擁有自己獨立的棧,也就是有自己獨立的局部變量
4、一個進程中可以同時擁有多個線程,即同時被系統調度的多條執行路線,但至少有一個主進程

二、線程基本特點
1、線程是進程的實體,可作爲系統獨立的調試和分派基本單位
2、線程有不同的狀態,系統提供了多種線程控制的原語(控制方法)
比如:創建線程、銷燬線程
3、線程不擁有自己的資源(唯一擁有的就是自己的棧空間),只擁有從屬於進程的全部資源,所有資源分配
都是面向進程的
4、一個進程中可以有多個線程同時執行,它們可以執行相同的代碼,也可以執行不同的代碼
5、同一進程內的線程都在同一地址空間下活動(0~4G),相對於多進程多線程的系統開銷小,任務切換快。
6、多進程協同工作時需要通信,而多線程間的數據交換不需要依賴類似IPC的特殊通信機制,簡單而高效
7、每個線程擁有自己獨立的線程ID、寄存器信息、函數棧等
8、線程之間也存在優先級

*注意:線程與進程的區別

三、POSIX線程
1、早期的UNIX操作系統中是沒有線程的,而是各計算機廠商提供了自己私有的線程庫
不易移植。
2、在1995年左右,定義了統一的線程編程接口,POSIX線程,即pthread
3、pthread包含一頭文件pthread.h,一個共享庫libpthread.so
4、功能:
線程管理:創建/銷燬、分離/聯合、設置/獲取屬性
線程同步(互斥):互斥量(鎖),條件變量,信號量

四、線程函數
1、創建線程
#include <pthread.h>

	int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                      void *(*start_routine) (void *), void *arg);
	功能:創建線程
	thread:獲取線程ID
	attr:創建線程時所需要的屬性設置,如果爲NULL按照默認方式創建線程
	start_routine:線程的入口函數
	arg:給線程入口函數傳遞的參數
	
	練習1:把TCP的S端的多進程改爲多線程。

2、等待線程結束
	int pthread_join(pthread_t thread, void **retval);
	功能:等待線程結束獲取線程入口函數的返回值,線程結束時該函數才返回
	thread:線程ID
	retval:指針變量的地址,用於獲取線程入口函數的返回值
	注意:線程入口函數在返回數據時,不能返回指向私有棧空間的指針,如果獲取到的是
		指向堆的指針,等待着要負責把該空間釋放
	注意:當主線程結束,子線程也結束
	
3、獲取線程ID
	pthread_t pthread_self(void);
	功能:返回當前線程ID
	
4、比較兩個線程ID
	int pthread_equal(pthread_t t1, pthread_t t2);
	功能:如果兩個線程ID是同一個線程,則返回0,否則返回-1
	注意:pthread_t不一定是unsigned long 類型,有些系統中它是結構體類型,所以
		無法使用==比較
	
5、線程終止
	void pthread_exit(void *retval);
	功能:調用者線程結束(從入口函數return)
	retval:會返回給pthread_join函數的第二個參數
	注意:如果是進程的最後一個線程,當調用pthread_exit時進程也就結束了
	
6、線程分離
	非分離:線程可以被創建者調用pthread_join等待(回收資源)
	分離狀態:線程不需要創建者等待,結束後自動釋放資源
	int pthread_detach(pthread_t thread);
	功能:使用調用線程與線程ID爲thread線程成爲分離狀態
	
7、線程取消
	int pthread_cancel(pthread_t thread);
	功能:向指定的線程發送取消操作
	注意:但對方不一定響應

	int pthread_setcancelstate(int state, int *oldstate);
	功能:設置調用者線程是否響應取消操作
	state:
		PTHREAD_CANCEL_ENABLE	允許響應
		PTHREAD_CANCEL_DISABLE	禁止響應
	oldstate:獲取舊的取消狀態
	
8、線程屬性

	typedef union
	{
		char __size[__SIZEOF_PTHREAD_ATTR_T];
		long int _align;
	}pthread_attr_t;
	猜測:不讓手動修改線程的各大項屬性,而使用pthread_attr_set/get
		系列函數來操作
		
	int pthread_attr_init(pthread_attr_t *attr);
	功能:初始化線程屬性
	
	int pthread_attr_destroy(pthread_attr_t *attr);
	功能:銷燬線程屬性
	
	int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
	功能:設置線程屬性中分離標誌
	
	int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);
	功能:獲取線程屬性中分離標誌
	PTHREAD_CREATE_DETACHED		分離
	PTHREAD_CREATE_JOINABLE		不分離		
	
	int pthread_attr_setscope(pthread_attr_t *attr, int scope);
	功能:設置線程屬性中線程的競爭範圍
	PTHREAD_SCOPE_SYSTEM
	PTHREAD_SCOPE_PROCESS
	
	int pthread_attr_getscope(pthread_attr_t *attr, int *scope);
	功能:獲取線程屬性中線程的競爭範圍
	
	int pthread_attr_setinheritsched(pthread_attr_t *attr,
                                    int inheritsched);
	功能:設置線程屬性中線程的調度策略的來源
	inheritsched
		PTHREAD_INHERIT_SCHED		繼承創建者
		PTHREAD_EXPLICIT_SCHED		單獨設置
	
	int pthread_attr_getinheritsched(pthread_attr_t *attr,
                                    int *inheritsched);
	功能:
	
	int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
	功能:設置線程屬性中線程的調度策略
	SCHED_FIFO		先進先出策略
	SCHED_RR		輪轉策略
	SCHED_OTHER		缺省
	
	int pthread_attr_getschedpolicy(pthread_attr_t *attr, int *policy);
	功能:獲取線程屬性中線程的調度策略

	int pthread_attr_setschedparam(pthread_attr_t *attr,
                                  const struct sched_param *param);
	功能:設置線程屬性中線程的調度參數(優先級別)
	param	最高級別0		
		
	int pthread_attr_getschedparam(pthread_attr_t *attr,
                                  struct sched_param *param);
								  
	int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
	功能:設置線程屬性棧尾的警戒區大小
	
	int pthread_attr_getguardsize(pthread_attr_t *attr, size_t *guardsize);							  
	功能:獲取線程屬性中棧尾的警戒區大小

	int pthread_attr_setstackaddr(pthread_attr_t *attr, void *stackaddr);
	功能:設置線程屬性中線程的棧底地址
	
	int pthread_attr_getstackaddr(pthread_attr_t *attr, void **stackaddr);
	功能:獲取線程屬性中線程的棧底地址
								  
	int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
	功能:設置線程屬性中線程的棧空間字節數
	
	int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);				  
	功能:獲取線程屬性中線程的棧空間字節數

	int pthread_attr_setstack(pthread_attr_t *attr,
                             void *stackaddr, size_t stacksize);
	int pthread_attr_getstack(pthread_attr_t *attr,
                             void **stackaddr, size_t *stacksize);

	使用方法:
		1、定義線程屬性結構體
			pthread_attr_t attr;
		2、初始化線程屬性結構體
			pthread_attr_init(&attr);
		3、使用pthread_attr_set系列函數對結構變量進行設置
		4、在創建線程時(pthread_create函數的第二各參數)使用線程屬性結構
			變量創建線程

	int pthread_getattr_np(pthread_t thread, pthread_attr_t *attr);
	功能:獲取指定線程的屬性
	
	作業1:實現聊天室

/線程同步****/

一、同步、競爭、互斥
當多個線程同時訪問其共享的資源時,需要相互協調,以防止出項數據不一致、不完整的問題,
能達到這種狀態叫線程同步
而有些資源在同一時刻只有一個線程訪問,對於這種資源的訪問需要競爭
當資源獲取到後,能夠防止資源被其它線程再次獲取的技術叫互斥

二、互斥量(鎖)
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

int pthread_mutex_init(pthread_mutex_t *_mutex,_const pthread_mutexattr_t *_mutexattr);
功能:初始化互斥量,使用第二互斥量來初始化第一個互斥量,如果第二個爲空,則使用
	默認參數初始化互斥量,也可以使用宏來初始化

int pthread_mutex_destroy(pthread_mutex_t *_mutex);
功能:銷燬互斥量
注意:互斥量是一個結構體,裏面有成員是指針,指向了堆內存數據,需要顯式初始化函數以及銷燬
	如果使用堆內存存儲互斥量,需要在調用了銷燬函數後,再進行free

int pthread_mutex_lock(pthread_mutex_t *_mutex);
功能:鎖定互斥量,當互斥量是鎖定狀態,此函數則阻塞(直到互斥量在其它線程中解鎖,調用者
	線程加鎖成功才返回)
注意:互斥量一但加鎖,只有它自己能解

int pthread_mutex_trylock(pthread_mutex_t *_mutex);
功能:嘗試鎖定互斥量,能鎖就鎖,不能鎖就返回

int pthread_mutex_timedlock(pthread_mutex_t *_restrict_mutex,
	const struct timespec *_restric_t_abstime);
功能:在指定時間內鎖定一個互斥量(使用的系統時間)
	
	struct timespec
	{
		_time_t tv_sec;			/*Seconds*/
		long int tv_nsec;		/*Nanoseconds*/
	}

int pthread_mutex_unlock(pthread_mutex_t *_mutex);
功能:解鎖

三、死鎖
多個線程進行等待對方的資源,在得到所有資源繼續運行前,都不會釋放自己的資源,這樣造成的
循環等待現象,稱爲死鎖

構成死鎖的四大必要條件:
	1、資源互斥
	2、佔有,還想佔有(請求並保持)
	3、資源不可剝奪
	4、環路等待(互相等待)

防止死鎖的方法:
	構成死鎖的四個條件只要破壞其中一個就構不成死鎖,死鎖一但形成就無法消除,因此最好
的方法就是避免產生死鎖
	1、破壞互斥條件,讓資源能夠共享,但缺點是不通用,因此有些資源不能共享,如打印機
	2、破壞請求並保持條件,採用預先分配的方法,在進程運行前一次申請號它所需的所有資源
		,但缺點是浪費資源
	3、破壞不可剝奪的條件,對已經佔有的資源的線程發送取消請求,但是實現比較複雜,而且
		還破壞業務邏輯
	4、破壞循環等待條件,爲每個資源進程編號,採用順序的資源分配方法,規定每個線程必須
		按照遞增的順序請求資源,缺點是編號必須相對穩定增加新的資源時會比較麻煩,而且
		有些特殊的業務邏輯不能完全按照指定的順序分配資源
	避免產生死鎖的算法(銀行家算法):
		1、貸款的額度不能超過銀行現有資源的總和
		2、分批向銀行貸款,但是貸款額度不能超過一開始最大需求量總和
		3、銀行如果不能滿足客戶的需要,必須及時給出答覆
		4、客戶必須在規定的時間內還款

	如果檢測死鎖:
		1、畫出資源分配圖,並簡化,模擬資源分配的流程
		2、監控線程或進程的棧內存使用情況
		3、設計看門狗機制(TCP心跳包)

四、信號量
線程的信號量與進程的信號量的機制是一樣的,但使用方法不同,用於控制、管理
線程間的資源共享
#include <semaphore.h>

	int sem_init(sem_t *sem, int pshared, unsigned int value);
	功能:初始化信號量(創建信號量)
	sem:信號量ID,輸出
	pshared:一般爲0(線程之間)進程中使用的
			非0表示進程間使用,但Linux不支持
	value:信號量的初始化值
	
	int sem_wait(sem_t *sem);
	功能:信號量減1,不夠減則阻塞(爲0時)
	
	int sem_trywait(sem_t *sem);
	功能:信號量減1,不夠減則立即返回-1

	int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
	功能:信號量減1,不夠減則阻塞,直到abs_timeout超時返回-1

	int sem_post(sem_t *sem);
	功能:信號量加1
	
	int sem_destroy(sem_t *sem);
	功能:銷燬信號量
	
	int sem_getvalue(sem_t *sem, int *sval);
	功能:獲取信號量的值

五、條件變量
條件變量可以讓線程在滿足特定的條件下暫停(睡眠),需要與互斥量配合使用
pthread_cond_t cond = PTHREAD_COND_INITIALIZER

extern int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t 
	*__resrtict_cond_attr);
功能:初始化條件變量
cond:待初始化的條件變量
cond_attr:條件變量的屬性

int pthread_cond_destroy(pthread_cond_t *cond);
功能:銷燬條件變量

int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
功能:讓調用者線程進入睡眠,並解鎖一個互斥量
cond:線程睡入的條件變量
mutex:線程睡眠前的要解鎖的互斥量(是不是鎖定狀態沒有關係)

int pthread_cond_signal(pthread_cond_t *cond);
功能:喚醒條件變量中的一個線程
注意:線程醒的前提條件是互斥量必須是解鎖狀態的,線程醒後會再次加鎖,如果不能加鎖就不會醒來

int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,
		struct timespec abstime);
功能:讓調用者線程進入睡眠(指定睡眠時間),並解鎖一個互斥量

六、哲學家就餐問題
提示,使用條件變量和互斥量實現

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