操作系統的”進程”很早就出現了,許多教科書上定義這個概念總是晦澀難懂。電腦技術發展太快了,簡單的概念經過無數次演化,也會變得複雜。我們追溯一下操作系統的發展歷史,就能理解進程解決了什麼問題、為什麼這樣設計。進程是獨立功能的程式的一次動態執行過程,也是系統資源分配的獨立實體。每個進程都擁有獨立的地址... ...
操作系統的"進程"很早就出現了,許多教科書上講述這個概念總是晦澀難懂。電腦技術發展太快了,簡單的概念經過無數次演化,也會變得複雜。我們追溯一下操作系統的發展歷史,就能理解進程解決了什麼問題、為什麼這樣設計。
1 操作系統的發展
1.2 手工操作階段
第一代電腦使用真空管構成集成電路實現計算,採用磁鼓或者磁芯作為儲存器,採用打孔卡片作為輸入輸出的介質,卡片上的孔洞位置代表機器指令或者數據。
程式員直接與硬體打交道,不需要操作系統。先將已穿孔的紙帶裝入輸入機,然後啟動輸入機把程式和數據輸入電腦記憶體,通過控制台開關啟動計算程式。計算完畢,印表機輸出計算結果。用戶取走結果並卸下紙帶後,才讓下一個用戶上機。這種手工操作方式有兩個弊端:1. 用戶獨占全機,資源利用率低;2. CPU等待手工操作,利用不充分。
1.2 聯機批處理系統
第二代電腦採用聯機批處理系統,主機與輸入機之間增加速度較快的磁帶機,在監督程式的自動控制下,電腦先將用戶作業讀入磁帶,再把磁帶上的作業讀入主機記憶體並輸出計算結果。完成上一批作業後,監督程式又從輸入機上輸入另一批作業,按上述步驟重覆處理。監督程式實現了作業到作業的自動轉接,減少了手工操作時間,提高了電腦的利用率,可以看作操作系統的雛形。
在等待作業輸入和結果輸出時,主機的CPU仍處於空閑狀態,系統利用率依然不高。
1.3 多道程式系統
第三代電腦採用晶體管邏輯元件及快速磁芯存儲器,電腦速度從每秒幾千次提高到幾十萬次,主存儲器的存貯量,從幾千提高到10萬以上。1958年,IBM公司製成了第一臺全部使用晶體管的電腦RCA501型。
此時的磁帶、硬碟等IO設備,完全跟不上高速CPU的腳步,CPU利用率非常低。人們引入了多道程式設計技術。把多個程式放入記憶體,並允許它們在CPU中交替運行,共用系統中的各種軟硬體資源。當一道程式因IO請求而暫停運行時,CPU便立即轉去運行另一道程式。多道程式設計技術使CPU得到充分利用,提高了整個系統的資源利用率和吞吐量。出現了作業調度管理、處理機管理、存儲器管理、外部設備管理、文件系統管理等功能,這標志著操作系統日趨成熟。
1.4 分時系統
第四代電腦允許多個用戶同時聯機使用電腦,分時系統應運而生,進一步提高系統整體利用率。
若一個用戶的作業在分配的時間片內不能完成計算,則暫時中斷該作業,把處理機讓給另一作業使用,等待下一輪再繼續其運行。電腦速度很快,作業運行輪轉得很快,給每個用戶獨占了一臺電腦的錯覺。用戶可以通過自己的終端向系統發出控制命令,在充分的人機交互情況下完成作業的運行。
1960年,麻省理工學院(MIT)開發了相容分時系統(Compatible Time Sharing System),支持大型主機通過多個終端機來聯機進行運算,並將結果從主機傳輸到終端機。此時終端機只具有輸入輸出的功能,本身不具任何運算或軟體安裝的能力。
為了強化大型主機的功能,讓主機的資源讓更多人來利用。1965年,由貝爾實驗室(Bell)、麻省理工學院(MIT)及通用電器公司(GE)共同發起了MULTICS(多路信息計算系統)的計劃,目標是讓大型主機連接1000部終端機,支持300個用戶同時使用。MULTICS項目是所有現代操作系統的鼻祖,並且首次提出了“進程”的概念。
進程就是用戶獨占電腦錯覺的基礎,每個用戶的作業被包裝成一個或者多個進程。進程是獨立功能的程式的一次動態執行過程,也是系統資源分配的獨立實體。
2 低速的IO設備
1928年,德國工程師Fritz Pfleumer發明瞭存儲模擬信號的錄音磁帶。工作原理是將粉碎的磁性顆粒用膠水粘在紙條上,但是紙條比較脆弱,錄音磁帶無法實用化。
1932年,IBM公司的奧地利裔工程師Gustav Tauschek發明瞭磁鼓存儲器,長度為16英寸,有40個磁軌,每分鐘可旋轉12500轉,僅可以存儲10KB數據。在磁芯存儲器出現之前廣泛用於電腦記憶體,被認為是硬碟驅動器的前身。磁鼓的優點為實用可靠、經濟實惠,而缺點是存儲容量太小、利用率低。
1948年,美國哈佛大學實驗室的王安博士發明瞭磁芯存儲器,次年又實現了磁芯存儲器“讀後即寫”的技術。原理是在鐵氧體磁環里穿進一根導線,導線中流過不同方向的電流時,可使磁環按兩種不同方向磁化,代表“1”或“0”的信息便以磁場形式儲存下來。最初的磁芯存儲器只有幾百個位元組的容量。在20世紀70年代被廣泛用作電腦的主存儲器,直到Intel的半導體DRAM記憶體批量生產。
1952年,IBM公司發佈了電腦業內的第一款磁帶機,需要人工手動操作,比如裝載、卸載和歸檔。
1956年,IBM公司製造了世界上第一塊硬碟350RAMAC。碟片直徑為24英寸,碟片數為50片,重量則是上百公斤,相當於兩個冰箱的體積,儲存容量只有5MB。
從早期的磁鼓到固態硬碟,IO設備速度提升了萬倍,依然追不上CPU,根本原因是計算和存儲的目標不一樣。CPU的最重要指標是速度快;而存儲設備則要容量大,其次是速度快,最後是造價低廉,這三點不可兼得。就好比兩個運動員,一個練短跑,一個練長跑,對身體素質的要求不一樣。
3 進程詳解
我們以Linux系統為例,瞭解一下進程的基本構成。
3.1 進程的分類
- 交互進程:由一個shell啟動的進程,優先順序較高,交互進程既可以在前臺運行,也可以在後臺運行。
- 批處理進程:這種進程和終端沒有聯繫,是一個進程式列,負責按照順序啟動其它進程。
- 守護進程:在後臺運行且不受任何終端控制的特殊進程,用於執行特定的系統任務。它一般在Linux啟動時開始執行,系統關閉時才結束。
3.2 進程的組成
Linux內核把進程稱為task,task_struct結構體是操作系統的進程式控制制塊,包含著一個進程的所有信息。task_struct結構體非常複雜,感興趣的朋友可以查閱文件https://github.com/torvalds/linux/blob/master/include/linux/sched.h
,以下列舉了比較重要的欄位:
- 1)進程的標識
pid_t pid:進程的唯一標識
pid_t tgid:線程組的領頭線程的pid成員的值
- 2)進程調度信息
need_resched:調度標誌
Nice:靜態優先順序
Counter:動態優先順序;重新調度進程時會在run_queue中選出Counter值最大的進程。也代表該進程的時間片,運行中不斷減少。
Policy:調度策略開始運行時被賦予的值。
rt_priority:實時優先順序
- 3)進程通信有關信息
unsigned long signal:進程接收到的信號。每位表示一種信號,共32種。
unsigned long blocked:進程所能接受信號的位掩碼。
Spinlock_t sigmask_lock:信號掩碼的自旋鎖
Long blocked:信號掩碼
Struct sem_undo *semundo:為避免死鎖而在信號量上設置的取消操作
Struct sem_queue *semsleeping:與信號量操作相關的等待隊列
struct signal_struct *sig:信號處理函數
- 4)上下文信息
struct desc_struct *ldt:進程關於CPU段式存儲管理的局部描述符表的指針
struct thread_struct tss:任務狀態段
- 5)時間信息
Start_time:進程創建時間
Per_cpu_utime:進程在執行時在用戶態上耗費的時間
Pre_cpu_stime:進程在執行時在系統態上耗費的時間
ITIMER_REAL:實時定時器,不論進程是否運行,都在實時更新
ITIMER_VIRTUAL:虛擬定時器,只有進程運行在用戶態時才會更新
ITIMER_PROF:概況定時器,進程在運行處於用戶態和系統態時更新
- 6)地址空間/虛擬記憶體信息
struct mm_struct * mm:記錄進程記憶體使用的信息
3.3 進程的記憶體分佈
為了更加高效地使用記憶體,Linux系統採用了虛擬記憶體的方案,將物理記憶體和磁碟都映射為虛擬記憶體地址,為進程提供統一的地址空間。虛擬記憶體有三個優點:1)將記憶體當作磁碟的緩存,在記憶體中只保留常用數據,必要時從記憶體和磁碟之間交換數據。2)簡化記憶體管理,為每個進程提供統一的地址空間。3)保護進程的記憶體空間不受其他進程影響。
每個進程都擁有獨立的地址空間。一個進程無法訪問另一個進程的變數和數據結構,如果想讓一個進程訪問另一個進程的資源,要使用進程間通信,比如管道、文件、套接字等。
進程的虛擬地址空間分為用戶虛擬地址空間和內核虛擬地址空間,所有進程共用內核虛擬地址空間,每個進程有獨立的用戶虛擬地址空間。以32位Linux系統為例,共有4G的定址能力。預設將高地址的1G空間分配給內核,稱為內核空間,剩下的3G空間分配給進程使用,稱為用戶空間。用戶空間從低地址到高地址空間包含5個部分:
- 代碼段(text segment):存放程式的可執行二進位代碼。
- 數據段(data segment):存放程式中已經初始化且初值不為0的全局變數和靜態局部變數,數據段屬於靜態記憶體分配。
- BSS段:存放未初始化的全局變數和靜態局部變數;初值為0的全局變數和靜態局部變數。
- 堆(heap):用於存放程式運行時動態分配的記憶體段,可動態擴張或者縮減。
- 棧(stack):由編譯器自動分配釋放,它存放函數內部聲明的非靜態局部變數,以及記錄函數調用過程的相關維護信息(稱為棧幀)。
3.4 進程的調度
每個CPU(或核心)在一個時間點上只能處理一個進程。操作系統調配CPU時間和其他資源,為進程分配一個狀態,進程的狀態隨著環境要求而改變。進程運行的整個生命周期劃分為六種狀態:
- Running:表示進程處在CPU的就緒隊列中,運行態或者就緒態的進程
- Disk Sleep:不可中斷狀態睡眠,一般表示進程正在跟硬體發送交互,並且交互過程中不允許被其他進程或中斷打斷
- Zombie:僵屍進程,表示進程實際上已經結束了,但是父進程還沒有回收它的資源(比如進程的描述符,PID等)
- Interruptible Sleep:可中斷狀態睡眠,表示進程因等待某個事件而被系統掛起(阻塞態)。當進程等待的事件發生時,就會被喚醒併進入Running狀態
- Idle:空閑狀態。用在不可中斷睡眠的內核線程上。硬體交互導致的不可中斷進程用D表示,但對某些內核線程來說,處在不可中斷睡眠時有可能實際上並沒有任何負載。
- Stopped OR Traced:表示進程處於暫停或者跟蹤狀態。向一個進程發送SIGSTOP信號,它就會因響應這個號變成暫停狀態(Stopped);向它發送SIGCONT信號,進程又會恢復運行(如果進程是終端里直接啟動的,則需要用fg命令恢復到前臺運行)。當用調試器調試一個進程時,在使用斷點中斷進程後,進程就會變成跟蹤狀態,這其實也是一種特殊的暫停狀態,只不過可以用調試器來跟蹤並按需要控制進程的運行。
- EXIT:進程已經消亡
4 參考
https://www.linuxprobe.com/linux-system-programming.html
https://zhuanlan.zhihu.com/p/442909853
https://blog.csdn.net/qq_41209741/article/details/82870876
公 眾 號:編碼磚家
獨立博客:codingbrick.com
文章出處:https://www.cnblogs.com/xiaoyangjia/p/16615795.html
本文版權歸作者所有,任何人或團體、機構全部轉載或者部分轉載、摘錄,請在文章明顯位置註明作者和原文鏈接。