1. 程式人生 > >linux伺服器開發二(系統程式設計)--程序相關

linux伺服器開發二(系統程式設計)--程序相關

程序相關的概念

程式與程序

  • 程式,是指編譯好的二進位制檔案,在磁碟上,不佔用系統資源(CPU、記憶體、開啟的檔案、裝置、鎖等等)。

  • 程序,是一個抽象的概念,與作業系統原理聯絡緊密。程序是活躍的程式,佔用系統資源。在記憶體中執行(程式執行起來,產生一個程序)。

  • 程式 --> 劇本(紙),程序 -->戲(舞臺、演員、燈光、道具等等)。同一個劇本可以在多個舞臺同時上演。同樣,同個程式也可以載入為不同的程序(彼此之間互不影響)。如:同時開兩個終端。各自都有一個bash,但彼此ID不同。

併發

  • 併發,在作業系統中,一個時間段中有多個程序都處於已啟動執行到執行完畢之間的狀態。但任一個時刻點上仍只有一個程序在執行。

  • 例如,當下,我們使用計算機時可以邊聽音樂邊聊天上網。若籠統的將他們均看做一個程序的話,為什麼可以同時執行呢?因為併發。

  • 分時複用CPU

分時複用CPU

單道程式設計

  • 所有程序一個一個排隊執行。若A阻塞,B只能等待,即使CPU處於空閒狀態。而在人機互動時阻塞的出現是必然的。所有這種模型在系統資源利用上及其不合理,在計算機發展史上存在不久,大部分已被淘汰了。

多道程式設計

  • 在計算機記憶體中同時存放幾道相互獨立的程式,它們在管理程式控制之下,相互穿插的執行。多道程式設計必須有硬體基礎作為保證。

  • 時鐘中斷即為多道程式設計模型的理論基礎。併發時,任意程序在執行期間都不希望放棄CPU。因此係統需要一種強制讓程序讓出CPU資源的手段。時鐘中斷有硬體基礎作為保障,對程序而言不可抗拒。作業系統中的中斷處理函式,來負責排程程式執行。

  • 在多道程式設計模型中,多個程序輪流使用CPU(分時複用CPU資源)。而當下常見CPU為納米級,1秒可以執行大約10億條指令。由於人眼的反應速度是毫秒級,所以看似同時在執行。

    1s = 1000ms
    1ms = 1000us
    1us = 1000ms
  • 實質上,併發是巨集觀並行,微觀序列! -- 推動了計算機蓬勃發展,將人類引入了多媒體時代。

CPU與MMU

CPU和MMU

  • 記憶體管理單元MMU

記憶體管理單元MMU

程序控制塊PCB

  • 我們知道,每個程序在核心中都有一個程序控制塊(PCB)來維護程序相關的資訊,Linux核心的程序控制塊是task_struct結構體。

  • /usr/src/linux-headers-3.16.0-30/include/linux/sched.h檔案中可以檢視struct task_struct結構體定義。其內部成員有很多,我們重點掌握以下部分即可:
    • 程序ID。系統中每個程序唯一的ID,在C語言中用pid_t型別表示,其實就是一個非負整數。
    • 程序的狀態,有就緒、執行、掛起、停止等狀態。
    • 程序切換時需要儲存和恢復的一些CPU暫存器。
    • 描述虛擬地址空間的資訊。
    • 描述控制終端的資訊。
    • 當前工作目錄(Current Working Directory)
    • umask掩碼。
    • 檔案描述符,包含很多指向file結構體的指標。
    • 和訊號相關的資訊。
    • 使用者id和組id。
    • 會話(Session)和程序組。
    • 程序可以使用的資源上限(Resource Limit)。

程序狀態

  • 程序基本的狀態有5種。分別為初始態,就緒態,執行態,掛起態與終止態。其中初始態為程序準備階段,常與就緒態結合來看。

程序狀態

環境變數

  • 環境變數,是指在作業系統中用來指定作業系統執行環境的一些引數。通常具備以下特徵:
    • 1、字串(本質)。
    • 2、有統一的格式:名=值[:值]。
    • 3、值用來描述程序環境資訊。
    • 儲存形式:與命令列引數類似。char*[]陣列,陣列名environ,內部儲存字串,NULL作為哨兵結尾。
    • 使用形式:與命令列引數類似。
    • 載入位置:與命令列引數類似。位於使用者區,高於stack的起始位置。
    • 引入環境變量表:須宣告環境變數。extern char **environ;
    • 練習:列印當前程序的所有環境變數。

      #include <stdio.h>
      
      extern char **environ;
      
      int main(int argc, char *argv[])
      {
          int i;
          for(i = 0; environ[i]; i++)
          {
              printf("%s\n", environ[i]);
          }
          return 0;
      }

常見環境變數

  • 按照慣例,環境變數字串都是name=value這樣的形式,大多數name由大寫字母加下劃線組成,一般把name的部分叫做環境變數,value的部分則是環境變數的值。環境變數定義了程序的執行環境,一些比較重要的環境變數的含義如下:
    • PATH
      • 可執行檔案的搜尋路徑。ls命令也是一個程式,執行它不需要提供完整的路徑名/bin/ls,然而通常我們執行當前目錄下的程式a.out卻需要提供完整的路徑名./a.out,這是因為PATH環境變數的值裡面包含了ls命令所在的目錄/bin,卻不包含a.out所在的目錄。PATH環境變數的值可以包含多個目錄,用:號隔開。在shell中用echo命令可以檢視這個環境變數的值:

        echo $PATH
    • SHELL
      • 當前shell,它的值通常是/bin/bash。
    • TERM
      • 當前終端型別,在圖形介面終端下它的值通常是xterm,終端型別決定了一些程式的輸出顯示方式,比如圖形介面終端可以顯示漢字,而字元終端一般不行。
    • LANG
      • 語言和locale, 決定了字元編碼以及時間、貨幣等資訊的顯示格式。
    • HOME
      • 當前使用者主目錄的路徑,很多程式需要在主目錄下儲存配置檔案,使得每個使用者在執行該程式時都有自己的一套配置。

getenv函式

  • 獲取環境變數

  • char *getenv(const char *name);
    • 成功:返回環境變數的值;
    • 失敗:NULL(name)不存在。
  • 練習:程式設計實現getenv函式。

setenv函式

  • 設定環境變數的值

  • int setenv(const char *name, const char * value, int overwrite);
    • 成功:0;
    • 失敗:-1
    • 引數overwrite取值:
      • 1:覆蓋原環境變數
      • 0:不覆蓋。(該引數常用於設定新環境變數,如:ABC=day-night)

unsetenv函式

  • 刪除環境變數name的定義

  • int unsetenv(const char *name);
    • 成功:0;
    • 失敗:-1
    • 注意事項:name不存在仍返回0(成功), 當name命名為"ABC="時則會出錯。
  • 示例

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main(int argc, char * argv[])
    {
        char * val;
        const char * name = "ABD";
    
        val = getenv(name);
        printf("1, %s = %s\n", name, val);//ABD = NULL
    
        setenv(name, "efg", 1);
    
        val = getenv(name);
        printf("2, %s = %s\n", name, val);//ABD = efg
    
        int ret = unsetenv(name);
        printf("ret = %d \n", ret);//0
    
        val = getenv(name);
        printf("3, %s = %s \n", name, val);//ABD = NULL
    
        return 0;
    }

程序控制

fork函式

  • 建立一個子程序。
  • pid_t fork(void);
    • 失敗返回-1;
    • 成功返回:1、父程序返回子程序的ID(非負);2、子程序返回0。
  • pid_t型別表示程序ID,但為了表示-1, 它是有符號整形。(0不是有效程序ID,init最小為1)。
  • 注意返回值,不是fork函式能返回兩個值,而是fork後,fork函式變為兩個,父子需各自返回一個。
  • 示例

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int main(int argc, char *argv[])
    {
        printf("father process exec begin...");
    
        pid_t pid = fork();
        if(pid == -1)
        {
            perror("fork error");
            exit(1);
        }
        else if(pid == 0)
        {
            printf("I'm child, pid = %u, ppid = %u \n", getpid(), getppid());
        }
        else
        {
            printf("I'm father, pid = %u, ppid = %u \n", getpid(), getppid());
            sleep(1);
        }
    
        printf("father process exec end...");
        return 0;
    }
  • 迴圈建立n個子程序
    • 一次fork函式呼叫可以建立一個子程序。那麼建立n個子程序應該怎麼實現呢?
    • 簡單想,for(i = 0; i< n; i++){ fork() }即可。但這樣建立的是N個子程序嗎?

迴圈建立n個子程序

  • 錯誤示例

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int main(int argc, char *argv[])
    {
        printf("father process exec begin...");
    
        pid_t pid;
        int i;
        for(i = 0; i < 5; i++)
        {
            pid_t pid = fork();
            if(pid == -1)
            {
                perror("fork error");
                exit(1);
            }
            else if(pid == 0)
            {
                printf("I'm %dth child, pid = %u, ppid = %u \n", i+1, getpid(), getppid());
            }
            else
            {
                printf("I'm father, pid = %u, ppid = %u \n", getpid(), getppid());
                sleep(1);
            }        
        }
    
        printf("father process exec end...");
        return 0;
    }
  • 正確的呼叫方式

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int main(int argc, char *argv[])
    {
        printf("father process exec begin...");
    
        pid_t pid;
        int i;
        for(i = 0; i < 5; i++)
        {
            pid_t pid = fork();
            if(pid == -1)
            {
                perror("fork error");
                exit(1);
            }
            else if(pid == 0)
            {
                //不讓子程序現建立孫子程序
                break;
            }       
        }
    
        if(i<5)
        {
            sleep(i);
            printf("I'm %dth child, pid = %u, ppid = %u \n", i+1, getpid(), getppid());
        }
        else
        {
            sleep(i);
            printf("I'm father");
        }
    
        return 0;
    }
  • getpid函式
    • 獲取程序ID。pid_t getpid(void);
  • getppid函式
    • 獲取父程序ID。pid_t getppid(void);
  • getuid函式
    • 獲取當前程序實際使用者ID。uid_t getuid(void);
    • 獲取當前程序有效使用者ID。uid_t geteuid(void);
  • getgid函式
    • 獲取當前程序使用使用者組ID。gid_t getgid(void);
    • 獲取當前程序有效使用者組ID。gid_t getegid(void);
  • 程序共享
    • 父子程序之間在fork後,有哪些相同,那些想異之處呢?
      • 父子相同處:全域性變數、.data、.text、棧、堆、環境變數、使用者ID、宿主目錄、程序工作目錄、訊號處理方式……
      • 父子不同處:程序ID、fork返回值、父程序ID、程序執行時間、鬧鐘(定時器)、未決定訊號集。
      • 似乎,子程序複製了父程序0-3G使用者空間內容,以及父程序的PCB,但pid不同。真的每fork一個子程序都要將父程序的0-3G地址空間完全拷貝一份,然後在對映至實體記憶體嗎?
      • 當然不是,父子程序間遵循讀時共享寫時複製的原則。這樣設計,無論子程序執行父程序的邏輯還是執行自己的邏輯都能節省記憶體開銷。
      • 練習:編寫程式測試,父子程序是否共享全域性變數。
      • 重點注意!躲避父子程序共享全域性變數的知識誤區!
    • 重點:父子程序共享:1、檔案描述符(開啟檔案的結構體)。2、mmap建立的對映區(程序間通訊詳解)。
    • 特別的,fork之後的父程序先執行還是子程序先執行不確定。取決於核心所使用的排程演算法。
  • gdb除錯
    • 使用gdb除錯的時候,gdb只能跟蹤一個程序。可以在fork函式呼叫之前,通過指令設定gdb除錯工具跟蹤父程序或者是跟蹤子程序。預設跟蹤父程序。
    • set follow-fork-mode child 命令設定gdb在fork之後跟蹤子程序。
    • set follow-fork-mode parent 設定跟蹤父程序。
    • 注意:一定要在fork函式呼叫之前設定才有效。

exec函式族

  • fork建立子程序後執行的是和父程序相同的程式(但有可能執行不同的程式碼分支), 子程序往往要呼叫一種exec函式以執行另一個程式。當程序呼叫一種exec函式時,該程序的使用者空間程式碼和資料完全被新程式替換,從新程式的啟動例程開始執行。呼叫exec並不建立新程序,所以呼叫exec前後該程序的id並未改變。

  • 將當前程序的.text、.data替換為所要載入的程式的.text、.data,然後讓程序從新的.text第一條指令開始執行,但程序ID不變,換核不換殼。

  • 其實有六種以exec開頭的函式,統稱exec函式:
    • int execl(const char path, const char arg, ...);
    • int execlp(const char file, const char arg, ...);
    • int execle(const char path, const char arg, ..., char * const envp[]);
    • int execv(const char path, char const argv[]);
    • int execvp(const char file, char const argv[]);
    • int execve(const path, char const argv[], char *const envp[]);

execlp函式

  • 載入一個程序,藉助PATH環境變數

  • int execlp(const char file, const char arg, ...); 成功:無返回;失敗:-1。

  • 引數1:要載入的程式的名字。該函式需要配合PATH環境變數來使用,當PAHT中所有目錄搜尋後沒有引數1則出錯返回。

  • 該函式通常用來呼叫系統程式。如:ls、date、cp、cat等命令。

  • 示例

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int main(int argc, char * argv[])
    {
        pid_t pid;
        pid = fork();
        if(pid == -1)
        {
            perror("fork error");
            exit(1);
        }
        else if (pid > 0)
        {
            sleep(1);
            printf("parent");
        }
        else
        {
            execlp("ls", "ls", "-l", "-a", NULL);
        }
    
        return 0;
    }

execl函式

  • 載入一個程序,通過路徑+程式名來載入。

  • int execl(const char path, const char arg, ...);成功:無返回;失敗:-1

  • 對比execlp, 如載入“ls”命令帶有-l,-F引數

    execlp("ls", "ls", "-l", "-F", NULL);           使用程式名在PATH中搜索
    execl("/bin/ls", "ls", "-l", "-F", NULL);       使用引數1給出的絕對路徑搜尋

execvp函式

  • 載入一個程序,使用自定義環境變數env。

  • int execvp(const char file, const char argv[]);

  • 變參形式:1、... 2、argv[] (main 函式也是變參函式,形式上等同於int main(int argc, char *argv0, ...))

  • 變參終止條件:1、NULL結尾;2、固參指定。

  • execvp與execlp引數形式不同,原理一致。

    char *argv[] = {"ls", "-l", "-a", NULL};
    execvp("ls", argv);
    execv("/bin/ls", argv);
  • 練習:將當前系統中的程序資訊,列印到檔案中。

    #include <unistd.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(int argc, char *argv[])
    {
        int fd;
        fd = open("ps.out", O_WRONLY | O_CREAT | O_TRUNC, 0644);
        if(fd < 0)
        {
            perror("open ps.out error");
            exit(1);
        }
    
        dup2(fd, STDOUT_FILENO);
    
        execlp("ps", "ps", "aux", NULL);//執行成功,後面的語句不會執行
        perror("execlp error");
        exit(1);
    
        return 0;
    }

exec函式族一般規律

  • exec函式一旦呼叫成功即執行新的程式,不返回。只有失敗才返回,錯誤值-1。所以通常我們直接在exec函式呼叫後直接呼叫perror和exit(),無需if判斷。

    l(list)             命令列引數列表。
    p(path)             搜尋file時使用path變數
    v(vector)           使用命令列引數陣列
    e(environment)      使用環境變數陣列,不使用程序原有的環境變數,設定新載入程式執行的環境變數。
  • 事實上,只有execve是真正的系統呼叫,其它五個函式最終都是呼叫execve,所以execve在man手冊第2節,其它函式在man手冊第3節。這些函式之間的關係如下圖所示。

exec函式族

相關推薦

linux伺服器開發(系統程式設計)--程序相關

程序相關的概念 程式與程序 程式,是指編譯好的二進位制檔案,在磁碟上,不佔用系統資源(CPU、記憶體、開啟的檔案、裝置、鎖等等)。 程序,是一個抽象的概念,與作業系統原理聯絡緊密。程序是活躍的程式,佔用系統資源。在記憶體中執行(程式執行起來,產生一個程序)。 程式 --&g

linux系統程式設計--程序相關概念

程式和程序 程式:二進位制檔案,佔用的磁碟空間,還沒執行 程序:啟動的程式,資料在記憶體中,佔用系統資源(CPU,實體記憶體) 並行和併發 併發:不是一個時間點的概念,而是一個時間段的概念,某個時間段內處理的請求數量 並行:增加伺服器或cpu對請求的處理 一個cpu把一個時間段分成若干時間碎片,每個時

linux系統程式設計-程序相關的基本概念

1. 程式和程序 程式:指編譯好的二進位制檔案,在磁碟上,不佔用系統資源(CPU、記憶體)永久的,靜態的。 程序:是抽象的概念,佔用系統資源,在記憶體中執行(程式執行產生程序)暫時的,動態的。 例子:linux同時開兩個終端,分別打開了同一個程式,但是對應的

Linux系統程式設計---程序替換

程序替換 我們知道,在 Windows 平臺下,我們可以通過雙擊執行可執行程式,讓這個可執行程式成為一個程序;而在 Linux 平臺,我們可以通過 ./ 執行,讓一個可執行程式成為一個程序。 但是,如果我們本來就執行著一個程式(程序),我們如何在這個程序內部啟動一個外部程式,由核

Linux系統程式設計---程序等待

程序等待 1.為什麼需要程序等待 我們知道,子程序退出,父程序如果不管不顧,那麼子程序就可能變為殭屍程序,進而佔用系統資源,造成記憶體洩漏, 另外,程序一旦變成殭屍狀態,那就刀槍不入,“殺人不眨眼”的kill-9也無能為力,因為誰也辦法殺死一個已經死去的程序。

Linux系統程式設計---程序終止

程序終止 程序的退出有三種場景: 程式碼執行完畢,結果正確 程式碼執行完畢,結果不正確 程式碼異常終止 程序退出方法 從main返回 呼叫exit函式 呼叫_exit函式 ctrl + c :x訊號終止

Linux伺服器集群系統()

 LVS叢集的體系結構 章文嵩 ([email protected]) 2002 年 4 月 本文主要介紹了LVS叢集的體系結構。先給出LVS叢集的通用體系結構,並討論了其的設計原則和相應的特點;最後將LVS叢集應用於建立可伸縮的Web、Media、Cache

Linux系統程式設計——程序的介紹

程序 我們平時寫的 C 語言程式碼,通過編譯器編譯,最終它會成為一個可執行程式,當這個可執行程式執行起來後(沒有結束之前),它就成為了一個程序。 程式是存放在儲存介質上的一個可執行檔案,而程序是程式執行的過程。程序的狀態是變化的,其包括程序的建立、排程和消亡。程式

Django專案:堡壘機(Linux伺服器主機管理系統)--01--01堡壘機重寫DJANGO賬戶表 python相關軟體安裝流程圖解————————python安裝——————python-3.7.1-amd64 python相關軟體安裝流程圖解————————pycharm安裝——————pyc

  python相關軟體安裝流程圖解————————python安裝——————python-3.7.1-amd64  https://www.cnblogs.com/ujq3/p/10098166.html   python相關軟體安裝流程圖解————————pyc

Linux系統程式設計——程序和執行緒的區別與聯絡

在許多經典的作業系統教科書中,總是把程序定義為程式的執行例項,它並不執行什麼, 只是維護應用程式所需的各種資源,而執行緒則是真正的執行實體。 為了讓程序完成一定的工作,程序必須至少包含一個執行緒。 程序,直觀點說,儲存在硬碟上的程式執行以後,會在記憶體空間裡形成

Linux系統程式設計——程序間通訊:共享記憶體

概述 共享記憶體是程序間通訊中最簡單的方式之一。共享記憶體允許兩個或更多程序訪問同一塊記憶體,就如同 malloc() 函式向不同程序返回了指向同一個實體記憶體區域的指標。當一個程序改變了這塊地址中的內容的時候,其它程序都會察覺到這個更改。 共享記憶體的特點: 1)共

Linux系統程式設計——程序間通訊:訊息佇列

概述 訊息佇列提供了一種在兩個不相關的程序之間傳遞資料的簡單高效的方法,其特點如下: 1)訊息佇列可以實現訊息的隨機查詢。訊息不一定要以先進先出的次序讀取,程式設計時可以按訊息的型別讀取。 2)訊息佇列允許一個或多個程序向它寫入或者讀取訊息。 3)與無名管道、命名管道一

Linux系統程式設計——程序間通訊:管道(pipe)

管道的概述 管道也叫無名管道,它是是 UNIX 系統 IPC(程序間通訊) 的最古老形式,所有的 UNIX 系統都支援這種通訊機制。 無名管道有如下特點: 1、半雙工,資料在同一時刻只能在一個方向上流動。 2、資料只能從管道的一端寫入,從另一端讀出。

Linux系統程式設計——程序間通訊:訊號中斷處理

什麼是訊號? 訊號是 Linux 程序間通訊的最古老的方式。訊號是軟體中斷,它是在軟體層次上對中斷機制的一種模擬,是一種非同步通訊的方式 。訊號可以導致一個正在執行的程序被另一個正在執行的非同步程序中斷,轉而處理某一個突發事件。 “中斷”在我們生活中經常遇到,譬如,我正在

Linux系統程式設計——程序間通訊概述

程序是一個獨立的資源分配單元,不同程序(這裡所說的程序通常指的是使用者程序)之間的資源是獨立的,沒有關聯,不能在一個程序中直接訪問另一個程序的資源(例如開啟的檔案描述符)。 但是,程序不是孤立的,不同的程序需要進行資訊的互動和狀態的傳遞等,因此需要程序間通訊( IPC:Inter Pr

Linux系統程式設計——程序替換:exec 函式族

在 Windows 平臺下,我們可以通過雙擊執行可執行程式,讓這個可執行程式成為一個程序;而在 Linux 平臺,我們可以通過 ./ 執行,讓一個可執行程式成為一個程序。 但是,如果我們本來就執行著一個程式(程序),我們如何在這個程序內部啟動一個外部程式,由核心將這個外部程式

Linux系統程式設計——程序的控制:結束程序、等待程序結束

結束程序 首先,我們回顧一下 C 語言中 continue, break, return 的作用: continue: 結束本次迴圈 break: 跳出整個迴圈,或跳出 switch() 語句 return: 結束當前函式 而我們可以通過 exit() 或 _exit() 來結束當前程序。

Linux系統程式設計程序間同步

我們知道,執行緒間同步有多種方式,比如:訊號量、互斥量、讀寫鎖,等等。那程序間如何實現同步呢?本文介紹兩種方式:互斥量和檔案鎖。 ##**互斥量mutex** 我們已經知道了互斥量可以用於線上程間同步,但實際上,互斥量也可以用於程序間的同步。為了達到這一目的,可以在pthread_mutex_init初始

Linux下Tomcat埠號以及程序相關命令

1. 檢視tomcat程序 ps -aux | grep tomcat (或者ps -ef | grep tomcat都行)   [plain] view plain copy root     

Linux基礎知識(系統日誌的相關命令)

一、系統日誌的採集規則 日誌可以直接看出系統中的錯誤,日誌在記憶體裡,由服務決定 vim /etc/rsyslog.conf 檢視並修改採集日誌檔案配置,為了讓我們把日誌採集到指定位置。 日誌採集格式:日誌型別.日誌級別 日誌存放檔名稱 1.日誌型別: auth 使用