目錄
1.馮諾依曼體系結構
?小結:
2.操作系統?
概念:
結構示意圖:?
理解操作系統:
用戶使用底層硬件層次圖:?編輯
?3.進程
概念
?結構示意圖
task_ struct內容分類?
典型用法示例
觀察進程:
了解 PID PPID
查看進程
1.馮諾依曼體系結構
- 輸入設備:鍵盤、鼠標、攝像頭、話筒、磁盤、網卡等,這些設備用于向計算機輸入數據和指令。
- 輸出設備:顯示器、聲卡、磁盤、網卡等,用于將計算機處理的結果輸出展示。
- CPU:由運算器和控制器組成,是計算機的核心部件,負責數據的運算和控制計算機各部件協調工作。
- 存儲器:內存,用于臨時存儲計算機正在運行的程序和數據。
CPU 不和外設直接打交道,CPU 只和內存打交道。?
外設 (輸入和輸出) 的數據,不是直接給 CPU 的,而是先要放入內存中。?
程序由代碼和數據組成,未被加載到內存時,以二進制文件形式存儲在磁盤等外部存儲設備中。?
數據流動:數據在計算機體系結構中流動并進行加工處理,從一個設備到另一個設備本質上是一種拷貝操作。?
數據設備間的拷貝的效率,決定了計算機整機的基本效率。
越靠近cpu,容量越小,速度越快,價格越高。
?小結:
- 存儲特性:距離 CPU 越近的存儲設備,數據處理效率越高,但成本也越高。
- 硬件數據流動規則:
- CPU 不直接與外設交互,僅與內存進行數據交換。
- 外設的數據需先存入內存,再由內存傳遞給 CPU 處理。
- 程序運行機制:
- 程序由代碼和數據組成,在運行時需要加載到內存中,因為 CPU 只會從內存中讀取代碼和數據。
- 當程序未被加載到內存時,以二進制文件形式存儲在磁盤等外部存儲設備中。
2.操作系統?
概念:
任何計算機系統都包含一個基本的程序集合,稱為操作系統(OS)。
籠統的理解,操作系統包括:
內核(進程管理,內存管理,文件管理,驅動管理)
其他程序(例如函數庫,shell程序等等)
操作系統是進行軟硬件資源管理的軟件。
從廣義角度看,它包括操作系統的內核以及操作系統的外殼(周邊程序),外殼給用戶提供使用操作系統的方式;
從狹義角度看,操作系統只是指其內核。內核是操作系統的核心部分,負責管理系統的硬件資源、提供基本的服務等;而外殼則是基于內核之上的部分,為用戶提供更友好的交互界面和操作方式。
結構示意圖:?
理解操作系統:
操作系統是進行軟硬件資源管理的軟件,而任何管理離不開這句話:
先描述,后組織。
?以管理學生為例: 管理學生的本質是對學生的數據進行管理。
凡是對特定的對象進行管理都是 先描述,后管理。
在操作系統中驅動層將操作系統的請求轉化為硬件能理解的命令。操作系統識別到相應的設備,創建相應的對象,硬件通過驅動層將硬件設備的相關屬性上傳到操作系統,通過鏈表的形式以便操作系統進行管理。
?
用戶使用底層硬件層次圖:
?我們經常使用的 scanf 和 printf 等本質需要接觸鍵盤和顯示器等底層硬件。但是我們不可能繞過管理系統進行接觸。操作系統保證其穩定性,用戶又不能直接接觸操作系統,這時候就需要用到操作系統提供的?系統調用接口?來滿足用戶的需求,也能保證操作系統的穩定性。
??
按照之前的知識,每種操作系統提供的調用接口讓用戶使用,但在學習C/C++等語言在Windows、Linux不同系統下,我們使用?scanf 和 printf 的方式沒有變化,說明我們使用的不是系統調用接口,而是通過用戶操作接口。我們拿之前在Linux寫的C語言程序查看依賴庫:
ldd [選項] [可執行文件或共享庫路徑]
?
libc就是我們C語言的依賴庫:?
???????
庫函數對系統調用的封裝:
許多高級編程語言中的標準庫函數會對系統調用進行封裝,以提供更方便、更友好的編程接口。例如,C 標準庫中的printf函數,它內部會調用 write 系統調用來將數據輸出到標準輸出設備(如終端)。這種封裝隱藏了系統調用的底層細節,提高了編程效率。
同時,一種編程語言至少需要一套語法,一套標準庫,一套編譯器。在不同的操作系統下,庫函數實現 printf 調用的系統調用接口不同,對其進行封裝可以實現跨平臺性,實現在不同操作系統下也能實現 printf。
?3.進程
概念
課本概念:程序的一個執行實例,正在執行的程序等
內核觀點:擔當分配系統資源(CPU時間,內存)的實體。
?結構示意圖
PCB 在 Linux 為 struct task_struct?
??
task_ struct內容分類?
- 標示符: 描述本進程的唯一標示符,用來區別其他進程。
- 狀態: 任務狀態,退出代碼,退出信號等。
- 優先級: 相對于其他進程的優先級。
- 程序計數器: 程序中即將被執行的下一條指令的地址。
- 內存指針: 包括程序代碼和進程相關數據的指針,還有和其他進程共享的內存塊的指針上下文數據: 進程執行時處理器的寄存器中的數據[休學例子,要加圖CPU,寄存器]。I/O狀態信息: 包括顯示的I/O請求,分配給進程的I/O設備和被進程使用的文件列表。記賬信息: 可能包括處理器時間總和,使用的時鐘數總和,時間限制,記賬號等。
- 其他信息
以開機、開機舉例:
操作系統關機時以二進制文件形式存儲在磁盤中。開機后,內核被加載到內存并初始化。初始化過程中,內核會靜態創建第一個進程(如 init)的 PCB,并存入內存。后續的進程如(fork( ))運行時動態分配PCB和資源,隨后CPU通過調度器選擇PCB開始工作,系統完成啟動。?
進程task_struct在不同的隊列中,就能分配不同的資源。?
?
?
ls 、file、程序的執行本質就是讓系統創建進程并運行
--- 我們自己寫的代碼形成的可執行 == 系統命令 == 可執行文件。
在linux中運行的大部分執行操作,本質都是運行進程!!!
file 是 Linux 系統中一個常用的命令行工具,用于識別文件的類型。它通過分析文件的內容、元數據或特定標識(而非僅依賴文件名后綴),判斷文件屬于哪種類型(如文本文件、可執行程序、壓縮包、圖片等)。
基本語法
file
file [選項] 文件名/路徑
常用選項
-b:僅顯示文件類型描述,不包含文件名(簡潔輸出)。
-i:顯示文件的 MIME 類型(如?text/plain、application/x-executable)。
-z:嘗試分析壓縮文件內部的文件類型(如?.zip、.tar.gz?中的文件)。
-L:如果文件是符號鏈接,顯示鏈接指向的目標文件的類型(默認顯示鏈接本身為 “symbolic link”)。
典型用法示例
觀察進程:
?
?編寫一個簡單的代碼并生成程序。
#include <stdio.h>
#include <unistd.h>int main(){while(1){printf("I am a process\n");sleep(1);}}
~
?運行程序使用命令為:
ps axj|grep process axj順序沒有要求
ps axj:ps?是查看進程狀態的命令,axj?是組合選項:
a:顯示所有用戶的進程(包括非當前終端的進程)。
x:顯示沒有控制終端的進程(如后臺運行的進程)。
j:以作業控制格式輸出,主要包含?PPID(父進程 ID)、PID(進程 ID)、PGID(進程組 ID)、SID(會話 ID)?等列,適合查看進程間的父子關系和會話信息。
| grep process:管道符?|?將前一個命令的輸出傳遞給?grep?過濾,grep process?用于篩選出包含 “process” 字符串的行(匹配進程名、命令行參數等,進程的默認命名與程序的名字一樣)。?
ps axj|grep process 本身也是一個進程。所以終止process程序后,還能看見process有關的進程。
?想要直觀地了解屬性可以加入
head -1&&ps axj
head -1?只取輸出的第一行,即表頭行(例如:PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND),用于說明各列的含義。
ps axj| head -1&&ps axj |grep process #打印進程信息后,取表頭再打印進程信息
了解 PID PPID
操作系統的進程調度器通過 一個唯一的標識符?來區分不同進程,對進程進行調度、分配 CPU 時間片等操作 ,所以有了PID,PID 是每個進程在系統中的唯一數字標識,就像每個人的身份證號一樣。
PPID 記錄創建當前進程的父進程的 ID,從而構建起進程間的父子關系,形成進程樹狀層次結構。這種結構有助于系統對進程進行管理,比如在進程清理時,當父進程結束,系統可以根據 PPID 找到所有子進程并進行相應處理(如在某些情況下,父進程退出后,子進程會被重新掛載到 init 進程下,init 進程的 PID 為 1 )。同時,系統管理員也可以通過查看進程的 PPID,了解進程的創建來源和相互關系,便于排查問題。
?getpid:用于獲取當前進程的進程 ID(PID),返回值類型為pid_t,無參數。
getppid:用于獲取當前進程的父進程 ID(PPID),返回值類型也為pid_t,同樣無參數。
?
?寫個簡易代碼來觀察
#include <stdio.h>
#include <unistd.h>int main(){pid_t pid = getpid();pid_t ppid = getppid();while(1){printf("I am a process PID=%d PPID=%d\n",pid,ppid);sleep(1);}}
?
?想要連續觀察進程的狀態,可以添加shell腳本實時觀察:
hile :;do ps axj| head -1&&ps axj |grep process;sleep 1; done 每隔一秒打印一次
?
?多次運行編寫的 process ,每次的子進程 PID 都不一樣 父進程 PPID 不變。
?觀察父進程,發現父進程由bash而來
?我們是否也能創建自己的子進程呢?
答案是肯定的,我們來了解一下fork。
fork()?是 Unix/Linux 系統中用于創建新進程的核心系統調用,通過復制當前進程(父進程)來生成一個幾乎完全相同的新進程(子進程)。以下是對其關鍵特性的解析:
核心功能
進程復制:fork()?會復制當前進程的所有資源(代碼段、數據段、堆、棧、文件描述符等),生成一個新的子進程。
調用后雙進程執行:調用?fork()?后,父進程和子進程會同時從?fork()?的下一行代碼開始執行,但返回值不同:?
進程類型 fork()
?調用返回值說明 父進程 子進程的 PID(正數) 父進程通過返回的 PID 識別和管理子進程 子進程 0 子進程通過返回 0 標識自己是子進程 失敗情況 -1 通常因內存不足、進程數超限等原因導致創建失敗
差異項 | 父進程 | 子進程 |
---|---|---|
代碼和數據來源 | 從磁盤加載而來 | 是父進程的 “副本”,默認繼承父進程的代碼和數據 |
進程 ID(PID) | 有唯一的 PID | 有唯一的 PID,與父進程不同 |
父進程 ID(PPID) | - | 等于父進程的 PID |
內存鎖 | 可能有通過 mlock () 等函數鎖定的內存區域 | 不繼承父進程的內存鎖 |
資源統計 | 有累計的資源使用統計,如 CPU 時間、內存占用等 | 資源使用統計重置為 0 |
文件鎖 | 可能有通過 flock () 等函數鎖定的文件 | 不繼承父進程的文件鎖 |
內存與資源的 “寫時復制(COW)”
為提高效率,fork()?使用 ** 寫時復制(Copy-On-Write)** 技術:初始時,子進程與父進程共享同一內存空間,不實際復制數據。
當任一進程(父或子)修改內存數據時,才會復制受影響的內存頁,確保修改獨立。
?我們寫一段代碼簡單了解一下
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>int main() {pid_t pid = fork();if (pid < 0) {perror("fork failed");return 1;} else if (pid == 0) {while(1){printf("Child process: ID=%d PID = %d, PPID = %d\n", pid , getpid(), getppid());sleep(2);}} else {while(1){printf("Parent process: ID=%d PID = %d, Child PID = %d\n",pid , getpid(), pid);sleep(2);}}return 0;
}
可以觀察到有兩個進程在同時運行:
刪除子進程:
?刪除父進程
?無論刪除子進程還是父進程都不影響其獨立性。
進程一定要有獨立性
?之前 進程 = 內核數據結構 task_struct +? 代碼 + 數據
父進程和子進程都有對應的task_struct結構,代碼是只讀的,父進程和子子進程的數據是獨立的。
fork()返回兩個值可以理解為分別從子進程和父進程的數據中返回的。
本質上是父進程和子進程在各自獨立的內存空間中,操作系統對同一個變量賦予了不同的值。
?
查看進程
除了之前 ps 命令查看進程,我們能在? /proc 路徑下查看進程:
proc 路徑下的進程按照?PID 命名。?
我們來查看進程的詳情
ls /proc/2699451 -l
?刪除可執行程序后,程序路徑顯示刪除,但是當前進程仍在運行。
?
這是因為我們刪除的是磁盤的文件,進程已經加載到內存里面且文件很小,沒有影響到其他進程且調度器也沒有更緊急的需求,所以仍然可以運行。
以前我們學過 fopen ("log.txt",“w”);
他是怎么打開識別到當前路徑的呢?
?我們先創建一個程序執行
?然后我們修改?log.txt?生成的路徑
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main()
{chdir("/home/new");FILE *fp = fopen("log.txt", "w");(void)fp;fclose(fp);while(1){printf("I am a process, pid: %d\n", getpid());sleep(1);}return 0;
}
重新編譯后再次運行程序:
?文件成功改變路徑。
fopen ("log.txt",“w”);
fopen函數調用時,他會尋找自己進程的cwd,當我們把文件名參數輸入(不是絕對路徑時),他默認會拼接當前進程的路徑。所以在當前目錄下生成我們需要的"log.txt"文件。
再見ヾ( ̄▽ ̄)Bye~Bye~