2018-2019-1 20189201 《LInux内核原理与分析》第九周作业
那一天我二十一歲,在我一生的黃金時代。我有好多奢望。我想愛,想吃,還想在一瞬間變成天上半明半暗的云。那一年我二十一歲,在我一生的黃金時代。我有好多想法。我思索,想象,我不知該如何行動,我想知道一個城市,一個國家,這個社會的運轉方式和發展動力。我思考人性,想象物質和精神,探求存在的意義。我渴望愛情 熱愛生活 追求美好。我希望自己可以長存 。
*********************************************
一、書本第八章知識總結【進程的切換和系統的一般執行過程】
1. 進程調度及時機
- 不同類型的進程有不同的調度需求,第一種分類:I/O-bound 會頻繁的進程I/O,通常會花費很多時間等待I/O操作的完成;CPU-bound 是計算密集型,需要大量的CPU時間進行運算,使得其他交互式進程反應遲鈍,因此需要不同的算法來使系統的運行更高效,以及CPU的資源最大限度的得到使用。第二種分類包括批處理進程(batch process);實時進程(real-time process)以及交互式進程(interactive process)。不同的分類需要不同的調度策略,即決定什么時候以怎樣的方式選擇一個新進程運行。Linux的調度基于分時和優先級。根據優先級排隊,且優先級是動態的。
- 進程調度的時機:進程調度的時機就是內核調用schedule函數的時機。當內核即將返回用戶空間時,內核會檢查need_resched標志是否設置。如果設置,則調用schedule函數,將從中斷處理程序返回用戶空間的時間點作為一個固定的調度時機點。schedule()函數實現調度:
1)中斷處理過程(包括時鐘中斷、I/O中斷、系統調用和異常)中,直接調用schedule(),或者返回用戶態時根據need_resched標記調用schedule()。
2)內核線程可以直接調用schedule()進行進程切換,也可以在中斷處理過程中進行調度,也就是說內核線程作為一類的特殊的進程可以主動調度,也可以被動調度。
3)用戶態進程無法實現主動調度,僅能通過陷入內核態后的某個時機點進行調度,即在中斷處理過程中進行調度。
2. 進程的上下文切換
- 為了控制進程的執行,內核必須有能力掛起正在 CPU 上執行的進程,并恢復以前掛起的某個進程的執行的過程,叫做進程切換、任務切換、上下文切換。掛起正在 CPU 上執行的進程,與中斷時保存現場是有區別的,中斷前后是在同一個進程上下文中,只是由用戶態轉向內核態執行。也即是說中斷是在同一個進程中執行的,進程上下文是在不同的進程中執行的。
- 進程上下文信息:
1)用戶地址空間:包括程序代碼,數據,用戶堆棧等。
2)控制信息:進程描述符,內核堆棧等。
3)硬件上下文(注意中斷也要保存硬件上下文只是保存的方法不同)。 - schedule()函數:此函數選擇一個新的進程來運行,并調用context_ switch進行上下文的切換,這個宏調用switch_ to來進行關鍵上下文切換
1)next = pick_ next_ task(rq, prev);//進程調度算法都封裝這個函數內部。
2)context_ switch(rq, prev, next);//進程上下文切換。
3)switch_ to利用了prev和next兩個參數:prev指向當前進程,next指向被調度的進程。
其代碼過程:
1)創建一些局部變量。
2)關閉內核搶占,初始化一部分變量。
3)選擇next進程。
4)完成進程的調度。
選擇一個新的進程來運行,并調用 context_switch 宏進行上下文的切換,這個宏又調用 switch_to 宏來進行關鍵上下文切換;switch_to 宏中定義了 prev 和 next 兩個參數:prev 指向當前進程,next 指向被調度的進程。 - 實際代碼中進程切換由兩個步驟組成:
1)切換頁全局目錄(RC3)以安裝一個新的地址空間,這樣不同的虛擬地址可以經過不同的頁表轉為不同的物理地址。
2)切換內核態堆棧和硬件上下文 - 進程切換上下文的代碼過程分析
- schedule()函數選擇一個新的進程來運行。
- 通過context_switch完成進程上下文切換。
- switch_ to()函數代碼分析:
- 保存當前進程的flags
- 把當前進程的堆棧基址壓棧
- switch_ to完成寄存器的切換:先保存當前進程的寄存器,再進行堆棧切換
- 再切換eip,這樣當前進程可以從新進程中恢復
- next_ ip一般是$1f(對于新創建的進程來說就是ret_ from_ fork)
- 表明下一進程棧頂是起點。
- next_ ip一般是$1f,對于新創建的子進程是ret_ from_forkjmp _ switch_ to是函數調用,通過寄存器傳遞參數;函數執行結束return的時候從下一條指令開始(即是新進程的開始)
- next進程曾經是prev進程,next執行完后執行的“下一個”其實是剛剛被切換的進程
進程切換關鍵環節分析示意圖
代碼分析
schedule()函數代碼:
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();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;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;}if (task_on_rq_queued(prev) || rq->skip_clock_update < 0)update_rq_clock(rq);next = pick_next_task(rq, prev);clear_tsk_need_resched(prev);clear_preempt_need_resched();rq->skip_clock_update = 0;if (likely(prev != next)) {rq->nr_switches++;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);} elseraw_spin_unlock_irq(&rq->lock);post_schedule(rq);sched_preempt_enable_no_resched();if (need_resched())goto need_resched; }switch_ to()函數代碼:
#define switch_to(prev, next, last) //prev指向當前進程,next指向被調度的進程 do { unsigned long ebx, ecx, edx, esi, edi;asm volatile("pushfl\n\t" //保存當前近程的flags"pushl %%ebp\n\t" //把當前進程的基址壓棧"movl %%esp,%[prev_sp]\n\t" //把當前進程的棧頂esp保存到thread.sp中"movl %[next_sp],%%esp\n\t" //把[next_sp]放到esp,從而這兩步完成了內核堆棧的切換"movl $1f,%[prev_ip]\n\t" //把1f放到[prev_ip]里,保存當前進程的EIP,當恢復prev進程時可從這里恢復"pushl %[next_ip]\n\t" //把next進程的起點,即ip的位置壓到堆棧中,next_ip一般是$1f__switch_canary"jmp __switch_to\n" //執行__switch_to()函數,通過寄存器[prev][next],eax和edx傳遞參數"1:\t""popl %%ebp\n\t" "popfl\n" /* output parameters */ : [prev_sp] "=m"(prev->thread.sp), //為了可讀性更好,用字符串[prev_sp]標記參數[prev_ip] "=m"(prev->thread.ip), "=a" (last),/* clobbered output registers: */ "=b" (ebx), "=c"(ecx), "=d" (edx), "=S" (esi), "=D"(edi)__switch_canary_oparam /* input parameters: */ : [next_sp] "m" (next->thread.sp), [next_ip] "m" (next->thread.ip), /* regparm parameters for __switch_to():*/ [prev] "a" (prev), [next] "d" (next)__switch_canary_iparam : /* reloaded segment registers */ "memory"); } while (0)3. Linux系統的一般執行過程分析
最一般的情況:正在運行的用戶態進程X切換到運行用戶態進程Y的過程
1)正在運行的用戶態進程X
2)發生中斷——save cs:eip/esp/eflags(current) to kernel stack,then load cs:eip(entry of a specific ISR) and ss:esp(point to kernel stack).
3)SAVE_ALL //保存現場
4)中斷處理過程中或中斷返回前調用了schedule(),其中的switch_to做了關鍵的進程上下文切換
5)標號1之后開始運行用戶態進程Y(這里Y曾經通過以上步驟被切換出去過因此可以從標號1繼續執行)
6)restore_all //恢復現場
7)iret - pop cs:eip/ss:esp/eflags from kernel stack
8)繼續運行用戶態進程YLinux系統的一般執行過程中的幾個特殊情況
1)通過中斷處理過程中的調度時機,用戶態進程與內核線程之間互相切換和內核線程之間互相切換,與最一般的情況非常類似,只是內核線程運行過程中發生中斷沒有進程用戶態和內核態的轉換
2)內核線程主動調用schedule(),只有進程上下文的切換,沒有發生中斷上下文的切換,與最一般的情況略簡略
3)創建子進程的系統調用在子進程中的執行起點及返回用戶態,如fork
4)加載一個新的可執行程序后返回到用戶態的情況,如execve
4. Linux操作系統架構概覽
- 任何計算機系統都包含一個基本的程序集合,稱為操作系統。
- 內核(進程管理,進程調度,進程間通訊機制,內存管理,中斷異常處理,文件系統,I/O系統,網絡部分)
- 其他程序(例如函數庫、shell程序、系統程序等等)
- 操作系統的目的
- 與硬件交互,管理所有的硬件資源
- 為用戶程序(應用程序)提供一個良好的執行環境
- 整體框架示意圖,如下
- ls命令執行過程示意圖,如下
二、實驗部分【理解進程調度時機跟蹤分析進程調度與進程切換的過程】
實驗步驟
1)打開實驗樓虛擬機
2)在shell中依次運行以下命令,獲取本次實驗的代碼,并編譯運行
cd LinuxKernel
rm menu -rf
git clone https://github.com/mengning/menu.git
cd menu
mv test_exec.c test.c
make rootfs
截圖如下:
3)關閉QEMU窗口,在shell窗口中,cd LinuxKernel回退到LinuxKernel目錄,使用下面的命令啟動內核并在CPU運行代碼前停下以便調試:
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S
4)接下來,我們就可以水平分割一個新的shell窗口出來,依次使用下面的命令啟動gdb調試
gdb
(gdb) file linux-3.18.6/vmlinux
(gdb) target remote:1234
5)關閉QEMU窗口,在shell窗口中,cd LinuxKernel回退到LinuxKernel目錄,使用下面的命令啟動內核并在CPU運行代碼前停下以便調試:
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S
6)接下來,我們就可以水平分割一個新的shell窗口出來,依次使用下面的命令啟動gdb調試
gdb
(gdb) file linux-3.18.6/vmlinux
(gdb) target remote:1234
7)并在內核函數schedule的入口處設置斷點,接下來輸入c繼續執行,則系統即可停在該函數處,接下來我們就可以使用命令n或者s逐步跟蹤,可以詳細瀏覽pick_next_task,switch_to等函數的執行過程,有圖為證:
三、實驗收獲
1. 關于進程調度與切換
本周視頻主要講解了進程切換的關鍵代碼switch_ to分析、Linux系統的一般執行過程、Linux系統架構和執行過程。
schedule()函數實現進程調度,
context_ switch完成進程上下文切換,
switch_ to完成寄存器的切換。
在調度時機方面,內核線程作為一類的特殊的進程可以主動調度,也可以被動調度。
而用戶態進程無法實現主動調度,僅能通過陷入內核態后的某個時機點進行調度,即在中斷處理過程中進行調度。
2. 硬中斷與軟中斷
Intel定義的中斷有:
1)硬中斷
CPU的兩根引腳(可屏蔽中斷和不可屏蔽中斷)。CPU在執行每條指令后檢測引腳的電平
一般外設都是以這種方式與CPU進行信號傳遞
2)軟中斷
包括零錯誤、系統調用、調試斷點等,在CPU執行指令過程中發生的各種特殊情況,稱為異常。
異常可以分為以下3種:
故障 出現問題,但是可以恢復到當前指令
退出 是不可恢復的嚴重故障,導致程序無法繼續運行
陷阱 程序主動產生的異常,在執行當前指令后發生。比如系統調用(int 0x80)等。
3. schedule()函數主要做了這么三件事:
1)針對搶占的處理;檢查prev的狀態,并且重設state的狀態;
2)next = pick_next_task(rq, prev); //進程調度;更新就緒隊列的時鐘;
3)context_switch(rq, prev, next); //進程上下文切換
轉載于:https://www.cnblogs.com/keady/p/10092871.html
總結
以上是生活随笔為你收集整理的2018-2019-1 20189201 《LInux内核原理与分析》第九周作业的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 文件怎么扫描到电脑里 如何将文件扫描到电
- 下一篇: u盘装win10蓝屏怎么办 U盘安装wi