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時,預留一段內存,內核看不見小於物理內存大小,內存管理要求較高
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章