1. 程式人生 > >第一次作業:基於Linux2.6內核源碼進程模型分析

第一次作業:基於Linux2.6內核源碼進程模型分析

fig 活動 ask ces this cpu inter next default

1、概括

  • 進程的基本概念
  • 操作系統是如何組織進程的
  • 進程是如何調度的
  • 對Linux操作系統進程模型的看法

2、什麽是進程

一個進程就是一個正在運行的程序。一個進程應該包含以下內容:
(1) 程序的代碼,既然進程是一個正在運行的程序,自然需要程序的代碼
(2) 程序的數據
(3) CPU寄存器的值,包括通用寄存器,程序計數器
(4) 堆(heap)是用來保存進程運行時動態分配的內存空間
(5) 棧(stack)有兩個用途,1保存運行的上下文信息。2在函數調用時保存被調用函數的形參或者局部變量
(6) 進程所占用的一組系統資源,如打開的文件

在 Windows 系統中只要打開任務管理器即可看到當前正在運行的進程的一些基本信息:

技術分享圖片

在Linux操作系統下,通過ps指令查看進程:

技術分享圖片

3、進程的組織

進程一般由進程控制塊(PCB)、程序段和數據段組成。

進程創建時,操作系統就新建一個PCB結構,它之後就常駐內存,任一時刻可以存取, 在進程結束時刪除。PCB是進程實體的一部分,是進程存在的唯一標誌。
當創建一個進程時,系統為該進程建立一個PCB;當進程執行時,系統通過其PCB 了 解進程的現行狀態信息,以便對其進行控制和管理;當進程結束時,系統收回其PCB,該進 程隨之消亡。操作系統通過PCB表來管理和控制進程。

對於一個真實的操作系統可能不叫PCB,比如Linux中叫做任務結構體(task struct)

PCB通常包括以下內容:

技術分享圖片

1、進程標識符PID

進程標識符:標誌各個進程,每個進程都有一個並且是唯一的標識號。

PID的定義:

pid_t pid;

PID的取值範圍是0~32767,即Linux操作系統中可以有32768個進程

#define PID_MAX_DEFAULT (CONFIG_BASE_SMALL ? 0x1000 :0x8000)

2、進程的狀態

#define TASK_RUNNING        0
#define TASK_INTERRUPTIBLE  1
#define TASK_UNINTERRUPTIBLE    2
#define __TASK_STOPPED      4
#define
__TASK_TRACED 8 /* in tsk->exit_state */ #define EXIT_ZOMBIE 16 #define EXIT_DEAD 32 /* in tsk->state again */ #define TASK_DEAD 64 #define TASK_WAKEKILL 128

系統中的每個進程都必然處於以上所列進程狀態中的一種。

  • TASK_RUNNING :運行態或就緒態。表示進程要麽正在執行,要麽正要準備執行。
  • TASK_INTERRUPTIBLE :淺睡眠狀態。表示進程被阻塞。能響應信號,直到被喚醒,進程的狀態就被設置為 TASK_RUNNING。
  • TASK_UNINTERRUPTIBLE :深睡眠狀態。的意義與TASK_INTERRUPTIBLE類似,無法響應信號。
  • 該進程等待一個事件的發生或某種系統資源。
  • __TASK_STOPPED :停止態 。表示進程被停止執行。
  • __TASK_TRACED : 對於進程本身來說,TASK_STOPPED和TASK_TRACED狀態很類似,都是表示進程暫停下來。
  •    而TASK_TRACED狀態相當於在TASK_STOPPED之上多了一層保護,處於TASK_TRACED狀態
  • 的進程不能響應SIGCONT信號而被喚醒。只能等到調試進程通過ptrace系統調用執行PTRACE_CONT、
  • PTRACE_DETACH等操作(通過ptrace系統調用的參數指定操作),或調試進程退出,
  • 被調試的進程才能恢復TASK_RUNNING狀態。
  • EXIT_ZOMBIE :僵屍態。此時進程不能被調度,但是PCB 未被釋放。
  • EXIT_DEAD :死亡態。表示一個已終止的進程,其PCB被釋放。EXIT_ZOMBIE和EXIT_DEAD 也可以存放在 exit_state 成員中。

3、進程狀態的切換過程和原因大致如下圖(圖片來自《Linux Kernel Development》):

技術分享圖片

4、進程的調度

4.1 完全公平調度器(Completely Fair Scheduler ,CFS)

CFS在2.6.23版本中首次被集成到內核中,仍然是處理非實時任務的默認調度器。它的主要思想是使用一棵紅黑樹作為調度隊列的數據結構。根據任務在CPU上運行的時間長短而將其有序地排列在樹中,這種時間被稱為虛擬運行時間(vruntime)。CFS采用ns級的粒度來說明任務的運行時間。

如圖所示,樹中的每個內部節點對應一個任務。左側的子節點對應於在CPU上運行時間更少的任務,因此左側的任務會更早地被調度,右側的子節點是那些迄今消耗CPU時間較多的任務,葉子節點在調度中不起任何作用。

技術分享圖片

4.2 調度實體

linux中可調度的不僅僅是進程,也可能是一個進程組,所以LInux就把調度對象抽象化成一個調度實體。就像是很多結構中嵌入list_node用於連接鏈表一樣,這裏需要執行調度的也就需要加入這樣一個調度實體。實際上,調度器直接操作的也是調度實體,只是會根據調度實體獲取到其對應的結構。 vruntime 記錄在進程執行期間,在虛擬時鐘上流逝的時間,用於CFS調度器

struct sched_entity {
    struct load_weight  load;       /* for load-balancing */
    struct rb_node      run_node;
    struct list_head    group_node;
    unsigned int        on_rq;

    u64         exec_start;    
    u64         sum_exec_runtime; 
    u64         vruntime;   
    u64         prev_sum_exec_runtime;

    u64         last_wakeup;
    u64         avg_overlap;

#ifdef CONFIG_SCHEDSTATS
    u64         wait_start;
    u64         wait_max;
    u64         wait_count;
    u64         wait_sum;

    u64         sleep_start;
    u64         sleep_max;
    s64         sum_sleep_runtime;

    u64         block_start;
    u64         block_max;
    u64         exec_max;
    u64         slice_max;

    u64         nr_migrations;
    u64         nr_migrations_cold;
    u64         nr_failed_migrations_affine;
    u64         nr_failed_migrations_running;
    u64         nr_failed_migrations_hot;
    u64         nr_forced_migrations;
    u64         nr_forced2_migrations;

    u64         nr_wakeups;
    u64         nr_wakeups_sync;
    u64         nr_wakeups_migrate;
    u64         nr_wakeups_local;
    u64         nr_wakeups_remote;
    u64         nr_wakeups_affine;
    u64         nr_wakeups_affine_attempts;
    u64         nr_wakeups_passive;
    u64         nr_wakeups_idle;
#endif

#ifdef CONFIG_FAIR_GROUP_SCHED
    struct sched_entity *parent;
    /* rq on which this entity is (to be) queued: */
    struct cfs_rq       *cfs_rq;
    /* rq "owned" by this entity/group: */
    struct cfs_rq       *my_q;
#endif
};

4.2 進程的選擇過程

1、schedule_tick函數:其主要作用就是根據進程運行時間觸發調度;在進程遇到資源等待被阻塞也可以顯示的調用調度器函數進行調度。

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;
}

2、pick_next_task()函數: 該函數還是處於主調度器的層面,沒有涉及到核心邏輯,所以還比較好理解。首先判斷當前CPu就緒隊列上的可運行進程數和CFS就緒隊列上的可運行進程數是否一致,如果一致就說明當前主就緒隊列上沒有只有CFS調度類的進程,那麽這樣直接調用CFS調度類的方法挑選下一個進程即可。否則還需要從最高級的調度類,層層選擇。按照stop_sched_class->rt_scheduled_class->fair_schedled_class->idle_sched_class這個順序,依次調用其pick函數,只有前一個調度類沒有找到可運行的進程,才會查找後一個調度類。

static inline struct task_struct *
 pick_next_task(struct rq *rq)
 {
     const struct sched_class *class;
     struct task_struct *p;
 
     /*
      * Optimization: we know that if all tasks are in
      * the fair class we can call that function directly:
      */
      /*如果所有任務都處於完全公平調度類,則可以直接選擇下一個任務*/
     if (likely(rq->nr_running == rq->cfs.h_nr_running)) {
         p = fair_sched_class.pick_next_task(rq);
         if (likely(p))
             return p;
     }
     /*從優先級最高的調度器類開始遍歷,順序為stop_sched_class->rt_scheduled_class->fair_schedled_class->idle_sched_class*/
     /*
     #define for_each_class(class)     for (class = sched_class_highest; class; class = class->next)
     */
     for_each_class(class) {
         p = class->pick_next_task(rq);
         if (p)
             return p;
     }
 
     BUG(); /* the idle class will always have a runnable task */
 }

5、對操作系統的看法

操作系統(Operating System,簡稱OS)是管理和控制計算機硬件與軟件資源的計算機程序,是直接運行在“裸機”上的最基本的系統軟件,任何其他軟件都必須在操作系統的支持下才能運行。

進程(Process)是計算機中的程序關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操作系統結構的基礎。

6、參考資料

  • https://blog.csdn.net/a2796749/article/details/47101533
  • https://www.cnblogs.com/ck1020/p/6089970.html
  • https://www.kernel.org/pub/linux/kernel/v2.6/

第一次作業:基於Linux2.6內核源碼進程模型分析