上一篇博文複習了操做系統總的概述——個人操做系統複習——操做系統概述 ,包括對操做系統的定義、發展歷程以及操做系統結構。接下來咱們就開始詳細複習計算機知識,包括進程、處理器、存儲器等等。本篇首先對進程這個及其重要的概念進行復習,這是進程系列的上篇。html
併發是什麼?很簡單,前面介紹的多道批處理系統就是典型的併發執行。這裏再次過一遍高性能的多道批處理系統,其本質在於保持對系統資源的佔用,CPU運行一個任務,若這個任務中斷,如須要IO請求之類的,那麼CPU直接去運行其餘任務,原任務的IO請求由IO設備本身處理。有一個著名的圖——表示併發:linux
如圖,假設計算機有輸入、計算、輸出這三個部件,一組任務順序執行,併發就是如圖流水線同樣的各部件配合。某一時刻,只有一個程序在佔用CPU(計算設備)。那麼什麼是並行呢?並行是創建在多核的基礎上的,即多個CPU同時運行,那麼有幾個CPU,同一時刻就有幾個程序同時運行。因此電腦只有一個CPU的苦逼程序員只能併發了。程序員
現代系統的實際併發比上圖要複雜的多,現代計算機系統的併發是以時間片爲基礎的,即在很短的時間內,每一個進程都分別運行一次。這樣,宏觀上,每一個進程都在不斷的運行,而實際上每一個進程的運行都是間斷運行的。那麼問題來了,什麼是進程呢?數組
進程的目的就是爲了對併發執行的程序進行控制。進程實體由程序段、數據段、PCB三部分構成。咱們知道計算機運行的本質就是對數據的處理的機器。session
——數據段就是各類數據數據結構
——程序段就是一系列操做計算機的指令,即操做數據的方法策略併發
——PCB 即進程控制塊(Process Control Block),控制運行程序段的時機。app
書本上是這樣定義進程的:「進程是進程實體的運行過程,是系統進行資源分配和調度的一個獨立單位」。異步
讓咱們理清楚思緒,進程是什麼?進程就是進程實體的運行過程,是一個過程。就是說,進程實體不運行,那就不叫進程。一個沒有被調用的進程實體,不叫進程。因此說,進程有動態的特徵。上面提到,進程的目的就是爲了對併發執行的程序進行控制。爲了在一個時間片內,運行多個程序才引進進程。因此說,進程有併發的特徵。進程實體是一個擁有獨立的資源(程序段和數據段)、(由於PCB)能獨立地接受調度並獨立的運行的基本單位。因此說,進程有獨立的特徵。進程運行的過程當中,因爲涉及到的資源衆多、運行環境不必定,也受到其餘進程的影響,因此,進程的運行狀況是不可具體預知的。因此書本定義,進程按各自獨立的、不可預知的速度向前推動。因此說,進程有異步的特徵。如上文所說,進程是進程實體這一數據結構被調用運行。因此說,進程有結構的特徵。ide
能夠這麼說,真正理解了進程的這五個特徵,纔算理解了進程這個概念。高手跟咱們這些菜鳥的最大區別,不就在於對系統的理解嗎?
進程有3種基本狀態:
(1)就緒(Ready)狀態
此時的進程擁有完整的進程實體,只要得到CPU,即只要被調用就能立刻執行,這種狀態被稱爲就緒狀態。處於這種狀態的進程都會被放進就緒隊列,以便隨時接受CPU調用。
(2)執行狀態
此時的進程已經得到CPU,正在執行。
(3)阻塞狀態
執行中的進程,由於某種緣由(IO請求)沒法繼續執行的一種暫停狀態,暫停完畢就會變成就緒狀態。
除了以上的3種基本狀態,有的系統額外還增長了一種狀態。
(4)掛起狀態
爲何須要掛起狀態?由於有時候但願某些正在執行的線程暫停下來,持續一段時間後,讓它回到以前的狀態。
掛起狀態是一種靜止的狀態,至關於把某個進程從執行的流水線上拿出來,等到須要的時候再把它放進去繼續執行。咱們來看前三種基本狀態,就緒 ->執行 -> 阻塞,阻塞完畢又回到就緒。因爲線程的異步性,阻塞是會在不肯定的有限時間內結束的。就是說,三種基本狀態是動態的,一般不存在一個線程一直處於某種狀態。掛起狀態相對於它們來講,是靜止的,由於它是被控制的,是對以不可預知的速度前進的線程的一種干擾。
此外,爲了管理的須要,一般還有兩種比較常見的狀態。
(5)建立狀態
咱們知道進程實體包括程序段、數據段和PCB。建立狀態指的是PCB已經被建立,由於某些緣由(程序段或數據段未放入內存等),進程還未被放入就緒隊列的這種狀態。
(6)終止狀態
線程的終止也是有個過程的。終止狀態指的是線程除了PCB之外的系統資源都被回收後的狀態。此時線程真正終止。
做爲進程實體的一部分,PCB是用來控制進程運行的一種數據結構。它包含了進程的狀態、優先級、運行的狀態、處理機狀態、程序數據的內存地址等各類信息,一旦被操做系統調用,操做系統就從PCB中獲取的信息,來恢復進程阻塞前的現場,繼續執行。PCB通常都保存在CPU的寄存器中。
1)進程標識符。用來標識惟一的一個進程。包括方便系統調用的內部標識符和方便用戶調用的外部標識符。進程標識符一般還包括父、子進程,以及所屬用戶等信息。
2)處理機狀態信息。
處理機狀態信息指的是處理機調用線程時的環境信息。處理機處理調用進程時,運行過程當中的許多信息都放在處理機的寄存器中。進程阻塞或掛起時,寄存器中的運行信息會保存到PCB中,以便進程下次被調用時恢復以前的運行現場。
3)進程調度信息。
進程調度信息指的是本進程調度所需的必要信息。包括,本進程的狀態(6種之一)、進程優先級、進程等待時間、進程執行時間(可能決定優先級)、阻塞緣由、父子進程關係等。
4)進程控制信息。
進程控制信息指的是進程的資源信息和進程切換時的所需信息,包括進程的程序和數據的內存地址、進程同步和通訊的機制、進程資源的清單、指向下一個進程PCB的指針(若PCB的組織方式是連接方式)等。
這裏咱們來看一下Unix中,PCB的具體結構,以便對PCB有一個清晰的認識。這玩意其實就這麼一回事:
(下面代碼摘自http://blog.sina.com.cn/s/blog_65403f9b0100gs3a.html)
struct task_struct { volatile long state; //說明了該進程是否能夠執行,仍是可中斷等信息 unsigned long flags; //Flage 是進程號,在調用fork()時給出 int sigpending; //進程上是否有待處理的信號 mm_segment_t addr_limit; //進程地址空間,區份內核進程與普通進程在內存存放的位置不一樣 //0-0xBFFFFFFF for user-thead //0-0xFFFFFFFF for kernel-thread //調度標誌,表示該進程是否須要從新調度,若非0,則當從內核態返回到用戶態,會發生調度 volatile long need_resched; int lock_depth; //鎖深度 long nice; //進程的基本時間片 //進程的調度策略,有三種,實時進程:SCHED_FIFO,SCHED_RR, 分時進程:SCHED_OTHER unsigned long policy; struct mm_struct *mm; //進程內存管理信息 int processor; //若進程不在任何CPU上運行, cpus_runnable 的值是0,不然是1 這個值在運行隊列被鎖時更新 unsigned long cpus_runnable, cpus_allowed; struct list_head run_list; //指向運行隊列的指針 unsigned long sleep_time; //進程的睡眠時間 //用於將系統中全部的進程連成一個雙向循環鏈表, 其根是init_task struct task_struct *next_task, *prev_task; struct mm_struct *active_mm; struct list_head local_pages; //指向本地頁面 unsigned int allocation_order, nr_local_pages; struct linux_binfmt *binfmt; //進程所運行的可執行文件的格式 int exit_code, exit_signal; int pdeath_signal; //父進程終止是向子進程發送的信號 unsigned long personality; //Linux能夠運行由其餘UNIX操做系統生成的符合iBCS2標準的程序 int did_exec:1; pid_t pid; //進程標識符,用來表明一個進程 pid_t pgrp; //進程組標識,表示進程所屬的進程組 pid_t tty_old_pgrp; //進程控制終端所在的組標識 pid_t session; //進程的會話標識 pid_t tgid; int leader; //表示進程是否爲會話主管 struct task_struct *p_opptr,*p_pptr,*p_cptr,*p_ysptr,*p_osptr; struct list_head thread_group; //線程鏈表 struct task_struct *pidhash_next; //用於將進程鏈入HASH表 struct task_struct **pidhash_pprev; wait_queue_head_t wait_chldexit; //供wait4()使用 struct completion *vfork_done; //供vfork() 使用 unsigned long rt_priority; //實時優先級,用它計算實時進程調度時的weight值 //it_real_value,it_real_incr用於REAL定時器,單位爲jiffies, 系統根據it_real_value //設置定時器的第一個終止時間. 在定時器到期時,向進程發送SIGALRM信號,同時根據 //it_real_incr重置終止時間,it_prof_value,it_prof_incr用於Profile定時器,單位爲jiffies。 //當進程運行時,無論在何種狀態下,每一個tick都使it_prof_value值減一,當減到0時,向進程發送 //信號SIGPROF,並根據it_prof_incr重置時間. //it_virt_value,it_virt_value用於Virtual定時器,單位爲jiffies。當進程運行時,無論在何種 //狀態下,每一個tick都使it_virt_value值減一當減到0時,向進程發送信號SIGVTALRM,根據 //it_virt_incr重置初值。 unsigned long it_real_value, it_prof_value, it_virt_value; unsigned long it_real_incr, it_prof_incr, it_virt_value; struct timer_list real_timer; //指向實時定時器的指針 struct tms times; //記錄進程消耗的時間 unsigned long start_time; //進程建立的時間 //記錄進程在每一個CPU上所消耗的用戶態時間和核心態時間 long per_cpu_utime[NR_CPUS], per_cpu_stime[NR_CPUS]; //內存缺頁和交換信息: //min_flt, maj_flt累計進程的次缺頁數(Copy on Write頁和匿名頁)和主缺頁數(從映射文件或交換 //設備讀入的頁面數); nswap記錄進程累計換出的頁面數,即寫到交換設備上的頁面數。 //cmin_flt, cmaj_flt, cnswap記錄本進程爲祖先的全部子孫進程的累計次缺頁數,主缺頁數和換出頁面數。 //在父進程回收終止的子進程時,父進程會將子進程的這些信息累計到本身結構的這些域中 unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap; int swappable:1; //表示進程的虛擬地址空間是否容許換出 //進程認證信息 //uid,gid爲運行該進程的用戶的用戶標識符和組標識符,一般是進程建立者的uid,gid //euid,egid爲有效uid,gid //fsuid,fsgid爲文件系統uid,gid,這兩個ID號一般與有效uid,gid相等,在檢查對於文件 //系統的訪問權限時使用他們。 //suid,sgid爲備份uid,gid uid_t uid,euid,suid,fsuid; gid_t gid,egid,sgid,fsgid; int ngroups; //記錄進程在多少個用戶組中 gid_t groups[NGROUPS]; //記錄進程所在的組 //進程的權能,分別是有效位集合,繼承位集合,容許位集合 kernel_cap_t cap_effective, cap_inheritable, cap_permitted; int keep_capabilities:1; struct user_struct *user; struct rlimit rlim[RLIM_NLIMITS]; //與進程相關的資源限制信息 unsigned short used_math; //是否使用FPU char comm[16]; //進程正在運行的可執行文件名 //文件系統信息 int link_count, total_link_count; //NULL if no tty 進程所在的控制終端,若是不須要控制終端,則該指針爲空 struct tty_struct *tty; unsigned int locks; //進程間通訊信息 struct sem_undo *semundo; //進程在信號燈上的全部undo操做 struct sem_queue *semsleeping; //當進程由於信號燈操做而掛起時,他在該隊列中記錄等待的操做 //進程的CPU狀態,切換時,要保存到中止進程的task_struct中 struct thread_struct thread; //文件系統信息 struct fs_struct *fs; //打開文件信息 struct files_struct *files; //信號處理函數 spinlock_t sigmask_lock; struct signal_struct *sig; //信號處理函數 sigset_t blocked; //進程當前要阻塞的信號,每一個信號對應一位 struct sigpending pending; //進程上是否有待處理的信號 unsigned long sas_ss_sp; size_t sas_ss_size; int (*notifier)(void *priv); void *notifier_data; sigset_t *notifier_mask; u32 parent_exec_id; u32 self_exec_id; spinlock_t alloc_lock; void *journal_info; };
系統中擁有衆多PCB,對應着衆多進程,那麼這些PCB怎麼組織的呢?通常有兩種組織方式:連接方式和索引方式。這兩種方式的共同點在於,正在執行的PCB,都有一個執行指針指向它。不一樣在於,連接方式的就緒隊列、阻塞隊列等,經過指針連接的方式組織。進程切換時,直接取就緒隊列指針便可,由於它指向的就是當前優先級最高的就緒的PCB,隨後就緒隊列指針指向其指向的下一個PCB。索引方式的就緒隊列、阻塞隊列等,經過一個表的形式來組織,就緒隊列指針指向這個表的第一條數據。這個表本質是一個指針數組,第一個指針指向的固然是優先級最高的就緒進程。
進程控制是什麼?本質就是切換進程狀態的控制。 通常由操做系統中的原語實現。原語即具備「原子操做」這種屬性的若干指令集合,說白了,就是這些指令集合,要麼所有執行,要麼所有不執行。不一樣操做系統的原語也是有區別的。
進程多是由系統內核收到請求而建立,也可能由進程自己建立,由進程自己建立的進程通常是子進程,它繼承父進程擁有的所有資源。建立進程由進程建立原語實現,一般由下面幾個步驟:
進程的終止是由操做系統執行的。當一個進程因各類緣由結束時,會通知操做系統。操做系統會調用進程終止原語來終止對應進程:
進程的阻塞是由進程自身主動執行的。但進程發現自身沒法繼續執行時,就主動調用進程阻塞原語,把本身阻塞:
進程的喚醒一般由其餘線程執行。但其餘線程因爲某些事件但願執行線程執行時,會調用進程喚醒原語將指定進程喚醒:
值得注意的是,進程喚醒和進程阻塞是一對做用恰好相反的原語。阻塞的進程必須由進程喚醒操做才能繼續執行。
進程掛起由自身或其餘進程執行。進程激活由其餘進程執行。過程很簡單,就不畫圖了:
1)進程掛起:若進程爲活動就緒,就將其改成靜止就緒;若進程爲活動阻塞,就將其改成靜止阻塞;若進程正在執行,則讓調度程序從新調度。
(PS:因爲沒有掛起隊列,因此須要把進程的PCB複製到指定的內存區域)
2)進程激活:若進程爲靜止就緒,就將其改成活動就緒;若進程爲靜止阻塞,就將其改成活動阻塞;
參考:《計算機操做系統(湯子瀛)》