歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux技術 >> 內核筆記2

內核筆記2

日期:2017/3/3 12:29:59   编辑:Linux技術
本文主要來自於趙炯先生編著的《Linux內核完全注釋》一書

Linux內核進程控制

程序是一個可執行文件,而進程是程序執行的一個實例。
進程由以下三部分組成:
1.可執行的代碼
2.數據
3.堆棧區
利用分時技術,Linux系統可以同時運行多個進程。同一時刻,一顆CPU只能運行一個進程,內核通過調度程序分時調度運行各個進程。
對於0.11內核,最多同時存在64個進程。除了第一個進程是由OS手動創建,其他的進程都是由父進程調用fork()函數創建出來。進程由PID來標示。每個進程只能執行自己的代碼和訪問自己的數據,進程間通信需要通過系統調用來執行。
同一個進程可以運行在內核態或用戶態,所以Linux的內核態堆棧和用戶態堆棧是分開的。

進程控制塊PCB(任務數據結構)

在Linux系統中,內核通過進程表管理進程,而進程表中的每一項都是一個指向task_struct類型的指針,即任務數據結構。
任務數據結構task_struct被定義在頭文件sched.h中,下面是task_struct的代碼段:
[code]struct task_struct {
/* these are hardcoded - don't touch */
    long state; /* -1 unrunnable, 0 runnable, >0 stopped 進程狀態*/
    long counter;
    long priority;
    long signal;
    struct sigaction sigaction[32];
    long blocked;   /* bitmap of masked signals */
/* various fields */
    int exit_code;
    unsigned long start_code,end_code,end_data,brk,start_stack;
    long pid,father,pgrp,session,leader;
    unsigned short uid,euid,suid;
    unsigned short gid,egid,sgid;
    long alarm;
    long utime,stime,cutime,cstime,start_time;
    unsigned short used_math;
/* file system info */
    int tty;        /* -1 if no tty, so it must be signed */
    unsigned short umask;
    struct m_inode * pwd;
    struct m_inode * root;
    struct m_inode * executable;
    unsigned long close_on_exec;
    struct file * filp[NR_OPEN];
/* ldt for this task 0 - zero 1 - cs 2 - ds&ss */
    struct desc_struct ldt[3];
/* tss for this task */
    struct tss_struct tss;
};

task_struct主要包括進程當前運行的狀態信息、信號、PID、父進程PID、運行時間累計值、正在使用的文件和任務的局部描述符以及任務狀態段信息
進程上下文:當一個進程運行時,cpu中寄存器的值、堆棧內容和進程狀態都被稱之為進程上下文。當內核需要切換到另一個進程時,必須保存前進程的上下文來確保下次返回時該進程能夠執行。

進程運行狀態

Linux中進程在其生命周期中可處於一組不同的狀態:

1.運行狀態running(可以在用戶態運行也可在內核態運行)
[code]     正在被cpu執行或准備就緒隨時可由調度程序執行

2. 可中斷睡眠狀態interruptible
[code] 發生以下情況時,進程可由interruptible轉到就緒態:
  -系統產生了一個中斷
  - 進程正在等待的資源被釋放
  - 進程收到一個信號

3.不可中斷睡眠狀
[code]處於該狀態的進程只有在被wake_up()函數明確喚醒才能轉換為就緒態。

4.暫停狀態
[code]當進程收到信號SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU時就會進入暫停狀態。可向其發送SIGCONT信號讓進程轉換到可運行狀態。

5.僵死狀態
[code]進程已經停止,父進程依舊沒有詢問其狀態。

當需要時,一個進程可以主動調用sleep_on()或interruptible_sleep_on()函數來釋放cpu使用權,進入睡眠狀態。
在內核態運行的進程不能被搶占,只能自己釋放cpu使用權。

進程初始化

當boot/目錄中的引導程序把內核從磁盤加載到內存之後,就開始執行系統初始化程序init/main.c。該程序首先確定如何分配物理內存,然後調用內核各部分初始化函數來對內存管理、塊設備、字符設備、中斷處理、進程調度以及軟硬盤進行初始化。 (關於main.c程序後續應該寫篇博文好好研究一下)
完成這些操作之後系統就可以運行了,程序首先把自己手動放到進程0(任務0)當中運行,然後調用fork()函數來創建進程1。在進程1中繼續初始化各自應用的環境並且啟用shell登錄程序。進程0會在cpu空閒時被執行,此時任務0僅執行pause()函數。
需要注意的是宏move_to_user_mode在這個過程中的作用:
宏move_to_user_mode實現了程序把自己手動放到進程0中這一功能,並且把運行特權級從內核態0級改為用戶態3級,仍然執行原先的代碼指令流。

創建新進程

Linux中所有進程都通過fork()系統調用,所有進程都是通過復制進程0得到的,都是進程0的子進程。
創建新進程的過程:
在任務數組中找出一個還沒有被任何進程使用的空項,0.11版本的內核的任務數組只有64個項,現在的Linux內核可存放的進程數已經遠遠大於這個量級。
在主內存區中為新建進程申請一頁內存來存放任務數據結構信息,並將當前進程的任務數據結構(PCB)復制過來
將新進程狀態置為不可中斷睡眠狀態來防止新建進程被調度函數執行
修改復制過來的任務數據結構,把當前進程設置為新進程的父進程,清除信號位,復位新進程各統計值,設置新進程的初始運行時間片為15個系統滴答
根據當前進程設置任務狀態段(TSS)中各寄存器的值。Intel80386程序員參考手冊-任務狀態段
設置新進程的代碼和數據段基址、限長
復制當前進程內存分頁管理的頁表
如果父進程有打開的文件,則將對應文件的打開次數增1
最後將新進程設置成可運行狀態並返回其PID

進程調度

Linux進程是搶占式的,被強占的進程仍然處於running態,只不過沒有被cpu執行,進程在內核態是不會被搶占的。
一、調度策略
1.schedule()函數首先掃描任務數組,通過比較每個就緒態(TASK_RUNNING)任務的運行時間來確定當前哪個進程運行的時間最少。哪個的值最大就表示那個進程運行的時間不長,於是就選中該進程,並使用任務轉換函數轉換至該進程。
2.每個任務的需要運行的時間片值counter = counter/2 + priority(優先權值)。
3. 如果沒有進程可運行,系統就會選擇進程0運行,進程0調度pause()把自己置為可中斷睡眠狀態並再次調用schedule(),其實schedule()並不在意進程0當前的狀態,只要系統空閒就調度進程0。
二、進程切換
切換任務由switch_to()宏定義的一段匯編代碼完成。
其功能主要是先檢測需要切換的是否為本進程,如果是則不做任何操作,如果不是則把內核全局變量current置為新進程的指針,然後長跳轉到新任務(進程)的任務狀態段TSS的地址處,造成cpu執行任務的切換操作,所以它的執行速度會非常快,然後要對新舊進程的TSS做保存和恢復工作,如下圖所示:

終止進程

當一個進程結束了運行或在半途中終止了運行,那麼內核就需要釋放該進程所占有的系統資源。
當一個用戶程式調用exit()系統調用後,就會執行內核函數d0_exit(),並做一系列的資源釋放工作,再最後並調用schedule函數去執行其他進程。
在進程終止時,他的任務數據結構仍然保留著,因為其父進程還需要使用其中的信息。
在子進程執行期間,父進程會使用wait()或waitpid()函數等待其子進程的結束。當等待的子進程被終止並處於僵死狀態時,父進程就會把子進程運行所使用的時間累加到自己的進程中,最終釋放已終止的子進程任務數據結構所占用的內存頁面,並置空子進程在任務數組中占用的指針項。
Copyright © Linux教程網 All Rights Reserved