1. 前言
從宏觀的角度來(lái)看,操作系統(tǒng)就是我們?nèi)粘J褂玫?Windows、MacOS、Linux 這類(lèi)的系統(tǒng),但是這種直觀的用戶交互界面只是操作系統(tǒng)的一小部分功能,操作系統(tǒng)如何決定系統(tǒng)的資源調(diào)度、如何處理內(nèi)存的分配以及如何管理網(wǎng)絡(luò)和文件系統(tǒng),這些都是隱藏在用戶界面之下的內(nèi)容。
從課程設(shè)計(jì)的角度來(lái)看,操作系統(tǒng)(Operating System)是計(jì)算機(jī)專(zhuān)業(yè)的核心專(zhuān)業(yè)課程,所以可以用來(lái)衡量候選人的計(jì)算機(jī)基本功。
對(duì)于后端程序員,如果是使用 Java 語(yǔ)言,Java 中的多線程會(huì)涉及到進(jìn)程和線程的關(guān)系,這是操作系統(tǒng)中的概念。如果使用 C++ 語(yǔ)言,那么無(wú)法避免內(nèi)存分配和管理,這也是操作系統(tǒng)中的基礎(chǔ)概念,因此操作系統(tǒng)是校招面試中的核心之一。
2. 進(jìn)程和線程
面試官提問(wèn): 操作系統(tǒng)中的進(jìn)程和線程有什么區(qū)別?
題目解析:
進(jìn)程和線程的區(qū)別是操作系統(tǒng)面試相關(guān)的出現(xiàn)頻率最高的題目,沒(méi)有之一。
在闡述進(jìn)程和線程的定義之前,最好能夠想清楚在操作系統(tǒng)中為什么會(huì)出現(xiàn)進(jìn)程這個(gè)概念。
2.1 進(jìn)程的產(chǎn)生背景
我們希望操作系統(tǒng)能夠同時(shí)處理多個(gè)任務(wù),例如在播放網(wǎng)易云音樂(lè)的同時(shí),也能在微信上聊天。計(jì)算機(jī)的核心處理硬件是 CPU,如果計(jì)算機(jī)只有一個(gè) CPU,那么播放音樂(lè)和處理聊天軟件都需要這個(gè) CPU 去執(zhí)行運(yùn)算邏輯,那如何做到時(shí)間上的同時(shí)處理?答案是 CPU 在多個(gè)邏輯任務(wù)之間來(lái)回切換,因?yàn)榍袚Q速度太快,所以看起來(lái)做到了并發(fā)執(zhí)行。
傳統(tǒng)操作系統(tǒng)的任務(wù)調(diào)度采用時(shí)間片輪轉(zhuǎn)的方式,在執(zhí)行一個(gè)任務(wù)達(dá)到時(shí)間限制時(shí)會(huì)暫停處理,然后切換到處理下一個(gè)任務(wù)。每一個(gè)任務(wù)執(zhí)行的時(shí)間間隔就是一個(gè)時(shí)間片,被執(zhí)行的任務(wù)處于運(yùn)行狀態(tài),被暫停的任務(wù)處于就緒狀態(tài),如圖所示:
假設(shè)存在兩個(gè)調(diào)度任務(wù),CPU 調(diào)度產(chǎn)生并發(fā)的 "錯(cuò)覺(jué)"2.2 進(jìn)程和線程的區(qū)別
首先給出進(jìn)程和線程的基本定義:
進(jìn)程(Process) :是操作系統(tǒng)任務(wù)調(diào)度的基本單元, 目的是為了實(shí)現(xiàn)操作系統(tǒng)的并發(fā)。
線程(Thread) :線程是進(jìn)程的子任務(wù),是進(jìn)程中實(shí)際運(yùn)行的任務(wù),線程是程序執(zhí)行的最小單元。
然后分析兩者之間的主要區(qū)別:
(1)包含關(guān)系:一個(gè)線程肯定歸屬于一個(gè)進(jìn)程,但是一個(gè)進(jìn)程可以包含多個(gè)線程。(2)內(nèi)存管理:操作系統(tǒng)會(huì)給進(jìn)程分配獨(dú)立的內(nèi)存空間,但是一個(gè)進(jìn)程下的多個(gè)線程共享內(nèi)存空間。以 Java 編程為例,同一個(gè) main 函數(shù)進(jìn)程下的多個(gè)線程共享代碼段(代碼和常量),以及數(shù)據(jù)段(全局變量和靜態(tài)變量),這些都是共享的內(nèi)存空間,不過(guò)需要注意,每個(gè)線程會(huì)有獨(dú)立的運(yùn)行??臻g。(3)單元定義:從內(nèi)存分配的角度就能看出,進(jìn)程是資源分配的最小單元,線程是 CPU 執(zhí)行的最小單元。(4)系統(tǒng)開(kāi)銷(xiāo):創(chuàng)建進(jìn)程和線程的系統(tǒng)開(kāi)銷(xiāo)是不同的,因?yàn)樵趧?chuàng)建和銷(xiāo)毀進(jìn)程時(shí),操作系統(tǒng)需要分配和回收內(nèi)存資源,創(chuàng)建線程不需要切換整體內(nèi)存空間,所以創(chuàng)建進(jìn)程的系統(tǒng)開(kāi)銷(xiāo)遠(yuǎn)大于創(chuàng)建線程;在切換進(jìn)程時(shí)需要保存 CPU 運(yùn)行的上下文,切換線程只需要切換 CPU 中少數(shù)寄存器的內(nèi)容,所以切換進(jìn)程的系統(tǒng)開(kāi)銷(xiāo)也遠(yuǎn)大于切換線程。(5)穩(wěn)定性分析:因?yàn)椴粫?huì)共用內(nèi)存空間,所以一個(gè)進(jìn)程掛了對(duì)另外的進(jìn)程影響很小,但是同一進(jìn)程下的線程是共享內(nèi)存的,所以一個(gè)線程掛了,會(huì)影響到其他線程。(6)通信:因?yàn)椴煌M(jìn)程處于不同的內(nèi)存空間,所以通信方式比較麻煩,具體方式將在之后的小節(jié)介紹。同一進(jìn)程下的線程之間通信方式相對(duì)簡(jiǎn)單,因?yàn)楣蚕韮?nèi)存,可以讀寫(xiě)相同的內(nèi)存空間。
3. 小結(jié)
本章節(jié)介紹了操作系統(tǒng)中進(jìn)程的起源原因,以及從操作系統(tǒng)內(nèi)存管理的角度分析了進(jìn)程和線程之間的區(qū)別。
1. 前言
上一小結(jié)談到了操作系統(tǒng)中進(jìn)程和線程的區(qū)別,其中進(jìn)程之間、線程之間的通信方式不同,進(jìn)程通信(Inter-Process Communication,簡(jiǎn)稱 IPC)是指不同進(jìn)程之間交換信息。操作系統(tǒng)中時(shí)刻都在進(jìn)行 IPC,例如微信讀取本地的文件,就是微信程序和文件系統(tǒng)進(jìn)程交互的過(guò)程。
2. 進(jìn)程間通信
面試官提問(wèn): 操作系統(tǒng)進(jìn)程之間的通信方式有哪些?有什么特點(diǎn)?
題目解析:
操作系統(tǒng)中最常用的 IPC 方式有 5 種,分別是管道、命名管道、信號(hào)、共享內(nèi)存以及套接字。
2.1 管道
管道(pipe),默認(rèn)指無(wú)名管道。管道在兩個(gè)進(jìn)程之間建立一個(gè)通道,一個(gè)進(jìn)程向這個(gè)通道寫(xiě)入字節(jié)流,另一個(gè)進(jìn)程從這個(gè)通道讀取字節(jié)流。用 C 語(yǔ)言描述管道示例:
#include <unistd.h> // 引入linux頭文件int pipe(int fd[2]); // 返回:如果成功返回0,失敗則返回-1
上述定義的 fd 對(duì)象,其中 fd[0] 表示讀文件描述符,f[1] 表示寫(xiě)文件描述符。
管道的寫(xiě)和讀操作假設(shè)存在兩個(gè)進(jìn)程,分別為進(jìn)程 A 和進(jìn)程 B,那么進(jìn)程 A 往 f[1] 寫(xiě)入,進(jìn)程 B 則從自身的 f[0] 讀取內(nèi)容。
需要注意管道是半雙工通信,也就是數(shù)據(jù)的流向是固定的,必須有一端是寫(xiě)入端,另一端是讀取端。
2.2 信號(hào)
信號(hào)(Signal)是 Unix 系統(tǒng)中就已有的 IPC 方式,繼承于 Unix 的 Linux 系統(tǒng)和 MacOS 系統(tǒng)也具有相同的通信方式。
信號(hào)的工作原理是向某個(gè)進(jìn)程發(fā)送特定的消息,目標(biāo)進(jìn)程在收到消息之后,就知道特定事件已經(jīng)發(fā)生,此時(shí)進(jìn)程可以忽略消息即不做處理,或者是處理消息調(diào)用固定的函數(shù)。
以 MacOS 為例,在 shell 終端輸入 kill -l 可以列出支出的全部信號(hào)名稱:HUP INT QUIT ILL TRAP ABRT EMT FPE KILL BUS SEGV SYS PIPE ALRM TERM URG STOP。
MacOS 支持的信號(hào)列表MacOS 支持的信號(hào)列表
2.3 共享內(nèi)存
共享內(nèi)容(Shared Memory)是指兩個(gè)進(jìn)程之間可以讀和寫(xiě)相同的操作系統(tǒng)內(nèi)存空間,每個(gè)進(jìn)程的操作對(duì)另外的進(jìn)程都是可見(jiàn)的,這種通信方式非常類(lèi)似線程之間的通信。
C 語(yǔ)言實(shí)現(xiàn)的共享內(nèi)存步驟:
(1)shmget() :創(chuàng)建一段共享內(nèi)存,或者引用已有的共享內(nèi)存的空間;
(2)shmat() :連接已有的共享內(nèi)存的地址;
(3)shmctl():建立連接之后,對(duì)共享內(nèi)存進(jìn)行讀寫(xiě)操作;
(4)shmdt():所有操作都執(zhí)行完成之后,斷開(kāi)連接。
共享內(nèi)存的操作模型2.4 命名管道
命名管道(Named Pipe)實(shí)際上就是先進(jìn)先出隊(duì)列(First In First Out,簡(jiǎn)稱 FIFO),候選人需要區(qū)分命名管道和管道,兩者最大的區(qū)別在于管道只能在具有親緣關(guān)系的兩個(gè)進(jìn)程之間通信,例如父子進(jìn)程之間或者兄弟進(jìn)程之間,命名管道則可以在任何兩個(gè)進(jìn)程之間通信,更加零活。
命名管道的讀寫(xiě)操作命名管道的讀寫(xiě)操作
如圖所示,用戶進(jìn)程 A 是寫(xiě)入進(jìn)程,寫(xiě)入的消息是 1 2 3 4 5,因?yàn)樽裱冗M(jìn)先出的原則,用戶進(jìn)程 B 讀出的消息順序也是 1 2 3 4 5。
2.5 套接字
上述介紹的 IPC 方式都是同一個(gè)主機(jī)內(nèi)進(jìn)程的交互方式,都是本地通信,套接字(Socket)一般用來(lái)處理不同主機(jī)進(jìn)程之間的通信,也就是遠(yuǎn)程通信,是網(wǎng)絡(luò)通信最常用的方式。Socket 通信需要 TCP 或者 UDP 協(xié)議的支持。使用 C 語(yǔ)言創(chuàng)建 Socket 的示例:
#include <sys/types.h>#include <sys/socket.h> //引入頭文件int socket(int domain, int type, int protocol); //創(chuàng)建一個(gè)socket
3. 小結(jié)
本章節(jié)介紹了 5 種最常見(jiàn)的進(jìn)程間通信方式,候選人需要掌握沒(méi)種通信方式的原理,最好能夠畫(huà)出原型圖,而操作系統(tǒng)級(jí)別的通信一般不需要我們手動(dòng)實(shí)現(xiàn),有興趣的同學(xué)可以了解下具體的實(shí)現(xiàn),例如使用 Socket API 實(shí)現(xiàn)通信的編碼方式,但是大部分實(shí)現(xiàn)接口并不會(huì)在面試中被考察,關(guān)注的重點(diǎn)在于定義。
1. 前言
操作系統(tǒng)中的很多資源都是多個(gè)進(jìn)程或者多個(gè)線程之間共享的,例如同一個(gè)文件,可能同時(shí)會(huì)被多個(gè)程序讀寫(xiě)?;蛘呤且粋€(gè)內(nèi)存變量,存在同時(shí)被多個(gè)線程修改的可能。如果資源能夠不能以合理的順序訪問(wèn)就可能產(chǎn)生沖突,這種競(jìng)爭(zhēng)資源的現(xiàn)象可能造成阻塞,引發(fā)死鎖。
2. 死鎖
面試官提問(wèn): 談?wù)劜僮飨到y(tǒng)中的死鎖是什么,產(chǎn)生死鎖的條件是什么?有什么解決方案?
題目解析:
這是一種經(jīng)典的提問(wèn)方法,對(duì)于一個(gè)問(wèn)題 A,面試官提出問(wèn)題 A 的定義,候選人闡明定義之后,才能確保候選人和面試官彼此討論的是同一個(gè)問(wèn)題,而不是牛頭不對(duì)馬嘴。其次才是討論這個(gè)問(wèn)題的起源,在提出問(wèn)題之后,當(dāng)然需要給出解決方案,解決方案的優(yōu)劣可以反應(yīng)候選人對(duì)面試題目的理解程度。
2.1 死鎖定義
死鎖(DeadLock)可以發(fā)生在多個(gè)進(jìn)程之間或者是多個(gè)線程之間,本文以線程作為觀察對(duì)象。那么死鎖的定義就是多個(gè)線程競(jìng)爭(zhēng)同一個(gè)資源造成的僵局,如果沒(méi)有外力推動(dòng),這種僵局會(huì)一直持續(xù)下去,線程的狀態(tài)都無(wú)法繼續(xù)推進(jìn)。例如操作系統(tǒng)中有兩個(gè)線程,分別是線程 A 和線程 B,線程 A 按照先獲取鎖 a 再獲取鎖 b 的順序占用鎖的資源,線程 B 按照先獲取鎖 b 再獲取鎖 a 的順序占用鎖。
線程死鎖案例線程死鎖案例
線程 A 占用了鎖 a,線程 B 占用了鎖 b,線程 A 需要占用鎖 b 之后才能釋放鎖 a,線程 B 則需要占用鎖 a 之后才能釋放鎖 b,兩個(gè)線程都進(jìn)入了等待對(duì)方釋放鎖的狀態(tài),造成死鎖。
2.2 死鎖條件
造成進(jìn)程或者線程死鎖有四個(gè)必要條件:
(1)互斥條件:進(jìn)程(線程)對(duì)于分配的資源有排他性,排他性是指一個(gè)資源在同一段時(shí)間內(nèi)只能被一個(gè)進(jìn)程(線程)占用。(2)請(qǐng)求和保持條件:進(jìn)程(線程)因?yàn)檎?qǐng)求資源導(dǎo)致阻塞時(shí),對(duì)于已經(jīng)獲得資源不會(huì)主動(dòng)釋放。通俗來(lái)說(shuō)就是已有的資源不會(huì)放棄,沒(méi)有的資源會(huì)持續(xù)請(qǐng)求。(3)不可剝奪條件:進(jìn)程(線程)在獲得的資源沒(méi)有使用完成之前,資源不能被剝奪,只能等進(jìn)程(線程)主動(dòng)釋放。(4)循環(huán)等待條件:所有等待的進(jìn)程(線程)在發(fā)生死鎖時(shí),都會(huì)形成一個(gè)死循環(huán)環(huán)路,這也是造成死鎖的直接原因。
2.3 死鎖解決方案
死鎖的解決方案就是從產(chǎn)生死鎖的必要條件入手,如果不滿足必要條件,那么就失去了發(fā)生死鎖的可能性。還是以線程為例,給出死鎖的解決方案:
(1)破壞請(qǐng)求條件:一次性給線程分配所有的資源,例如上述案例直接給線程 A 分配鎖 a 和鎖 b。(2)破壞保持條件:只要存在任何一個(gè)資源不能被分配,已有被分配的資源也不能保持。例如上述案例中線程 A 不能獲得鎖 b,那么需要主動(dòng)釋放鎖 a。(3)破壞不可剝奪條件:只要存在任何一個(gè)資源不能被分配,已有被分配的資源可以被強(qiáng)制釋放。(4)破壞循環(huán)等待條件:所有的線程按照提前指定的順序請(qǐng)求資源,釋放資源的順序剛好相反。
避免死鎖的經(jīng)典算法有銀行家算法,我們把操作系統(tǒng)比喻成銀行家,操作系統(tǒng)管理的資源就是銀行中的資金,進(jìn)程(線程)就是顧客,獲取資源的過(guò)程就是向銀行家索要貸款的過(guò)程,線程在獲取資源前需要申明自己需要的每種資源的最大數(shù)量,操作系統(tǒng)計(jì)算在分配這些資源之后,是否會(huì)讓系統(tǒng)處于不安全的狀態(tài)??偨Y(jié)來(lái)看,銀行家算法是一個(gè)動(dòng)態(tài)判斷死鎖的算法。
3. 小結(jié)
本章節(jié)介紹了死鎖的定義、產(chǎn)生原因以及典型的解決方案,與死鎖對(duì)應(yīng)的還有活鎖的概念,活鎖在嘗試獲取資源失敗之后,會(huì)主動(dòng)釋放自己持有的鎖,然后再重試整個(gè)拿鎖流程。與活鎖對(duì)應(yīng)的是進(jìn)程的饑餓,如果一個(gè)進(jìn)程永遠(yuǎn)拿不到需要的資源,服務(wù)會(huì)一直阻塞,被比喻為饑餓。關(guān)于死鎖,還會(huì)涉及到編程實(shí)戰(zhàn)中如何避免死鎖以及死鎖的檢測(cè)條件,候選人可以自行深入探討。
1. 前言
之前的小節(jié)中介紹了操作系統(tǒng)的進(jìn)程,操作系統(tǒng)中有個(gè)創(chuàng)建進(jìn)程的重要方法就是 fork 函數(shù),當(dāng)需要執(zhí)行和本進(jìn)程相關(guān)的獨(dú)立任務(wù)時(shí),一般需要?jiǎng)?chuàng)建一個(gè)有血緣關(guān)系的子進(jìn)程。
2. fork 函數(shù)
面試官提問(wèn): Linux 系統(tǒng)中的 fork 函數(shù)是什么,有什么用途?
題目解析:
首先從定義上看,fork 函數(shù)的作用是在一個(gè)進(jìn)程的基礎(chǔ)上創(chuàng)建新的進(jìn)程,原有的進(jìn)程被定義為父進(jìn)程,新的進(jìn)程被定義為子進(jìn)程。
在 C 語(yǔ)言中調(diào)用 fork() 函數(shù)即實(shí)現(xiàn) fork 功能,示例:
#include<unistd.h> //包含fork函數(shù)的頭文件pid_t fork(void); //fork的返回類(lèi)型為pid_t,我們可以看成int類(lèi)型認(rèn)識(shí)一個(gè)函數(shù),需要從函數(shù)的定義入手,了解函數(shù)做了什么事情,入?yún)⑹鞘裁?,出參是什么。我們?C 語(yǔ)言實(shí)現(xiàn)的一個(gè)典型的 fork 的程序入手,示例:
#include <unistd.h>#include <stdio.h> int main (){ pid_t fpid; int count = 0; fpid = fork(); if (fpid == 0) { //返回值是0,說(shuō)明是子進(jìn)程 printf("i am the child process, process id is %d\n", getpid()); count++; } else if(fpid > 0) { //返回值>0,說(shuō)明是父進(jìn)程 printf("i am the parent process, process id is %d\n", getpid()); count++; } else { //返回值<0,說(shuō)明fork發(fā)生異常 printf("fork encounter exception, process id is %d\n", getpid()); } //打印計(jì)數(shù)器 printf("after fork, counting result : %d\n", count); return 0;}
在 MacOS 系統(tǒng)上編譯運(yùn)行案例示例代碼,運(yùn)行結(jié)果如下圖。
test.c 編譯執(zhí)行結(jié)果
如果是不了解函數(shù)原理的前提下,僅僅從代碼層面分析,在調(diào)用 fork() 函數(shù)之后,代碼會(huì)進(jìn)入 if-else 判斷邏輯,在控制臺(tái)輸出一條語(yǔ)句,然后在控制臺(tái)打印計(jì)數(shù)器的數(shù)值。但是從真正執(zhí)行的結(jié)果來(lái)看,這兩個(gè)打印動(dòng)作都分別執(zhí)行了兩次,并不符合我們的預(yù)期。fork 之后的代碼邏輯被執(zhí)行了兩次,而且兩次進(jìn)入的不同的分支,所以重點(diǎn)在于 fork 函數(shù)到底有啥作用。
按照定義、入?yún)⒑统鰠⑷阶叩目蚣?,首先是分析函?shù)的定義,調(diào)用 fork 函數(shù)之后發(fā)生了什么事情:
(1)分配內(nèi)存:分配新的內(nèi)存空間給子進(jìn)程;
(2)拷貝數(shù)據(jù):拷貝父進(jìn)程的數(shù)據(jù)結(jié)構(gòu)給子進(jìn)程;
(3)加入列表:將新生成的子進(jìn)程添加到操作系統(tǒng)的進(jìn)程列表;
(4)返回結(jié)果:fork 函數(shù)調(diào)度并且返回。
然后是分析 fork 函數(shù)的入?yún)?,fork 函數(shù)入?yún)⑹?void,也就是自動(dòng)同步進(jìn)程的上下文,不需要手動(dòng)聲明。
最后是分析 fork 函數(shù)的出參,fork 函數(shù)和程序員日常接觸的函數(shù)不同,我們?cè)?C 或者 Java 中定義的函數(shù)只會(huì)有一個(gè)返回值,fork 函數(shù)則是調(diào)用一次,返回二次。調(diào)用方(例如上述案例的 main 函數(shù))根據(jù)返回值的不同判斷處于父進(jìn)程還是子進(jìn)程。
(1)返回值 < 0:調(diào)用失敗,一般是因?yàn)椴僮飨到y(tǒng)中的進(jìn)程個(gè)數(shù)達(dá)到上限或者內(nèi)存不足以分配給新的進(jìn)程;
(2)返回值 = 0:調(diào)用成功,并且處于子進(jìn)程;
(3)返回值 > 0:調(diào)用成功,并且處于父進(jìn)程。
現(xiàn)在就不難理解,從調(diào)用 fork() 函數(shù),代碼實(shí)際上是被父子進(jìn)程分別執(zhí)行了一次,父進(jìn)程的進(jìn)程 id 是 52331,子進(jìn)程的進(jìn)程 id 是 52332。
在掌握原理之后,我們繼續(xù)探究 fork 函數(shù)的應(yīng)用場(chǎng)景。fork 函數(shù)的本質(zhì)在原有的進(jìn)程基礎(chǔ)上創(chuàng)建一個(gè)新的進(jìn)程,所以在網(wǎng)絡(luò)通信中使用較多,例如在客戶端發(fā)送一個(gè) HTTP 請(qǐng)求打到服務(wù)器時(shí),服務(wù)器進(jìn)程 fork 出一個(gè)子進(jìn)程用于處理單個(gè)請(qǐng)求,父進(jìn)程則繼續(xù)等待其他的請(qǐng)求。
3. 小結(jié)
本章節(jié)介紹了 Linux 的 fork 函數(shù),fork 如同其英文名,就是進(jìn)程的分叉。fork 函數(shù)簡(jiǎn)化了操作系統(tǒng)的進(jìn)程管理,又提供了一個(gè)簡(jiǎn)單的多進(jìn)程生成方案,在操作系統(tǒng)中的地位非常核心,候選人需要注意 fork 函數(shù)調(diào)用 1 次,返回 2 次的核心特性以及返回值。
1. 前言
Linux 是基于 Unix 系統(tǒng)開(kāi)發(fā)的開(kāi)源操作系統(tǒng)內(nèi)核,目前常見(jiàn)的發(fā)行版本 Ubuntu、RedHat、CentOS 等,互聯(lián)網(wǎng)服務(wù)器一般都部署的是 Linux 系統(tǒng)。因?yàn)槭褂脠?chǎng)景不同,Windows 系統(tǒng)更適合個(gè)人日常辦公,相對(duì)于 Windows 系統(tǒng)的復(fù)雜圖形化界面而言,Linux 一般只在遠(yuǎn)程服務(wù)器上部署純命令行界面,所以熟悉 Linux 系統(tǒng)的常用命令比較重要。
2. Linux 常用命令
面試官提問(wèn): Linux 系統(tǒng)的常用操作命令能枚舉一下嗎?
題目解析:
這是一道偏實(shí)戰(zhàn)的題目,面試官的本意是考察候選人對(duì)于 Linux 系統(tǒng)實(shí)際操作的經(jīng)驗(yàn),可以從列舉出一些常用的 Linux 命令并且給出使用案例。
2.1 ls
ls 是英文 List 的縮寫(xiě),會(huì)枚舉出當(dāng)前工作目錄的所有文件。
ls 命令效果2.2 cd
cd 是英文 change directory 的縮寫(xiě),用于切換當(dāng)前工作目錄。
(1)cd + 目錄,進(jìn)入到該目錄。
(2)cd + ~,進(jìn)入 Home 目錄。
(3)cd + ..,返回到上一個(gè)目錄。
cd 命令效果2.3 cat
cat 是英文 concatenate and print files 的縮寫(xiě),用于連接文件并且打印輸出到控制臺(tái)。
(1)cat + 文件名,打印輸出文件內(nèi)容。
(2)cat + 文件名 1 + > + 文件名 2,將文件 1 的內(nèi)容輸出到文件 2 中。
cat 命令效果2.4 grep
grep 是英文 Global Regular Expression Print(全局正則表達(dá)式匹配打印) 的縮寫(xiě),是一個(gè)常用的文本搜索工具,使用正則表達(dá)式匹配規(guī)則,然后輸出匹配結(jié)果。
(1)例如 netstat -ntlp 命令會(huì)在控制臺(tái)輸出當(dāng)前所有的 TCP 端口使用情況,那么配合 grep 使用可以單獨(dú)提煉出需要的端口。netstat -ntulp | grep 3306 用于單獨(dú)查看 3306 TCP 端口的使用情況。
(2)例如 cat + 文件名輸出文件內(nèi)容之后,查詢文件指定內(nèi)容。
grep 命令查詢 test.txt 文件中的 Hello 內(nèi)容,輸出存在 Hello 的行
2.5 mv & cp
mv 是英文 move 的縮寫(xiě),mv 命令的作用是移動(dòng)操作系統(tǒng)的文件。用法是 mv + 原始文件路徑 + 目標(biāo)文件路徑。
cp 是英文 copy 的縮寫(xiě),也是文件操作命令,作用是復(fù)制操作系統(tǒng)的文件。用法是 cp + 原始文件路徑 + 目標(biāo)文件路徑。
最基礎(chǔ)的案例如下:
(1)mv test.txt ./test.txt 將 test.txt 文件移動(dòng)到上一層文件夾中。
(2)cp test.txt ./test.txt 將 test.txt 文件復(fù)制到上一層文件夾中。
2.6 Ping
ping 命令是操作系統(tǒng)中常用的網(wǎng)絡(luò)命令,Windows 系統(tǒng)也可以執(zhí)行 ping 操作,區(qū)別是 Linux 下的 ping 進(jìn)程不會(huì)自動(dòng)停止。
執(zhí)行 ping 命令會(huì)使用 ICMP 網(wǎng)絡(luò)協(xié)議,用來(lái)檢測(cè)當(dāng)前主機(jī)和目標(biāo)主機(jī)是否聯(lián)通。
(1)ping + 域名,最常用的是 ping www.baidu.com,百度服務(wù)器肯定不會(huì)宕機(jī),如果連接失敗,說(shuō)明是本機(jī)網(wǎng)絡(luò)存在故障。
(2)ping + IP 地址,檢測(cè)指定 IP 地址的機(jī)器是否聯(lián)通。
ping 百度服務(wù)器,輸出結(jié)果能看到連接的機(jī)器 IP 地址,以及網(wǎng)絡(luò)請(qǐng)求響應(yīng)時(shí)間
2.7 chmod
chomd 是英文 change mode 的縮寫(xiě),用于改變文件的讀寫(xiě)權(quán)限。
Linux 系統(tǒng)的文件調(diào)用權(quán)限分為三種:文件所有者(Owner)、組(Group)、其他用戶(Other Users)。所有者一般是創(chuàng)建文件的用戶,所有者可以讓同組用戶訪問(wèn)文件,以及改變文件對(duì)于其他用戶的讀寫(xiě)限制。
Linux 的文件權(quán)限管理很?chē)?yán)格,每個(gè)文件和每個(gè)目錄(目錄本質(zhì)上也是一個(gè)文件)都有讀和寫(xiě)的權(quán)限限制,指定的用戶有指定的權(quán)限訪問(wèn)指定的內(nèi)容。
權(quán)限范圍:u(user)表示文件的所有者;g(group)表示和文件所有者同一個(gè)組的用戶;o(other)表示除當(dāng)前用戶的其他人;a(all)表示所有用戶組的所有人。
操作范圍:r(read)表示設(shè)置文件為可讀權(quán)限;w(write)表示設(shè)置文件為可寫(xiě)權(quán)限;x(execute)表示設(shè)置文件為可執(zhí)行權(quán)限。
一些常見(jiàn)案例如下:
(1)chmod a+x test.txt 表示設(shè)置 test.txt 文件對(duì)所有用戶都開(kāi)放了可執(zhí)行權(quán)限。
(2)chmod a-x test.txt 表示設(shè)置 test.txt 文件對(duì)所有用戶都關(guān)閉了可執(zhí)行權(quán)限。
3. 小結(jié)
本章節(jié)介紹了幾個(gè)最基礎(chǔ)的 Linux 系統(tǒng)常見(jiàn)操作命令,除了上述命令之外,還有一些常用命令,例如 rmdir、find、sudo、top 命令等。候選人可以自行在 MacOS 系統(tǒng)或者 Ubuntu 系統(tǒng)上實(shí)踐操作。
1. 前言
之前的小節(jié)中介紹了操作系統(tǒng)的進(jìn)程,操作系統(tǒng)中有個(gè)創(chuàng)建進(jìn)程的重要方法就是 fork 函數(shù),當(dāng)需要執(zhí)行和本進(jìn)程相關(guān)的獨(dú)立任務(wù)時(shí),一般需要?jiǎng)?chuàng)建一個(gè)有血緣關(guān)系的子進(jìn)程。
2. fork 函數(shù)
面試官提問(wèn): Linux 系統(tǒng)中的 fork 函數(shù)是什么,有什么用途?
題目解析:
首先從定義上看,fork 函數(shù)的作用是在一個(gè)進(jìn)程的基礎(chǔ)上創(chuàng)建新的進(jìn)程,原有的進(jìn)程被定義為父進(jìn)程,新的進(jìn)程被定義為子進(jìn)程。
在 C 語(yǔ)言中調(diào)用 fork() 函數(shù)即實(shí)現(xiàn) fork 功能,示例:
#include<unistd.h> //包含fork函數(shù)的頭文件pid_t fork(void); //fork的返回類(lèi)型為pid_t,我們可以看成int類(lèi)型代碼塊12
認(rèn)識(shí)一個(gè)函數(shù),需要從函數(shù)的定義入手,了解函數(shù)做了什么事情,入?yún)⑹鞘裁?,出參是什么。我們?C 語(yǔ)言實(shí)現(xiàn)的一個(gè)典型的 fork 的程序入手,示例:
#include <unistd.h>#include <stdio.h> int main (){ pid_t fpid; int count = 0; fpid = fork(); if (fpid == 0) { //返回值是0,說(shuō)明是子進(jìn)程 printf("i am the child process, process id is %d\n", getpid()); count++; } else if(fpid > 0) { //返回值>0,說(shuō)明是父進(jìn)程 printf("i am the parent process, process id is %d\n", getpid()); count++; } else { //返回值<0,說(shuō)明fork發(fā)生異常 printf("fork encounter exception, process id is %d\n", getpid()); } //打印計(jì)數(shù)器 printf("after fork, counting result : %d\n", count); return 0;}
在 MacOS 系統(tǒng)上編譯運(yùn)行案例示例代碼,運(yùn)行結(jié)果如下圖。
test.c 編譯執(zhí)行結(jié)果
如果是不了解函數(shù)原理的前提下,僅僅從代碼層面分析,在調(diào)用 fork() 函數(shù)之后,代碼會(huì)進(jìn)入 if-else 判斷邏輯,在控制臺(tái)輸出一條語(yǔ)句,然后在控制臺(tái)打印計(jì)數(shù)器的數(shù)值。但是從真正執(zhí)行的結(jié)果來(lái)看,這兩個(gè)打印動(dòng)作都分別執(zhí)行了兩次,并不符合我們的預(yù)期。fork 之后的代碼邏輯被執(zhí)行了兩次,而且兩次進(jìn)入的不同的分支,所以重點(diǎn)在于 fork 函數(shù)到底有啥作用。
按照定義、入?yún)⒑统鰠⑷阶叩目蚣?,首先是分析函?shù)的定義,調(diào)用 fork 函數(shù)之后發(fā)生了什么事情:
(1)分配內(nèi)存:分配新的內(nèi)存空間給子進(jìn)程;(2)拷貝數(shù)據(jù):拷貝父進(jìn)程的數(shù)據(jù)結(jié)構(gòu)給子進(jìn)程;(3)加入列表:將新生成的子進(jìn)程添加到操作系統(tǒng)的進(jìn)程列表;(4)返回結(jié)果:fork 函數(shù)調(diào)度并且返回。
然后是分析 fork 函數(shù)的入?yún)?,fork 函數(shù)入?yún)⑹?void,也就是自動(dòng)同步進(jìn)程的上下文,不需要手動(dòng)聲明。
最后是分析 fork 函數(shù)的出參,fork 函數(shù)和程序員日常接觸的函數(shù)不同,我們?cè)?C 或者 Java 中定義的函數(shù)只會(huì)有一個(gè)返回值,fork 函數(shù)則是調(diào)用一次,返回二次。調(diào)用方(例如上述案例的 main 函數(shù))根據(jù)返回值的不同判斷處于父進(jìn)程還是子進(jìn)程。
(1)返回值 < 0:調(diào)用失敗,一般是因?yàn)椴僮飨到y(tǒng)中的進(jìn)程個(gè)數(shù)達(dá)到上限或者內(nèi)存不足以分配給新的進(jìn)程;(2)返回值 = 0:調(diào)用成功,并且處于子進(jìn)程;(3)返回值 > 0:調(diào)用成功,并且處于父進(jìn)程。
現(xiàn)在就不難理解,從調(diào)用 fork() 函數(shù),代碼實(shí)際上是被父子進(jìn)程分別執(zhí)行了一次,父進(jìn)程的進(jìn)程 id 是 52331,子進(jìn)程的進(jìn)程 id 是 52332。
在掌握原理之后,我們繼續(xù)探究 fork 函數(shù)的應(yīng)用場(chǎng)景。fork 函數(shù)的本質(zhì)在原有的進(jìn)程基礎(chǔ)上創(chuàng)建一個(gè)新的進(jìn)程,所以在網(wǎng)絡(luò)通信中使用較多,例如在客戶端發(fā)送一個(gè) HTTP 請(qǐng)求打到服務(wù)器時(shí),服務(wù)器進(jìn)程 fork 出一個(gè)子進(jìn)程用于處理單個(gè)請(qǐng)求,父進(jìn)程則繼續(xù)等待其他的請(qǐng)求。
3. 小結(jié)
本章節(jié)介紹了 Linux 的 fork 函數(shù),fork 如同其英文名,就是進(jìn)程的分叉。fork 函數(shù)簡(jiǎn)化了操作系統(tǒng)的進(jìn)程管理,又提供了一個(gè)簡(jiǎn)單的多進(jìn)程生成方案,在操作系統(tǒng)中的地位非常核心,候選人需要注意 fork 函數(shù)調(diào)用 1 次,返回 2 次的核心特性以及返回值。
1. 前言
操作系統(tǒng)的核心管理邏輯可以簡(jiǎn)化為進(jìn)程管理、內(nèi)存管理、文件管理。之前的小節(jié)已經(jīng)介紹了進(jìn)程的基本概念,每個(gè)進(jìn)程都有獨(dú)立的地址空間,這些地址空間被分為大小相同的塊,定義為頁(yè)(Page)。然而物理機(jī)的內(nèi)存硬件空間是有限的,舉例來(lái)說(shuō),我們裝配最常見(jiàn)的 4G 內(nèi)存條,但是很多進(jìn)程例如單機(jī)游戲,運(yùn)行時(shí)都需要占用幾個(gè) G 的內(nèi)存空間,所以就需要用到虛擬內(nèi)存。
2. 頁(yè)面置換算法
面試官提問(wèn): 操作系統(tǒng)的頁(yè)面置換算法是什么?常用算法有哪些?
題目解析:
首先要明確頁(yè)面置換算法是針對(duì)內(nèi)存管理的算法。
頁(yè)面置換算法是虛擬內(nèi)存的運(yùn)行機(jī)制核心,內(nèi)存被分頁(yè)之后,每個(gè)頁(yè)都是一段連續(xù)的地址,每個(gè)進(jìn)程擁有的都是一段虛擬地址,需要經(jīng)過(guò)內(nèi)存管理單元(Memory Management Unit,也就是 MMU)將虛擬地址轉(zhuǎn)換為物理地址。
操作系統(tǒng)的 CPU 和內(nèi)存都是稀缺資源,所以資源比較緊張,內(nèi)存具有非常高的 I/O 速度,但是空間很小。硬盤(pán)具有很大的存儲(chǔ)空間,但是 I/O 能力一般。所以操作系統(tǒng)綜合了兩者的特性,將硬盤(pán)作為內(nèi)存的緩存,虛擬內(nèi)存就是硬盤(pán)空間的一部分。進(jìn)程運(yùn)行時(shí),操作系統(tǒng)訪問(wèn)內(nèi)存空間,如果訪問(wèn)的頁(yè)面在內(nèi)存中不存在則從硬盤(pán)中將其調(diào)入,如果內(nèi)存沒(méi)有空閑空間,則將內(nèi)存中的一段數(shù)據(jù)調(diào)出到硬盤(pán)空間。
我們介紹三種最常見(jiàn)的內(nèi)存管理算法:LRU、FIFO、OPT 算法。
2.1 LRU 算法
LRU(Least Recently Used)即最近最少使用算法,算法的核心思想是如果在過(guò)去一段時(shí)間沒(méi)有訪問(wèn)過(guò)的頁(yè)面,在未來(lái)最近一段時(shí)間也不會(huì)訪問(wèn)。
算法的實(shí)現(xiàn)是給每個(gè)頁(yè)面設(shè)置一個(gè)時(shí)間戳,記錄最近一次訪問(wèn)的時(shí)間,如果發(fā)生缺頁(yè)錯(cuò)誤,則從所有頁(yè)面中淘汰時(shí)間戳最久遠(yuǎn)的一個(gè)。
LRU 算法案例示例:
訪問(wèn)頁(yè)面序號(hào)5028048
物理塊 15558888
物理塊 2
000000
物理塊 3
22244
是否發(fā)生缺頁(yè)是是是是否是否
對(duì)于第 4 次訪問(wèn)頁(yè)面時(shí),因?yàn)樗形锢韷K都有頁(yè)面,所以發(fā)生缺頁(yè)錯(cuò)誤,需要替換出最近最少訪問(wèn)的頁(yè)面,也就是序號(hào)為 5 的頁(yè)面,即物理塊 1 的內(nèi)容被置換。
2.2 FIFO 算法
FIFO(First In First Out)即先進(jìn)先出算法,也就是常見(jiàn)數(shù)據(jù)結(jié)構(gòu)的隊(duì)列模型。當(dāng)物理塊存儲(chǔ)空間不夠時(shí),優(yōu)先淘汰在最先進(jìn)入物理塊的頁(yè)面,也就是駐留時(shí)間最久的頁(yè)面。
FIFO 算法雖然相對(duì)簡(jiǎn)單,但是不符合操作系統(tǒng)的實(shí)際運(yùn)行情況,因?yàn)轳v留時(shí)間最久的頁(yè)面,大多數(shù)情況是經(jīng)常訪問(wèn)的頁(yè)面,F(xiàn)IFO 算法會(huì)增加缺頁(yè)錯(cuò)誤的概率。
FIFO 算法案例示例:
訪問(wèn)頁(yè)面序號(hào)5028048
物理塊 15558888
物理塊 2
000040
物理塊 3
22224
是否發(fā)生缺頁(yè)是是是是否是否
FIFO 算法相對(duì)于 LRU 的區(qū)別在于,只考慮頁(yè)面的駐留時(shí)間,在第 6 次訪問(wèn)頁(yè)面時(shí),不會(huì)因?yàn)樯洗问切蛱?hào) 0 的頁(yè)面剛進(jìn)行了訪問(wèn)就不進(jìn)行替換,因?yàn)樾蛱?hào) 0 的頁(yè)面是最早進(jìn)入物理塊的,所以替換為了序號(hào)為 4 的頁(yè)面。
2.3 OPT 算法
OPT(Optiomal)最優(yōu)頁(yè)面置換算法,算法的核心思想是置換最長(zhǎng)時(shí)間不會(huì)使用的頁(yè)面,這需要預(yù)判未來(lái)的頁(yè)面置換順序,目前的操作系統(tǒng)無(wú)法做到對(duì)于進(jìn)程未來(lái)需要使用的頁(yè)面進(jìn)行預(yù)測(cè),所以算法也沒(méi)有實(shí)際落地的實(shí)現(xiàn)。主要作用是對(duì)于已經(jīng)給定的頁(yè)面順序,作為最優(yōu)置換算法的比較標(biāo)桿,比如對(duì)于給定的頁(yè)面順序 5 0 2 8 0 4 8 可以對(duì)比 FIFO 算法以及 OPT 算法的頁(yè)面置換效率,判斷 FIFO 算法是否足夠高效。
訪問(wèn)頁(yè)面序號(hào)50280480
物理塊 155588888
物理塊 2
0000000
物理塊 3
222444
是否發(fā)生缺頁(yè)是是是是否是否否
對(duì)于訪問(wèn)頁(yè)面序號(hào)是 4 時(shí),因?yàn)榭梢钥吹轿磥?lái)會(huì)有頁(yè)面序號(hào) 8 和 0 的訪問(wèn),不會(huì)有序號(hào) 2 的訪問(wèn),所以置換物理塊 3 中的頁(yè)面序號(hào) 2。
3. 小結(jié)
頁(yè)面調(diào)度的目的是盡可能少的發(fā)生缺頁(yè)錯(cuò)誤,因?yàn)榘l(fā)生缺頁(yè)錯(cuò)誤時(shí)需要從硬盤(pán)置換空間,所以會(huì)降低進(jìn)程的執(zhí)行效率。常見(jiàn)的頁(yè)面置換算法除了本文介紹的 FIFO、LRU、OPT,還有時(shí)鐘置換算法,候選人可以自行了解。