深刻源碼分析Linux進程模型

  1、操做系統是怎麼組織進程的html

         一、進程的概念
node

               在進程模型中,計算機上全部可運行的軟件,一般也包括操做系統,被組織成若干順序進程,簡稱進程。一個進程應該包含以下內容:linux

              (1)程序的代碼,既然進程是一個正在運行的程序,天然須要程序的代碼;算法

              (2)程序的數據;windows

              (3)CPU寄存器的值,包括通用寄存器,程序計數器;數組

              (4)堆(heap)是用來保存進程運行時動態分配的內存空間;安全

              (5)棧(stack)有兩個用途,1保存運行的上下文信息。2在函數調用時保存被調用函數的形參或者局部變量;函數

              (6)進程所佔用的一組系統資源,如打開的文件。性能

        二、進程的組織學習

                系統爲每一個進程維護了一個進程控制塊(Process Control Block,PCB),用來保存與該進程有關的各類狀態信息。PCB只是基本原理中的說法,對於一個真實的操做系統可能不叫PCB,就像我所要講的Linux中,PCB叫作任務結構體(task struct)。

struct task_struct {

        //進程的運行時狀態
        volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */
        void *stack;   
        atomic_t usage;

        //進程當前的狀態
        /*
        0x00000002表示進程正在被建立;

        0x00000004表示進程正準備退出;

        0x00000040 表示此進程被fork出,可是並無執行exec;

        0x00000400表示此進程因爲其餘進程發送相關信號而被殺死 。
        */
        unsigned int flags;     /* per process flags, defined below */


        unsigned int ptrace;
        int on_rq;

        //表示此進程的運行優先級,prio表示動態優先級,根據static_prio和交互性獎罰算出,static_prio是進程的靜態優先級,在進程建立時肯定,範圍從-20到19,越小優先級越高。
        int prio, static_prio, normal_prio;

        //進程的運行優先級
        unsigned int rt_priority;

        //list_head結構體
         struct list_head tasks;

        //mm_struct結構體,描述了進程內存的相關狀況
         struct mm_struct *mm, *active_mm;

         /* per-thread vma caching */
         u32 vmacache_seqnum;
         struct vm_area_struct *vmacache[VMACACHE_SIZE];

        /* task state */
        //進程的狀態參數
         int exit_state;
         int exit_code, exit_signal;

        //父進程退出後信號被髮送
        int pdeath_signal;  /*  The signal sent when the parent dies  */ 

         /* scheduler bits, serialized by scheduler locks */
         unsigned sched_reset_on_fork:1;
         unsigned sched_contributes_to_load:1;
         unsigned sched_migrated:1;
         unsigned sched_remote_wakeup:1;
         unsigned :0; /* force alignment to the next boundary */

         /* unserialized, strictly 'current' */
         unsigned in_execve:1; /* bit to tell LSMs we're in execve */
         unsigned in_iowait:1;

         struct restart_block restart_block;

        //進程號
        pid_t pid;
        //進程組號
        pid_t tgid;


         //進程的親身父親
         struct task_struct __rcu *real_parent; /* real parent process */
         //進程的如今的父親,可能爲繼父
         struct task_struct __rcu *parent; /* recipient of SIGCHLD, wait4() reports */
         //進程的孩子鏈表
         struct list_head children;      /* list of my children */
         //進程兄弟的鏈表
         struct list_head sibling;       /* linkage in my parent's children list */
         //主線程的進程描述符
         struct task_struct *group_leader;       /* threadgroup leader */

         /* PID/PID hash table linkage. */
         struct pid_link pids[PIDTYPE_MAX];

         //該進程的全部線程鏈表
         struct list_head thread_group;
         struct list_head thread_node;

        //該進程使用cpu時間的信息,utime是在用戶態下執行的時間,stime是在內核態下執行的時間。
        cputime_t utime, stime;

        cputime_t gtime;
        struct prev_cputime prev_cputime;

        //啓動時間,,只是時間基準不同
        u64 start_time;         /* monotonic time in nsec */
        u64 real_start_time;    /* boot based time in nsec */

        struct task_cputime cputime_expires;

        //list_head的CPU時間
        struct list_head cpu_timers[3];

        //保存進程名字的數組,通常數組大小爲15位
        char comm[TASK_COMM_LEN];

        /* file system info */
        //文件系統信息
        struct nameidata *nameidata;

        /* 文件系統信息計數*/
        int link_count, total_link_count;

        /* filesystem information */
        //文件系統相關信息結構體
         struct fs_struct *fs;

        /* open file information */
        //打開文件信息的結構體
         struct files_struct *files;

        /* namespaces */
         struct nsproxy *nsproxy;

        /* signal handlers */
        //信號相關信息的句柄
         struct signal_struct *signal;
         struct sighand_struct *sighand;

         struct callback_head *task_works;

         struct audit_context *audit_context;

         struct seccomp seccomp;

        /* Thread group tracking */
         u32 parent_exec_id;
         u32 self_exec_id;

        /* journalling filesystem info */
         void *journal_info;

        /* VM state */
         struct reclaim_state *reclaim_state;

         struct backing_dev_info *backing_dev_info;

         struct io_context *io_context;

         unsigned long ptrace_message;
         siginfo_t *last_siginfo; /* For ptrace use.  */


         /*
          * time slack values; these are used to round up poll() and
          * select() etc timeout values. These are in nanoseconds.
          */
         //鬆弛時間值,用來記錄select和poll的超時時間,單位爲ns
         u64 timer_slack_ns;
         u64 default_timer_slack_ns;


        /* CPU-specific state of this task */
        //該進程在特定CPU下的狀態
        struct thread_struct thread;

};

                PID每一個常常都有本身的「身份證號碼」,即PID號,PID是重要的系統資源,它是用以區分各個進程的基本依據,可使用ps來查看進程的PID。一個task struct對應一個PID。

 2、進程狀態如何轉換(給出進程狀態轉換圖)             

狀態 描述
TASK_RUNNING 就緒態或者運行態,進程就緒能夠運行,可是不必定正在佔有CPU,對應進程狀態的R
TASK_INTERRUPTIBLE 睡眠態,可是進程處於淺度睡眠,能夠響應信號,通常是進程主動sleep進入的狀態,對應進程狀態S
TASK_UNINTERRUPTIBLE 睡眠態,深度睡眠,不響應信號,典型場景是進程獲取信號量阻塞,對應進程狀態D
TASK_ZOMBIE 殭屍態,進程已退出或者結束,可是父進程還不知道,沒有回收時的狀態,對應進程狀態Z
TASK_STOPED 中止,調試狀態,對應進程狀態T


      

 

 

 

 

 

       (1)兩狀態進程模型

                在該模型中,一個進程要麼正在執行,要麼沒有在執行,沒有其餘狀態,因此進程所處的狀態有兩種:運行態、未運行態。進程狀態的轉換方式如1.1圖所示。

               

 

                                                                       圖1.1 兩狀態轉換圖

          (2)三狀態進程模型

            在該模型中,進程所處的狀態有三種:運行態、就緒態、和阻塞態。進程狀態的轉換方式如圖1.2所示。

                 

                                                          圖1.2    三狀態轉換圖

         (3)五狀態進程模型相較於三狀態進程模型而言,在三狀態進程模型的基礎上另增長了兩個狀態:新建態和退出態。進程狀態的轉換方式如圖1.3所示。

                                                           圖1.3  五狀態轉換圖

  3、進程是如何調度的

         在Linux操做系統中,有實時進程和普通今天之分,這裏我想圍繞普通進程的調度展開,對o(1)調度算法和CFS調度算法進行分析。

         一、o(1)調度算法

         在Linux2.6中,o(1)調度被採用,它是對普通進程進行調度的一種調度算法。由於Linux2.6版的調度算法與Linux2.4版相比在性能等方面的改進很是大,且它的時間複雜度爲恆定的o(1),故把它稱爲o(1)調度算法。

        renqueue結構體的部分定義以下:

struct runqueue
{
    unsigned long nr_running;
    task_t *curr;
    prio_array_t *active,*expired,array[2];
}

      上述結構體列舉了一些比較重要的字段:

             (1)nr_running:就緒進程的數目,等於活動進程和過時進程之和;

       (2)curr:指向正在運行的進程;

       (3)active:指向表示活動進程集的結構體;

       (4)expired:指向表示過時進程集的結構體;

       (5)arrays[2]:爲active和expired分配靜態空間。

    調度的核心步驟:

       (1)若當前處理器的運行隊列上沒有任何可運行態進程,那麼爲了實現多處理器間的負載平衡,則從其餘處理器上調一些可運行態進程進來。若調完後當前處理器的運行隊列上仍是沒有進程可運行,則在處理器上運行idle進程來使處理器處於低功耗模式直到有可運行態進程出現。若運行隊列上有可運行態進程則執行步驟(2)

       (2)判斷active隊列是否爲空。當active隊列爲空,即表示全部活動進程都運行完了,這時就要讓expired隊列的過時進程再次變爲活動進程。可是並不須要將expired隊列中的進程一個個移進active隊列中去,只須要將active和expired的指針指向的地址互換就好了。具體內核實現代碼以下:

struct prop_array *array=rq->active;
if(array->nr_active!=0)
{
    re->active=rq->expired;
    rq->expired=array;
}

 

           (3)步驟(2)執行成功後active隊列中一定存在活動進程,這時經過active中的優先級位圖bitmap來選擇優先級最高且有可運行態進程的優先級隊列,具體如圖1.4所示。

                                       圖1.4 經過bitmap來選擇優先級隊列的示意圖

                   (4)最後選擇active的bitmap所指向鏈表中的第一個進程便可。

          經過以上4個核心步驟,o(1)調度器基本完成了對於可運行態進程的一次調度過程。

          二、CFS調度算法

          從Linux2.6開始,考慮到o(1)調度的一些不足以及公平性方面的缺陷,因此改用徹底公平調度(CFS)算法。不一樣於o(1)調度,CFS調度基於公平的理念,對進程一視同仁,再也不對交互式進程進行區別,也再也不根據進程的平均睡眠時間來肯定獎勵bonus並以此來調整動態優先級。取而代之,CFS經過權重使每一個進程都能過得到公平的運行時間。更爲關鍵的是,CFS調度算法的設計和實現都很簡單,且實際性能很是優越。CFS調度器的總體結構如圖1.5所示。

                                                               圖1.5 CFS調度器的總體結構

               相對比於o(1)調度,CFS調度沒有用運行隊列來維護可運行態進程,而是用來紅黑樹來組織普通進程。紅黑樹本質上是一顆二叉查找樹,它具備如下五個特色:

                        (1)每一個葉結點都是空結點,而且他們都是黑色的;

                        (2)根結點是黑色的;

                        (3)紅色結點的子結點一定是黑色的;

                        (4)對於任意結點而言,其到葉結點的每條路徑上的黑色結點的書目都相同;

                        (5)每一個結點不是黑色就是紅色;

               這些特色決定了紅黑樹是自平衡的,雖然紅黑樹沒有達到恆定o(1)的時間複雜度可是它最差的時間複雜度也爲o(logn),這就決定了它能夠在插入和刪除等操做上表現的很是高效。CFS使用的紅黑樹是以時間爲順序的,它的結點由調度實體來描述,而進程的虛擬運行時間和權重也存放在這個結構中,圖1.6描繪了CFS中紅黑樹的結果。

                                                                  圖1.6 CFS紅黑樹的結構

              內核經過紅黑樹來對虛擬運行時間進行排序,紅黑樹的最左側結點的虛擬運行時間最少,因此該結點所表示的進程將是下一個要被調度的進程。

             三、兩種算法的比較

             經過上面對於o(1)調度和CFS調度的分析,咱們發現他們的區別歸根結底在於二者的設計思想的不一樣。

             o(1)調度是經過優先級來實現對時間片的絕對映射,而CFS調度對於時間片的映射則是經過權重完成的,CFS調度相比o(1)調度的最主要優點在於它實現了進程間的調度的相對公平,而這也是調度算法設計中很是重要的一個部分。雖然o(1)調度的恆定的時間複雜o(1)也是一大特點,只是CFS的o(logn)的性能已經可以知足於系統的須要,並且CFS的設計與實現簡單,實際運行高效,不像o(1)調度有那麼多的複雜公式,彌補了o(1)調度的許多不足之處。因此,這些因素最終決定了CFS調度要取代o(1)調度的位置。可是也不能徹底否認o(1)調度算法,它對於之後進程調度算法的發展仍是有許多值得借鑑的地方。

 四、談談本身對Linux操做系統的見解

         我想經過和windows系統的比較來談個人見解。

          Linux有如下幾個優勢:

                  (1)追求免費的正版,微軟公司開發的平臺下的軟件都是明碼標價的,若是不購買,那麼就是屬於盜版,爲了追求正版,因此只能選擇linux,特別是對於公司,則是爲了防止出現版權糾紛問題;

                  (2)學習,linux開放源代碼,是學習系統和軟件開發的平臺,也是國外不少研究機構開發系統的盜版源泉;

                  (3)防毒,有許多網站掛馬,給客戶安全形成了影響,linux做爲一個小衆平臺,感染病毒的機率比較低,所以爲了放心地看大片,因此安裝了它;

                  (4)最後,也是最重要的一點,裝逼,linux是一個高大上的名詞,使用linux就和高手掛上了勾,特別是compiz等軟件,讓windows的客戶看得目瞪口呆,而後能夠高大上地享受你們對高手地崇拜。

       windows有如下幾個優勢:

                   (1)windows造成了良好地生態圈,有着豐富實用地軟件支持,造成了良好地盈利模式,在windows平臺下,你的須要能夠獲得貼心的維護,固然你要付出你的money;

                   (2)維護成本低,技術文檔資料詳實,不想linux什麼都要靠本身、靠社區;

                   (3)最重要的是對咱們這些初學者比較友善。

        說真的,做爲一個計算機菜鳥,若是要我選擇的話,我更喜歡windows,不過想成爲大佬,仍是應該使用Linux。

、參考資料

https://wenku.baidu.com/view/2003f071cbaedd3383c4bb4cf7ec4afe04a1b1c8.html?qq-pf-to=pcqq.c2c

https://blog.csdn.net/xxpresent/article/details/71023637

相關文章
相關標籤/搜索