【Linux教程】Linux系統零基礎程式設計入門,想當大神?這些你都要學
✍檔案和檔案系統
檔案是Linux系統中最重要的抽象,大多數情況下你可以把linux系統中的任何東西都理解為檔案,很多的互動操作其實都是通過檔案的讀寫來實現的。
♨ 檔案描述符
在Linux核心中,檔案是用一個整數來表示的,稱為檔案描述符,通俗的來說,你可以理解它是檔案的id(唯一識別符號)
♨普通檔案
▶普通檔案就是位元組流組織的資料。
▶檔案並不是通過和檔名關聯來實現的,而是通過關聯索引節點來實現的,檔案節點擁有檔案系統為普通檔案分配的唯一整數值(ino),並且存放著一些檔案的相關元資料。
♨目錄與連結
▶ 正常情況下檔案是通過檔名來開啟的。
▶目錄是可讀名稱到索引編號之間的對映,名稱和索引節點之間的配對稱為連結。
▶可以把目錄看做普通檔案,只是它包含著檔名稱到索引節點的對映(連結)
✍程序
程序是僅次於檔案的抽象概念,簡單的理解,程序就是正在執行的目的碼,活動的,正在執行的程式。不過在複雜情況下,程序還會包含著各種各樣的資料,資源,狀態甚至虛擬計算機。
你可以這麼理解程序:它是競爭計算機資源的基本單位。
♨程序、程式與執行緒
▶程式
程式,簡單的來說就是存在磁碟上的二進位制檔案,是可以核心所執行的程式碼
▶程序
當一個使用者啟動一個程式,將會在記憶體中開啟一塊空間,這就創造了一個程序,一個程序包含一個獨一無二的PID,和執行者的許可權屬性引數,以及程式所需程式碼與相關的資料。
程序是系統分配資源的基本單位。
一個程序可以衍生出其他的子程序,子程序的相關許可權將會沿用父程序的相關許可權。
▶執行緒
每個程序包含一個或多個執行緒,執行緒是程序內的活動單元,是負責執行程式碼和管理程序執行狀態的抽象。
執行緒是獨立執行和排程的基本單位。
♨程序的層次結構(父程序與子程序)
在程序執行的過程中可能會衍生出其他的程序,稱之為子程序,子程序擁有一個指明其父程序PID的PPID。子程序可以繼承父程序的環境變數和許可權引數。
於是,linux系統中就誕生了程序的層次結構——程序樹。
程序樹的根是第一個程序(init程序)。
▶過程呼叫的流程: fork & exec
一個程序生成子程序的過程是,系統首先複製(fork)一份父程序,生成一個暫存程序,這個暫存程序和父程序的區別是pid不一樣,而且擁有一個ppid,這時候系統再去執行(exec)這個暫存程序,讓他載入實際要執行的程式,最終成為一個子程序的存在。
▶程序的結束
當一個程序終止時,並不會立即從系統中刪除,核心將在記憶體中儲存該程序的部分內容,允許父程序查詢其狀態(這個被稱為等待終止程序)。
當父程序確定子程序已經終止,該子程序將會被徹底刪除。
但是如果一個子程序已經終止,但父程序卻不知道它的狀態,這個程序將會成為殭屍程序
♨服務與程序
簡單的說服務(daemon)就是常駐記憶體的程序,通常服務會在開機時通過init.d中的一段指令碼被啟動。
♨程序通訊
程序通訊的幾種基本方式:管道,訊號量,訊息佇列,共享記憶體,快速使用者控制元件互斥。
✍程式,程序和執行緒
現在我們再次詳細的討論這三個概念
▶程式(program)
程式是指編譯過的、可執行的二進位制程式碼,儲存在儲存介質上,不執行。
▶程序(process)
程序是指正在執行的程式。
程序包括了很多資源,擁有自己獨立的記憶體空間。
▶執行緒
執行緒是程序內的活動單元。
包括自己的虛擬儲存器,如棧、程序狀態如暫存器,以及指令指標。
在單執行緒的程序中,執行緒即程序。而在多執行緒的程序中,多個執行緒將會共享同一個記憶體地址空間
✍ 執行一個程序
建立一個程序,在unix系統中被分為了兩個流程。
● 把程式載入記憶體並執行程式映像的操作:exec
● 建立一個新程序:fork
♨exec
▶ 最簡單的exec系統呼叫函式:execl()
● 函式原型:
int execl(const char * path,const chr * arg,...)
execl()呼叫將會把path所指的路徑的映像載入記憶體,替換當前程序的映像。
引數arg是以第一個引數,引數內容是可變的,但最後必須以NULL結尾。
●舉例:
int ret; ret = execl("/bin/vi","vi",NULL); if (ret == -1) { perror("execl"); }
上面的程式碼將會通過/bin/vi替換當前執行的程式
注意這裡的第一個引數vi,是unix系統的預設慣例,當建立、執行程序時,shell會把路徑中的最後部分放入新程序的第一個引數,這樣可以使得程序解析出二進位制映像檔案的名字。
int ret; ret = execl("/bin/vi","vi","/home/mark/a.txt",NULL); if (ret == -1) { perror("execl"); }
上面的程式碼是一個非常有代表性的操作,這相當於你在終端執行以下命令:
vi /home/mark/a.txt
●返回值:
正常情況下其實execl()不會返回,呼叫成功後會跳轉到新的程式入口點。
成功的execl()呼叫,將改變地址空間和程序映像,還改變了很多程序的其他屬性。
不過程序的PID,PPID,優先順序等引數將會被保留下來,甚至會保留下所開啟的檔案描述符(這就意味著它可以訪問所有這些原本程序開啟的檔案)。
失敗後將會返回-1,並更新errno。
●其他exec系函式
略,使用時查詢
♨fork
通過fork()系統呼叫,可以建立一個和當前程序映像一模一樣的子程序。
●函式原型
pid_t fork(void)
呼叫成功後,會建立一個新的程序(子程序),這兩個程序都會繼續執行。
●返回值
如果呼叫成功,
父程序中,fork()會返回子程序的pid,在子程序中返回0;
如果失敗,返回-1,並更新errno,不會建立子程序。
●舉例
我們看下面這段程式碼
#include <unistd.h> #include <stdio.h> int main () { pid_t fpid; //fpid表示fork函式返回的值 int count=0; printf("this is a process\n"); fpid=fork(); if (fpid < 0) printf("error in fork!"); else if (fpid == 0) { printf("i am the child process, my process id is %d\n",getpid()); printf("我是爹的兒子\n"); count++; } else { printf("i am the parent process, my process id is %d\n",getpid()); printf("我是孩子他爹\n"); count++; } printf("統計結果是: %d\n",count); return 0; }
這段程式碼的執行結果比較神奇,是這樣的:
this is a process i am the parent process, my process id is 21448 我是孩子他爹 統計結果是: 1 i am the child process, my process id is 21449 我是爹的兒子 統計結果是: 1
在執行了fork()之後,這個程式就擁有了兩個程序,父程序和子程序分別往下繼續執行程式碼,進入了不同的if分支。
如何理解pid在父子程序中不同?
其實就相當於連結串列,程序形成了連結串列,父程序的pid指向了子程序的pid,因為子程序沒有子程序,所以pid為0。
●寫時複製
傳統的fork機制是,呼叫fork時,核心會複製所有的內部資料結構,複製程序的頁表項,然後把父程序的地址空間按頁複製給子程序(非常耗時)。
現代的fork機制採用了一種惰性演算法的優化策略。
為了避免複製時系統開銷,就儘可能的減少“複製”操作,當多個程序需要讀取他們自己那部分資源的副本時,並不複製多個副本出來,而是為每個程序設定一個檔案指標,讓它們讀取同一個實際檔案。
顯然這樣的方式會在寫入時產生衝突(類似併發),於是當某個程序想要修改自己的那個副本時,再去複製該資源,(只有寫入時才複製,所以叫寫時複製)這樣就減少了複製的頻率。
♨聯合例項
在程式中建立一個子程序,開啟另一個應用。
pid_t pid; pid = fork(); if (pid == -1) perror("fork"); //子程序 if (!pid) { const char * args[] = {"windlass",NULL}; int ret; // 引數以陣列方式傳入 ret = execv("/bin/windlass",args); if (ret == -1) { perror("execv"); exit(EXIT_FAILURE); } }
上面的程式建立了一個子程序,並且使子程序運行了/bin/windlas程式。
✍終止程序
♨exit()
●函式原型
void exit (int status)
該函式用於終止當前的程序,引數status只用於標識程序的退出狀態,這個值將會被傳送給當前程序的父程序用於判斷。
還有一些其他的終止呼叫函式,在此不贅述。
如果你也很想學程式設計,從零基礎開始,一起來學程式設計,可以來我的零基礎程式設計學習基地【點選進入】!
還有(原始碼,零基礎教程,專案實戰教學視訊)!帶你入個門還是簡簡單單啦~
涉及:遊戲開發、課程設計、常用軟體開發、程式設計基礎知識、黑客等等...