1. 程式人生 > 其它 >二本畢業三年,憑什麼拿到騰訊T8offer?——Linux核心面試題整理(含答案)

二本畢業三年,憑什麼拿到騰訊T8offer?——Linux核心面試題整理(含答案)

技術標籤:核心linuxc++後端架構

1.Linux 中主要有哪幾種核心鎖?

Linux 的同步機制從 2.0 到 2.6 以來不斷髮展完善。從最初的原子操作,到後來的訊號量,從大核心鎖到今天的自旋鎖。這些同步機制的發展伴隨 Linux 從單處理器到對稱多處理器的過渡;伴隨著從非搶佔核心到搶佔核心的過度。Linux 的鎖機制越來越有效,也越來越複雜。

自旋鎖最多隻能被一個可執行執行緒持有,如果一個執行執行緒試圖請求一個已被爭用已經被持有)的自旋鎖,那麼這個執行緒就會一直進行忙迴圈——旋轉——等待鎖重新可用。要是鎖未被爭用,請求它的執行執行緒便能立刻得到它並且繼續進行。自旋鎖可以在任何時刻防止多於一個的執行執行緒同時進入臨界區。

訊號量的睡眠特性,使得訊號量適用於鎖會被長時間持有的情況;只能在程序上下文中使用,因為中斷上下文中是不能被排程的;另外當代碼持有訊號量時,不可以再持有自旋鎖。

Linux 核心中的同步機制:原子操作、訊號量、讀寫訊號量和自旋鎖的 API,另外一些同步機制,包括大核心鎖、讀寫鎖、大讀者鎖、RCU (Read-Copy Update, 顧名思義就是讀-拷貝修改),和順序鎖。

2.Linux 中的使用者模式和核心模式是什麼含意?

MS-DOS 等作業系統在單一的 CPU 模式下執行,但是一些類 Unix 的作業系統則使用了雙模式,可以有效地實現時間共享。在 Linux 機器上,CPU 要麼處於受信任的核心模式,要麼處於受限制的使用者模式。除了核心本身處於核心模式以外,所有的使用者程序都執行在使用者模式之中。

核心模式的程式碼可以無限制地訪問所有處理器指令集以及全部記憶體和 I/O 空間。如果使用者模式的程序要享有此特權,它必須通過系統呼叫向裝置驅動程式或其他核心模式的程式碼發出請求。另外,使用者模式的程式碼允許發生缺頁,而核心模式的程式碼則不允許。

在 更早的核心中,僅僅使用者模式的程序可以被上下文切換出局,由其他程序搶佔。除非發生以下兩種情況,否則核心模式程式碼可以一直獨佔 CPU:
(1) 它自願放棄 CPU;
(2) 發生中斷或異常。
核心引入了核心搶佔,大多數核心模式的程式碼也可以被搶佔。

3.怎樣申請大塊核心記憶體?

在 Linux 核心環境下,申請大塊記憶體的成功率隨著系統執行時間的增加而減少, 雖然可以通過 vmalloc 系列呼叫申請物理不連續但虛擬地址連續的記憶體,但畢竟其使用效率不高且在 32 位系統上 vmalloc 的記憶體地址空間有限。所以,一般的建議是在系統啟動階段申請大塊記憶體,但是其成功的概率也只是比較高而已,而不是 100%。如果程式真的比較在意這個申請的成功與否,只能退用“啟動記憶體”Boot Memory)。下面就是申請並匯出啟動記憶體的一段示例程式碼:

void* x_bootmem = NULL; EXPORT_SYMBOL(x_bootmem); 
unsigned long x_bootmem_size = 0; EXPORT_SYMBOL(x_bootmem_size); 
static int init x_bootmem_setup(char *str) 
{ 
x_bootmem_size = memparse(str, &str); x_bootmem = alloc_bootmem(x_bootmem_size); 
printk("Reserved %lu bytes from %p for x\n", x_bootmem_size, x_bootmem); return 1; 
} 
  setup("x-bootmem=", x_bootmem_setup); 
 

可見其應用還是比較簡單的,不過利弊總是共生的,它不可避免也有其自身的限制:
記憶體申請程式碼只能連線進核心,不能在模組中使用。被申請的記憶體不會被頁分配器和 slab 分配器所使用和統計,也就是說它處於系統的可見記憶體之外,即使在將來的某個地方你釋放了它。
一般使用者只會申請一大塊記憶體,如果需要在其上實現複雜的記憶體管理則需要自己實現。在不允許記憶體分配失敗的場合,通過啟動記憶體預留記憶體空間將是我們唯一的選擇。

4.使用者程序間通訊主要哪幾種方式?

1)管道 Pipe):管道可用於具有親緣關係程序間的通訊,允許一個程序和另一個與它有共同祖先的程序之間進行通訊
2)命名管道 named pipe):命名管道克服了管道沒有名字的限制,因此,除具有管道所具有的功能外,它還允許無親緣關係程序間的通訊。命名管道在檔案系統中有對應的檔名。命名管道通過命令 mkfifo 或系統呼叫 mkfifo 來建立。
3)訊號 Signal):訊號是比較複雜的通訊方式,用於通知接受程序有某種事件發生,除了用於程序間通訊外,程序還可以傳送訊號給程序本身;linux 除了支援
Unix 早期訊號語義函式 sigal 外,還支援語義符合 Posix.1 標準的訊號函式
sigaction 實際上,該函式是基於 BSD 的,BSD 為了實現可靠訊號機制,又能夠統一對外介面,用 sigaction 函式重新實現了 signal 函式)。
4)訊息 Message)佇列:訊息佇列是訊息的連結表,包括 Posix 訊息佇列 system
5) 訊息佇列。有足夠許可權的程序可以向佇列中新增訊息,被賦予讀許可權的程序則可以讀走佇列中的訊息。訊息佇列克服了訊號承載資訊量少,管道只能承載無格式位元組流以及緩衝區大小受限等缺
6)訊號量 semaphore):主要作為程序間以及同一程序不同執行緒之間的同步手段。
7)套接字 Socket):更為一般的程序間通訊機制,可用於不同機器之間的程序間通訊。起初是由 Unix 系統的 BSD 分支開發出來的,但現在一般可以移植到其它類 Unix 系統上:Linux 和 System V 的變種都支援套接字。

5.通過夥伴系統申請核心記憶體的函式有哪些?

在物理頁面管理上實現了基於區的夥伴系統 zone based buddy system)。對不同區的記憶體使用單獨的夥伴系統(buddy system)管理,而且獨立地監控空閒頁。相應介面 alloc_pages(gfp_mask, order) , _ _get_free_pages(gfp_mask,
order)等。補充知識:
1.原理說明

  • 頁全域性目錄(Page Global Directory)
  • 頁中間目錄(Page Middle Directory)
    頁全域性目錄包含若干頁上級目錄的地址,頁上級目錄又依次包含若干頁中間目錄的地址,而頁中間目錄又包含若干頁表的地址,每一個頁表項指 向一個頁框。
    Linux 中採用 4KB 大小的 頁框作為標準的記憶體分配單元。

5.1.夥伴系統演算法
為了避免出現這種情況,Linux 核心中引入了夥伴系統演算法(buddy system)。把所有的空閒頁框分組為 11 個 塊連結串列,每個塊連結串列分別包含大小為 1,2,4,8,16,32,64,128,256,512 和 1024 個連續頁框的頁框塊。最大可以申請 1024 個連 續頁框,對應 4MB 大小的連續記憶體。每個頁框塊的第一個頁框的實體地址是該塊大小的整數倍。
頁框塊在釋放時,會主動將兩個連續的頁框塊合併為一個較大的頁框塊。
slab 分配器源於 Solaris 2.4 的 分配演算法,工作於物理記憶體頁框分配器之上, 管理特定大小物件的快取,進行快速而高效的記憶體分配。

5.2.常用記憶體分配函式
unsigned long get_free_pages(gfp_t gfp_mask, unsigned int order)
get_free_pages 函式是最原始的記憶體分配方式,直接從夥伴系統中獲取原始頁框,返回值為第一個頁框的起始地址。 get_free_pages 在實現上只是封裝了 alloc_pages 函 數,從程式碼分析,alloc_pages 函式會分配長度為 1< struct kmem_cache *kmem_cache_create(const char *name, size_t size void (ctor)(void, struct kmem_cache *, unsigned long),
void *kmem_cache_alloc(struct kmem_cache *c, gfp_t flags) kmem_cache_create/ kmem_cache_alloc 是基於 slab 分配器的一種記憶體分配方式,適用於反覆分配釋放同一大小記憶體塊的場合。首先用 kmem_cache_create 建立一個快取記憶體區域,然後用 kmem_cache_alloc 從 該快取記憶體區域中獲取新的記憶體塊。kmem_cache_alloc 一次能分配的最大記憶體由 mm/slab.c 檔案中的
MAX_OBJ_ORDER 巨集定義,在預設的 2.6.18 核心版本中,該巨集定義為 5,於是一次

最多能申請 1<<5 * 4KB 也就是 128KB 的連續實體記憶體。分析核心原始碼發現,
kmem_cache_create 函式的 size 引數大於 128KB 時會呼叫 BUG()。測試結果驗證了分析結果,用 kmem_cache_create 分配超過 128KB 的記憶體時使核心崩潰。void *kmalloc(size_t size, gfp_t flags)

5.3.vmalloc
前面幾種記憶體分配方式都是物理連續的,能保證較低的平均訪問時間。但是在某些場合中,對記憶體區的請求不是很頻繁,較高的記憶體訪問時間也 可以接受,這是就可以分配一段線性連續,物理不連續的地址,帶來的好處是一次可以分配較大塊的記憶體。圖 3-1 表 示的是 vmalloc 分配的記憶體使用的地址範圍。vmalloc 對一次能分配的記憶體大小沒有明確限制。出於效能考慮,應謹慎使用 vmalloc 函式。在測試過程中, 最大能一次分配 1GB 的空間。

5.4.dma_alloc_coherent
ma_addr_t *dma_handle, gfp_t gfp)

5.5.ioremap
ioremap 是一種更直接的記憶體“分配”方式,使用時直接指定物理起始地址和需要分配記憶體的大小,然後將該段 實體地址對映到核心地址空間。ioremap 用到的實體地址空間都是事先確定的,和上面的幾種記憶體 分配方式並不太一樣,並不是分配一段新的實體記憶體。ioremap 多用於裝置驅動,可以讓 CPU 直接訪問外部裝置的 IO 空間。ioremap 能對映的記憶體由原有的實體記憶體空間決定,所以沒有進行測試。
如果要分配大量的連續實體記憶體,上述的分配函式都不能滿足,就只能用比較特殊的方式,在 Linux 內 核引導階段來預留部分記憶體。
void* alloc_bootmem(unsigned long size)

5.6通過核心引導引數預留頂部記憶體

3.幾種分配函式的比較
get_free_pages 直接對頁框進行操作 4MB 適用於分配較大量的連續實體記憶體
kmalloc 基於 kmem_cache_alloc 實現 128KB 最常見的分配方式,需要小於頁框大小的記憶體時可以使用 dma_alloc_coherent 基於 alloc_pages 實現 4MB 適用於 DMA 操 作alloc_bootmem 在啟動 kernel 時,預留一段記憶體,核心看不見小於實體記憶體大小,記憶體管理要求較高

6.Linux 虛擬檔案系統的關鍵資料結構有哪些?(至少寫出四個)

struct super_block, struct inode, struct file, struct dentry;

7.對檔案或裝置的操作函式儲存在那個資料結構中?

struct file_operations

8.Linux 中的檔案包括哪些?

執行檔案,普通檔案,目錄檔案,連結檔案和裝置檔案,管道檔案。

9.建立程序的系統呼叫有那些?

clone(),fork(),vfork();系統呼叫服務例程:sys_clone,sys_fork,sys_vfork;

10.呼叫 schedule()進行程序切換的方式有幾種?

1.系統呼叫 do_fork();
2.定時中斷 do_timer();
3.喚醒程序 wake_up_process
4.改變程序的排程策略 setscheduler();
5.系統呼叫禮讓 sys_sched_yield();

11.Linux 排程程式是根據程序的動態優先順序還是靜態優先順序來排程程序的?

Liunx 排程程式是根據根據程序的動態優先順序來排程程序的,但是動態優先順序又是根據靜態優先順序根據演算法計算出來的,兩者是兩個相關聯的值。因為高優先順序的程序總是比低優先順序的程序先被排程,為防止多個高優先順序的程序佔用 CPU 資源,導致其他程序不能佔有 CPU,所以引用動態優先順序概念

12.程序排程的核心資料結構是哪個?

struct runqueue, struct task_struct, struct sched_struct

13.如何載入、解除安裝一個模組?

insmod 載入,rmmod 解除安裝

14.模組和應用程式分別執行在什麼空間?

模組執行在核心空間,應用程式執行在使用者空間

15.Linux 中的浮點運算由應用程式實現還是核心實現?

應用程式實現,Linux 中的浮點運算是利用數學庫函式實現的,庫函式能夠被應用程式連結後呼叫,不能被核心連結呼叫。這些運算是在應用程式中執行的,然後再把結果反饋給系統。Linux 核心如果一定要進行浮點運算,需要在建立核心時選上 math-emu,使用軟體模擬計算浮點運算,據說這樣做的代價有兩個:使用者在安裝驅動時需要重建核心,可能會影響到其他的應用程式,使得這些應用程式在做浮點運算的時候也使用 math-emu,大大的降低了效率。

16.模組程式能否使用可連結的庫函式?

模組程式執行在核心空間,不能連結庫函式。

17.TLB 中快取的是什麼內容?

TLB,頁表快取,當線性地址被第一次轉換成實體地址的時候,將線性地址和實體地址的對應放到 TLB 中,用於下次訪問這個線性地址時,加快轉換速度。

18.Linux 中有哪幾種裝置?

字元裝置和塊裝置。網絡卡是例外,他不直接與裝置檔案對應,mknod 系統呼叫用來建立裝置檔案。

19.字元裝置驅動程式的關鍵資料結構是哪個?

字元裝置描述符 struct cdev,cdev_alloc()用於動態的分配 cdev 描述符,
cdev_add()用於註冊一個 cdev 描述符,cdev 包含一個 struct kobject 型別的

資料結構它是核心的資料結構

20.裝置驅動程式包括哪些功能函式?

open(),read(),write(),llseek(),realse();

21.如何唯一標識一個裝置?

Linux 使用一個裝置編號來唯一的標示一個裝置,裝置編號分為:主裝置號和次裝置號,一般主裝置號標示裝置對應的驅動程式,次裝置號對應裝置檔案指向的裝置,在核心中使用 dev_t 來表示裝置編號,一般它是 32 位長度,其中 12 位用於表示主裝置號,20 位用於表示次裝置號,利用 MKDEV(int major,int minor);用於生成一個 dev_t 型別的物件。

22.Linux 通過什麼方式實現系統呼叫?

靠軟體中斷實現的,首先,使用者程式為系統呼叫設定引數,其中一個編號是系統呼叫編號,引數設定完成後,程式執行系統呼叫指令,x86 上的軟中斷是有 int 產生的,這個指令會導致一個異常,產生一個事件,這個事件會導致處理器跳轉到核心態並跳轉到一個新的地址。並開始處理那裡的異常處理程式,此時的異常處理就是系統呼叫程式。

23.Linux 軟中斷和工作佇列的作用是什麼?

Linux 中的軟中斷和工作佇列是中斷處理。

1.軟中斷一般是“可延遲函式”的總稱,它不能睡眠,不能阻塞,它處於中斷上下文,不能進城切換,軟中斷不能被自己打斷,只能被硬體中斷打斷(上半部),可以併發的執行在多個 CPU 上。所以軟中斷必須設計成可重入的函式,因此也需要自旋鎖來保護其資料結構。
2.工作佇列中的函式處在程序上下文中,它可以睡眠,也能被阻塞,能夠在不同的程序間切換。已完成不同的工作。

可延遲函式和工作佇列都不能訪問使用者的程序空間,可延時函式在執行時不可能有任何正在執行的程序,工作佇列的函式有核心程序執行,他不能訪問使用者空間地址。

最近準備總結關於面試的一個系列。感興趣的朋友可以小關注一波。感謝!
對技術、面試,求職有興趣需求的朋友,歡迎加入我的技術交流群,後面會升級成付費群,大家一起交流技術~(202432010)及時提前加入哦(群內有我整理的一些面試題、電子書,技術視訊教程等,歡迎自取)
在這裡插入圖片描述