Linux之進程第一談

    這是一篇遲來的博客因爲最近整理的東西有點多,沒來得及發,之前寫過在Linux下關於進程管理相關的命令,這裏開始,重新聊一聊進程。


重提進程概念


    首先,怎麼理解進程?最簡單的話來說,我們寫一個簡單的C程序,編譯鏈接生成一個可執行文件,這個可執行文件叫做可執行程序,然後我們開始運行它,我們都知道,計算機中,真正具有處理能力的只有CPU,與CPU之間進行數據交換的只有內存,因此,我們要運行該程序,就要將可執行文件加載到內存中,交給CPU執行,我們把加載到內存中正在被執行的程序就叫做一個進程。

    我們可以看到,進程實際上是程序的動態運行實例,它能夠分配到CPU和內存的資源,擔當了系統資源的承擔者。

    這是我們對進程最基本的一個認識,然後我們開始考慮其他的問題,CPU開始執行該程序,但內存中不可能每次只載入一個程序去運行(要真這樣的話,它會慢到讓你想拆了它),在這幾個G的內存當中,載入很多程序,那麼內存怎麼來管理他們,CPU又該如何知道我該調用哪個進程呢?

    這個就像學校對學生信息的管理一樣,學校並不直接和每個學生打交道,但卻能知道每個學生的信息,原因就在於學校將所有人的信息都建了一張表,也就是數據庫,只要將數據庫合理管理,就可以知道每個學生的信息了。對於內存而言,要管理大量的進程信息,它對每個進程都建立了一個結構體,將進程所有需要關注的信息都保存在了這個結構體中,然後又使用了一些較爲優的數據結構,將所有的結構體信息串了起來,以後內存的管理工作就是這些數據結構,這大大減少了內存的壓力,也方便了下次CPU來運行這些進程時的優先級的問題。有個很重要的結構叫做PCB。這個接下來會提到。

    還要多說一點的是,即使某個進程已經加載到了內存中,它也不一定在運行,在內存中受內存調度、程序本身等原因,會處於各種狀態。

    到這裏,我們就可以對進程的概念做一個總結:

        進程是操作系統中程序的一個運行實例,它承擔了分配系統資源的實體。進程是一種動態描述,因爲要加載到內存中去運行,但並不是所有的進程都在運行。當一個程序加載到內存中之後,除了程序數據本身,操作系統還提供了一整套的數據結構來維護這個進程,一個最典型的數據結構就是PCB。對進程的調度管理只需要我們將數據結構有效的組織起來,採用適當的算法。


   PCB(Process Control)即進程控制塊, 在Linux內核中,PCB是一個叫task_struct的獨立結構體,裏面包含着關於進程的信息,具體內容這裏暫時不討論,只關注我們常用到的一些:

task_struct 最常見的成員

標識符:即 PID 

狀態

優先級:區別權限

程序計數器PC:以PC爲代表,在CPU內部各種寄存器,用來表示當前進程執行的狀態,一旦程序運行時被切出,就需要保存當前寄存器到PCB(個別需要保存到內核)當中。

內存指針:包括程序代碼進程相關數據的指針,還有其他進程共享的內存塊的指針

硬件上下文數據

I/O狀態信息:

記賬信息:

    關於task_struct,可以在/include/linux/sched.h文件中找到,所有運行在系統裏的進程都以task_struct的形式存在內存中。因此,CPU就不在關注每個進程,只需要將所有的PCB組織起來即可。在Linux中,操作系統是通過雙鏈表的形式組織和管理這些結構體的,每個結點還可能被置於其他的結構當中。對調度器而言,當我們知道了各個進程優先級之後,需要對各個進程的PCB進行組織,實現快速找到目標結點,例如:哈希、最大堆和最小堆。這些算法都依賴與數據結構的支撐。

    進程信息可以通過/proc系統文件夾查看,要獲取PID爲 *** 的進程信息,需要查看/proc/*** 這個文件夾,可以通過top或ps等命令查詢。前面提到過,這裏就不再多說。


進程標識符


    爲了區分每個進程,我們可以通過進程標識符來做到,就像每個人的ID CARD一樣,確定身份的唯一標識。在Linux下,pid是一個無符號長×××的數據。

    進程id(PID)

    父進程id(PPID)

    在Linux下獲得pid和ppid的方法如下:

#include <sys/types.h>
#include <unistd.h>
    printf("%d",getpid());
    printf("%d",getppid());


進程位置


     當程序執行時,操作系統將可執行程序複製到內存中,程序轉化爲進程有以下幾個步驟:

    1、內核將程序讀入內存,前提:程序分配內存空間

    2、內核爲進程保存PID及相應的狀態信息,把進程放到運行隊列中等待執行。程序轉化爲進程後就可被操作系統的調度程序執行。

     程序轉化爲進程,除了PCB外還有其他的數據結構,例如進程地址空間結構體(mem_struct),這個後面再解釋。

     進程的內存映像:即進程地址空間,是指內核在內存中如何存放可執行程序文件。在將程序轉化爲進程的過程中,操作系統將可執行程序從硬盤複製到內存中,佈局如下:

wKiom1iqR-STmDWuAAD5PFaLens489.png   

 這裏有幾點需要我們注意:

       1、程序的地址空間是虛擬內存,不是物理內存

    2、從低地址到高地址依次是:正文(代碼段),只讀靜態數據區(已從初始化的全部變量,未初始化的全局變量),堆區,共享區,棧區。其中,堆棧相對而生

    3、每一個進程都有一個地址空間,是在內存中真實存在的一塊空間。

    4、堆棧段向上還有區域:命令行參數和環境變量

    瞭解了這些內容之後,這裏我們在Linux環境下,對地址空間內容做一簡單驗證。

測試代碼:

// mymem.c
#include <stdio.h>
#include <stdlib.h>

int g_val = 10;
int g_val1;
int main(int argc, char* argv[],char* env[])
{
    int* mem1 = (int*)malloc(4);
    int* mem2 = (int*)malloc(4);
    int* mem3 = (int*)malloc(4);
    int* mem4 = (int*)malloc(4);
    const char* msg = "hello world";
    int a;
    int b;
    int c;
    printf("code : %p\n", main);
    printf("read only : %p\n",msg);
    printf("g init value: %p\n",&g_val);
    printf("uninit value: %p\n",&g_val1);
    printf("heap1: %p\n",mem1);
    printf("heap2: %p\n",mem2);
    printf("heap3: %p\n",mem3);
    printf("heap4: %p\n",mem4);
    printf("stack: %p\n", &msg);
    printf("stack: %p\n", &a);
    printf("stack: %p\n", &b);
    printf("stack: %p\n", &c);
    printf("argc與argv####################################\n");
    printf("argc addr : %p\n",&argc);
    int i = 0;
    for(; i< argc;i++)
    {
        printf("%d -> %s --> %p\n", i, argv[i] );
    }
    printf("環境變量####################################\n");
    for(i=0;env[i] != NULL;i++)
    {
        printf("%d -> %s --> %p\n", i, env[i]);
    }
    return 0;
}


    觀察結果我們可以發現,除了從正文到堆區之間的地址順序符合我們的預期,還有一些其他值得我關注的內容。

    我們沒有設置任何的環境變量,依舊打印出了一堆環境變量,這裏簡單討論main函數中環境變量的來源,是由於shell在執行某個進程時,並不是自己去執行,而是派生一個子進程去執行,在這裏的這些環境變量都是繼承自當前shell。

     我們可以這樣理解,凡是在shell下運行的程序,都有着一份環境變量。這些環境變量繼承自shell,在shell下運行的進程都可以看到這份環境變量,那麼就可以通過這些環境變量修改自己的一些行爲,這就是配置環境變量之後,讓操作系統體現出不同行爲的一個原因。

進程和程序的區別

    首先來理解一個概念叫做進程映像

    進程映像:把程序加載到內存中時,操作系統創建的一系列的數據結構,統一叫做進程映像。

     進程映像的位置依賴於使用的內存管理方案。

     可執行程序沒有堆棧,因爲程序只有加載到內存中才會分配堆棧(堆棧是一個運行時概念)。

     可執行程序雖然也有未初始化數據段,但它並不被存儲在位於硬盤中的可執行文件。在Linux下  size a.out  命令可以查看程序的組成。

    這裏解釋data段和bss段(better save spaces)。已初始化的全局變量保存在data段,未初始化的全局變量保存在bss段。這裏所謂bss段的節省空間是針對硬盤(存儲器)而言的,在硬盤中,未初始化的全局變量在硬盤中只需要記錄其類型即可,當加載到內存中之後,會默認給隨機值(或初始化爲0),也就是說,在內存中,已初始化和未初始化的全局變量所棧空間大小一致。

  主要區別:

     可執行程序是靜態的,內存映像是隨程序執行動態變化的。

     進程是可以被調度器調度的,並且可以在內存中運行,而且可以被切換,擁有自己的硬件上下文。

     進程擁有運行時堆棧,而程序沒有。


進程狀態


"R (running)", /* 0 */     運行

"S (sleeping)", /* 1 */    可中斷睡眠,可外接喚醒

"D (disk sleep)", /* 2 */  深度睡眠,不可中斷,只有自己醒來

"T (stopped)", /* 4 */     停止
"t (tracing stop)", /* 8 */

"X (dead)", /* 16 */        死亡

"Z (zombie)", /* 32 */      殭屍狀態


    D狀態是操作系統爲了照顧硬件而設定的,通常異味着IO壓力大或系統遇重大問題,通常運維人員遇到;

    t狀態暫不談論;

    Z(僵死狀態):殭屍進程。我們可以這樣理解,子進程是父進程派生出來執行某一項任務的,父進程並不關心子進程死活,只關心的是子進程是否將任務完成(通過退出碼判斷)子進程退出之後,父進程或關心它的進程需要讀取到它的退出碼來判斷該進程是否完成工作,如果子進程退出立即釋放PCB,導致無法獲得退出碼,從而導致殭屍進程.殭屍進程不回收,會造成內存泄漏問題.

    R狀態是最容易理解的狀態,但需要知道的是,即使某個進程處於R狀態,那麼此時,它也不一定正在CPU上運行。因爲CPU會將所有的處於R狀態的PCB組織起來,供操作系統調度。某一時刻只有一個進程在CPU上運行,其他running狀態的PCB都在運行隊列當中

    通過kill命令可以改變進程狀態,具體操作參照

http://muhuizz.blog.51cto.com/11321490/1896983


    進程基本概念就說到這裏,下一篇,主要是關於進程控制的內容,關於本文中用到的代碼,可以在下方地址下載:

https://github.com/muhuizz/Linux/blob/master/Linux%E7%B3%BB%E7%BB%9F%E7%BC%96%E7%A8%8B/%E8%BF%9B%E7%A8%8B/code/mymem.c



    -----muhuizz整理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章