linux核心經典面試題
LINUX核心面試題摘選
經典問題:
1) Linux中主要有哪幾種核心鎖?
2) Linux中的使用者模式和核心模式是什麼含意?
3) 怎樣申請大塊核心記憶體?
4) 使用者程序間通訊主要哪幾種方式?
5) 通過夥伴系統申請核心記憶體的函式有哪些?
6) 通過slab分配器申請核心記憶體的函式有?
7) Linux的核心空間和使用者空間是如何劃分的(以32位系統為例)?
8) vmalloc()申請的記憶體有什麼特點?
9) 使用者程式使用malloc()申請到的記憶體空間在什麼範圍?
10) 在支援並使能MMU的系統中,Linux核心和使用者程式分別執行在實體地址模式還是虛擬地址模式?
11) ARM處理器是通過幾級也表進行儲存空間對映的?
12) Linux是通過什麼元件來實現支援多種檔案系通的?
13) Linux虛擬檔案系統的關鍵資料結構有哪些?(至少寫出四個)
14) 對檔案或裝置的操作函式儲存在那個資料結構中?
15) Linux中的檔案包括哪些?
16) 建立程序的系統呼叫有那些?
17) 呼叫schedule()進行程序切換的方式有幾種?
18) Linux排程程式是根據程序的動態優先順序還是靜態優先順序來排程程序的?
19) 程序排程的核心資料結構是哪個?
20) 如何載入、解除安裝一個模組?
21) 模組和應用程式分別執行在什麼空間?
22) Linux中的浮點運算由應用程式實現還是核心實現?
23) 模組程式能否使用可連結的庫函式?
24) TLB中快取的是什麼內容?
25) Linux中有哪幾種裝置?
26) 字元裝置驅動程式的關鍵資料結構是哪個?
27) 裝置驅動程式包括哪些功能函式?
28) 如何唯一標識一個裝置?
29) Linux通過什麼方式實現系統呼叫?
30) Linux軟中斷和工作佇列的作用是什麼?
1) Linux中主要有哪幾種核心鎖?
答:Linux的同步機制從2.0到2.6以來不斷髮展完善。從最初的原子操作,到後來的訊號量,從大核心鎖到今天的自旋鎖。這些同步機制的發展伴隨Linux從單處理器到對稱多處理器的過渡;
伴隨著從非搶佔核心到搶佔核心的過度。Linux的鎖機制越來越有效,也越來越複雜。
Linux的核心鎖主要是自旋鎖和訊號量。
自旋鎖最多隻能被一個可執行執行緒持有,如果一個執行執行緒試圖請求一個已被爭用(已經被持有)的自旋鎖,那麼這個執行緒就會一直進行忙迴圈——旋轉——等待鎖重新可用。要是鎖未被爭用,請求它的執行執行緒便能立刻得到它並且繼續進行。自旋鎖可以在任何時刻防止多於一個的執行執行緒同時進入臨界區。
Linux中的訊號量是一種睡眠鎖。如果有一個任務試圖獲得一個已被持有的訊號量時,訊號量會將其推入等待佇列,然後讓其睡眠。這時處理器獲得自由去執行其它程式碼。當持有訊號量的程序將訊號量釋放後,在等待佇列中的一個任務將被喚醒,從而便可以獲得這個訊號量。
訊號量的睡眠特性,使得訊號量適用於鎖會被長時間持有的情況;只能在程序上下文中使用,因為中斷上下文中是不能被排程的;另外當代碼持有訊號量時,不可以再持有自旋鎖。
Linux 核心中的同步機制:原子操作、訊號量、讀寫訊號量和自旋鎖的API,另外一些同步機制,包括大核心鎖、讀寫鎖、大讀者鎖、RCU (Read-Copy Update,顧名思義就是讀-拷貝修改),和順序鎖。
2) Linux中的使用者模式和核心模式是什麼含意?
答:MS-DOS等作業系統在單一的CPU模式下執行,但是一些類Unix的作業系統則使用了雙模式,可以有效地實現時間共享。在Linux機器上,CPU要麼處於受信任的核心模式,要麼處於受限制的使用者模式。除了核心本身處於核心模式以外,所有的使用者程序都執行在使用者模式之中。
核心模式的程式碼可以無限制地訪問所有處理器指令集以及全部記憶體和I/O空間。如果使用者模式的程序要享有此特權,它必須通過系統呼叫向裝置驅動程式或其他核心模式的程式碼發出請求。另外,使用者模式的程式碼允許發生缺頁,而核心模式的程式碼則不允許。
在2.4和更早的核心中,僅僅使用者模式的程序可以被上下文切換出局,由其他程序搶佔。除非發生以下兩種情況,否則核心模式程式碼可以一直獨佔CPU:
(1) 它自願放棄CPU;
(2) 發生中斷或異常。
2.6核心引入了核心搶佔,大多數核心模式的程式碼也可以被搶佔。
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 V訊息佇列。有足夠許可權的程序可以向佇列中新增訊息,被賦予讀許可權的程序則可以讀走佇列中的訊息。訊息佇列克服了訊號承載資訊量少,管道只能承載無格式位元組流以及緩衝區大小受限等缺
(5)共享記憶體:使得多個程序可以訪問同一塊記憶體空間,是最快的可用IPC形式。是針對其他通訊機制執行效率較低而設計的。往往與其它通訊機制,如訊號量結合使用,來達到程序間的同步及互斥。
(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.原理說明
Linux核心中採 用了一種同時適用於32位和64位系統的內 存分頁模型,對於32位系統來說,兩級頁表足夠用了,而在x86_64系 統中,用到了四級頁表。
*頁全域性目錄(Page Global Directory)
*頁上級目錄(Page Upper Directory)
*頁中間目錄(Page Middle Directory)
*頁表(Page Table)
頁全域性目錄包含若干頁上級目錄的地址,頁上級目錄又依次包含若干頁中間目錄的地址,而頁中間目錄又包含若干頁表的地址,每一個頁表項指向一個頁框。Linux中採用4KB大小的 頁框作為標準的記憶體分配單元。
多級分頁目錄結構
1.1.夥伴系統演算法
在實際應用中,經常需要分配一組連續的頁框,而頻繁地申請和釋放不同大小的連續頁框,必然導致在已分配頁框的記憶體塊中分散了許多小塊的空閒頁框。這樣,即使這些頁框是空閒的,其他需要分配連續頁框的應用也很難得到滿足。
為了避免出現這種情況,Linux核心中引入了夥伴系統演算法(buddy system)。把所有的空閒頁框分組為11個 塊連結串列,每個塊連結串列分別包含大小為1,2,4,8,16,32,64,128,256,512和1024個連續頁框的頁框塊。最大可以申請1024個連 續頁框,對應4MB大小的連續記憶體。每個頁框塊的第一個頁框的實體地址是該塊大小的整數倍。
假設要申請一個256個頁框的塊,先從256個頁框的連結串列中查詢空閒塊,如果沒有,就去512個 頁框的連結串列中找,找到了則將頁框塊分為2個256個 頁框的塊,一個分配給應用,另外一個移到256個頁框的連結串列中。如果512個頁框的連結串列中仍沒有空閒塊,繼續向1024個頁 框的連結串列查詢,如果仍然沒有,則返回錯誤。
頁框塊在釋放時,會主動將兩個連續的頁框塊合併為一個較大的頁框塊。
1.2.slab分配器
slab分配器源於 Solaris 2.4的 分配演算法,工作於物理記憶體頁框分配器之上,管理特定大小物件的快取,進行快速而高效的記憶體分配。
slab分配器為每種使用的核心物件建立單獨的緩衝區。Linux核心已經採用了夥伴系統管理實體記憶體頁框,因此 slab分配器直接工作於夥伴系統之上。每種緩衝區由多個 slab組成,每個 slab就是一組連續的實體記憶體頁框,被劃分成了固定數目的物件。根據物件大小的不同,預設情況下一個 slab最多可以由 1024個頁框構成。出於對齊 等其它方面的要求,slab中分配給物件的記憶體可能大於使用者要求的物件實際大小,這會造成一定的 記憶體浪費。
2.常用記憶體分配函式
2.1.__get_free_pages
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)
__get_free_pages函式是最原始的記憶體分配方式,直接從夥伴系統中獲取原始頁框,返回值為第一個頁框的起始地址。__get_free_pages在實現上只是封裝了alloc_pages函式,從程式碼分析,alloc_pages函式會分配長度為1<
2.2.kmem_cache_alloc
struct kmem_cache *kmem_cache_create(const char *name, size_t size,
size_t align, unsigned long flags,
void (*ctor)(void*, struct kmem_cache *, unsigned long),
void (*dtor)(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的記憶體時使核心崩潰。
2.3.kmalloc
void *kmalloc(size_t size, gfp_t flags)
kmalloc是核心中最常用的一種記憶體分配方式,它通過呼叫kmem_cache_alloc函式來實現。kmalloc一次最多能申請的記憶體大小由include/linux/Kmalloc_size.h的內容來決定,在預設的2.6.18核心版本中,kmalloc一 次最多能申請大小為131702B也就是128KB字 節的連續實體記憶體。測試結果表明,如果試圖用kmalloc函式分配大於128KB的記憶體,編譯不能通過。
2.4.vmalloc
void *vmalloc(unsigned long size)
前面幾種記憶體分配方式都是物理連續的,能保證較低的平均訪問時間。但是在某些場合中,對記憶體區的請求不是很頻繁,較高的記憶體訪問時間也可以接受,這是就可以分配一段線性連續,物理不連續的地址,帶來的好處是一次可以分配較大塊的記憶體。圖3-1表示的是vmalloc分配的記憶體使用的地址範圍。vmalloc對一次能分配的記憶體大小沒有明確限制。出於效能考慮,應謹慎使用vmalloc函式。在測試過程中, 最大能一次分配1GB的空間。
Linux核心部分記憶體分佈
2.5.dma_alloc_coherent
void *dma_alloc_coherent(struct device *dev, size_t size,
ma_addr_t *dma_handle, gfp_t gfp)
DMA是一種硬體機制,允許外圍裝置和主存之間直接傳輸IO資料,而不需要CPU的參與,使用DMA機制能大幅提高與裝置通訊的吞吐量。DMA操作中,涉及到CPU高速緩 存和對應的記憶體資料一致性的問題,必須保證兩者的資料一致,在x86_64體系結構中,硬體已經很 好的解決了這個問題,dma_alloc_coherent和__get_free_pages函式實現差別不大,前者實際是呼叫__alloc_pages函式來分配記憶體,因此一次分配記憶體的大小限制和後者一樣。__get_free_pages分配的內 存同樣可以用於DMA操作。測試結果證明,dma_alloc_coherent函 數一次能分配的最大記憶體也為4M。
2.6.ioremap
void * ioremap (unsigned long offset, unsigned long size)
ioremap是一種更直接的記憶體“分配”方式,使用時直接指定物理起始地址和需要分配記憶體的大小,然後將該段實體地址對映到核心地址空間。ioremap用到的實體地址空間都是事先確定的,和上面的幾種記憶體 分配方式並不太一樣,並不是分配一段新的實體記憶體。ioremap多用於裝置驅動,可以讓CPU直接訪問外部裝置的IO空間。ioremap能對映的記憶體由原有的實體記憶體空間決定,所以沒有進行測試。
2.7.Boot Memory
如果要分配大量的連續實體記憶體,上述的分配函式都不能滿足,就只能用比較特殊的方式,在Linux內 核引導階段來預留部分記憶體。
2.7.1.在核心引導時分配記憶體
void* alloc_bootmem(unsigned long size)
可以在Linux核心引導過程中繞過夥伴系統來分配大塊記憶體。使用方法是在Linux核心引導時,呼叫mem_init函式之前 用alloc_bootmem函式申請指定大小的記憶體。如果需要在其他地方呼叫這塊記憶體,可以將alloc_bootmem返回的記憶體首地址通過EXPORT_SYMBOL導 出,然後就可以使用這塊記憶體了。這種記憶體分配方式的缺點是,申請記憶體的程式碼必須在連結到核心中的程式碼裡才能使用,因此必須重新編譯核心,而且記憶體管理系統看不到這部分記憶體,需要使用者自行管理。測試結果表明,重新編譯核心後重啟,能夠訪問引導時分配的記憶體塊。
2.7.2.通過核心引導引數預留頂部記憶體
在Linux核心引導時,傳入引數“mem=size”保留頂部的記憶體區間。比如系統有256MB內 存,引數“mem=248M”會預留頂部的8MB記憶體,進入系統後可以呼叫ioremap(0xF800000,0x800000)來申請這段記憶體。
3.幾種分配函式的比較
分配原理最大記憶體其他
__get_free_pages直接對頁框進行操作4MB適用於分配較大量的連續實體記憶體
kmem_cache_alloc基於slab機制實現128KB適合需要頻繁申請釋放相同大小記憶體塊時使用
kmalloc基於kmem_cache_alloc實現128KB最常見的分配方式,需要小於頁框大小的記憶體時可以使用
vmalloc建立非連續實體記憶體到虛擬地址的對映物理不連續,適合需要大記憶體,但是對地址連續性沒有要求的場合
dma_alloc_coherent基於__alloc_pages實現4MB適用於DMA操 作
ioremap實現已知實體地址到虛擬地址的對映適用於實體地址已知的場合,如裝置驅動
alloc_bootmem在啟動kernel時,預留一段記憶體,核心看不見小於實體記憶體大小,記憶體管理要求較高
6) 通過slab分配器申請核心記憶體的函式有?
答:
struct kmem_cache* kmem_cache_create(const char *name,size_t size,size_t align,unsigned long flags,void (*ctor)(void*))
該函式建立一個新的快取記憶體。
void *kmem_cache_alloc(struct kmem_cache* cachep,gfp_t flags)
該函式從給定的快取記憶體cachep中返回一個指向物件的指標。
int kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags)
該函式從給定的快取記憶體cachep中返回一個指向物件的指標。
7) Linux的核心空間和使用者空間是如何劃分的(以32位系統為例)?
答:Linux記憶體空間共有4G,高地址的1G為核心空間,低地址的3G為使用者空間。
8) kmalloc和vmalloc()申請的記憶體有什麼特點?
答: (1)kmalloc和vmalloc是分配的是核心的記憶體地址。
(2) kmalloc保證分配的記憶體在物理上是連續的,當然在虛擬地址空間上也是連續的。
(3) vmalloc保證的是在虛擬地址空間上的連續,vmalloc()則是位於實體地址非連續,虛地址連續區,起始位置由VMALLOL_START來決定,一般作為交換區、模組的分配。
9) 使用者程式使用malloc()申請到的記憶體空間在什麼範圍?
答:linux下虛擬地址空間分給程序本身的是3GB(windows下預設是2GB),那麼一般程式使用malloc()申請到的記憶體空間最多3GB,但是一般情況下是達不到的,可以用一個程式在自己機器上做測試。
程式碼如下:
#include<stdio.h>
#include<stdlib.h>
unsigned maximum=0;
int main(int argc,char *argv[])
{
unsigned blocksize[]={1024*1024,1024,1};
int i,count;
for(i=0;i<3;i++){
for(count=1;;count++){
void *block=malloc(maximum+blocksize[i]*count);
if(block){
maximum=maximum+blocksize[i]*count;
free(block);
}else{
break;
}
}
}
printf("maximum malloc size=%u bytes\n",maximum);
}
10) 在支援並使能MMU的系統中,Linux核心和使用者程式分別執行在實體地址模式還是虛擬地址模式?
答:Linux核心程式執行在實體地址模式,Linux使用者程式執行在虛擬地址模式。
11) Linux虛擬檔案系統的關鍵資料結構有哪些?
答:struct super_block :超級塊物件 ,它代表一個具體的已安裝檔案系統;
struct inode :索引節點物件,它代表一個具體檔案;
struct dentry: 目錄項物件,它代表一個目錄項,是路徑的一個組成部分;
struct file: 檔案物件,它代表程序開啟的檔案。
12) 對檔案或裝置的操作函式儲存在那個資料結構中?
答:struct file_operations
13) Linux中的檔案包括哪些?
答:執行檔案,普通檔案,目錄檔案,連結檔案和裝置檔案,管道檔案。
14) 建立程序的系統呼叫有那些?
答:clone(),fork(),vfork();系統呼叫服務例程:sys_clone,sys_fork,sys_vfork;
15) 呼叫schedule()進行程序切換的方式有幾種?
答:
(1).系統呼叫 do_fork();
(2).定時中斷 do_timer();
(3).喚醒程序 wake_up_process();
(4).改變程序的排程策略 setscheduler();
(5).系統呼叫禮讓 sys_sched_yield();
16) Linux排程程式是根據程序的動態優先順序還是靜態優先順序來排程程序的?
答:Liunx排程程式是根據根據程序的動態優先順序來排程程序的,但是動態優先順序又是根據靜態優先順序根據演算法計算出來的,兩者是兩個相關聯的值。因為高優先順序的程序總是比低優先順序的程序先被排程,為防止多個高優先順序的程序佔用CPU資源,導致其他程序不能佔有CPU,所以引用動態優先順序概念.
17) 程序排程的核心資料結構是哪個?
答:struct runqueue.
18) 如何載入、解除安裝一個模組?
答:insmod載入,rmmod解除安裝.
19) 模組和應用程式分別執行在什麼空間?
答:模組執行在核心空間,應用程式執行在使用者空間.
20) Linux中的浮點運算由應用程式實現還是核心實現?
答:應用程式實現,Linux中的浮點運算是利用數學庫函式實現的,庫函式能夠被應用程式連結後呼叫,不能被核心連結呼叫。這些運算是在應用程式中執行的,然後再把結果反饋給系統。Linux核心如果一定要進行浮點運算,需要在建立核心時選上math-emu,使用軟體模擬計算浮點運算,據說這樣做的代價有兩個:使用者在安裝驅動時需要重建核心,可能會影響到其他的應用程式,使得這些應用程式在做浮點運算的時候也使用math-emu,大大的降低了效率。
21) 模組程式能否使用可連結的庫函式?
答:模組程式執行在核心空間,不能連結庫函式。
22) TLB中快取的是什麼內容?
答:TLB,頁表快取,當線性地址被第一次轉換成實體地址的時候,將線性地址和實體地址的對應放到TLB中,用於下次訪問這個線性地址時,加快轉換速度。
23) Linux中有哪幾種裝置?
答:字元裝置和塊裝置。
24) 裝置驅動程式包括哪些功能函式?
答:裝置驅動有:open(),read(),write(),llseek(),realse();
25) 如何唯一標識一個裝置?
答:Linux使用一個裝置編號來唯一的標示一個裝置,裝置編號分為:主裝置號和次裝置號,一般主裝置號標示裝置對應的驅動程式,次裝置號對應裝置檔案指向的裝置,在核心中使用dev_t來表示裝置編號,一般它是32位長度,其中12位用於表示主裝置號,20位用於表示次裝置號,利用MKDEV(int major,int minor);用於生成一個dev_t型別的物件。
26) Linux通過什麼方式實現系統呼叫?
答:靠軟體中斷實現的,首先,使用者程式為系統呼叫設定引數,其中一個編號是系統呼叫編號,引數設定完成後,程式執行系統呼叫指令,x86上的軟中斷是有int產生的,這個指令會導致一個異常,產生一個事件,這個事件會導致處理器跳轉到核心態並跳轉到一個新的地址。並開始處理那裡的異常處理程式,此時的異常處理就是系統呼叫程式。
27) Linux軟中斷和工作佇列的作用是什麼?
答:
Linux中的軟中斷和工作佇列是中斷處理。
1.軟中斷一般是“可延遲函式”的總稱,它不能睡眠,不能阻塞,它處於中斷上下文,不能進城切換,軟中斷不能被自己打斷,只能被硬體中斷打斷(上半部),可以併發的執行在多個CPU上。所以軟中斷必須設計成可重入的函式,因此也需要自旋鎖來保護其資料結構。
2.工作佇列中的函式處在程序上下文中,它可以睡眠,也能被阻塞,能夠在不同的程序間切換。已完成不同的工作。
可延遲函式和工作佇列都不能訪問使用者的程序空間,可延時函式在執行時不可能有任何正在執行的程序,工作佇列的函式有核心程序執行,他不能訪問使用者空間地址。
28) 字元裝置驅動程式的關鍵資料結構是哪個?
答:字元裝置描述符struct cdev, cdev_alloc()用於動態的分配cdev描述符,cdev_add()用於註冊一個cdev描述符,cdev包含一個struct kobject 型別的資料結構它是核心的資料結構