Linux下高級C編程
第一章 unix/linux系統的基本概念
第二章 unix/linux系統下的編程基礎和開發方式
第三章 unix/linux系統下的記憶體管理
第四章 unix/linux系統下的檔案管理和目錄操作
第五章 unix/linux系統下的行程管理
第六章 unix/linux系統下的信號處理
第七章 unix/linux系統下的行程間通信
第八章 unix/linux系統下的網路編程
第九章 unix/linux系統下的多執行緒編程
文章目錄
- Linux下高級C編程
- 前言
- 一、linux系統的基本概念
- 1.linux系統簡介
- 2.gcc/cc基本使用
- 3.常見的編譯選項
- 4.常用的C程式檔案后綴
- 5.多檔案編程
- 6.預定義宏
- 7.環境變數的概念和使用
- 二、linux系統下的編程基礎和開發方式
- 1.庫檔案的概念和使用
- 2.靜態庫的生成和呼叫
- 3.共享庫的生成和呼叫
- 4.共享庫動態加載
- 5.C語言的錯誤處理
- 6.環境表的概念和使用
- 三、linux系統下的記憶體管理
- 1.程式和行程的概念
- 2.行程中的記憶體區域劃分 (記憶體地址從小到大,其中堆和堆疊沒有明確的分割線,可以適當的調整)
- 3.字串存盤形式之間的比較
- 4.虛擬記憶體管理技術
- 5.段錯誤的由來
- 6.使用malloc 申請動態記憶體的特性
- 7.使用free 釋放動態記憶體的特性
- 8.記憶體處理的相關函式
- 四、linux系統下的檔案管理和目錄操作
- 1.基本概念
- 2.檔案的相關處理函式
- 3.檔案描述符
- 4.檔案的非讀寫函式
- 5.目錄管理
- 五、linux系統下的行程管理
- 1. 基本概念
- 2. 基本命令
- 3.各種ID的獲取
- 4.行程相關函式
- 中斷的概念:
- 六、linux系統下的信號處理
- 1.信號的基本概念和分類
- 2.信號的處理方式
- 3.信號的處理函式
- 4.計時器
- 七、linux系統下的行程間通信
- 1.概念:
- 2.行程間通信的方式
- 3.使用訊息佇列實作行程間通信
- 4.使用管道實作行程間通信
- 5.使用共享記憶體實作行程間通信
- 6.使用信號量集實作行程間通信
- 八、linux系統下的網路編程
- 1. 網路的基本常識
- 2.基于socket的一對一通信模型
- 3.基于TCP的協議的通信模型
- 4.基于UDP協議的通信模型
- TCP協議和UDP協議的比較
- 九、linux系統下的多執行緒編程
- 1.基本概念
- 2.執行緒的相關函式
- 3.執行緒的同步問題
- 4.使用信號量 實作執行緒的同步問題
- 5.使用條件變數實作執行緒的同步問題
- 總結
前言
主要學習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函式介面,熟練使用這些介面,可以實作相應的簡單功能.
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/183884.html
標籤:其他
