面试必会系列 - 4.1 程序员必须掌握的:计算机组成、操作系统知识点汇总
本文已收錄至 Github(MD-Notes),若博客中圖片模糊或打不開,可以來我的 Github 倉庫,包含了完整圖文:https://github.com/HanquanHq/MD-Notes,涵蓋了互聯(lián)網(wǎng)大廠面試必問的知識點(diǎn),講解透徹,長期更新中,歡迎一起學(xué)習(xí)探討 ~
更多內(nèi)容,可以訪問:
面試必會系列專欄:https://blog.csdn.net/sinat_42483341/category_10300357.html
操作系統(tǒng)系列專欄:https://blog.csdn.net/sinat_42483341/category_10519484.html
目錄
- 計(jì)組、操作系統(tǒng)
- CPU電路原理
- CPU的基本組成
- 超線程
- 計(jì)算機(jī)的組成
- 存儲器的層次結(jié)構(gòu)
- 多核CPU
- 緩存行對齊偽共享問題
- 緩存行大小
- Intel CPU 緩存一致性協(xié)議
- 其他 CPU 緩存一致性協(xié)議
- 非同一訪問內(nèi)存 NUMA
- 操作系統(tǒng)啟動(dòng)過程
- 匯編語言(機(jī)器語言)的執(zhí)行過程
- 名詞
- 常識
- 吞吐量:單位時(shí)間可以讀取的數(shù)據(jù)
- 總線的分類
- 進(jìn)程、線程
- 進(jìn)程
- 1、僵尸進(jìn)程
- 2、孤兒進(jìn)程
- 線程
- 思考:java線程是用戶級線程還是內(nèi)核級線程?
- 協(xié)程(纖程)
- 纖程
- 纖程的優(yōu)勢
- 原語
- 中斷、系統(tǒng)調(diào)用
- 用戶態(tài)、內(nèi)核態(tài)
- mmap
- 內(nèi)存管理
- LRU算法(解決內(nèi)存不夠用的問題)
- 虛擬內(nèi)存
- 缺頁中斷
- Linux 內(nèi)核同步機(jī)制
- 關(guān)于同步理論的一些基本概念
- Linux 內(nèi)核同步常用方法
- Linux VFS 虛擬文件系統(tǒng)
- 硬鏈接、軟鏈接
- 文件描述符 fd
- PageCache 頁緩存
- 為什么 Java 程序員不要使用直接 IO,而要使用 Buffered 形式的IO?
- Linux
- Linux 目錄樹
- Linux 文件類型
- Linux 常用命令
- 進(jìn)程相關(guān)
- 網(wǎng)絡(luò)相關(guān)
計(jì)組、操作系統(tǒng)
匯編和操作系統(tǒng)代碼,又是不同層面的東西。匯編是針對CPU編程。系統(tǒng)內(nèi)核方法,是針對操作系統(tǒng)編程。
CPU電路原理
CPU的基本組成
PC -> Program Counter 程序計(jì)數(shù)器 (記錄當(dāng)前指令地址)
Registers -> 暫時(shí)存儲CPU計(jì)算需要用到的數(shù)據(jù)
ALU -> Arithmetic & Logic Unit 運(yùn)算單元
CU -> Control Unit 控制單元
MMU -> Memory Management Unit 內(nèi)存管理單元
超線程
一個(gè)ALU對應(yīng)多個(gè)Registers
切換線程的時(shí)候不需要進(jìn)行上下文切換,只需要切換到對應(yīng)的Registers即可
計(jì)算機(jī)的組成
存儲器的層次結(jié)構(gòu)
添加緩存,是為了解決 CPU 到不同部件的速度差別過大的問題
多核CPU
不同的核心有各自獨(dú)立的 L1,L2,但共享 L3 級緩存。為什么是3級?這是工業(yè)上的經(jīng)驗(yàn)值。
按塊讀取:根據(jù)程序局部性原理,提高效率,充分發(fā)揮總線CPU針腳一次性讀取更多數(shù)據(jù)的能力
讀取數(shù)據(jù)是通過 DMA 硬件進(jìn)行的,直接內(nèi)存訪問,用來做數(shù)據(jù)的搬運(yùn)。通過協(xié)處理器,而不需要走CPU。偏向硬件,驅(qū)動(dòng)開發(fā)的人員會接觸。
緩存行對齊偽共享問題
緩存行大小
緩存行越大,局部性空間效率越高,但讀取時(shí)間慢;緩存行越小,局部性空間效率越低,但讀取時(shí)間快。取一個(gè)折中值,目前 Intel CPU 緩存行64字節(jié)
Intel CPU 緩存一致性協(xié)議
怎么保證數(shù)據(jù)的一致性?Intel MESI 緩存一致性協(xié)議,數(shù)據(jù)四種狀態(tài):
-
M: 被修改(Modified)
該緩存行只被緩存在該CPU的緩存中,并且是被修改過的(dirty),即與主存中的數(shù)據(jù)不一致,該緩存行中的內(nèi)存需要在未來的某個(gè)時(shí)間點(diǎn)(允許其它CPU讀取請主存中相應(yīng)內(nèi)存之前)寫回(write back)主存。當(dāng)被寫回主存之后,該緩存行的狀態(tài)會變成獨(dú)享(exclusive)狀態(tài)。
-
E: 獨(dú)享的(Exclusive)
該緩存行只被緩存在該CPU的緩存中,它是未被修改過的(clean),與主存中數(shù)據(jù)一致。該狀態(tài)可以在任何時(shí)刻當(dāng)有其它CPU讀取該內(nèi)存時(shí)變成共享狀態(tài)(shared)。同樣地,當(dāng)CPU修改該緩存行中內(nèi)容時(shí),該狀態(tài)可以變成Modified狀態(tài)。
-
S: 共享的(Shared)
該狀態(tài)意味著該緩存行可能被多個(gè)CPU緩存,并且各個(gè)緩存中的數(shù)據(jù)與主存數(shù)據(jù)一致(clean),當(dāng)有一個(gè)CPU修改該緩存行中,其它CPU中該緩存行可以被作廢(變成無效狀態(tài)(Invalid))。
-
I: 無效的(Invalid)
該緩存是無效的(可能有其它CPU修改了該緩存行)。
其工作原理是,當(dāng)CPU1在獲取計(jì)算機(jī)內(nèi)存i時(shí),這是CPU2也可以獲取這個(gè)變量i。當(dāng)CPU1對它取回的變量進(jìn)行操作后。CPU1的i會被標(biāo)記為被修改的(Modified),他需要將i寫入主內(nèi)存將i標(biāo)記為獨(dú)享狀態(tài)(Exclusive),這時(shí)CPU1會立即將i這個(gè)變量寫入計(jì)算機(jī)內(nèi)存。這時(shí)計(jì)算機(jī)內(nèi)存中的i會被標(biāo)記為共享的(Shared)同時(shí)CPU2中的i會被標(biāo)記為無效的(Invalid),然后CPU2就只能重新從計(jì)算機(jī)內(nèi)存中獲取新的i變量。然后在進(jìn)行CPU2中i的操作。
其他 CPU 緩存一致性協(xié)議
其他廠商的 CPU 也有緩存一致性協(xié)議,例如 MSI,MESI,MOSI,Synapse,Firefly,Dragon 等等。
盡管有緩存一致性協(xié)議,有些無法被緩存的數(shù)據(jù),或者跨越多個(gè)緩存行的數(shù)據(jù),依然必須使用總線鎖。
一種情況,假設(shè)有兩個(gè)線程想要修改的數(shù)據(jù),位于同一個(gè)緩存行 cache line,你改的時(shí)候要通知我,我改的時(shí)候要通知你,于是因?yàn)榫彺嫘械拇嬖?#xff0c;相比沒有緩存行而言,反而效率會比較低。
于是誕生了一種編程模式:緩存行對齊,對于某些高競爭級別訪問的數(shù)據(jù),為了保證不發(fā)生偽共享,可以采用緩存行對齊的編程方式,即,使用多個(gè) long 類型的變量,將數(shù)據(jù)前后緩存行撐起來。例如:單機(jī)最快的內(nèi)存消息隊(duì)列 disruptor,核心是一個(gè) ring buffer,它的 cursor 前后分別放了 7個(gè) long 類型數(shù)字。
JDK7中,很多采用long padding提高效率
JDK8,加入了@Contended注解(實(shí)驗(yàn))需要加上:JVM -XX:-RestrictContended
非同一訪問內(nèi)存 NUMA
UMA:同一內(nèi)存訪問。多個(gè)CPU通過一條總線,訪問同一個(gè)內(nèi)存。
現(xiàn)在很多服務(wù)器的架構(gòu)是使用NUMA的,因?yàn)閁MA不以拓展:隨著CPU的數(shù)量增多,許多時(shí)間被浪費(fèi)在CPU爭搶內(nèi)存資源上。
NUMA:非同一訪問內(nèi)存。每個(gè)CPU有自己專屬的內(nèi)存,CPU對于自己插槽上的內(nèi)存訪問是有優(yōu)先級的。
ZGC 垃圾回收器可以做到 NUMA aware,如果探測到計(jì)算機(jī)實(shí)現(xiàn)了NUMA的話,分配內(nèi)存會優(yōu)先分配該線程所在CPU的最近內(nèi)存。
操作系統(tǒng)啟動(dòng)過程
通電 -> bios uefi 工作 -> 自檢 -> 到硬盤固定位置加載 bootloader -> 讀取可配置信息 -> CMOS
bootloader 在硬盤上的位置是固定的,放在硬盤的第一個(gè)扇區(qū)上
CMOS 用來存儲可以配置的信息,需要通電才能存儲信息,例如開機(jī)密碼。主板上有塊電池給它通電。
操作系統(tǒng)的第一條指令在硬盤中也是固定的
匯編語言(機(jī)器語言)的執(zhí)行過程
匯編語言的本質(zhì):機(jī)器語言的助記符 其實(shí)它就是機(jī)器語言
計(jì)算機(jī)通電 -> CPU讀取內(nèi)存中程序(電信號輸入)->時(shí)鐘發(fā)生器不斷震蕩通斷電 ->推動(dòng)CPU內(nèi)部一步一步執(zhí)行(執(zhí)行多少步取決于指令需要的時(shí)鐘周期)->計(jì)算完成->寫回(電信號)->寫給顯卡輸出(sout,或者圖形)
名詞
異地持久化
常識
秒 > 毫秒 > 微妙 > 納秒
磁盤尋址:毫秒級別(磁頭移動(dòng)等)
內(nèi)存尋址:納秒級別(磁盤、內(nèi)存差距10W倍)
吞吐量:單位時(shí)間可以讀取的數(shù)據(jù)
磁盤IO:百兆級別(SATA3 幾百M(fèi)B,PCI-E GB級別)
總線的分類
數(shù)據(jù)總線、地址總線、控制總線
進(jìn)程、線程
進(jìn)程是操作系統(tǒng)分配資源的基本單位,線程是操作系統(tǒng)執(zhí)行調(diào)度的基本單位。
進(jìn)程
- 進(jìn)程 是處于執(zhí)行期的程序以及相關(guān)資源的總稱,分配了獨(dú)立的內(nèi)存空間
- 所有的進(jìn)程都是 PID 為 1 的 init 進(jìn)程的后代
- 內(nèi)核把進(jìn)程的列表放在任務(wù)隊(duì)列 task list 中,鏈表中的每一項(xiàng)都是 進(jìn)程描述符
- 進(jìn)程描述符包含:它打開的文件、進(jìn)程的地址空間、掛起的信號、進(jìn)程的狀態(tài)等
- 進(jìn)程狀態(tài)
- 運(yùn)行:進(jìn)程是可執(zhí)行的
- 可中斷:正在睡眠,等待某些條件的達(dá)成,可以因?yàn)槭盏叫盘柖崆氨粏拘?/li>
- 不可中斷:即使收到信號,也不會被喚醒
- 被其他進(jìn)程跟蹤:例如通過 ptrace 對調(diào)試程序進(jìn)行跟蹤
- 停止:進(jìn)程沒有投入運(yùn)行,也不能投入運(yùn)行
- 進(jìn)程的創(chuàng)建
- Linux 中,父進(jìn)程通過 fork() 創(chuàng)建子進(jìn)程,fork() 內(nèi)部調(diào)用的是 clone()
- Linux 的 fork() 使用 寫時(shí)拷貝 頁實(shí)現(xiàn)
- 進(jìn)程的調(diào)度
- Linux 使用 CFS(Completely Fair Scheduler 完全公平調(diào)度)
- 確保給每個(gè)進(jìn)程公平的 處理器使用比,按 優(yōu)先級 分配 時(shí)間片的比例,記錄每個(gè)進(jìn)程的執(zhí)行時(shí)間,如果有一個(gè)進(jìn)程執(zhí)行時(shí)間不到他應(yīng)該分配的比例(比如給了你50%,你只用了10%),就優(yōu)先執(zhí)行這個(gè)進(jìn)程作為補(bǔ)償。例如,一個(gè)是用戶鼠標(biāo)移動(dòng)優(yōu)先級高,另一個(gè)是 IO 密集型優(yōu)先級低
- 用 紅黑樹 維護(hù) 可運(yùn)行進(jìn)程隊(duì)列,每次運(yùn)行紅黑樹中 最左邊葉子結(jié)點(diǎn) 代表的進(jìn)程
- 分為實(shí)時(shí)進(jìn)程(始終優(yōu)先于普通進(jìn)程)、普通進(jìn)程(根據(jù) nice 值分配時(shí)間片)
- Linux 使用 CFS(Completely Fair Scheduler 完全公平調(diào)度)
1、僵尸進(jìn)程
在大多數(shù)情況下,少量僵尸進(jìn)程的存在,對操作系統(tǒng)沒有太大影響,它基本不再占用任何資源了,它在內(nèi)存中唯一占用的資源就是PCB了。僵尸進(jìn)程的產(chǎn)生原因,有可能父進(jìn)程是一個(gè)daemon進(jìn)程,C程序開發(fā)人員在父進(jìn)程沒有釋放子進(jìn)程,這時(shí)會出現(xiàn)僵尸進(jìn)程。可以使用內(nèi)核中的wait函數(shù),釋放僵尸進(jìn)程。
2、孤兒進(jìn)程
孤兒進(jìn)程產(chǎn)生的原因:子進(jìn)程還活著,父進(jìn)程掛了。孤兒進(jìn)程和孤兒線程沒有太大區(qū)別,因?yàn)樵贚inux中,進(jìn)程和線程沒有太大區(qū)別。孤兒進(jìn)程的影響:影響不大。
線程
- 線程是在進(jìn)程中活動(dòng)的對象
- 內(nèi)核調(diào)度的對象是 線程,而不是 進(jìn)程
- Linux 把所有的線程都當(dāng)做進(jìn)程來實(shí)現(xiàn),線程僅僅被視為一個(gè)與其他進(jìn)程共享某些資源的進(jìn)程,只不過和其他線程共享同一個(gè)主進(jìn)程中的內(nèi)存空間 ——《Linux內(nèi)核設(shè)計(jì)與實(shí)現(xiàn)》
- 線程共享進(jìn)程的內(nèi)存空間,沒有自己獨(dú)立的內(nèi)存空間
思考:java線程是用戶級線程還是內(nèi)核級線程?
java 線程不是純用戶級線程:java中有個(gè) fork join 框架,利用多處理技術(shù)進(jìn)行 maprudce 的工作,證明了內(nèi)核是可以感知到用戶線程的存在,因此才會將多個(gè)線程調(diào)度到多個(gè)處理器中。
java應(yīng)用程序中的某個(gè)線程阻塞,是不會引起整個(gè)進(jìn)程的阻塞。
java 線程不是純內(nèi)核級線程:如果使用純粹的內(nèi)核級線程,那么有關(guān)線程的所有管理工作都是內(nèi)核完成的,用戶程序中沒有管理線程的代碼。顯然,java線程庫提供了大量的線程管理機(jī)制,因此java線程絕不是純粹的內(nèi)核級線程。
馬士兵:“ JVM 線程和內(nèi)核中的線程是 1:1 的關(guān)系”
綜上,java線程是混合型的線程模型,一般而言,是通過lwp將用戶級線程映射到內(nèi)核線程中。
補(bǔ)充:《現(xiàn)代操作系統(tǒng)》P79:“Java 支持用戶級線程”
協(xié)程(纖程)
JVM(HotSpot)和操作系統(tǒng)的線程是一一對應(yīng)的:JVM空間的一個(gè)線程,對應(yīng)操作系統(tǒng)中的一個(gè)線程,這是重量級線程。
操作系統(tǒng)可以開啟的線程是有限的(1萬個(gè)已經(jīng)很卡了),而纖程是用戶空間的,不需要與內(nèi)核打交道,輕量級,切換速度快,可以啟動(dòng)幾萬個(gè),甚至幾十萬個(gè)都沒有問題。
纖程
纖程可以被理解為:線程里的線程,在JVM層級用軟件進(jìn)行內(nèi)部的線程協(xié)調(diào)。
纖程是被 JVM 管理,運(yùn)行在用戶態(tài),操作和調(diào)度不經(jīng)過操作系統(tǒng)內(nèi)核。
關(guān)于纖程的實(shí)現(xiàn):纖程可以分配自己獨(dú)立的纖程棧,相當(dāng)于自己實(shí)現(xiàn)了一個(gè)小小的操作系統(tǒng)級別的調(diào)度程序。
Java 通過 Quaser 類庫可以支持纖程,但目前還不是很成熟,據(jù)說如果大規(guī)模應(yīng)用在生產(chǎn)級別的話,還是有一些小bug存在的。
纖程的優(yōu)勢
1、占有資源很少。操作系統(tǒng)啟動(dòng)一個(gè)線程,占用 1M 的內(nèi)存空間,而啟動(dòng)一個(gè)纖程只需要 4K 空間
2、由于輕量級,切換簡單
3、由于輕量級,可以啟動(dòng)幾十萬個(gè)纖程都沒有問題。
原語
- 是一種特殊的程序,運(yùn)行在 內(nèi)核態(tài)
- 處于操作系統(tǒng)最底層,是最接近硬件的部分
- 這種程序的運(yùn)行具有原子性,只能一氣呵成,不可被中斷
- 運(yùn)行時(shí)間較短、調(diào)用頻繁
- 原語采用“關(guān)中斷指令”和“開中斷指令”實(shí)現(xiàn)
中斷、系統(tǒng)調(diào)用
系統(tǒng)調(diào)用表及源碼鏈接 https://filippo.io/linux-syscall-table/
系統(tǒng)調(diào)用 是用戶空間訪問內(nèi)核的唯一手段,除 異常 和 陷入 外,它是內(nèi)核的唯一合法入口。
Linux 的系統(tǒng)調(diào)用,作為 C 庫的一部分提供,用戶空間進(jìn)程 用 系統(tǒng)調(diào)用號 來指明執(zhí)行哪個(gè)系統(tǒng)調(diào)用。
進(jìn)行系統(tǒng)調(diào)用的步驟:
用戶態(tài)、內(nèi)核態(tài)
Intel CPU 分為三級:ring 0,1,2,3三個(gè)級別
Linux 只使用了 0,3 兩個(gè)級別。
Linux 內(nèi)核是運(yùn)行在 ring 0 級別的,能訪問所有指令;應(yīng)用程序只能訪問 Ring 3 級。
用戶空間的應(yīng)用程序運(yùn)行在 ring 3 級別,某些指令是不能被訪問的,想要讀硬盤、寫硬盤、寫網(wǎng)卡等,對于系統(tǒng)的關(guān)鍵訪問,需要通過操作系統(tǒng)內(nèi)核,因此需要轉(zhuǎn)換為內(nèi)核態(tài)。正是因?yàn)檫@樣的分層,使得現(xiàn)在的Linux,Windows非常健壯,不會輕易被程序搞崩。
Linux 內(nèi)核提供了 200 多個(gè)系統(tǒng)調(diào)用,例如 sendfile read write pthread fork等對外暴露的函數(shù)。
內(nèi)核空間:只有內(nèi)核能訪問的空間。用戶是不可能將這塊內(nèi)存干掉的。
用戶空間:用戶可以直接操作的空間。
mmap
mmap將 用戶的線性地址空間 直接映射到了 內(nèi)核的 pagecache 地址,如果是臟的需要寫的話,依然受pagecache 的影響,才能最終刷寫到磁盤中去。
內(nèi)存管理
DOS時(shí)代 - 同一時(shí)間只能有一個(gè)進(jìn)程在運(yùn)行(也有一些特殊算法可以支持多進(jìn)程)
windows9x - 多個(gè)進(jìn)程裝入內(nèi)存存在的問題:
- 內(nèi)存不夠用
- 互相打擾
為了解決這兩個(gè)問題,誕生了現(xiàn)在的內(nèi)存管理系統(tǒng):使用虛擬地址、分頁裝入、軟硬件結(jié)合尋址。
將內(nèi)存分頁(因?yàn)閮?nèi)存不夠用),內(nèi)存中分成固定大小的頁框4K,把硬盤上的程序也分成4K大小的塊。另外維護(hù)一個(gè)頁框page frame,用到哪一塊,就將哪一塊加載進(jìn)內(nèi)存中。
例如,執(zhí)行QQ.exe時(shí),把它的頁表記錄下來,執(zhí)行時(shí),用到頁表中的哪一頁,就將這頁加載進(jìn)內(nèi)存中。
在加載的過程中,如果內(nèi)存已經(jīng)滿了,會把最不常用的一塊放到swap分區(qū), 把最新的一塊加載進(jìn)來,這個(gè)就是著名的LRU算法。這就是交換分區(qū)的由來。
LRU算法(解決內(nèi)存不夠用的問題)
LeetCode 146 題,頭條要求15分鐘內(nèi)手撕,阿里去年也要求手撕。
只允許使用 HashMap 實(shí)現(xiàn) LRU 算法,哈希表(保證查找操作O(1)) + 雙向鏈表 (保證排序操作和新增操作 O(1)))
- LRU (Least recently used) 最近最少使用,如果數(shù)據(jù)最近時(shí)間被訪問過,那么將來被訪問的幾率也更高。
- LFU (Least frequently used) 最不經(jīng)常使用,如果一個(gè)數(shù)據(jù)在最近一段時(shí)間內(nèi)使用次數(shù)很少,那么在將來一段時(shí)間內(nèi)被使用的可能性也很小。
虛擬內(nèi)存
以DOS Win31 …這類系統(tǒng)為例,A進(jìn)程、B進(jìn)程是可以互相操作內(nèi)存的。為了保證互不影響,讓進(jìn)程工作在虛擬空間。在程序中用到的空間地址不再是直接的物理地址,而是虛擬的地址,這樣,A進(jìn)程永遠(yuǎn)不可能訪問到B進(jìn)程的空間。
**虛擬空間多大呢?**虛擬空間的大小就是尋址空間,要看操作系統(tǒng)是多少位的。例如,64位系統(tǒng)的虛擬空間是2^64,32為系統(tǒng)的虛擬空間是2^32。虛擬空間比物理空間大很多 ,單位是 byte
**為什么使用虛擬內(nèi)存?**站在虛擬的角度,進(jìn)程是獨(dú)享整個(gè)系統(tǒng) + CPU
**地址是怎么映射的?**內(nèi)存映射:偏移量 + 段的基地址 = 線性地址 (虛擬空間)
線性地址,是通過 OS + MMU(Memory Management Unit內(nèi)存管理單元)來映射到真正的物理地址。只有操作系統(tǒng)內(nèi)核知道虛擬內(nèi)存中地址對應(yīng)的真正的物理地址,應(yīng)用程序是不知道的,這樣保證了系統(tǒng)的安全。
缺頁中斷
進(jìn)程P1,P2,P3,P4都認(rèn)為自己是獨(dú)占整個(gè)內(nèi)核的,實(shí)際上是共享操作系統(tǒng)內(nèi)核。
MMU給每一個(gè)進(jìn)程分配他們的內(nèi)存資源。
如果內(nèi)存裝滿了,使用LRU算法將最不常使用的頁放入硬盤的交換空間中。
在執(zhí)行一條指令時(shí),如果發(fā)現(xiàn)需要用到頁在內(nèi)存中沒有,那么停止該指令的執(zhí)行,并產(chǎn)生一個(gè)缺頁異常(中斷),由內(nèi)核處理并加載,之后,原先引起的異常的指令就可以繼續(xù)執(zhí)行,而不再產(chǎn)生異常。
Linux 內(nèi)核同步機(jī)制
關(guān)于同步理論的一些基本概念
- 臨界區(qū)(critical area): 訪問或操作共享數(shù)據(jù)的代碼段
簡單理解:synchronized大括號中部分(原子性) - 競爭條件(race conditions)兩個(gè)線程同時(shí)擁有臨界區(qū)的執(zhí)行權(quán)
- 數(shù)據(jù)不一致:data unconsistency 由競爭條件引起的數(shù)據(jù)破壞
- 同步(synchronization)避免race conditions
- 鎖:鎖是完成同步的手段(門鎖,門后是臨界區(qū),只允許一個(gè)線程存在)
上鎖解鎖必須具備原子性(如果連上鎖的過程都會被打斷的話,就沒有什么意義了) - 原子性(象原子一樣不可分割的操作)
- 有序性(禁止指令重排)
- 可見性(一個(gè)線程內(nèi)的修改,另一個(gè)線程可見)
互斥鎖 排他鎖 共享鎖 分段鎖
Linux 內(nèi)核同步常用方法
了解一下Linux內(nèi)核實(shí)現(xiàn)的一些同步方法,了解一些關(guān)于線程控制的理論知識,可以去印證Java層面的同步知識。
用的話要用C語言去調(diào)用。
相當(dāng)于:讀的時(shí)候是共享鎖,寫的時(shí)候是排他鎖
是一個(gè)重量級鎖,線程會進(jìn)入wait,適合長時(shí)間持有的鎖情況
(多個(gè)寫,可以分段寫,比較少用)(分段鎖)
vfork() 在子進(jìn)程結(jié)束時(shí)通過完成變量叫醒父進(jìn)程 類似于(Latch)
在網(wǎng)絡(luò)編程中有完成端口(Completion Port)
序列計(jì)數(shù)器(從0開始,寫時(shí)增加(+1),寫完釋放(+1),讀線程不對計(jì)數(shù)器做任何操作)
寫的時(shí)候,不妨礙讀線程:
如果讀線程發(fā)現(xiàn)是偶數(shù),說明在讀的時(shí)候沒有任何人改變過。
如果讀線程發(fā)現(xiàn)是奇數(shù),說明你讀到的可能是中間狀態(tài),你可以選擇繼續(xù)自旋,等待值變?yōu)榕紨?shù)。
讀前讀后序列一樣,說明沒有寫線程打斷。
Linux VFS 虛擬文件系統(tǒng)
Virtual Filesystem Switch,虛擬文件系統(tǒng),是一個(gè)目錄樹。樹上不同的節(jié)點(diǎn)可映射到物理的文件地址,可掛載。
相當(dāng)于一個(gè)解耦層,在具體的文件系統(tǒng)之上抽象的一層,為能夠給各種文件系統(tǒng)提供一個(gè)通用的接口,使上層的應(yīng)用程序能夠使用通用的接口訪問不同文件系統(tǒng)、不同的驅(qū)動(dòng)。
硬鏈接、軟鏈接
- 硬鏈接
- 兩個(gè)變量名指向了同一個(gè)物理位置,硬鏈接的文件擁有相同的 inode,操作系統(tǒng)是靠inode來區(qū)分文件的。2個(gè)inode相同的文件,代表它們是一個(gè)文件。
- 如果刪掉了其中一個(gè)文件,另外一方還能找到這個(gè)文件。相當(dāng)于只是刪除了一個(gè)引用。除非你把硬鏈接和源文件都刪除, 這個(gè)文件才被刪除。
- 軟鏈接
- 軟鏈接是兩個(gè)獨(dú)立的文件,相當(dāng)于創(chuàng)建了一個(gè)“快捷方式”
- 文件的共享用戶只有該文件的 路徑名,只有文件擁有者才擁有 指向其索引節(jié)點(diǎn)的指針
- 當(dāng)符號鏈接被刪除時(shí),并不會影響源文件。但是當(dāng)源文件被刪除時(shí),符號鏈接就找不到源文件了,會標(biāo)紅報(bào)錯(cuò)。
- 軟鏈接有著 自己的 inode 號 以及用戶數(shù)據(jù)塊。
- 軟鏈接可跨文件系統(tǒng),硬鏈接不行
- 共性
- 無論是硬鏈接還是軟連接,如果修改任意一方,另外一個(gè)文件也會看到這個(gè)變化。
文件描述符 fd
任何程序都有:0 標(biāo)準(zhǔn)輸入,1 標(biāo)準(zhǔn)輸出, 2 報(bào)錯(cuò)輸出
-
lsof 列出系統(tǒng)中打開的文件
lsof -op $$看見當(dāng)前進(jìn)程的文件描述符的細(xì)節(jié),包括偏移量、指針等等
-
/proc/$$/fd是當(dāng)前程序的所有的文件描述符
PageCache 頁緩存
PageCache 通常 4K,本來是用來優(yōu)化IO的性能(優(yōu)先走內(nèi)存),但它的缺點(diǎn)是刷寫硬盤不及時(shí),在突然關(guān)機(jī)或異常斷電時(shí),有丟失數(shù)據(jù)的可能
為什么 Java 程序員不要使用直接 IO,而要使用 Buffered 形式的IO?
一次寫一個(gè)緩沖區(qū)大小,減少調(diào)用 write 的次數(shù),只不過是每一次寫入的數(shù)據(jù)量比較大。減少了用戶態(tài)到內(nèi)核態(tài)的來回切換帶來的性能損耗。
- ByteBuffer
- allocate將字節(jié)數(shù)組分配到了堆上,是 JVM 堆內(nèi) 的線性地址空間
- allocateDirect將字節(jié)數(shù)組分配到 JVM 的堆外 內(nèi)存中,是 C 語言可以直接訪問的。
操作系統(tǒng)沒有絕對的數(shù)據(jù)可靠性。為什么要設(shè)計(jì) pagecache,是為了減少硬件的IO的調(diào)用,想要優(yōu)先使用內(nèi)存,這樣能夠提速。如果追求性能,就要在一致性、可靠性之間做出權(quán)衡。
從大方面來看,在現(xiàn)在的分布式系統(tǒng)當(dāng)中,如果你追求數(shù)據(jù)存儲的可靠性(保持緩存和磁盤的強(qiáng)一致,對于每一次對數(shù)據(jù)的微小改變,都要去刷寫磁盤),仍然避免不了單點(diǎn)故障的問題。單點(diǎn)故障會讓你為了保持強(qiáng)一致而耗費(fèi)的能損耗一毛錢收益都沒有。
這就是為什么我們使用主從復(fù)制、主備HA
這就是為什么 Kafka,ES 都有副本的概念,而副本是從 socket 得到的。副本又分為同步的異步的區(qū)別,這些都是后話了,我們以后再講…
Linux
Linux 目錄樹
常見目錄說明:
- /bin: 存放二進(jìn)制可執(zhí)行文件(ls、cat、mkdir等),常用命令一般都在這里;
- /etc: 存放系統(tǒng)管理和配置文件;
- /home: 存放所有用戶文件的根目錄,是用戶主目錄的基點(diǎn),比如用戶user的主目錄就是/home/user,可以用~user表示;
- /usr : 用于存放系統(tǒng)應(yīng)用程序;
- /opt: 額外安裝的可選應(yīng)用程序包所放置的位置。一般情況下,我們可以把tomcat等都安裝到這里;
- /proc: 虛擬文件系統(tǒng)目錄,是系統(tǒng)內(nèi)存的映射。可直接訪問這個(gè)目錄來獲取系統(tǒng)信息;
- /root: 超級用戶(系統(tǒng)管理員)的主目錄(特權(quán)階級o);
- /sbin: 存放二進(jìn)制可執(zhí)行文件,只有root才能訪問。這里存放的是系統(tǒng)管理員使用的系統(tǒng)級別的管理命令和程序。如ifconfig等;
- /dev: 用于存放設(shè)備文件;
- /mnt: 系統(tǒng)管理員安裝臨時(shí)文件系統(tǒng)的安裝點(diǎn),系統(tǒng)提供這個(gè)目錄是讓用戶臨時(shí)掛載其他的文件系統(tǒng);
- /boot: 存放用于系統(tǒng)引導(dǎo)時(shí)使用的各種文件;
- /lib : 存放著和系統(tǒng)運(yùn)行相關(guān)的庫文件 ;
- /tmp: 用于存放各種臨時(shí)文件,是公用的臨時(shí)文件存儲點(diǎn);
- /var: 用于存放運(yùn)行時(shí)需要改變數(shù)據(jù)的文件,也是某些大文件的溢出區(qū),比方說各種服務(wù)的日志文件(系統(tǒng)啟動(dòng)日志等。)等;
- /lost+found: 這個(gè)目錄平時(shí)是空的,系統(tǒng)非正常關(guān)機(jī)而留下“無家可歸”的文件(windows下叫什么.chk)就在這里。
Linux 文件類型
Linux支持很多文件類型,其中非常重要的文件類型有: 普通文件,目錄文件,鏈接文件,設(shè)備文件,管道文件,Socket套接字文件等。
Linux 常用命令
進(jìn)程相關(guān)
ps -ef / ps -aux: 查看當(dāng)前系統(tǒng)正在運(yùn)行進(jìn)程,展示格式不同。如果想查看特定的進(jìn)程,使用:ps aux | grep redis (查看包括redis字符串的進(jìn)程),也可使用 pgrep redis
kill -9 進(jìn)程pid: 殺死進(jìn)程(-9 表示強(qiáng)制終止)
網(wǎng)絡(luò)相關(guān)
網(wǎng)絡(luò)通信命令:
- 查看當(dāng)前系統(tǒng)的網(wǎng)卡信息:ifconfig / ip -a
- 查看當(dāng)前系統(tǒng)的端口使用:netstat -an
總結(jié)
以上是生活随笔為你收集整理的面试必会系列 - 4.1 程序员必须掌握的:计算机组成、操作系统知识点汇总的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java多线程:示例代码
- 下一篇: 爬虫实战:CentOS安装JDK,部署J