天天看點

Linux學習之旅(16)----程序

程式、程序和線程:

程式:一組指令的有序集合,程式本身沒有任何運作的含義,它隻是一個靜态的實體。

程序:是具有一定獨立功能的程式關于某個資料集合上的一次運作活動,是系統進行資源配置設定和排程的一個獨立機關。

線程:線程是程序的一個是CPU排程和分派的基本機關,它是比程序更小的能獨立運作的基本機關,線程自己基本上不擁有系統資源,隻擁有一點在運作中必不可少的資源(如程式計數器,一組寄存器和棧),一個線程可以建立和撤銷另一個線程。

程序和程式的差別:

(1)程序是動态的,而程式是靜态的。

(2)程序有一定的生命周期,而程式是指令的集合,本身無“運動“的含義。

(3)一個程序隻能對應一個程式,一個程式可以對應一個或多個程序。

程序和線程的差別:

(1)排程的基本機關

在傳統的OS(作業系統)程序是作為獨立排程和分派的基本機關,是以在傳統OS程序是能夠運作的基本機關。在每次排程是,程序都需要上下文切換,開銷大。為了減少系統的開銷,是以新型的OS就引入了線程的概念,将線程作為資源排程和配置設定的基本機關,因為線程擁有比程序更小的資源,在切換時,切換的代價遠小于程序。

(2)并發性

在引入線程的作業系統中,不僅僅程序之間可以并發執行,而且在一個程序中的多個線程也可以并發執行,還允許一個程序的所有線程都并發執行和不同程序的線程也可以并發,這使得傳統的OS既有更好的并發行,進而能更加有效的提高系統資源的使用率和和系統的吞吐率。

(3)擁有資源

程序可以擁有資源,并作為作業系統中擁有資源的一個基本機關。而線程本身并不擁有系統資源,而是僅有一點必不可少的、能夠保證獨立運作的資源O(如:TCB(線程控制塊)、程式計數器等)。同時還允許多個線程共享該程序的所擁有的資源。

(4)獨立性

同一程序的不同線程之間的獨立性要比不同程序之間的獨立性低。這是因為,每個線程都有一個獨立的位址空間和其他資源,除了共享全局變量之外,不允許其他程序通路,但線程會共享程序的所擁有的資源,是以獨立性會變差。

(5)系統開銷

線程的出現就是為了解決程序引起的系統開銷大的原因。是以在系統開銷的方面,線程的系統開銷遠小于程序。

(6)支援多處理機系統

在處理機系統中,如果采用傳統的單線程程序,不管有多少處理機,一個程序就隻能運作在一個處理機上,而對于多線程而言,一個程序可以配置設定在多個線程上同時配置設定到多個處理機上運作,加快了程序的完成。

程序的特點:

(1)動态性:程序的實質是程序實體的執行過程,是以,動态性就是程序的最常見的特性。

(2)并發行:是指多個程序實體同存于記憶體中,且能夠在一段時間内同時運作。

(3)獨立性:程序實體是一個能夠獨立運作、獨立擷取資源和獨立接受的基本機關,但凡沒有履歷PCB的程式多沒有作為一個獨立的機關參與運作。

(4)異步性:程序是按異步方式獨立運作的,即按各自獨立的,不可以預知的速度向前推進。

程序的基本狀态及轉換

Linux學習之旅(16)----程式

程序原語

就是由若幹條指令組成,用于完成一定功能的一個過程。一般程序原語為一個“原子操作”,即不可能在分割的操作,意思就是要麼不做,要麼完成,在執行期間是不可打斷的。

pid_t  fork();
           

用于建立子程序,子程序會将父程序的0-3g使用者空間下的所有東西全部”複制“(如:程式、資料等),進而由作業系統在3-4g的核心空間中為子程序建立一個PCB(程序的唯一辨別)。

fork()函數為調用一次傳回兩次。在父程序中傳回子程序的PID,在子程序中傳回0。子程序在建立出來之後,會接着父程序沒有執行完的部分繼續執行(父進執行過的,子程序不會再去執行。)。

再這裡解釋以下建立是的子程序的”複制“。這裡的複制并不是說将父程序的0-3g的東西真的全部拷貝,那麼太過于麻煩,同時也沒有意義,因為子程序和父程序再0-3g是完全一樣的,所有作業系統建立子程序時為其0-3g和父程序的0-3g作了一個映射。也就是說在建立程序時,隻為子程序建立了一個PCB(程序控制塊)。但當子程序修改某個變量時系統就會單獨為子程序複制這個變量,這就是所謂的“讀時共享,寫時複制。”

這裡說明一下,一個程式在運作時,系統會為其配置設定一個程序,而這個程序的父程序就是運作它的shell bash。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
    pid_t pid;
    //fork()調用一次傳回兩次。在父程序中傳回子程序的id号,在子程序傳回0.
    pid=fork();//2個程序,子程序會接着父程序向下執行。
    //pid2=fork();//4個程序,兩個程序都會執行
    if(pid>0)
    {
        //getpid()擷取目前程序id,
        //getppid()擷取目前程序父程序的id
        while(1)
        {
            printf("I am father,my pid:%d\n",getpid());
            printf("I am father,my father Pid:%d\n",getppid());
            sleep(1);
        }
    }
    else if(pid==0)
    {
        while(1)
        {
            printf("I am chrid,my pid:%d\n",getpid());
            printf("I am chrid,my father Pid:%d\n",getppid());
            sleep(3);
        }
    }
    else 
    {
        perror("fork");
        exit(1);
    }
    return 0;
}
           
Linux學習之旅(16)----程式

 當執行fork()完成後,子程序就已經建立成功。

pid_t  getpid();       //擷取程序ID
pid_t  getppid();      //擷取父程序ID
uid_t  getuid();       //擷取實際使用者ID
uid_t  geteuid();       //擷取有效使用者ID
gid_t  getgid();        //擷取實際使用者組ID
gid_t  getegid();       //擷取有效組ID
           

exec族

使用fork()建立出來的程序和如果不作處理和父程序時完全相同,這有什麼用那?有什麼辦法可以讓子程序去幹不父程序不一樣的事情那?這時就用到了exec()函數族。當一個程式調用了一種exec()函數時,該程序的使用者空間和資料完全被新程式替代,使用exec()函數并不建立新程序,是以調用exec()函數前後程序的ID号不會發生變化。

在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 *path,char *const argv[]);
int execve(const char *path,char *const argv[],char *const ebvp[]);
           

exec()函數如何調用成功,就會去執行新的程式,如果失敗會傳回-1.

l:表示list,即在使用時需要将每一個參數都列出來,使用NULL表示結束,例如:execl()、execlp().

p:表示path,如果尋找不到該檔案,會在系統的PATH中尋找。

v:表示vector,需要将參數使用數組的形式構造出來(會使函數看着會比較簡潔)。

e:表示environment,可以将一份新的環境變量傳給它,會在新的環境變量中尋找目前程式。

實際上,隻有execve()函數時真正的系統調用,其他5個都是最終調用execve。6個函數的調用過程。

Linux學習之旅(16)----程式
#include <stdio.h>
#include <unistd.h>
#include <error.h>
int main()
{
    int pid=fork();
    if(pid>0)
    {
        //父程序
        printf("我是程序%d\n",getpid());
        //wait()會等待子程序結束
        int id=wait(NULL);
        printf("程序為:%d的子程序以結束\n",id);
    }
    else if(pid==0)
    {
        //子程序
        printf("我是程序:%d,我的父程序是%d\n",getpid(),getppid());
        //這裡需要些絕對路徑,NULL表示參數結束。
        int exec=execl("/home/kk/code/c++/process/hello",NULL);
        if(exec==-1)
        {
            perror("execl");
        }
    }
    else
    {
        perror("fork"); 
    }
    return 0;
}
           
Linux學習之旅(16)----程式

fork()函數産生子程序的過程:

       系統先給新的程序配置設定資源,例如存儲資料和代碼的空間。然後把原來的程序的所有值都複制到新的新程序中,隻有少數值與原來的程序的值不同。相當于克隆了一個自己。

僵屍程序:子程序先于父程序被釋放,但父程序沒有回收子程序的資源(PCB),這是子程序就會稱為“僵屍程序”。

孤兒程序:父程序先于子程序被釋放,一般子程序是由父程序負責回收的,但父程序這是已經結束。就會導緻子程序成為“孤兒程序”。孤兒程序會自動被1号程序(init)“領養”,這是1号程序會成為孤兒程序的父程序,負責回收“孤兒程序”。

僵屍程序:

#include <stdio.h>
#include <errno.h>
#include <unistd.h>
int main()
{
    int pid=fork();
    if(pid>0)
    {
        //父程序會死循環
        while(1)
        {
            printf("我是父程序,我的ID:%d\n",getpid());
            sleep(3);
        }
    }
    else if(pid==0)
    {
        //子程序在列印完會結束。
        printf("我是子程序,我的ID:%d\n",getpid());
    }
    else
    {
        perror("fork");
    }
    return 0;
}
           
Linux學習之旅(16)----程式

這時的子程序就成為“僵屍程序”。“僵屍程序”是非常危險的,會造成記憶體洩漏。

wait()和waitpid()

那如何防止僵屍程序的産生那?linux系統提供了wait()和waitpid()函數,這兩者都是讓父程序回收子程序,不同的是:wait()是阻塞的,即如果子程序沒有全部結束,父程序就會一直等待,不會向下繼續執行。而waitpid()通過參數的設定,可以将函數轉換位非阻塞的。

孤兒程序

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
int main()
{
    int pid=fork();
    if(pid>0)
    {
        printf("我是父程序,我的程序ID為:%d\n",getpid());
        sleep(1);
    }
    else if(pid==0)
    {
        while(1)
        {
            printf("我是子程序,我的程序ID為:%d,我的父程序的ID為:%d\n",getpid(),getppid());
            sleep(3);
        }
    }
    return 0;
}
           
Linux學習之旅(16)----程式

這是的子程序就是一個孤兒程序。當父程序結束時,會導緻shell自動從背景移動到前台,這是雖然程式還在運作,但sehll也是也可運作的,應為shell和1号程序不是同一個程序,不會互相影響。