一、什么是進程
進程是指計算機已運行的程序。程序本身只是指令、數據及其組織形式的描述。進程就是一個程序的執行實例,也就是正在執行的程序。在linux操作系統的中,進程就是一個擔當分配系統資源CPU時間、內存的實體。進程控制的主要功能是對系統中的所有進程實施有效的管理,它具有創建新進程、撤銷已有進程、實現進程狀態之間的轉換等功能。進程在運行中不斷地改變其運行狀態。一個進程在運行期間,不斷地從一種狀態轉換到另一種狀態,它可以多次處于就緒狀態和執行狀態,也可以多次處于阻塞狀態。
目前,只有CPU的資源管理器能主動發起調度,而其他資源像內存,網絡,顯卡等資源管理器都是提供申請服務調度的,所以要必須理解我們常說的進程調度都是指調度CPU。
進程發展史的主要內容:
-
單道批處理系統時期:早期的計算機只支持運行單個程序,從磁帶或卡片讀入程序,由操作員操作控制運行。這種系統被稱為單道批處理系統,沒有多個程序共同運行的概念,也沒有進程的概念。
-
多道批處理系統時期:20世紀60年代早期,隨著計算機技術的發展,多道批處理操作系統應運而生,允許多個程序同時進入內存并在CPU上運行。這里的每個程序稱為一個作業(Job),但是沒有進程的概念。
-
分時操作系統時期:20世紀60年代中期,分時操作系統應運而生,引入了多用戶的概念,這為進程的發展鋪平了道路。在分時系統中,操作系統可以運行多個進程并為它們提供時間片,每個進程只有在它的時間片內獲得CPU的控制權。這一時期UNIX系統的開發者對進程進行了深入的研究。
-
多任務操作系統時期:20世紀80年代,多任務操作系統開始出現,這種操作系統允許多個進程同時運行,每個進程都有自己的地址空間、寄存器和堆棧等。操作系統必須管理和控制各個進程的運行狀態,這為進程的實現和管理提供了更大的靈活性和可控性。
-
現代操作系統時期:21世紀以來,隨著計算機技術的不斷發展,操作系統和進程的概念也在不斷更新和完善。現代操作系統支持多核處理器、多線程和分布式系統等技術,允許進程在不同的計算機上運行,進一步增強了進程的靈活性和可擴展性。同時,在容器技術的發展下,進程管理變得更加高效和簡單,如Docker等。
二、進程的生命周期
Linux操作系統是一個多任務操作系統。 系統中的各個進程可以分時復用CPU時間片,通過有效的進程調度策略,實現多任務并行執行。進程并不總是可以立即運行。有時候它必須等待來自外部信號源、不受其控制的事件,例如在文本編輯器中等待鍵盤輸入。在事件發生之前,進程無法運行。進程可能有以下幾種狀態:
1.創建狀態:創建新進程。
2.等待狀態:進程能夠運行,但沒有得到許可,因為CPU分配給另一個進程。調度器可以在下一次任務切換時選擇該進程。
3.運行狀態:進程正在cpu中執行。
4.睡眠狀態:進程正在睡眠無法運行,因為它在等待一個外部事件。
5.終止狀態:進程消亡。
系統將所有進程保存在一個進程表中,無論其狀態是運行、睡眠或等待。但睡眠進程會特別標記出來,調度器會知道它們無法立即運行。睡眠進程會分類到若干隊列中,因此它們可在適當的時間喚醒,例如在進程等待的外部事件已經發生時。詳細的進程狀態間關系圖如下所示:
Linux內核在include/linux/sched.h文件中也為進程定義了多種狀態:
/* Used in tsk->state: */
#define TASK_RUNNING 0x0000
#define TASK_INTERRUPTIBLE 0x0001
#define TASK_UNINTERRUPTIBLE 0x0002
#define __TASK_STOPPED 0x0004
#define __TASK_TRACED 0x0008
/* Used in tsk->exit_state: */
#define EXIT_DEAD 0x0010
#define EXIT_ZOMBIE 0x0020
#define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD)
/* Used in tsk->state again: */
#define TASK_PARKED 0x0040
#define TASK_DEAD 0x0080
#define TASK_WAKEKILL 0x0100
#define TASK_WAKING 0x0200
#define TASK_NOLOAD 0x0400
#define TASK_NEW 0x0800
#define TASK_STATE_MAX 0x1000
其中比較常用的就是下面的5種:
- TASK_RUNNING(可運行狀態):對應上圖的等待狀態或者運行狀態。它是指進程處于可運行的狀態,或許正在運行,或許在就緒隊列(本書中也稱為調度隊列)中等待運行。
- TASK_INTERRUPTIBLE(可中斷睡眠態):對應上圖的睡眠狀態。進程進入睡眠狀態(被阻塞)來等待某些條件的達成或者某些資源的就位,一旦條件達成或者資源就位,內核就可以把進程的狀態設置成TASK_RUNNING并將其加入就緒隊列中。也有人將這個狀態稱為淺睡眠狀態。
- TASK_UNINTERRUPTIBLE(不可中斷態):對應上圖的睡眠狀態。這個狀態和上面的TASK_INTERRUPTIBLE狀態類似,唯一不同的是,進程在睡眠等待時不受干擾,對信號不做任何反應,所以這個狀態稱為不可中斷態。通常使用ps命令看到的被標記為D狀態的進程,就是處于不可中斷態的進程,不可以發送SIGKILL信號使它們終止,因為它們不響應信號。也有人把這個狀態稱為深度睡眠狀態。
- EXIT_ZOMBIE(僵尸態):對應上圖的終止狀態。進程已經消亡,但是task_struct數據結構還沒有釋放,這個狀態叫作僵尸態。
- __TASK_STOPPED(終止態):對應上圖的終止狀態。進程停止運行。task_struct中的資源包括task_struct數據已經釋放完了。
三、進程優先級
并非所有進程都具有相同的重要性。除了大家熟悉的進程優先級之外,進程還有不同的關鍵度類別,以滿足不同需求。首先要知道linux的進程分為以下幾種,每一種的優先級都是不一樣的:
1.期限進程:優先級為-1。優先級最高。
2.實時進程:優先級1-99,優先級數值越大便是優先級越高。
3.普通進程:優先級為100-139,優先級數值越小便是優先級越高。可以通過nice值改變普通進程優先級。
4.空閑進程:優先級為140。優先級最低。
四、進程表示
Linux內核涉及進程和程序的所有算法都圍繞一個名為 task_struct 的數據結構建立,該結構定義在 include/linux/sched.h 中。這是系統中主要的一個結構。task_struct 包含很多成員,將進程與各個內核子系統聯系起來,我們看看代碼:
struct task_struct {
#ifdef CONFIG_THREAD_INFO_IN_TASK/** For reasons of header soup (see current_thread_info()), this* must be the first element of task_struct.*/struct thread_info thread_info;//把thread_info從棧中抽出來,方便current宏的實現
#endif/* -1 unrunnable, 0 runnable, >0 stopped: */volatile long state; //表示進程的狀態標志/** This begins the randomizable portion of task_struct. Only* scheduling-critical items should be added above here.*/randomized_struct_fields_startvoid *stack;//指向內核棧的指針refcount_t usage;//進程描述符使用計數,被置為2時,表示進程描述符正在被使用而且其相應的進程處于活動狀態/* Per task flags (PF_*), defined further below: */unsigned int flags;//標記,表示進程的類型unsigned int ptrace;#ifdef CONFIG_SMPstruct llist_node wake_entry;int on_cpu;
#ifdef CONFIG_THREAD_INFO_IN_TASK/* Current CPU: */unsigned int cpu;//當前運行的cpuid
#endifunsigned int wakee_flips;unsigned long wakee_flip_decay_ts;struct task_struct *last_wakee;/** recent_used_cpu is initially set as the last CPU used by a task* that wakes affine another task. Waker/wakee relationships can* push tasks around a CPU where each wakeup moves to the next one.* Tracking a recently used CPU allows a quick search for a recently* used CPU that may be idle.*/int recent_used_cpu;//上一次調度時使用的cpuidint wake_cpu;
#endifint on_rq;int prio;//進程的當前優先級。一般等于normal_prio,但是當進程A占有互斥鎖并且進程B在等待鎖的時候,會把A進程的優先級臨時提高int static_prio;//普通進程的靜態優先級,就是120+nice,若是限期進程或者實時進程則為0int normal_prio;//普通進程的正常優先級,一般等于static_prio,若是限期進程則為-1,若是實時進程,則為99-rt_priorityunsigned int rt_priority;//實時進程的優先級,數值越大優先級越高,如果是限期進程或者普通進程,則為0const struct sched_class *sched_class;//進程的調度類struct sched_entity se;//普通進程的調度實體struct sched_rt_entity rt;//實時進程的調度實體
#ifdef CONFIG_CGROUP_SCHEDstruct task_group *sched_task_group;//指向進程所在的cgroup
#endifstruct sched_dl_entity dl;//deadline進程的調度實體#ifdef CONFIG_UCLAMP_TASK/* Clamp values requested for a scheduling entity */struct uclamp_se uclamp_req[UCLAMP_CNT];/* Effective clamp values used for a scheduling entity */struct uclamp_se uclamp[UCLAMP_CNT];
#endif#ifdef CONFIG_PREEMPT_NOTIFIERS/* List of struct preempt_notifier: */struct hlist_head preempt_notifiers;
#endif#ifdef CONFIG_BLK_DEV_IO_TRACEunsigned int btrace_seq;
#endifunsigned int policy;//進程調度策略int nr_cpus_allowed;//該進程允許使用的cpu的數量const cpumask_t *cpus_ptr;//表示該進程允許在哪個cpu上運行cpumask_t cpus_mask;//表示該進程不允許在哪個cpu上運行...struct sched_info sched_info;//記錄進程運行時的實時信息struct list_head tasks;//進程鏈表
#ifdef CONFIG_SMPstruct plist_node pushable_tasks;struct rb_node pushable_dl_tasks;
#endifstruct mm_struct *mm;//指向內存描述符,如果是內核進程,則為空struct mm_struct *active_mm;//指向內存描述符,如果是內核進程,則指向從進程借用的內存描述符/* Per-thread vma caching: */struct vmacache vmacache;#ifdef SPLIT_RSS_COUNTINGstruct task_rss_stat rss_stat;
#endifint exit_state;//進程的退出狀態,int exit_code;//進程的終止代號int exit_signal;//進程接受到的退出信號/* The signal sent when the parent dies: */int pdeath_signal;//表示當父進程死亡時要發送的信號,然后托管給0進程/* JOBCTL_*, siglock protected: */unsigned long jobctl;
...unsigned long atomic_flags; /* Flags requiring atomic access. */struct restart_block restart_block;pid_t pid;//表示該進程的進程號pid_t tgid;//表示該進程的線程號#ifdef CONFIG_STACKPROTECTOR/* Canary value for the -fstack-protector GCC feature: */unsigned long stack_canary;
#endif/** Pointers to the (original) parent process, youngest child, younger sibling,* older sibling, respectively. (p->father can be replaced with* p->real_parent->pid)*//* Real parent process: */struct task_struct __rcu *real_parent;//指向創建進程的進程描述符(生父進程),如果生父進程不存在了,指向進程1(systemd進程)/* Recipient of SIGCHLD, wait4() reports: */struct task_struct __rcu *parent;//指向進程的當前父進程,一般和real_parent一致,只有在調試的時候指向調試的進程/** Children/sibling form the list of natural children:*/struct list_head children;//指向該進程的子進程鏈表struct list_head sibling;//指向進程的兄弟進程struct task_struct *group_leader;//一般是指向自己,如果該進程是用戶創建的線程,則指向創建線程的進程.../* Process credentials: *//* Tracer's credentials at attach: */const struct cred __rcu *ptracer_cred;/* Objective and real subjective task credentials (COW): */const struct cred __rcu *real_cred;//指向客觀和真實的主觀任務憑證/* Effective (overridable) subjective task credentials (COW): */const struct cred __rcu *cred;//指向有效的主觀任務憑證#ifdef CONFIG_KEYS/* Cached requested key. */struct key *cached_requested_key;
#endif/** executable name, excluding path.** - normally initialized setup_new_exec()* - access it with [gs]et_task_comm()* - lock it with task_lock()*/char comm[TASK_COMM_LEN];//進程的名稱struct nameidata *nameidata;#ifdef CONFIG_SYSVIPCstruct sysv_sem sysvsem;//用于信號量struct sysv_shm sysvshm;//用于共享內存
#endif
#ifdef CONFIG_DETECT_HUNG_TASKunsigned long last_switch_count;unsigned long last_switch_time;
#endif/* 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;//指向進程的信號處理程序描述符sigset_t blocked;//表示被阻塞信號的掩碼sigset_t real_blocked;//臨時掩碼/* Restored if set_restore_sigmask() was used: */sigset_t saved_sigmask;//使用了set_restore_sigmask()則恢復的掩碼struct sigpending pending;//存放私有掛起信號的數據結構unsigned long sas_ss_sp;//信號處理程序備用堆棧的地址size_t sas_ss_size;//堆棧的大小unsigned int sas_ss_flags;//堆棧的標志位struct callback_head *task_works;
...
#ifdef CONFIG_LOCKDEP
# define MAX_LOCK_DEPTH 48ULu64 curr_chain_key;int lockdep_depth;//表示獲取大內核鎖的次數unsigned int lockdep_recursion;struct held_lock held_locks[MAX_LOCK_DEPTH];
#endif
...
};