嵌入式Linux设备驱动面试题汇总
大家平時在寫驅動的時候,驅動相關的知識都會用到,但真到面試的時候,很難快速流暢的回答面試提出的問題,特意從網上收集整理網友遇到的問題
驅動大概的分為三部分:基礎部分,同步相關,還有中斷部分。中斷,同步相關基本都是必問的。
基礎部分
驅動中操作物理絕對地址為什么要先ioremap?
因為在內核中操作的都是虛擬地址,內核訪問不到物理地址,只能通過ioremap映射為虛擬地址 內核才能訪問此內存空間
設備驅動模型三個重要成員是?platform總線的匹配規則是?在具體應用上要不要先注冊驅動再注冊設備?有先后順序沒?
設備驅動模型的三個重要成員是總線,驅動,設備。
platfoem總線的匹配規則是:要匹配的設備和驅動都要注冊,驅動和設備的匹配規則如下
1.基于設備樹風格的匹配
2.匹配ID表(即platform_device設備名是否出現在platform_driver的id表內)
3.匹配platform_device設備名和驅動的名字
4.基于ACPI風格的匹配
設備可以在設備樹里注冊,也可以通過代碼注冊設備,匹配成功會去調用驅動程序里的probe函數(probe函數在這個platform_driver結構體中注冊)。
insmod 一個驅動模塊,會執行模塊中的哪個函數?rmmod呢?這兩個函數在設計上要注意哪些?遇到過卸載驅動出現異常沒?是什么問題引起的?
insmod調用init函數,rmmod調用exit函數。這兩個函數在設計時 要注意在init函數中申請的資源在exit函數中要釋放,包括存儲,ioremap,定時器,工作隊列等等。也就是一個模塊注冊進內核,退出內核時要清理所帶來的影響,帶走一切不留下一點痕跡。
卸載模塊時曾出現卸載失敗的情形,原因是存在進程正在使用模塊,檢查代碼后發現產生了死鎖的問題。
copy_to_user()和copy_from_user()主要用于實現什么功能?一般用于file_operations結構的哪些函數里面?
由于內核空間和用戶空間是不能互相訪問的,如果需要訪問就必須借助內核函數進行數據讀寫。copy_to_user():完成內核空間到用戶空間的復制,copy_from_user():是完成用戶空間到內核空間的復制。一般用于file_operations結構里的read,write,ioctl等內存數據交換作用的函數。當然,如果ioctl沒有用到內存數據復制,那么就不會用到這兩個函數。
請簡述主設備號和次設備號的用途。如果執行mknod chartest c 4 64,創建chartest設備。請分析chartest使用的是那一類設備驅動程序。
chartest 由驅動程序4管理,該文件所指的設備是64號設備。(感覺類似于串口終端或者字符設備終端)。
設備驅動程序中如何注冊一個字符設備?分別解釋一下它的幾個參數的含義。
注冊一個字符設備驅動有兩種方法:
該注冊函數可以將cdev結構嵌入到自己的設備特定的結構中。cdev是一個指向結構體cdev的指針,而fops是指向一個類似于file_operations結構(可以是file_operations結構,但不限于該結構)的指針.
該注冊函數是早期的注冊函數,major是設備的主設備號,name是驅動程序的名稱,而fops是默認的file_operations結構(這是只限于file_operations結構)。對于register_chrdev的調用將為給定的主設備號注冊0-255作為次設備號,并為每個設備建立一個對應的默認cdev結構。
Linux設備中字符設備與塊設備有什么主要的區別?請分別列舉一些實際的設備說出它們是屬于哪一類設備。
- 字符設備:字符設備是個能夠像字節流(類似文件)一樣被訪問的設備,由字符設備驅動程序來實現這種特性。字符設備驅動程序通常至少實現open,close,read和write系統調用。字符終端、串口、鼠標、鍵盤、攝像頭、聲卡和顯卡等就是典型的字符設備。
- 塊設備:和字符設備類似,塊設備也是通過/dev目錄下的文件系統節點來訪問。塊設備上能夠容納文件系統,如:u盤,SD卡,磁盤等。
字符設備和塊設備的區別僅僅在于內核內部管理數據的方式,也就是內核及驅動程序之間的軟件接口,而這些不同對用戶來講是透明的。在內核中,和字符驅動程序相比,塊驅動程序具有完全不同的接口。
查看驅動模塊中打印信息應該使用什么命令?如何查看內核中已有的字符設備的信息?如何查看正在使用的有哪些中斷號?
linux中引入模塊機制有什么好處?
首先,模塊是預先注冊自己以便服務于將來的某個請求,然后他的初始化函數就立即結束。換句話說,模塊初始化函數的任務就是為以后調用函數預先作準備。好處:
設備驅動模型三個重要成員是?platform總線的匹配規則是?在具體應用上要不要先注冊驅動再注冊設備?有先后順序沒?
設備驅動模型三個重要成員是 總線、設備、驅動;
platfoem總線的匹配規則是:要匹配的設備和驅動都要注冊,設備可以在設備樹里注冊,也可以通過代碼注冊設備,匹配成功會去調用驅動程序里的probe函數(probe函數在這個platform_driver結構體中注冊)。
字符型驅動設備怎么創建設備文件
手動創建:mknod /dev/led c 250 0,其中dev/led 為設備節點 c 代表字符設備 250代表主設備號 0代表次設備號
還有UDEV/MDEV自動創建設備文件的方式,UDEV/MDEV是運行在用戶態的程序,可以動態管理設備文件,包括創建和刪除設備文件,運行在用戶態意味著系統要運行之后。在 /etc/init.d/rcS 腳本文件中會執行 mdev -s 自動創建設備節點。
內核函數mmap的實現原理,機制?
mmap函數實現把一個文件映射到一個內存區域,從而我們可以像讀寫內存一樣讀寫文件,他比單純調用read/write也要快上許多。在某些時候我們可以把內存的內容拷貝到一個文件中實現內存備份,當然,也可以把文件的內容映射到內存來恢復某些服務。另外,mmap實現共享內存也是其主要應用之一,mmap系統調用使得進程之間通過映射同一個普通文件實現共享內存。
linux中內存劃分及如何使用?虛擬地址及物理地址的概念及彼此之間的轉化,高端內存概念?
1. 用戶虛擬地址
這是在用戶空間進程所能看到的常規地址。每個進程多有自己的虛擬地址,并且可以使用大于物理內存大小的空間。
2. 物理地址
該地址在處理器和系統內存之間使用,對應與真是物理地址。
3. 總線地址
所有的地址都可以稱為總線地址,因為開發環境下所有的設備都是接在總線上,如AXI總線,APB總線,PCI總線 I2C總線 SPI總線。也就會存在很多種地址空間。
4. 內核邏輯地址
內核邏輯地址組成了內核的常規地址空間。該地址映射了部分(或者全部)內存,并經常被視為物理地址。
邏輯地址使用硬件內建的指針大小,因此在安裝了大量內存的32位系統中,它無法尋址全部的物理內存。
邏輯地址通常保存在unsigned long或者void *這樣類型的變量中。kmalloc返回的內存就是內核邏輯地址。
5. 內核虛擬地址
內核虛擬地址與物理地址的映射不必是一對一的,而這是虛擬地址的特點。
所有邏輯地址都是內核虛擬地址,但是許多內核虛擬地址不是邏輯地址。vmalloc分配的內存就是一個虛擬地址。
4G的進程地址空間被人為的分為兩個部分——用戶空間與內核空間。用戶空間從0到3G(0xC0000000),內核空間占據3G到4G。用戶進程通常情況下只能訪問用戶空間的虛擬地址,不能訪問內核空間虛擬地址。只有用戶進程進行系統調用(代表用戶進程在內核態執行)等時刻可以訪問到內核空間。
虛擬地址,即邏輯地址,是指由程序產生的與段相關的偏移地址部分。
物理地址 (physical address): 放在尋址總線上的地址。
地址空間大于1G的內存區域稱之為高端內存
總結:高端內存的作用就是用于建立臨時地址映射,用于kernel申請user空間內存
malloc(), vmalloc()和kmalloc()區別
內存管理MMU的作用?
嵌入式設備,為加快啟動速度,可以做哪些方面的優化?
linux默認的安裝內核相當龐大,為了保證系統的兼容性和靈活性,支持熱插拔操作,內核啟動時要進行大量的硬件檢測和初始化工作,而嵌入式的硬件都是固定的,只需要選擇需要的硬件驅動就可以,不需要全部的硬件驅動都檢測;因此可以進行適當的裁剪內核達到縮小啟動linux系統的目的;同時可以統計驅動模塊的耗時時間,對耗時較長的模塊驅動加以分析,優化;
簡述linux系統啟動過程
Linux系統的啟動過程并不是大家想象中的那么復雜,其過程可以分為5個階段:
介紹一下Linux設備樹
DTS即DeviceTree Source 設備樹源碼,是一種描述硬件的數據結構,以樹狀節點的方式描述一個設備的各種硬件信息細節:CPU、GPIO、時鐘、中斷、內存等,形成類似文本文件dts,直接透過它傳遞給Linux,使得驅動程序與硬件分離,只需要修改dts文件,便能實現需求。設備樹易于擴展,硬件有變動時不需要重新編譯內核或驅動程序,只需要提供不一樣的dtb文件。
DTS和DTSI(源文件)
dts文件是一種ASCII文本對DeviceTree的描述,放置在內核的/arch/arm/boot/dts目錄。一般而言,一個.dts文件對應一個ARM的machine。
由于一個SOC可能有多個不同的電路板(.dts文件為板級定義,.dtsi文件為SoC級定義),而每個電路板擁有一個.dts。這些dts勢必會存在許多共同部分,為了減少代碼的冗余,設備樹將這些共同部分提煉保存在.dtsi文件中,供不同的dts共同使用。編譯工具編譯.dts生成的二進制文件(.dtb),uboot在引到內核時,會預先讀取.dtb到內存,進而由內核解析,系統啟動。.dtsi的使用方法,類似于C語言的頭文件,在dts文件中需要進行include .dtsi文件。當然,dtsi本身也支持include另一個dtsi文件。dtb、dtsi、dtb文件作用、關系
U-boot的啟動流程
1、匯編階段
A、初始化關鍵硬件:關閉看門狗、中斷、MMU和Cache(緩存)等,開啟時鐘、串口、Flash和內存等。
目的:為了U-boot穩定性,關掉不必要或影響穩定性的硬件,打開運行U-boot必須的硬件。即通過使U-boot運行單純化,從而保證U-boot的穩定性。
B、U-boot自搬移:U-boot自己將自己從Flash搬移到內存(RAM)運行。
目的:提高U-boot的運行速度。因為內存要比Flash速度快。
2、C語言階段:
A、初始化大部分硬件;
B、將Linux內核(Kernel)從Flash中“搬移”到內存中運行;
C、運行內核(Kernel)。
另外一個回答:
這一過程可以分為兩個階段,各個階段的功能如下:
第一階段的功能:
- 硬件設備初始化;
- 加載u-boot第二階段的代碼到RAM空間;
- 設置好棧;
- 跳轉到u-boot第二階段代碼的入口處;
第二階段的功能:
- 初始化本階段使用的硬件設備;
- 檢測系統內存映射;
- 將Linux內核從Flash讀取到RAM中;
- 為Linux內核設置啟動參數;
- 調用Linux內核。
中斷
IRQ和FIQ有什么區別,在CPU里面是是怎么做的?
IRQ(Interrupt Request):指中斷模式。
FIQ(Fast Interrupt Request):指快速中斷模式。
IRQ與FIQ是ARM處理器的兩種不同編程模式(ARM有7種處理模式)。
不要小看這幾個寄存器,ARM在編譯的時候,如果你FIQ中斷處理程序足夠用這幾個獨立的寄存器來運作,它就不會進行通用寄存器的壓棧,這樣也省了一些時間。
寫過完整匯編系統的都比較明白這點的差別,18只能放一條指令,為了不與1C處的FIQ沖突,這個地方只能跳轉,而FIQ不一樣,1C以后沒有任何中斷向量表了,這樣可以直接在1C處放FIQ的中斷處理程序,由于跳轉的范圍限制,至少少了一條跳轉指令。
IRQ的響應并不及時,從Verilog仿真來看,IRQ會延遲幾個指令周期才跳轉到中斷向量處,看起來像是在等預取的指令執行完。FIQ的響應不清楚,也許比IRQ快。
請簡述中斷于DMA的區別。Linux設備驅動程序中,使用哪個函數注冊和注銷中斷處理程序?
1)DMA:是一種無須CPU的參與就可以讓外設與系統內存之間進行雙向數據傳輸的硬件機制,使用DMA可以使系統CPU從實際的I/O數據傳輸過程中擺脫出來,從而大大提高系統的吞吐率.
中斷:是指CPU在執行程序的過程中,出現了某些突發事件時CPU必須暫停執行當前的程序,轉去處理突發事件,處理完畢后CPU又返回源程序被中斷的位置并繼續執行。
所以中斷和DMA的區別就是DMA不需CPU參與而中斷是需要CPU參與的。
中斷的上半部分和下半部分的問題:請說明分成上半部分和下半部分的原因,為何要分?該如何實現?
中斷分成上半部分和下班部分的原因主要是因為內核要保證內核中進程的正常調度和運行,中斷程序所以需要短小精悍,但是有些驅動在中斷處理程序需要完成大量的工作,所以就很耗時。為了解決這個問題就Linux 將中斷處理程序分解為兩個半部:頂半部(top half)和底半部(bottom half)。
頂半部完成盡可能少的比較緊急的功能,底半部就由tasklet或者工作隊列的方法實現 具體的實現效果如下
struct tasklet_struct tlet;
tasklet_init(&tlet, jit_tasklet_fn, (unsigned long) data);
參數
第一個:定義的tasklet變量
第二個:函數
第三個:數據 傳遞給回調函數的數據
void jit_tasklet_fn(unsigned long arg)
{
//中斷的底半部 執行該函數的時候,已經出中斷了
printk("in jit_tasklet_fn jiffies=%ld\n",jiffies);}
在需要調度的地方調用以下函數
tasklet_schedule(&tlet);
一般在中斷函數當中調度在不晚于下一個時鐘滴答之前執行
【tasklet 和定時器的區別】
1. 執行時間
定時器的執行:時間是確定的
tasklet:不確定的
2.tasklet 執行耗時的操作的
軟中斷是如何實現的?
軟中斷是用軟件方式模擬硬件中斷的概念,實現宏觀上的異步執行效果。
寫一個中斷服務需要注意哪些?如果中斷產生之后要做比較多的事情你是怎么做的?
第一:中斷處理例程應該盡量短,把能放在后半段(tasklet,等待隊列等)的任務盡量放在后半段。
評:寫一個中斷服務程序要注意快進快出,在中斷服務程序里面盡量快速采集信息,包括硬件信息,然后推出中斷,要做其它事情可以使用工作隊列或者tasklet方式。也就是中斷上半部和下半部。
第二:中斷服務程序中不能有阻塞操作。
第三:中斷服務程序注意返回值,要用操作系統定義的宏做為返回值,而不是自己定義的OK,FAIL之類的。
中斷能不能睡眠,為什么?下半部能不能睡眠?
中斷發生以后,CPU跳到內核設置好的中斷處理代碼中去,由這部分內核代碼來處理中斷。這個處理過程中的上下文就是中斷上下文。
為什么可能導致睡眠的函數都不能在中斷上下文中使用呢? 首先睡眠的含義是將進程置于“睡眠”狀態,在這個狀態的進程不能被調度執行。然后,在一定的時機,這個進程可能會被重新置為“運行”狀態,從而可能被調度 執行。 可見,“睡眠”與“運行”是針對進程而言的,代表進程的task_struct結構記錄著進程的狀態。內核中的“調度器”通過task_struct對進 程進行調度。
但是,中斷上下文卻不是一個進程,它并不存在task_struct,所以它是不可調度的。所以,在中斷上下文就不能睡眠。
工作隊列可以把工作推后,交由一個內核線程去執行——這個下半部總是會在進程上下文執行。這樣,通過工作隊列執行的代碼能占盡進程上下文的所有優勢,最重要的是工作隊列允許重新調度甚至是睡眠。下半部是可以睡眠的
如果你需要用一個可以重新調度的實體來執行你的下半部處理,你應該使用工作隊列,它是唯一能在進程上下文運行的下半部實現的機制,也只有它才可以睡眠,這意味著你在你需要獲得大量的內存時,在你需要獲取信號量時,在你需要執行阻塞式的IO操作時,它都會非常有用,如果你不需要用一個內核線程來推后執行工作,那么就考慮使用tasklet吧!
tasklet和workqueue區別?
tasklet運行于中斷上下文,不允許阻塞 、休眠,而workqueue運行與進程上下文,可以休眠和阻塞。
同步
異步IO和同步IO的區別?
如果是同步IO,當一個IO操作執行時,應用程序必須等待,直到此IO執行完,相反,異步IO操作在后臺運行,IO操作和應用程序可以同時運行,提高系統性能,提高IO流量; 在同步文件IO中,線程啟動一個IO操作然后就立即進入等待狀態,直到IO操作完成后才醒來繼續執行,而異步文件IO中,線程發送一個IO請求到內核,然后繼續處理其他事情,內核完成IO請求后,將會通知線程IO操作完成了。
死鎖產生的原因及四個必要條件
產生死鎖的原因主要是:
(1) 因為系統資源不足。
(2) 進程運行推進的順序不合適。
(3) 資源分配不當等。
如果系統資源充足,進程的資源請求都能夠得到滿足,死鎖出現的可能性就很低,否則就會因爭奪有限的資源而陷入死鎖。其次,進程運行推進順序與速度不同,也可能產生死鎖。
產生死鎖的四個必要條件:
(1) 互斥條件:一個資源每次只能被一個進程使用。
(2) 請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。
(3) 不剝奪條件:進程已獲得的資源,在末使用完之前,不能強行剝奪。
(4) 循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關系。
這四個條件是死鎖的必要條件,只要系統發生死鎖,這些條件必然成立,而只要上述條件之一不滿足,就不會發生死鎖。
死鎖的解除與預防:
理解了死鎖的原因,尤其是產生死鎖的四個必要條件,就可以最大可能地避免、預防和解除死鎖。所以,在系統設計、進程調度等方面注意如何不讓這四個必要條件成立,如何確定資源的合理分配算法,避免進程永久占據系統資源。此外,也要防止進程在處于等待狀態的情況下占用資源。因此,對資源的分配要給予合理的規劃
自旋鎖和信號量在互斥使用時需要注意哪些?在中斷服務程序里面的互斥是使用自旋鎖還是信號量?還是兩者都能用?為什么?
使用自旋鎖的進程不能睡眠,使用信號量的進程可以睡眠。
中斷服務例程中的互斥使用的是自旋鎖,原因是在中斷處理例程中,硬中斷是關閉的;但是要注意這樣會丟失可能到來的中斷。
原子操作你怎么理解?為了實現一個互斥,自己定義一個變量作為標記來作為一個資源只有一個使用者行不行?
原子操作指的是無法被打斷的操作。
第二句話的意思是:
定義一個變量,比如 int flag =0;
if(flag == 0)
{
flag = 1;
操作臨界區;
flag = 0;
1
2
}
驅動里面為什么要有并發、互斥的控制?如何實現?講個例子?
并發(concurrency)指的是多個執行單元同時、并行被執行,而并發的執行單元對共 享資源(硬件資源和軟件上的全局變量、靜態變量等)的訪問則很容易導致競態(raceconditions)。
解決競態問題的途徑是保證對共享資源的互斥訪問,所謂互斥訪問就是指一個執行單元 在訪問共享資源的時候,其他的執行單元都被禁止訪問。
訪問共享資源的代碼區域被稱為臨界區,臨界區需要以某種互斥機制加以保護,中斷屏蔽,原子操作,自旋鎖,和信號量都是linux設備驅動中可采用的互斥途徑。
linux中的同步機制?spinlock與信號量的區別?
linux中的同步機制:自旋鎖、信號量、讀寫鎖、循環緩沖區
spinlock在得不到鎖的時候,程序會循環訪問鎖,性能下降
信號量在得不到鎖的時候會休眠,等到可以獲得鎖的時候,繼續執行。
同步和互斥區別?
相交進程之間的關系主要有兩種,同步與互斥。
- 所謂互斥,是指散步在不同進程之間的若干程序片斷,當某個進程運行其中一個程序片段時,其它進程就不能運行它們之中的任一程序片段,只能等到該進程運行完這個程序片段后才可以運行。
- 所謂同步,是指散步在不同進程之間的若干程序片斷,它們的運行必須嚴格按照規定的某種先后次序來運行,這種先后次序依賴于要完成的特定的任務。
顯然,同步是一種更為復雜的互斥,而互斥是一種特殊的同步。也就是說互斥是兩個線程之間不可以同時運行,他們會相互排斥,必須等待一個線程運行完畢,另一個才能運行,
而同步也是不能同時運行,但他是必須要安照某種次序來運行相應的線程(也是一種互斥)!
總結:
- 互斥:是指某一資源同時只允許一個訪問者對其進行訪問,具有唯一性和排它性。但互斥無法限制訪問者對資源的訪問順序,即訪問是無序的。
- 同步:是指在互斥的基礎上(大多數情況),通過其它機制實現訪問者對資源的有序訪問。在大多數情況下,同步已經實現了互斥,特別是所有寫入資源的情況必定是互斥的。少數情況是指可以允許多個訪問者同時訪問資源
spinlock自旋鎖和信號量的概念?
自旋鎖在同一時刻只能被最多一個內核任務持有,所以一個時刻只有一個線程允許存在于臨界區中。這點可以應用在多處理機器、或運行在單處理器上的搶占式內核中需要的鎖定服務。
信號量的用法和自旋鎖有相似的地方。linux中的信號量是一種睡眠鎖。如果有一個任務試圖獲得一個已被持有的信號量時,信號量會將其推入等待隊列,然后讓其睡眠。這時處理器獲得自由去執行其它代碼。當持有信號量的進程將信號量釋放后,在等待隊列中的一個任務將被喚醒,從而便可以獲得這個信號量。
USB設備的枚舉過程?
(1) Get Device Descriptor。主機的第一個命令要求得到設備描述符,此SETUP 包為8 個字節數據(80,06,00,01,00,00,40,00),發向地址0,端口0。“40”表示返回數據長度最大為40H 個字節。實際上,只返回一個包,即數組DEV_DESC[ ]中的前8 個字節,用于說明設備的描述符的真實長度和設備的類型。
(2) Set Address。接著是設置設備地址處理事件,主機發送一個含有指定地址的數據包(00,05,02,00,00,00,00,00),在主機只有一個USB 設備的時候,這個地址一般會是2,最大地址127,USB 協議中可以連接127 個設備。設置地址事件處理結束后,設備進入地址狀態,主機以后會在新的指定地址處訪問設備。
(3) Get Device Descriptor。主機再次發送請求得到設備描述符的數據包(80,06,00,01,00,00,12,00),與上次不同的是,要求的數據的長度是實際的數據長度,同時是發送到Set Address命令所設置的地址。
(4) 讀取全部Configuration Descriptor。接著主機要求得到設備全部的配置描述符、接口描述符和節點描述符(80,06,00,02,00,00,40,00),由于主機不知道設備描述符的真實長度,因此它要求得到64個字節。
(5) Set Interface,主機發送數據包(01,0B,00,00,00,00,00,00),設置接口值為0。
(6) Set Conifguration,確定USB設備工作在哪一個配置下。對于U盤設備來說,一般只有1個配置值,其值為01。主機發送數據包(00,09,01,00,00,00,00,00)。
(7) 如果以上步驟都正確,主機將找到新設備,并且配置成功,該設備可以正常使用,可以進行后續的U盤枚舉過程了。
(8) 用busHound觀察計算機對于U盤的枚舉過程,發現上述步驟后還有一個GetMaxLun的操作,但是實際上對于U盤來說忽略該步驟也沒有問題。
?
總結
以上是生活随笔為你收集整理的嵌入式Linux设备驱动面试题汇总的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 手机APP三年内将彻底消失,以后全靠H5
- 下一篇: layuiadmin 模版渲染完成执行