Unix/linux C(uc)與標準C語言最大的區別在於,標準C只是一種編程語言,而在此基礎上還依賴於具體的操作系統,脫離了unix/linux系統的uc在其他地方可能不被識別。
(1)Unix/Linux系統的基本概述
用gcc將源文件翻譯爲可執行文件過程:
預處理:主要宏替換,頭文件替換。
宏替換:#define用一個自定義的量代替一個數,如果要修改這個量的值,只要在最初的定義處修改即可。同時也定義了一些預定義宏:
__FILE__ -主要用於獲取當前文件名%s
__LINE__ -主要用於獲取當前宏所在的行號 %d
__DATE__ -主要用於獲取日期信息 %s
__TIME__ -主要用於獲取時間信息 %s
頭文件替換:#include “a.h”表示先從當前程序所在目錄開始尋找,然後去系統默認目錄,#include <a.h>表示表示從系統默認所在目錄開始尋找。
預處理指令:全部都以#開頭
#pragma GCC dependency 文件名
=>表示當前文件依賴於指定的文件名,如果指定文件名的最後一次修改時間晚於當前文件,則產生警告信息
#pragma pack(整數n)超過4按4計算
=>主要用於設置對齊和補齊方式
#warining 字符串
=> 表示產生一個警告信息
#error 字符串
=> 表示產生一個錯誤信息
編譯:檢查語法的錯誤,同時將源文件翻譯爲彙編文件.s
彙編:將彙編文件轉化爲目標文件.o
鏈接:若編譯時遇到調用其他文件的函數或變量時,會留下一個接口,鏈接部分則是找到這些接口要調用的函數或變量所在位置,將這些目標文件整合到一起,轉化爲可執行文件。
庫文件:
一般爲了調用者或者使用者的方便,會將完成一個具體功能是所有.o文件打包成一個庫,調用或使用時只需要這個庫文件和其對應的頭文件即可。庫文件分爲兩種:靜態庫和動態共享庫。
靜態庫:使用這個庫也就是鏈接時,如果要調用其他文件的函數或變量,會直接拷貝過來,所以目標文件會很大,但是運行時可以脫離靜態庫,不需要各種跳轉。
動態共享庫:使用這個庫也就是鏈接時,如果要調用其他文件的函數或變量,會將要調用的函數或變量所在的地址拷貝過來,所以目標文件小,但是運行時不能脫離這個庫,要各種跳轉。可通過dlopen函數打開/加載共享庫,dlsym函數在共享庫中查找指定的函數地址信息。
函數出錯:
1.對於返回值爲int的函數,返回值是負數則代表出錯,所以如果int型函數要返回一個負數值一般通過一個指針帶出去,而返回值只表示是否出錯,-1爲出錯,0爲正常,返回值是指針則NULL表示出錯,正確則是要返回的地址。
2.#include<error.h> errno是一個int型的全局變量,當程序運行出錯會自動將出錯原因序號值賦給errno。可以通過sterror(errno)輸出對應errno的出錯原因,也可以直接用perror(char *s)打印出&s:最近一次出錯原因
main函數的原型:
int main(int argc,char* argv[]) 可以外部設定運行這個程序時的命令行參數值和每一個參數的位置
(2)Unix/Linux系統下的內存管理技術
在對文件的打開,讀,寫這些操作中,標C與uc相比效率更高,因爲標C有標準輸入輸出的緩衝區,對於uc是用文件描述符來進行讀寫,文件描述符是一個整數,它在內核裏對應着一個文件表,存儲了當前文件的各種信息,當close()時是將該整數與文件表關係脫離,如果該文件表不再對應任何一個文件描述符則刪除。
文件描述符是從3開始,0,1,2分別代表標準輸入輸出和標準錯誤。
利用fcntl函數可以對該文件描述符複製,獲取狀態,實現文件鎖。
(3)Unix/Linux系統下的進程管理
程序最開始只有一個進程,也就是父進程運行,當遇到fork()函數時會創建一個新的進程叫子進程,同時fork之後的程序會由父進程和子進程同時開始執行,但我們當然希望他們分工幹不同的事情,因此通過fork之後的返回值來判斷是父進程還是子進程,若爲0,則表示當前是子進程,若不爲0則表示當前是父進程,同時返回的值表示子進程的進程號,PID表示進程編號,用getpid函數獲取。
使用fork創建的子進程,會將父進程的內存區域除了代碼區複製一遍,因此子進程的運行是在fork之前的運行結果基礎之上開始的,但是之後子進程和父進程分別有各自的內存區域,除了代碼區會共享。
父進程可利用wait函數來掛起當前進程,直到一個子進程結束並返回子進程結束運行狀態。
Vfork()函數與fork()的區別是創建的子進程會直接佔用父進程的內存區域而不是複製,導致父進程暫時掛起,一般與exec系列的函數搭配使用讓子進程去執行一個別的程序,成爲一個全新的進程,同時解除父進程的阻塞狀態。如果用fork+exec則要複製一遍內存區域,而對於馬上執行其他程序的子進程來說是多此一舉。
(4)Unix/Linux系統下的信號處理
信號本質上來說就是一種軟件中斷,信號可以通過鍵盤(Ctrl+C等等)、程序出錯(段錯誤信號11 SIGSEGV)、特定發送信號的函數(kill(),sleep()等等)來發出,信號是異步的,對於程序而言並不知道什麼時候信號會到來,但一旦收到一個信號會立即進行處理。處理的方式有三種,默認處理(終止程序)、忽略處理、自定義處理(運行特定的函數),可通過signal函數進行設定。也可通過特定函數將所有信號屏蔽,待恢復之後再查看屏蔽期間收到過的信號。
(5)Unix/Linux系統下的進程間的通信
重點:
通信的主要方式
(1)文件
(2)信號
(3)管道
(4)共享內存
(5)消息隊列(重點)
(6)信號量集
(7)網絡通信(重點)
1.文件
多個進程可通過訪問同一個文件來進行通信
2.信號
不同進程間可通過kill()函數等向其他進程發送信號
3.管道
管道的本質還是文件,分爲有名管道(任意通信)和無名管道(父子進程間通信)
有名管道: mkfifo a.pipe(創建管道)
echo hello > a.pipe(數據不能寫進去)
另外一箇中斷中輸入:
cat a.pipe(數據傳遞到這裏)
因此管道實際上並不能存儲數據,它只是一個傳遞數據的通道
無名管道:int pipe(intpipefd[2])返回兩個文件描述符,其中pipefd[0]表示是讀端,pipefd[1]表示是寫端。
4.共享內存
本質上是內核維護的一塊內存區域,兩個進程可以共同使用這一塊內存區域來進行通信。過程開始與消息隊列類似要生成一個key值,通過key值創建或獲取共享內存地址,用shmat()函數將共享內存地址掛接到本進程地址空間上,使用完畢後要脫接。
5.消息隊列
將消息放到消息隊列中後,由其他進程進行讀取,這個消息隊列會一直持續到內核重啓,如何獲取消息隊列是通過key值,雙方通過約定一個文件路徑和ID號,用ftok()來生成一個相同key值,通過該key值就能用msgget()函數獲取到消息隊列號,類似於文件描述符,雙方可通過該消息隊列進行通信。
6.信號量集
信號量集本質上是一個計數器,多個進程要同時訪問一個共享資源時,可設定一個信號量集表示當前可訪問共享資源的進程數,每當有一個進程要訪問資源,該信號減1,當一個進程訪問完畢該信號加1,當該信號變爲0時表示訪問該資源的進程數已達最大值,要等待。
7.網絡通信
常用的網絡協議:
TCP:傳輸控制協議,面向連接的協議,類似於打電話
UDP:用戶數據報協議,非面向連接的協議,類似於發短信
IP:通過IP可以定位到具體的一臺電腦
端口:通過端口號可定位到這臺電腦上的一個具體運行的進程
Socket:參考上篇博客
(6)Unix/Linux系統下的多線程編程
線程隸屬於進程,進程是重量級單位,每當創建一個新的進程要新開闢一塊內存,每個進程有獨立的內存區域,而線程是輕量級單位,新創建的線程共享內存資源,但每個線程都有獨立的棧區。多個線程相互獨立又相互影響,當訪問同一個文件時一般採用互斥鎖。