1. 程式人生 > >深入源碼分析進程模型

深入源碼分析進程模型

== prev 間隔 correct 系統資源 動態 enable -i 錯誤

1.操作系統是怎麽組織進程的

struct task_struct {

......

/* 進程狀態 */
volatile long state;
/* 指向內核棧 */
void *stack;
/* 用於加入進程鏈表 */
struct list_head tasks;
......

/* 指向該進程的內存區描述符 */
struct mm_struct *mm, *active_mm;

........

/* 進程ID,每個進程(線程)的PID都不同 */
pid_t pid;
/* 線程組ID,同一個線程組擁有相同的pid,與領頭線程(該組中第一個輕量級進程)pid一致,保存在tgid中,線程組領頭線程的pid和tgid相同 */


pid_t tgid;
/* 用於連接到PID、TGID、PGRP、SESSION哈希表 */
struct pid_link pids[PIDTYPE_MAX];

........

/* 指向創建其的父進程,如果其父進程不存在,則指向init進程 */
struct task_struct __rcu *real_parent;
/* 指向當前的父進程,通常與real_parent一致 */
struct task_struct __rcu *parent;

/* 子進程鏈表 */
struct list_head children;
/* 兄弟進程鏈表 */


struct list_head sibling;
/* 線程組領頭線程指針 */
struct task_struct *group_leader;

/* 在進程切換時保存硬件上下文(硬件上下文一共保存在2個地方: thread_struct(保存大部分CPU寄存器值,包括內核態堆棧棧頂地址和IO許可權限位),內核棧(保存eax,ebx,ecx,edx等通用寄存器值)) */
struct thread_struct thread;

/* 當前目錄 */
struct fs_struct *fs;

/* 指向文件描述符,該進程所有打開的文件會在這裏面的一個指針數組裏 */


struct files_struct *files;

........

  /* 信號描述符,用於跟蹤共享掛起信號隊列,被屬於同一線程組的所有進程共享,也就是同一線程組的線程此指針指向同一個信號描述符 */
  struct signal_struct *signal;
  /* 信號處理函數描述符 */
  struct sighand_struct *sighand;

  /* sigset_t是一個位數組,每種信號對應一個位,linux中信號最大數是64
   * blocked: 被阻塞信號掩碼
   * real_blocked: 被阻塞信號的臨時掩碼
   */
  sigset_t blocked, real_blocked;
  sigset_t saved_sigmask; /* restored if set_restore_sigmask() was used */
  /* 私有掛起信號隊列 */
  struct sigpending pending;


........
}

/* wq為某個等待隊列的隊列頭 */
void sleep_on (wait_queue_head_t *wq)
{
    /* 聲明一個等待隊列結點 */
    wait_queue_t wait;

    /* 用當前進程初始化這個等待隊列結點 */
    init_waitqueue_entry (&wait, current);

    /* 設置當前進程狀態為TASK_UNINTERRUPTIBLE */
    current->state = TASK_UNINTERRUPTIBLE;

    /* 將這個代表著當前進程的等待隊列結點加入到wq這個等待隊列 */
    add_wait_queue (wq, &wait);

    /* 請求調度器進行調度,執行完schedule後進程會被移除CPU運行隊列,只有等待隊列喚醒後才會重新回到CPU運行隊列 */
    schedule ();

    /* 這裏進程已經被等待隊列喚醒,重新移到CPU運行隊列,也就是等待的條件已經為真,喚醒後第一件事就是將自己從
等待隊列wq中移除 */ remove_wait_queue (wq, &wait); }


所有處於TASK_RUNNING狀態的進程都會被放入CPU的運行隊列,它們有可能在不同CPU的運行隊列中。

  系統沒有為TASK_STOPED、EXIT_ZOMBIE和EXIT_DEAD狀態的進程建立專門的鏈表,因為處於這些狀態的進程訪問比較簡單,可通過PID和通過特定父進程的子進程鏈表進行訪問。

  所有TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE都會被放入相應的等待隊列,系統中有很多種等待隊列,有些是等待磁盤操作的終止,有些是等待釋放系統資源,有些是等待時間經過固定的間隔,每個等待隊列它的喚醒條件不同,比如等待隊列1是等待系統釋放資源A的,等待隊列2是等待系統釋放資源B的。因此,等待隊列表示一組睡眠進程,當某一條件為真時,由內核喚醒這條等待隊列上的進程。

等待隊列是等待系統釋放資源A,而等待隊列中所有的進程都是希望能夠占有這個資源A的,就像我們編程中用到的信號量,這時候系統的做法不是將這個等待隊列中所有的進程都進行喚醒,而是只喚醒一個。內核區分這種互斥進程的原理就是這個等待隊列中所有的等待隊列結點wait_queue_t中的flags被設置為1(默認是0)

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

Linux進程狀態有:
R (TASK_RUNNING),可執行狀態。
S (TASK_INTERRUPTIBLE),可中斷的睡眠狀態。
D (TASK_UNINTERRUPTIBLE),不可中斷的睡眠狀態。
Z (TASK_DEAD - EXIT_ZOMBIE),退出狀態,進程成為僵屍進程。
T (TASK_STOPPED or TASK_TRACED),暫停狀態或跟蹤狀態。
X (TASK_DEAD - EXIT_DEAD),退出狀態,進程即將被銷毀。

只有在該狀態的進程才可能在CPU上運行。同一時刻可能有多個進程處於可執行狀態,這些進程的task_struct結構(進程控制塊)
被放入對應CPU的可執行隊列中(一個進程最多只能出現在一個CPU的可執行隊列中)。進程調度器從各個CPU的可執行隊列中分別選擇
一個進程在該CPU上運行。

正在CPU上執行的進程定義為RUNNING狀態、可執行但尚未被調度執行的進程定義為READY狀態,這兩種狀態統一為
TASK_RUNNING狀態。

只有當進程從“內核運行態”轉移到“睡眠狀態”時,內核才會進行進程切換操作。在內核態下運行的進程不能被其它進程搶占,而且一個進程不能改變另一個進程的狀態。為了避免進程切換時造成內核數據錯誤,內核在執行臨界區代碼時會禁止一切中斷。

進程的三種基本狀態

就緒(Ready)狀態

當進程已分配到除CPU以外的所有必要的資源,只要獲得處理機便可立即執行,這時的進程狀態稱為就緒狀態。

執行(Running)狀態

當進程已獲得處理機,其程序正在處理機上執行,此時的進程狀態稱為執行狀態。

阻塞(Blocked)狀態

正在執行的進程,由於等待某個事件發生而無法執行時,便放棄處理機而處於阻塞狀態。引起進程阻塞的事件可有多種,例如,等待I/O完成、申請緩沖區不能滿足、等待信件(信號)等。

(1) 就緒→執行處於就緒狀態的進程,當進程調度程序為之分配了處理機後,該進程便由就緒狀態轉變成執行狀態。

 (2) 執行→就緒處於執行狀態的進程在其執行過程中,因分配給它的一個時間片已用完而不得不讓出處理機,於是進程從執行狀態轉變成就緒狀態。

 (3) 執行→阻塞正在執行的進程因等待某種事件發生而無法繼續執行時,便從執行狀態變成阻塞狀態。

 (4) 阻塞→就緒處於阻塞狀態的進程,若其等待的事件已經發生,於是進程由阻塞狀態轉變為就緒狀態。

技術分享圖片

3.進程是如何調度的

static void __sched __schedule(void)
 {
     struct task_struct *prev, *next;
     unsigned long *switch_count;
     struct rq *rq;
     int cpu;
 
 need_resched:
     /*禁止內核搶占*/
     preempt_disable();
     cpu = smp_processor_id();
     /*獲取CPU 的調度隊列*/
     rq = cpu_rq(cpu);
     rcu_note_context_switch(cpu);
     /*保存當前任務*/
     prev = rq->curr;
 
     schedule_debug(prev);
 
     if (sched_feat(HRTICK))
         hrtick_clear(rq);
 
     /*
      * Make sure that signal_pending_state()->signal_pending() below
      * can‘t be reordered with __set_current_state(TASK_INTERRUPTIBLE)
      * done by the caller to avoid the race with signal_wake_up().
      */
     smp_mb__before_spinlock();
     raw_spin_lock_irq(&rq->lock);
 
     switch_count = &prev->nivcsw;
      /*  如果內核態沒有被搶占, 並且內核搶占有效
         即是否同時滿足以下條件:
         1  該進程處於停止狀態
         2  該進程沒有在內核態被搶占 */
     if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {
         if (unlikely(signal_pending_state(prev->state, prev))) {
             prev->state = TASK_RUNNING;
         } else {
             deactivate_task(rq, prev, DEQUEUE_SLEEP);
             prev->on_rq = 0;
 
             /*
              * If a worker went to sleep, notify and ask workqueue
              * whether it wants to wake up a task to maintain
              * concurrency.
              */
             if (prev->flags & PF_WQ_WORKER) {
                 struct task_struct *to_wakeup;
 
                 to_wakeup = wq_worker_sleeping(prev, cpu);
                 if (to_wakeup)
                     try_to_wake_up_local(to_wakeup);
             }
         }
         switch_count = &prev->nvcsw;
     }
 
     pre_schedule(rq, prev);
 
     if (unlikely(!rq->nr_running))
         idle_balance(cpu, rq);
     /*告訴調度器prev進程即將被調度出去*/
     put_prev_task(rq, prev);
     /*挑選下一個可運行的進程*/
     next = pick_next_task(rq);
     /*清除pre的TIF_NEED_RESCHED標誌*/
     clear_tsk_need_resched(prev);
     rq->skip_clock_update = 0;
    /*如果next和當前進程不一致,就可以調度*/
     if (likely(prev != next)) {
         rq->nr_switches++;
         /*設置當前調度進程為next*/
         rq->curr = next;
         ++*switch_count;
         /*切換進程上下文*/
         context_switch(rq, prev, next); /* unlocks the rq */
         /*
          * The context switch have flipped the stack from under us
          * and restored the local variables which were saved when
          * this task called schedule() in the past. prev == current
          * is still correct, but it can be moved to another cpu/rq.
          */
         cpu = smp_processor_id();
         rq = cpu_rq(cpu);
     } else
         raw_spin_unlock_irq(&rq->lock);
 
     post_schedule(rq);
   
     sched_preempt_enable_no_resched();
     if (need_resched())
         goto need_resched;
}

進程提供了兩種優先級,一種是普通的進程優先級,第二個是實時優先級,前者使用SCHEED_NORMAL調度策略,後者可選SCHED_FIFO或SCHED_rr調度。任何時候,實時進程的優先級都高於普通進程,實時進程只會被更高級的實時進程搶占,同時實時進程之間是按照FIFO(一次機會做完)或者RR(多次輪轉)規則調度的
首先說一下實時進程的調度:
實時進程,只有靜態優先級,因為內核不會根據休眠時間等因素對其靜態優先級做調整,默認的實時優先級範圍是0~99
不同於普通的進程,系統調度時。實時優先級高的進程總是先於優先級低的進程執行,直到實時優先級高的實時進程無法執行。如果有數個優先級相同的實時進程,那麽系統就會按照進程出現在隊列上的順序選擇進程。
不同的調度策略的實時進程只有在相同優先級的時候才有可比性:
1)對於FIFO的進程,意味著只有當前進程執行完畢才會輪到其他進程執行。由此可見相當霸道。
2)對於RR進程,一旦時間片消耗完畢,則會將該進程置於隊列的末尾,然後運行其他相同優先級的進程,如果沒有其他相同優先級的進程,則該進程會繼續執行。

對於實時進程,高優先級的進程先執行,他執行到沒法執行,采會輪到優先級低的進程執行。等級制度行當森嚴。
普通進程:
SCHED_ORHER:基於動態優先級進行調度,其動態優先級可以理解為調度器為每個進程根據多個因素計算出的權值。

4.談談自己對該操作系統進程模型的看法

Linux系統是一個具有先天病毒免疫能力的操作系統,很少受到病毒攻擊。對於一個開放式系統而言,在方便用戶的同時,很可能存在安全隱患。

不過,利用Linux自帶防火墻、入侵檢測和安全認證等工具,及時修補系統的漏洞,就能大大提高Linux系統的安全性,讓黑客們無機可乘。

深入源碼分析進程模型