米哈遊,啟動!

圖解學習網站:https://xiaolincoding.com

大家好,我是小林。

今天來分享一位同學米哈遊的面經,投遞的崗位是雲原生,同學是校招生沒有云原生的基礎,所以面試沒有問雲原生的內容,但是都在計算機基礎方面的內容,都是經典的面試問題。

不管是面後端開發、客戶端開發、測試開發等崗位,計算機基礎的內容都逃不開的,包括社招面大廠,即使工作了好幾年,也會問考察一些計算機基礎的問題,所以同學們一定要好好掌握。

這個面試難在的是在算法, 抽五星卡概率,不愧是遊戲大廠,出的題目也是遊戲背景。

操作系統

死鎖發生條件是什麼?

死鎖只有同時滿足以下四個條件纔會發生:

  • 互斥條件:互斥條件是指多個線程不能同時使用同一個資源
  • 持有並等待條件:持有並等待條件是指,當線程 A 已經持有了資源 1,又想申請資源 2,而資源 2 已經被線程 C 持有了,所以線程 A 就會處於等待狀態,但是線程 A 在等待資源 2 的同時並不會釋放自己已經持有的資源 1
  • 不可剝奪條件:不可剝奪條件是指,當線程已經持有了資源 ,在自己使用完之前不能被其他線程獲取,線程 B 如果也想使用此資源,則只能在線程 A 使用完並釋放後才能獲取。
  • 環路等待條件:環路等待條件指的是,在死鎖發生的時候,兩個線程獲取資源的順序構成了環形鏈

如何避免死鎖?

避免死鎖問題就只需要破環其中一個條件就可以,最常見的並且可行的就是使用資源有序分配法,來破環環路等待條件。那什麼是資源有序分配法呢?線程 A 和 線程 B 獲取資源的順序要一樣,當線程 A 是先嚐試獲取資源 A,然後嘗試獲取資源 B 的時候,線程 B 同樣也是先嚐試獲取資源 A,然後嘗試獲取資源 B。也就是說,線程 A 和 線程 B 總是以相同的順序申請自己想要的資源。

介紹一下操作系統內存管理

操作系統設計了虛擬內存,每個進程都有自己的獨立的虛擬內存,我們所寫的程序不會直接與物理內打交道。

有了虛擬內存之後,它帶來了這些好處:

  • 第一,虛擬內存可以使得進程對運行內存超過物理內存大小,因爲程序運行符合局部性原理,CPU 訪問內存會有很明顯的重複訪問的傾向性,對於那些沒有被經常使用到的內存,我們可以把它換出到物理內存之外,比如硬盤上的 swap 區域。
  • 第二,由於每個進程都有自己的頁表,所以每個進程的虛擬內存空間就是相互獨立的。進程也沒有辦法訪問其他進程的頁表,所以這些頁表是私有的,這就解決了多進程之間地址衝突的問題。
  • 第三,頁表裏的頁表項中除了物理地址之外,還有一些標記屬性的比特,比如控制一個頁的讀寫權限,標記該頁是否存在等。在內存訪問方面,操作系統提供了更好的安全性。

Linux 是通過對內存分頁的方式來管理內存,分頁是把整個虛擬和物理內存空間切成一段段固定尺寸的大小。這樣一個連續並且尺寸固定的內存空間,我們叫(_Page_)。在 Linux 下,每一頁的大小爲 4KB。虛擬地址與物理地址之間通過頁表來映射,如下圖:頁表是存儲在內存裏的,內存管理單元 (_MMU_)就做將虛擬內存地址轉換成物理地址的工作。

而當進程訪問的虛擬地址在頁表中查不到時,系統會產生一個缺頁異常,進入系統內核空間分配物理內存、更新進程頁表,最後再返回用戶空間,恢復進程的運行。

介紹copy on write

主進程在執行 fork 的時候,操作系統會把主進程的「頁表」複製一份給子進程,這個頁表記錄着虛擬地址和物理地址映射關係,而不會複製物理內存,也就是說,兩者的虛擬空間不同,但其對應的物理空間是同一個。這樣一來,子進程就共享了父進程的物理內存數據了,這樣能夠節約物理內存資源,頁表對應的頁表項的屬性會標記該物理內存的權限爲只讀

不過,當父進程或者子進程在向這個內存發起寫操作時,CPU 就會觸發寫保護中斷,這個寫保護中斷是由於違反權限導致的,然後操作系統會在「寫保護中斷處理函數」裏進行物理內存的複製,並重新設置其內存映射關係,將父子進程的內存讀寫權限設置爲可讀寫,最後纔會對內存進行寫操作,這個過程被稱爲「**寫時複製(Copy On Write)**」。

寫時複製顧名思義,在發生寫操作的時候,操作系統纔會去複製物理內存,這樣是爲了防止 fork 創建子進程時,由於物理內存數據的複製時間過長而導致父進程長時間阻塞的問題。

copy on write節省了什麼資源

節省了物理內存的資源,因爲 fork 的時候,子進程不需要複製父進程的物理內存,避免了不必要的內存複製開銷,子進程只需要複製父進程的頁表,這時候父子進程的頁表指向的都是共享的物理內存。

只有當父子進程任何有一方對這片共享的物理內存發生了修改操作,纔會觸發寫時複製機制,這時候纔會複製發生修改操作的物理內存。

線程和進程區別

  • 本質區別:進程是操作系統資源分配的基本單位,而線程是任務調度和執行的基本單位
  • 在開銷方面:每個進程都有獨立的代碼和數據空間(程序上下文),程序之間的切換會有較大的開銷;線程可以看做輕量級的進程,同一類線程共享代碼和數據空間,每個線程都有自己獨立的運行棧和程序計數器(PC),線程之間切換的開銷小
  • 穩定性方面:進程中某個線程如果崩潰了,可能會導致整個進程都崩潰。而進程中的子進程崩潰,並不會影響其他進程。
  • 內存分配方面:系統在運行的時候會爲每個進程分配不同的內存空間;而對線程而言,除了CPU外,系統不會爲線程分配內存(線程所使用的資源來自其所屬進程的資源),線程組之間只能共享資源
  • 包含關係:沒有線程的進程可以看做是單線程的,如果一個進程內有多個線程,則執行過程不是一條線的,而是多條線(線程)共同完成的;線程是進程的一部分,所以線程也被稱爲輕權進程或者輕量級進程

線程切換爲什麼比進程切換快,節省了什麼資源?

線程切換比進程切換快是因爲線程共享同一進程的地址空間和資源,線程切換時只需切換堆棧和程序計數器等少量信息,而不需要切換地址空間,避免了進程切換時需要切換內存映射表等大量資源的開銷,從而節省了時間和系統資源。

linux 如何查看進程狀態?

可以通過 ps 命令或者 top 命令來查看進程的狀態。比如我想看 nginx 進程的狀態,可以在 linux 輸入這條命令:top 命令除了能看進程的狀態,還能看到系統的信息,比如系統負載、內存、cpu 使用率等等

linux 如何查看線程狀態?

在 ps 和 top 命令加一下參數,就能看到線程狀態了:

top -H


ps -eT | grep <進程名或線程名>
image.png

網絡

如何查看網絡連接情況?

可以通過 netstat 命令來查看網絡連接的情況,比如下面,我通過 命令:

netstat -napt

顯示了服務器上的 tcp 連接狀態,可以觀察到每一個 tcp 連接的狀態,以及四元組信息(源 ip 地址、目標 ip 地址,源端口、源 ip)

tcp連接過程?

image.png
  • 一開始,客戶端和服務端都處於 CLOSE 狀態。先是服務端主動監聽某個端口,處於 LISTEN 狀態
  • 客戶端會隨機初始化序號(client_isn),將此序號置於 TCP 首部的「序號」字段中,同時把 SYN 標誌位置爲 1,表示 SYN 報文。接着把第一個 SYN 報文發送給服務端,表示向服務端發起連接,該報文不包含應用層數據,之後客戶端處於 SYN-SENT 狀態。
  • 服務端收到客戶端的 SYN 報文後,首先服務端也隨機初始化自己的序號(server_isn),將此序號填入 TCP 首部的「序號」字段中,其次把 TCP 首部的「確認應答號」字段填入 client_isn + 1, 接着把 SYN 和 ACK 標誌位置爲 1。最後把該報文發給客戶端,該報文也不包含應用層數據,之後服務端處於 SYN-RCVD 狀態。
  • 客戶端收到服務端報文後,還要向服務端回應最後一個應答報文,首先該應答報文 TCP 首部 ACK 標誌位置爲 1 ,其次「確認應答號」字段填入 server_isn + 1 ,最後把報文發送給服務端,這次報文可以攜帶客戶到服務端的數據,之後客戶端處於 ESTABLISHED 狀態。
  • 服務端收到客戶端的應答報文後,也進入 ESTABLISHED 狀態。

server a和server b,如何判斷兩個服務器正常連接?出錯怎麼辦?

直不會發送數據給客戶端,那麼服務端是永遠無法感知到客戶端宕機這個事件的,也就是服務端的 TCP 連接將一直處於 ESTABLISH 狀態,佔用着系統資源。

爲了避免這種情況,TCP 搞了個保活機制。這個機制的原理是這樣的:定義一個時間段,在這個時間段內,如果沒有任何連接相關的活動,TCP 保活機制會開始作用,每隔一個時間間隔,發送一個探測報文,該探測報文包含的數據非常少,如果連續幾個探測報文都沒有得到響應,則認爲當前的 TCP 連接已經死亡,系統內核將錯誤信息通知給上層應用程序。

在 Linux 內核可以有對應的參數可以設置保活時間、保活探測的次數、保活探測的時間間隔,以下都爲默認值:

net.ipv4.tcp_keepalive_time=7200
net.ipv4.tcp_keepalive_intvl=75  
net.ipv4.tcp_keepalive_probes=9
  • tcp_keepalive_time=7200:表示保活時間是 7200 秒(2小時),也就 2 小時內如果沒有任何連接相關的活動,則會啓動保活機制
  • tcp_keepalive_intvl=75:表示每次檢測間隔 75 秒;
  • tcp_keepalive_probes=9:表示檢測 9 次無響應,認爲對方是不可達的,從而中斷本次的連接。

也就是說在 Linux 系統中,最少需要經過 2 小時 11 分 15 秒纔可以發現一個「死亡」連接。

注意,應用程序若想使用 TCP 保活機制需要通過 socket 接口設置 SO_KEEPALIVE 選項才能夠生效,如果沒有設置,那麼就無法使用 TCP 保活機制。

如果開啓了 TCP 保活,需要考慮以下幾種情況:

  • 第一種,對端程序是正常工作的。當 TCP 保活的探測報文發送給對端, 對端會正常響應,這樣 TCP 保活時間會被重置,等待下一個 TCP 保活時間的到來。
  • 第二種,對端主機宕機並重啓。當 TCP 保活的探測報文發送給對端後,對端是可以響應的,但由於沒有該連接的有效信息,會產生一個 RST 報文,這樣很快就會發現 TCP 連接已經被重置。
  • 第三種,是對端主機宕機(_注意不是進程崩潰,進程崩潰後操作系統在回收進程資源的時候,會發送 FIN 報文,而主機宕機則是無法感知的,所以需要 TCP 保活機制來探測對方是不是發生了主機宕機_),或對端由於其他原因導致報文不可達。當 TCP 保活的探測報文發送給對端後,石沉大海,沒有響應,連續幾次,達到保活探測次數後,TCP 會報告該 TCP 連接已經死亡

TCP 保活的這個機制檢測的時間是有點長,我們可以自己在應用層實現一個心跳機制。

比如,web 服務軟件一般都會提供 keepalive_timeout 參數,用來指定 HTTP 長連接的超時時間。如果設置了 HTTP 長連接的超時時間是 60 秒,web 服務軟件就會啓動一個定時器,如果客戶端在完成一個 HTTP 請求後,在 60 秒內都沒有再發起新的請求,定時器的時間一到,就會觸發回調函數來釋放該連接。

項目

因爲項目寫了 raft 分佈式 kv 的項目,問了一下 raft 算法相關的問題

  • 一致性是怎麼保證的
  • leader選舉過程是怎麼樣的?
  • leader崩潰後重新加入如何保證日誌一致

手撕

  • 給了一道go相關題目,看打印出什麼,關於append的
  • hard算法:抽五星卡概率,給了個思路,沒寫出來,目測gg了
推薦閱讀:
米哈遊,順利進入二面!
美團基架,我先開衝了!
好難!騰訊面試體驗已結束。。。
激動!再次被阿里雲撈了一波
艱難走到字節終面了!