構(gòu)和操作系統(tǒng)進(jìn)程的概念)
馮諾-依曼體系結(jié)構(gòu)馮·諾伊曼體系結(jié)構(gòu)von Neumann architecture是 1945 年馮·諾伊曼在《EDVAC 報(bào)告草案》中提出、奠定了現(xiàn)代通用計(jì)算機(jī)基礎(chǔ)的體系結(jié)構(gòu)核心思想是存儲(chǔ)程序stored-program。我們常見(jiàn)的計(jì)算機(jī)如筆記本。我們不常見(jiàn)的計(jì)算機(jī)如服務(wù)器大部分都遵守馮諾依曼體系。部件作用運(yùn)算器 ALU算術(shù)/邏輯運(yùn)算控制器 CU取指、譯碼、發(fā)控制信號(hào)、管理時(shí)序存儲(chǔ)器存放指令和數(shù)據(jù)統(tǒng)一編址輸入設(shè)備把外部信息送入計(jì)算機(jī)輸出設(shè)備把結(jié)果送出運(yùn)算器 控制器 CPU再加上主存構(gòu)成“主機(jī)”輸入輸出設(shè)備統(tǒng)稱外設(shè)。核心特征存儲(chǔ)程序程序指令和數(shù)據(jù)一樣以二進(jìn)制形式存在同一存儲(chǔ)器里可像數(shù)據(jù)一樣被讀寫(xiě)、修改——這是與早期“插線板編程”機(jī)器的根本區(qū)別。指令順序執(zhí)行CPU 按 PC程序計(jì)數(shù)器指向的地址逐條取指、譯碼、執(zhí)行默認(rèn)順序推進(jìn)遇跳轉(zhuǎn)才改變流向。指令與數(shù)據(jù)共用存儲(chǔ)器和總線同一時(shí)刻只能要么取指令、要么取數(shù)據(jù)總線分時(shí)復(fù)用。工作原理取指—譯碼—執(zhí)行循環(huán)PC → 取指令 → 譯碼 → 取操作數(shù) → 執(zhí)行 → 寫(xiě)回 → PC 更新 → 循環(huán)這就是進(jìn)程時(shí)看到的“CPU 一條條執(zhí)行指令”的底層模型。主要優(yōu)點(diǎn)與缺陷優(yōu)點(diǎn)通用性強(qiáng)程序可存儲(chǔ)、可加載、可修改硬件不必為每個(gè)任務(wù)重新設(shè)計(jì)。馮·諾伊曼瓶頸CPU 與存儲(chǔ)器速度差距大且指令和數(shù)據(jù)共享一條總線取指與取數(shù)爭(zhēng)帶寬限制吞吐。緩解手段包括 Cache、流水線、多級(jí)存儲(chǔ)、預(yù)取等。CPU不能直接與輸入設(shè)備和輸出設(shè)備連接也是因?yàn)镃PU和它們的速度差距太大了. 差了大概6次方. 所以引入了儲(chǔ)存器, 先將輸入設(shè)備的要處理的數(shù)據(jù)加載到內(nèi)存, CPU處理后將數(shù)據(jù)輸出到內(nèi)存中等待數(shù)據(jù)設(shè)備的調(diào)用.操作系統(tǒng)概念任何計(jì)算機(jī)系統(tǒng)都包含一個(gè)基本的程序集合稱為操作系統(tǒng)(OS)?;\統(tǒng)的理解操作系統(tǒng)包括:內(nèi)核(進(jìn)程管理內(nèi)存管理文件管理驅(qū)動(dòng)管理)其他程序(例如函數(shù)庫(kù)shell程序等等)目的方便性提供友好接口用戶無(wú)需關(guān)心硬件細(xì)節(jié)。有效性效率提高資源利用率與系統(tǒng)吞吐如多道程序、并發(fā)、緩沖、虛存??蓴U(kuò)充性結(jié)構(gòu)清晰便于添加新功能/新硬件。開(kāi)放性遵循標(biāo)準(zhǔn)便于互操作。更本質(zhì)的目標(biāo)并發(fā)、共享、虛擬、異步——讓多個(gè)任務(wù)安全、高效地共用一臺(tái)機(jī)器。功能功能內(nèi)容進(jìn)程管理進(jìn)程創(chuàng)建/撤銷(xiāo)、調(diào)度、同步互斥、通信、死鎖處理。你學(xué)的 fork、進(jìn)程狀態(tài)、父進(jìn)程回收都屬于這塊內(nèi)存管理分配回收、地址映射、虛擬內(nèi)存、分頁(yè)/分段、頁(yè)面置換、內(nèi)存保護(hù)文件管理文件的組織、目錄、讀寫(xiě)、權(quán)限、磁盤(pán)空間分配Linux “一切皆文件”設(shè)備管理驅(qū)動(dòng)、緩沖、中斷處理、設(shè)備分配與 I/O 調(diào)度用戶接口命令行 shell、圖形界面、系統(tǒng)調(diào)用編程接口其他保護(hù)與安全、網(wǎng)絡(luò)通信、錯(cuò)誤檢測(cè)與處理理解管理相當(dāng)與先對(duì)需要處理的數(shù)據(jù), 描述出來(lái)(即使用結(jié)構(gòu)體struct或class類,將需要處理的數(shù)據(jù)儲(chǔ)存起來(lái)), 再對(duì)這些數(shù)據(jù)進(jìn)行組織(使用學(xué)習(xí)的數(shù)據(jù)結(jié)構(gòu)或STL容器, 將整體的數(shù)據(jù)組織聯(lián)系起來(lái))系統(tǒng)調(diào)用和庫(kù)函數(shù)概念在開(kāi)發(fā)角度操作系統(tǒng)對(duì)外會(huì)表現(xiàn)為一個(gè)整體但是會(huì)暴露自己的部分接口供上層開(kāi)發(fā)使用這部分由操作系統(tǒng)提供的接口叫做系統(tǒng)調(diào)用。系統(tǒng)調(diào)用在使用上功能比較基礎(chǔ)對(duì)用戶的要求相對(duì)也比較高所以有心的開(kāi)發(fā)者可以對(duì)部分系統(tǒng)調(diào)用進(jìn)行適度封裝從而形成庫(kù)有了庫(kù)就很有利于更上層用戶或者開(kāi)發(fā)者進(jìn)行二次開(kāi)發(fā)。這里就體現(xiàn)了操作系統(tǒng)對(duì)于用戶是不信任的, 但是又想讓用戶調(diào)用操作系統(tǒng)對(duì)硬件進(jìn)行向下管理, 所以就有了系統(tǒng)調(diào)用的接口, 但是由于系統(tǒng)調(diào)用的接口對(duì)于用戶還是太復(fù)雜, 就對(duì)接口進(jìn)行適當(dāng)封裝形成了庫(kù).這里就像我們生活中的銀行, 我們將錢(qián)存入銀行, 其實(shí)就是將錢(qián)放入銀行金庫(kù). 可實(shí)際上我們并沒(méi)有去銀行的金庫(kù)將錢(qián)放入. 這也是銀行對(duì)我們的不信任.(群眾里面有壞人) 所以實(shí)際上銀行設(shè)計(jì)了有ATM和人工窗口, 這些接口對(duì)錢(qián)進(jìn)行調(diào)用. 可是對(duì)于年邁的老人來(lái)說(shuō)這些銀行的流程太復(fù)雜, 所以好的銀行大廳就會(huì)有一些銀行的工作人員對(duì)前來(lái)辦理業(yè)務(wù)的人進(jìn)行幫助.(這就相當(dāng)與封裝了系統(tǒng)調(diào)用的接口)進(jìn)程一、進(jìn)程的概念程序 vs 進(jìn)程程序是磁盤(pán)上的靜態(tài)可執(zhí)行文件代碼數(shù)據(jù)進(jìn)程是程序的一次運(yùn)行實(shí)例是系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位。同一個(gè)程序跑多次就是多個(gè)進(jìn)程。組成進(jìn)程 程序代碼 數(shù)據(jù) PCB。Linux 用task_struct描述 PCB記錄 PID、PPID、狀態(tài)、優(yōu)先級(jí)、地址空間、打開(kāi)的文件、信號(hào)、 CPU 上下文等。PID 與 PPID每個(gè)進(jìn)程唯一 PIDPPID 指向創(chuàng)建它的父進(jìn)程。PID 1 是 systemd或 init沒(méi)有父進(jìn)程負(fù)責(zé)收養(yǎng)孤兒。地址空間每個(gè)進(jìn)程有獨(dú)立的虛擬地址空間代碼段、數(shù)據(jù)段、堆、棧、共享庫(kù)互不干擾。PCB 在 Linux 里是task_structPCB 是概念名Linux 的具體實(shí)現(xiàn)就是struct task_struct定義在include/linux/sched.h幾千行。每個(gè)進(jìn)程對(duì)應(yīng)一個(gè)task_struct實(shí)例由alloc_task_struct()分配。task_struct不包含代碼和數(shù)據(jù)的字節(jié)內(nèi)容它裝的是元數(shù)據(jù) 指針struct task_struct { pid_t pid, tgid; // 身份 volatile long state; // 狀態(tài) R/S/D/Z struct mm_struct *mm; // 地址空間描述代碼/數(shù)據(jù)在哪 struct files_struct *files; // 打開(kāi)的文件表 struct fs_struct *fs; // cwd、root struct signal_struct *signal; // 信號(hào)處理 struct task_struct *parent, *children, *sibling; // 家族鏈 struct list_head tasks; // 掛到全局進(jìn)程鏈表 ... };代碼和數(shù)據(jù)實(shí)際存放在該進(jìn)程的虛擬地址空間里用戶態(tài)內(nèi)存由 mm_struct 頁(yè)表描述、映射到物理頁(yè)。mm_struct 里再指向一串 vm_area_struct代碼段、堆、棧、共享庫(kù)各一段。內(nèi)核實(shí)際是“一組 struct 通過(guò)指針連成數(shù)據(jù)結(jié)構(gòu)”task_struct 不是孤立的它同時(shí)掛進(jìn)多種組織全局雙向鏈表 tasks遍歷所有進(jìn)程父子鏈表 children/siblingpstree 看到的樹(shù)PID 哈希表按 PID 快速查找CFS 調(diào)度器的紅黑樹(shù)按 vruntime 排序等待隊(duì)列等二、Linux 下查看進(jìn)程的基本操作ps—— 靜態(tài)快照最基礎(chǔ)ps # 當(dāng)前終端的前臺(tái)進(jìn)程 ps aux # 所有進(jìn)程BSD 風(fēng)格USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND ps -ef # 所有進(jìn)程System V 風(fēng)格UID PID PPID C STIME TTY TIME CMD ps -ef | grep nginx # 過(guò)濾某程序 ps -u lcb # 指定用戶 ps -p 102617 # 指定 PID ps -o pid,ppid,stat,cmd -p 102617 # 自定義列 ps aux --sort-%cpu # 按 CPU 降序 ps -eLf # 含線程LWP ps -ef --forest # 樹(shù)狀顯示父子關(guān)系top / htop —— 動(dòng)態(tài)實(shí)時(shí)top # 實(shí)時(shí)刷新按 P 按CPU排序、M 按內(nèi)存、k 殺進(jìn)程、q 退出 htop # 更友好需安裝支持鼠標(biāo)、樹(shù)狀、搜索 F3按名字查找pgrep -a nginx # 輸出 PID 命令行 pidof nginx # 只輸出 PID pgrep -af python.*test # 匹配完整命令行pstree —— 進(jìn)程樹(shù)pstree -p # 顯示 PID能看清 sshd - bash - test 的父子鏈/proc —— 內(nèi)核暴露的進(jìn)程信息ls /proc/PID/ # 該進(jìn)程的各類信息 cat /proc/PID/status # 狀態(tài)、UID、內(nèi)存、線程數(shù) cat /proc/PID/cmdline # 啟動(dòng)命令行 ls -l /proc/PID/exe # 指向可執(zhí)行文件 ls -l /proc/PID/cwd # 當(dāng)前工作目錄 ls -l /proc/PID/fd # 打開(kāi)的文件描述符作業(yè)控制當(dāng)前 shell 內(nèi)的進(jìn)程jobs # 查看后臺(tái)/暫停作業(yè) ./test # 后臺(tái)運(yùn)行 CtrlZ # 暫停到后臺(tái) bg %1 / fg %1 # 轉(zhuǎn)后臺(tái)繼續(xù) / 調(diào)回前臺(tái)終止與調(diào)整kill PID # 發(fā) SIGTERM(15)禮貌請(qǐng)求退出 kill -9 PID # SIGKILL強(qiáng)殺不可捕獲 kill -l # 列出所有信號(hào) pkill -f nginx # 按名字/命令行殺 renice -n 5 -p PID # 調(diào)整優(yōu)先級(jí)其他常用who / w # 當(dāng)前登錄用戶及終端 lsof -p PID # 進(jìn)程打開(kāi)的文件/端口 ss -tlnp # 端口與對(duì)應(yīng)進(jìn)程fork()創(chuàng)建子進(jìn)程通過(guò)上面的知識(shí)我們了解到了程序沒(méi)一次運(yùn)行就會(huì)創(chuàng)建一個(gè)進(jìn)程, 那么我們能不能在程序運(yùn)行時(shí)創(chuàng)建一個(gè)進(jìn)程呢? 這時(shí)我們就要用到fork()了.我們?cè)贚inux下運(yùn)行這個(gè)段代碼#includestdio.h#includesys/types.h#includeunistd.hintmain(){intretfork();if(ret0){perror(fork);return1;}elseif(ret0){//childprintf(I am child : %d!, ret: %d\n,getpid(),ret);}else{//fatherprintf(I am father : %d!, ret: %d\n,getpid(),ret);}sleep(1);return0;}如果以我們學(xué)習(xí)C語(yǔ)言的理解看到if-else語(yǔ)句,肯定只會(huì)有進(jìn)入其中一個(gè)代碼段執(zhí)行, 但我們運(yùn)行后發(fā)現(xiàn),ret 0和ret 0這兩個(gè)代碼段的代碼都執(zhí)行了.1.為什么fork給父子返回各自的不同返回值? 2.為什么一個(gè)函數(shù)會(huì)返回兩次??本質(zhì)子進(jìn)程是父進(jìn)程在 fork 時(shí)刻的“克隆”fork()由內(nèi)核的copy_process()完成分配新的task_struct復(fù)制父進(jìn)程的地址空間、文件表、信號(hào)設(shè)置等然后讓新進(jìn)程加入調(diào)度隊(duì)列。子進(jìn)程從 fork() 返回處開(kāi)始執(zhí)行返回值 0不會(huì)重走 fork 之前的代碼。