天天看點

LinuxC系統程式設計——程序控制(1)

1.linux程序

程序:是作業系統資源管理的最小機關

程序是動态的,是程式的一次執行過程,而程式是靜态的,程序是運作中的程式,程式隻是儲存在硬碟上的一些可執行的代碼而已,隻有當程式轉換為程序後,作業系統才為其配置設定記憶體單元。

2.程序辨別

在作業系統中每個程序都是通過唯一的程序ID辨別的。程序ID是一個非負數,每個程序除了程序ID外還有一些其他辨別,它們可以通過相應的函數獲得。

3.使用者ID群組ID:

實際使用者ID(uid):辨別運作該程序的使用者

有效使用者ID(euid):辨別以什麼身份來運作程序,eg:某普通使用者A以root身份運作A程式,則這個程式的uid就是A使用者的ID,而有效使用者ID是root使用者的ID。

實際組ID(gid):它是實際使用者所屬組的組ID。

有效組ID(egid):有效使用者所屬組的組ID。

4.linux程序的結構

linux中程序由代碼段、資料段、堆棧段3部分組成。代碼段存放程式的可執行代碼。資料段存放程式的全局變量、常量、靜态變量。堆棧段中的堆用于存放動态配置設定(malloc)的記憶體變量,棧用于函數調用,存放着函數的參數,函數内部定義的局部變量。

5.程序狀态

  • 運作狀态(R):程式正在運作
  • 可中斷等待狀态(S):程序正在等待某個事件完成(如資料到達)。等待過程可被信号或定時器喚醒。
  • 不可中斷等待狀态(D):程序等待某個事件完成,在等待過程中不可以被信号或定時器喚醒,必須等待直到等待的事件發生。
  • 僵死狀态(Z):程序已終止,但程序描述符依然存在,直到父程序調用wait()函數後釋放。
  • 停止狀态(T):程序因為收到SIGSTOP、SIGSTP、SIGTOU、SIGTIN信号後停止運作或者該程序正在被跟蹤(調試狀态)。

6.程序控制

linux程序控制包括建立程序、執行新程式、退出程序以及改變程序的優先級,通常通過一系列的系統調用來控制程序。主要系統調用如下:

- fork:用于建立一個新程序

- exit:用于終止程序

- exec:用于執行一個應用程式

- wait:将父程序挂起,等待子程序終止

- getpid:擷取目前程序的程序ID

- nice:改變程序的優先級

7.程序的記憶體映像

linux下程式轉化成程序

linux下C程式的生成分為4個階段:預編譯、編譯、彙編、連結。程式轉化為程序通常要經過以下步驟:

  1. 核心将程式讀入記憶體,為程式配置設定記憶體空間
  2. 核心為該程序配置設定程序标示符(PID)和其他所需資源
  3. 核心為程序儲存PID及相應的狀态資訊,把程序放到運作隊列中等待執行。程式轉化為程序之後就可以被作業系統的排程程式執行了。
程序的記憶體映像:程序的記憶體映像是指核心在記憶體中如何存放可執行程式檔案

linux程式影像從記憶體的低位址到高位址依次如下:

  1. 代碼段:即二進制機器代碼,代碼段隻讀,可被多個程序共享。
  2. 資料段:存儲已被初始化的變量,包括全局變量和已被初始化的靜态變量。
  3. 未初始化資料段:存儲未被初始化的靜态變量。
  4. 堆:用于存放資料運作中動态配置設定的變量
  5. 棧:用于函數待用,儲存函數的傳回位址、函數的參數、函數内部定義的局部變量

8.程序的建立

建立程序有兩種方式,一是由作業系統建立,二是由父程序建立。作業系統建立的程序,它們之間是平等的,一般不存在資源繼承關系。而對于父程序建立的程序(子程序),它和父程序存在隸屬關系。

fork函數
#include <unistd.h>
   pid_t fork(void);
           

fork函數有兩個傳回值,fork函數調用成功之後目前程序就已經分為兩個程序,即每個程序有一個傳回值,父程序的傳回值是子程序的ID,子程序的傳回值是0,若fork函數執行失敗,則隻有一個傳回值-1,通過不同的傳回值可以區分父子程序。

在fork之後父子程序誰先執行是不确定的,取決于核心使用的排程算法

fork在建立程序失敗的原因通常是父程序擁有的子程序的個數超過了規定的限制(errno值為EAGAIN),或是可供使用的記憶體不足(errno值為ENOMEN)。

子程序會繼承父程序的很多屬性主要有使用者ID、組ID、目前工作目錄、根目錄、打開的檔案、建立檔案時使用的屏蔽字

、信号屏蔽字、上下文環境、共享的存儲段、資源限制等。

同時子程序與父程序還有一些不相同的屬性:

  • 子程序有它自己唯一的程序ID
  • fork的傳回值不同,父程序傳回子程序的ID,子程序傳回0
  • 不同的父程序ID,子程序的父程序ID為建立它的父程序
  • 子程序共享父程序打開的檔案描述符,但父程序對檔案描述符的改變不會影響子程序中的檔案描述符
  • 子程序不繼承父程序設定的檔案鎖
  • 子程序不繼承父程序設定的警告
  • 子程序的未決信号集被清空
    信号的”未決“是一種狀态,指的是從信号的産生到信号被處理前的這一段時間

9.孤兒程序

如果一個子程序的父程序先于子程序結束,子程序就成為一個孤兒程序,它由init(是核心啟動的第一個使用者級程序)程序收養,成為init程序的子程序

孤兒程序的測試代碼如下:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>

int main(void)
{
    pid_t pid;

    pid = fork();
    switch(pid)
    {
        case :
        {
            while()
            {
                printf("child process is run, pid is %d, parent id is %d.\n", getpid(), getppid());
                sleep();
            }
            break;
        }
        case -:
        {
            perror("process creat failed:");
            exit(-);
            break;
        }
        default:
        {
            printf("parent process is run!");
            exit();
        }
    }
    return ;
}
           
LinuxC系統程式設計——程式控制(1)

由執行結果可以看出剛開始子程序的父程序pid為6994,當父程序執行結束退出後,子程序成為孤兒程序。

10.vfork函數

vfork也可以用來建立一個新程序,同樣是調用一次,傳回兩次,與fork函數不同的是建立的子程序和父程序共享父程序的位址空間,子程序對該位址空間的修改對父程序同樣有效。使用vfork建立的程序保證子程序先運作,當它調用exec或exit之後,父程序才有可能被排程運作。如果在調用exec或exit之前子程序要依賴父程序的某個行為,就會導緻死鎖。 當fork一個程序之後立即調用了exec執行另外一個應用程式,那麼fork過程子程序對父程序的位址空間的複制将是一個多餘的過程。vfork不會拷貝父程序的位址空間。

11.建立守護程序

守護程序(daemon):指在背景運作的、沒有控制終端與之相連的程序。

編寫建立守護程序有如下要點:

  • 讓程序在背景執行。調用fork産生一個子程序,然後使得父程序退出。
  • 調用setsid建立一個新對話,控制終端,登陸會話和程序組通常是從父程序繼承下來的,
    當程序是會話組長時,調用setsid會失敗(第一點已保證不是會話組長),setsid調用成功後,程序成為新的會話組長和程序組長,并與原來的登陸會話和程序組脫離,由于會話過後才能夠對控制終端的獨占性,程序同時與控制終端脫離
  • 禁止程序重新打開控制終端。此時程序已經成為一個無終端的會話組長,但是它可以重新申請打開一個終端。為了避免可以通過使程序不再是會話組長來實作:再一次通過fork建立新的子程序,使調用fork的程序退出。
  • 關閉不再需要的檔案描述符。建立的子程序從父程序繼承打開的檔案描述符,如不關閉,回浪費系統資源,造成程序所在檔案系統無法卸下以及引起無法預料的錯誤。先得到最高的檔案描述符值,然後用一個循環程式,關閉0到最高檔案描述符值的所有檔案描述符。
  • 将目前目錄更改為根目錄,當守護程序目前工作目錄在一個裝配檔案系統中時,系統不能被拆卸。
  • 将文字建立時使用的屏蔽字設定為0,程序會繼承父程序使用的檔案建立屏蔽字,這樣在子程序中檔案建立屏蔽字可能回拒絕某些許可權,為了防止,一般使用umask(0)将屏蔽字清零。
  • 處理SIGCHLD信号。非必須,如果父程序不等待子程序結束,子程序将成為僵屍程序,占用系統資源,但當父程序等待子程序結束會增加父程序的負擔,影響伺服器程序的并發性。linux下可以将SIGCHLD信号的操作設為SIG_IGN,這樣就不會産生僵屍程序。

繼續閱讀