1. 程式人生 > 實用技巧 >【Linux教程】Linux系統零基礎程式設計入門,想當大神?這些你都要學

【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只用於標識程序的退出狀態,這個值將會被傳送給當前程序的父程序用於判斷。

還有一些其他的終止呼叫函式,在此不贅述。


如果你也很想學程式設計,從零基礎開始,一起來學程式設計,可以來我的零基礎程式設計學習基地【點選進入】!

還有(原始碼,零基礎教程,專案實戰教學視訊)!帶你入個門還是簡簡單單啦~

涉及:遊戲開發、課程設計、常用軟體開發、程式設計基礎知識、黑客等等...