步入進程世界:初學者必懂的操作系統概念
- 一. 馮諾依曼體系結構
- 1.1 背景與歷史
- 1.2 組成部分
- 1.3 意義
- 二. 進程
- 2.1 進程概念
- 2.1.1 PCB(進程控制塊)
- 2.2 查看進程
- 2.2.1 使用系統文件查看
- 2.2.2 使?top和ps這些??級?具來獲取
- 2.2.3 通過系統調用獲取進程標識符
- 2.3 創建子進程
- 2.4 進程狀態
- 2.4.1 查看進程狀態
- 三. 最后
前言
在計算機系統中,進程是執行程序的基本單位。它不僅僅是代碼的集合,更是操作系統管理和分配資源的核心對象。每當我們運行一個應用程序,操作系統就會為其創建一個進程,使得程序能夠在計算機中獨立執行并進行資源管理。理解進程的概念對于深入學習操作系統和高效利用計算機資源至關重要。 接下來的篇章將帶領大家深入探討進程管理。
💬 歡迎討論:如果你在學習過程中有任何問題或想法,歡迎在評論區留言,我們一起交流學習。你的支持是我繼續創作的動力!
👍 點贊、收藏與分享:覺得這篇文章對你有幫助嗎?別忘了點贊、收藏并分享給更多的小伙伴哦!你們的支持是我不斷進步的動力!
🚀 分享給更多人:如果你覺得這篇文章對你有幫助,歡迎分享給更多對Linux OS感興趣的朋友,讓我們一起進步!
一. 馮諾依曼體系結構
1.1 背景與歷史
馮·諾依曼體系結構是現代計算機的基礎設計模型,由約翰·馮·諾依曼于1945年提出。該結構的核心思想是將程序和數據存儲在同一個內存中,計算機通過中央處理單元(CPU)按順序執行指令。再此向大佬致敬。👏
1.2 組成部分
- CPU(中央處理器)由運算器和控制器組成。
存儲器(內存)是CPU進行讀、獲取的必要手段,CPU執行程序,必須先加載,將代碼及數據加載到內存,CPU執行代碼,訪問數據。
結構由五個基本組成部分構成:輸入設備、輸出設備、存儲器、運算器和控制單元。
1.3 意義
它的意義在于簡化了計算機設計,提高了計算機的可編程性,使得不同的程序可以通過修改存儲器中的指令和數據來實現多種功能。馮·諾依曼體系結構成為現代計算機系統的標準架構,推動了計算機技術的飛速發展。
二. 進程
2.1 進程概念
進程是程序的一次執行實例,是操作系統進行資源分配和調度的基本單位。它擁有獨立的內存空間、系統資源(如文件句柄、網絡端口)和運行狀態。
本人理解:進程 = 內核數據結構對象 + 代碼和數據 (進程 = PCB(進程控制塊) + 代碼和數據)
2.1.1 PCB(進程控制塊)
進程的所有屬性全保存在PCB中,Linux操作系統下的PCB為 task_struct, 該結構體為Linux內核中的一種數據結構,它會被加載到內存(RAM)中。
task_struct具有的部分信息,另一部分的信息將在后續的博客講解
標?符: 描述本進程的唯?標?符,?來區別其他進程。
? 狀態: 任務狀態,退出代碼,退出信號等。
? 優先級: 相對于其他進程的優先級。
? 程序計數器: 程序中即將被執?的下?條指令的地址。
? 內存指針: 包括程序代碼和進程相關數據的指針,還有和其他進程共享的內存塊的指針
? 上下?數據: 進程執?時處理器的寄存器中的數據[休學例?,要加圖CPU,寄存器]。
? I∕O狀態信息: 包括顯?的I/O請求,分配給進程的I∕O設備和被進程使?的?件列表。
? 記賬信息: 可能包括處理器時間總和,使?的時鐘數總和,時間限制,記賬號等。
OS如何對各種進程進行的管理的?先描述,在組織。
所有運行的進程都以 task_struct 鏈表的形式存在內核中。
2.2 查看進程
2.2.1 使用系統文件查看
命令格式:
ls /proc/ [pid]
- 功能
查看指定進程的進程信息。不加pid則查看所有進程的信息
2.2.2 使?top和ps這些??級?具來獲取
命令格式:
- ps ajx | grep myprocess | grep -v grep
2.2.3 通過系統調用獲取進程標識符
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
printf("pid: %d\n", getpid());
printf("ppid: %d\n", getppid());
return 0;
}
- getpid()系統調用,用于獲取當前進程的 PID(Process ID),在子進程中調用 getpid(),返回的是子進程自身的 PID。
- getppid()系統調用,用于獲取當前進程的父進程PID。
2.3 創建子進程
示例代碼:
#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>int gval = 100;int main()
{printf("父進程開始運行 ,pid: %d\n",getpid());pid_t id = fork();if(id < 0){perror("id");return 1;}else if(id == 0){printf("我是一個子進程 !, 我的pid:%d, 我的父進程id:%d, gval: %d\n",getpid(),getppid(),gval);sleep(5);//childwhile(1){sleep(1);printf("子進程修改變量 : %d -> %d", gval, gval+10);gval+=10;printf("我是一個子進程 !, 我的pid:%d, 我的父進程id:%d\n",getpid(),getppid());}}else{//fatherwhile(1){sleep(1);printf("我是一個父進程 !, 我的pid:%d, 我的父進程id:%d, gval: %d\n",getpid(),getppid(),gval);}}printf("進程開始運行 ,pid: %d\n",getpid());return 0;
}
子進程會共享父進程的代碼和數據,如果父子任何一方對數據進行修改,OS系統會在底層第數據進行拷貝,讓目標進程修改這個拷貝。
問題1:為什么fork給父子返回各自不同的返回值?
- 父進程中的返回值:
fork() 在父進程中返回 子進程的 PID(進程 ID)。這個 PID 是一個正整數,唯一標識子進程。父進程可以使用該 PID 來跟蹤子進程,執行如等待子進程結束、獲取子進程的狀態等操作。
- 子進程中的返回值:
在子進程中,fork() 返回 0。子進程通過這個返回值可以判斷自己是否是子進程,父進程通過返回值判斷是否是父進程。
問題2:為什么一個函數會返回兩次?
- 父進程的情況:
返回值是子進程的 PID(進程ID),是一個正整數。
父進程用這個 PID 來識別和管理子進程。父進程可以使用這個 PID 來執行如 wait()、waitpid() 等系統調用,等待子進程終止或獲取子進程的退出狀態。
- 子進程的情況:
返回值是 0。
子進程用返回值 0 來判斷自己是子進程,以便執行不同于父進程的代碼。子進程可能會通過這個返回值執行某些特定的初始化工作或處理。
結論:fork() 會返回兩次是因為它在父進程和子進程中分別執行,父進程獲得的是子進程的 PID,子進程獲得的是 0。通過這個返回值,父進程和子進程可以執行不同的代碼,保證了進程的正確管理和并行執行。
問題3:fork兩個返回值各種給??如何返回?
- 父進程:父進程使用返回子進程的PID,來管理或等待子進程。
- 子進程:子進程返回0,子進程用這個值來判斷自己是子進程,以執行不同于父進程代碼邏輯(比如初始化、執行任務等)。
- fork返回負值,表示fork調用失敗(資源不足等)它會返回 -1,并且沒有子進程創建。操作系統會設置 errno 來表示具體的錯誤原因。
2.4 進程狀態
請看Linux內核關于進程狀態的描述:
static const char *const task_state_array[] = {
“R (running)”, /*0 */
“S (sleeping)”, /*1 */
“D (disk sleep)”, /*2 */
“T (stopped)”, /*4 */
“t (tracing stop)”, /*8 */
“X (dead)”, /*16 */
“Z (zombie)”, /*32 */
};
- R(running): 運行狀態,表示某進程正在運行或準備運行。
- S(sleep): 睡眠狀態,進程在等待某些事件完成(也稱為淺度睡眠,可中斷睡眠)。
- D(disk sleep): 磁盤休眠狀態,進程通常在等待I/O操作完成(也稱為深度睡眠,不可中斷睡眠)。
- T(stopped): 停止狀態,可以通過發送信號讓該進程繼續進行。
- t(Tracing stop): 通常是在進行調試或進程跟蹤時出現的狀態。
- X(dead): 死亡狀態,不會出現在進程列表中。
- Z(Zombie): 僵尸狀態,子進程已結束,父進程為獲取子進程的退出信息。
2.4.1 查看進程狀態
使用ps aux 或 ps ajx 命令可以查看進程的詳細狀態。
命令選項含義:
- a:顯??個終端所有的進程,包括其他??的進程。
- x:顯?沒有控制終端的進程,例如后臺運?的守護進程。
- j:顯?進程歸屬的進程組ID、會話ID、?進程ID,以及與作業控制相關的信息。
- u:以??為中?的格式顯?進程信息,提供進程的詳細信息,如??、CPU和內存使?情況等。
僵尸進程危害
進程的退出狀態必須被維持下去,因為他要告訴關?它的進程(?進程),你交給我的任務,我辦的怎么樣了。可?進程如果?直不讀取,那?進程就?直處于Z狀態。
? 維護退出狀態本?就是要?數據維護,也屬于進程基本信息,所以保存在task_struct(PCB)中,
換句話說,Z狀態?直不退出,PCB?直都要維護?是的!
? 那?個?進程創建了很多?進程,就是不回收,是不是就會造成內存資源的浪費。因為數據結構對象本?就要占?內存,想想C中定義?個結構體變量(對象),是要在內存的某個位置進?開辟空間!
? 內存泄漏
孤兒進程
?進程如果提前退出,那么?進程后退出,進?Z之后,那該如何處理呢?
? ?進程先退出,?進程就稱之為“孤?進程”
? 孤?進程被1號init進程領養,當然要有init進程回收
如何避免孤兒進程
- 父進程等待子進程退出(避免子進程成為孤兒進程)
父進程可以通過適當的進程管理確保它會在子進程結束時正確地回收子進程的資源,避免進程成為孤兒進程。常用的技術包括:
wait() 或 waitpid():父進程可以使用 wait() 或 waitpid() 來等待子進程結束,并獲取子進程的退出狀態,從而清理和回收子進程的資源。這樣,即使父進程在結束之前退出,它也能確保子進程的資源得到回收。
- 使用 signal() 或 sigaction() 捕獲終止信號
如果父進程結束時沒有來得及清理子進程的資源,可以通過捕獲特定信號(如 SIGCHLD)來及時回收子進程的資源。通過設置 SIGCHLD 信號處理函數,父進程可以在子進程結束時自動清理。
- 避免父進程直接退出
父進程應該在子進程完成后再退出,例如通過使用 wait() 等系統調用等待子進程完成后再退出。如果父進程在子進程結束前退出,可能導致子進程變成孤兒進程。
定期檢查子進程狀態,使用 waitpid() 等方法主動回收子進程的資源。
三. 最后
本文主要介紹了操作系統中的進程管理,包括馮諾依曼體系結構、進程概念、進程控制塊(PCB)、進程狀態及如何查看和管理進程。
馮諾依曼體系結構是現代計算機設計的基礎,它將程序和數據存儲在同一內存中,提升了計算機的可編程性。進程則是程序的執行實例,操作系統通過進程控制塊(PCB)來管理進程的資源和狀態。進程通過 fork() 創建子進程,父子進程通過返回值區分身份。進程有不同的狀態,如運行、睡眠、停止等,父進程可以通過 wait() 等系統調用回收子進程資源,避免孤兒進程的產生。如果父進程提前退出,子進程可能成為孤兒進程,操作系統會由 init 進程接管。為了避免孤兒進程,父進程應在子進程結束后再退出,并通過信號捕獲機制及時清理資源。
路雖遠,行則將至;事雖難,做則必成
親愛的讀者們,下一篇文章再會!! \color{Red}親 愛 的 讀 者 們 , 下 一 篇 文 章 再 會 ! ! 親愛的讀者們,下一篇文章再會!!