嵌入式Linux——驅動調試:修改內核代碼來定位系統僵死問題

簡介:

    在驅動運行時可能會碰到系統因爲某些不明原因而僵死的問題。而本文主要就是介紹通過修改內核代碼來將僵死的位置找出。

 Linux內核:linux-2.6.22.6

 所用開發板:JZ2440 V3(S3C2440A)

聲明:

    本文是看完韋東山老師視頻後所做的課後總結。文中主要內容還是老師視頻中所講的。但有部分內容是看其他網友博客總結。

內核時鐘中斷:

    關於內核時鐘內容轉載自:把握linux內核設計思想(六):內核時鐘中斷

    內核中很多函數是基於時間驅動的,其中有些函數需要週期或者定期執行。比如有的每秒鐘執行100次,有的在等待一個相對時間之後執行。除此之外,內核還必須管理系統的時間日期。

    週期性產生的時間都是由系統定時器驅動的,系統定時器是一種可編程硬件芯片。他可以以固定頻率產生中斷,該中斷就是所謂的定時器中斷,其所對應的中斷處理函數負責更新系統時間,也負責執行需要週期性運行的任務。

     系統定時器以某種頻率自行觸發時鐘中斷,該頻率可以通過編程預定,稱作節拍率。當時鍾中斷髮生時,內核就通過一種特殊的中斷處理器對其進行處理。內核知道連續兩次時鐘中斷的間隔時間,該間隔時間就稱爲節拍。內核就是靠這種已知的時鐘中斷間隔來計算實際時間和系統運行時間的。內核通過控制時鐘中斷維護實際時間,另外內核也爲用戶提供一組系統調用獲取實際日期和實際時間。時鐘中斷對操作系統的管理來說十分重要,系統更新運行時間、更新實際時間、均衡調度程序中個處理器上運行隊列、檢查進程是否用盡時間片等工作都利用時鐘中斷來週期執行。

    系統定時器頻率是通過靜態預定義的,也就是HZ,體系結構不同,HZ的值也不同。內核在drivers\md\raid6.h文件中定義,

#define HZ 1000

    linux內核衆多子系統都依賴時鐘中斷工作,所以是時鐘中斷頻率的選擇必須考慮頻率所有子系統的影響。提高節拍就使得時鐘中斷產生的更頻繁,中斷處理程序就會更加頻繁的執行,這樣就提高了時間驅動時間的準確度,誤差更小。如HZ=100,那麼時鐘每10ms中斷一次,週期事件每10ms運行一次,如果HZ=1000,那麼週期事件每1ms就會運行一次,這樣依賴定時器的系統調用能夠以更高的精度運行。既然提高時鐘中斷頻率這麼好,那爲何要將HZ設置爲100呢?因爲提高時鐘中斷頻率也會產生副作用,中斷頻率越高,系統的負擔就增加了,處理器需要花時間來執行中斷處理程序,中斷處理器佔用cpu時間越多。這樣處理器執行其他工作的時間及越少,並且還會打亂處理器高速緩存。所以選擇時鐘中斷頻率時要考慮多方面,要取得各方面的折中的一個合適頻率。

    內核有一個全局變量jiffies,該變量用來記錄系統起來以後產生的節拍總數。系統啓動是,該變量被設置爲0,此後每產生一次時鐘中斷就增加該變量的值。

定位僵死的驅動:

    從上面對內核時鐘中斷的介紹中我們知道,在內核中會定時的觸發時鐘中斷,同時我們也知道即使是在系統僵死的情況下,內核還是會不斷觸發時鐘中斷的。我們就可以利用這個時鐘中斷來確定發生僵死的驅動模塊。而具體的做法就是在系統時鐘中斷的處理函數中加入打印語句來將當前僵死的驅動線程的pid和線程名打印出來,這樣我們就可以知道是那個驅動模塊發生僵死了。

    同時我們會發現當我們在系統中輸入命令: cat /proc/interrupts 時我們會看到當前系統使用的所有的系統中斷,而其中就有系統時鐘中斷。它顯示爲: 30:           XXX         s3c          S3C2410  Timer  Tick,其中30表示中斷號。我們從arch\arm\plat-s3c24xx\time.c中的函數:

static void __init s3c2410_timer_init (void)
{
	s3c2410_timer_setup();
	setup_irq(IRQ_TIMER4, &s3c2410_timer_irq);
}

    可以看出IRQ_TIMER4就是這個時鐘處理函數的中斷號。而IRQ_TIMER4在include\asm-arm\arch-s3c2410\irqs.h中定義爲:

#define S3C2410_CPUIRQ_OFFSET	 (16)
#define S3C2410_IRQ(x) ((x) + S3C2410_CPUIRQ_OFFSET)
#define IRQ_TIMER4     S3C2410_IRQ(14)

    所以從上面的代碼我們可以看出時鐘中斷的中斷號爲30 。而上面的XXX是這個中斷髮生的次數。而S3C2410  Timer  Tick就是發生中斷的名稱了。我們就是根據這個名稱找到相應的中斷結構體的:

static struct irqaction s3c2410_timer_irq = {
	.name		= "S3C2410 Timer Tick",
	.flags		= IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
	.handler	= s3c2410_timer_interrupt,
};
    而從上面可以看出s3c2410_timer_interrupt就是時鐘中斷處理函數了。那麼我們就是要在這個中斷處理函數中加入我們的打印語句來實現將僵死的驅動pid和名稱打印出來。

    而我們的想法是這樣的:如果一個進程運行的時間超過十秒我們就將這個進程的pid和名稱打印出來。所以我們按着這個想法在s3c2410_timer_interrupt函數中加入代碼:

/* 如果十秒之內都是同一個進程在運行,就打印該進程pid和名稱 */
static pid_t pre_pid;
static int cnt = 0;
if(pre_pid == current->pid){
	cnt++;
}else{
	cnt = 0;
	pre_pid = current->pid;
}

if(cnt >= 10*HZ){
	cnt = 0;
	printk("s3c2410_timer_interrupt: pid = %d, name = %s .\n",current->pid,current->comm);
}

    而上面程序中的current其實是一個宏,他的定義爲:

#define current (get_current())
static inline struct task_struct *get_current(void) __attribute_const__;

    首先這個current是一個task_struct結構體。而在內核中每一個進程都有一個task_struct結構體來表示他。在這個task_struct結構體中存放着關於該進程的信息。而current獲得當前的進程的task_struct。所以current中存放的是當前進程的所有信息。而我們上面函數中的pid和comm分別是當前進程的pid和名稱。

     現在將我們的測試驅動程序中加入一個死循環,然後裝載這個驅動程序,並運行他的測試程序,我們會發現系統僵死,但是會打印導致系統僵死的驅動模塊。但是具體的驅動模塊哪裏出現錯誤僵死我們就不知道了。那麼我們就要想辦法找到是哪裏出錯而導致系統僵死的。我們結合上一篇文章的知識知道,當我們知道一個程序中僵死位置的PC值我們就可以定位出是哪裏出現錯誤了。

    同時我們知道在Linux內核中當我們的進程被打斷的時候會保護現場,即將各個寄存器的值壓入棧中。而當我們的中斷處理函數操作完成後我們將恢復現場,即從棧中將各個寄存器的值讀出。如下圖:


    按着這個原理來修改程序來讓他將我們僵死的進程的PC值打印出來。但是我們發現在時鐘中斷函數中並沒有保存寄存器的結構體。而在總中斷函數中有這個結構體:pt_regs。而PC值被定義爲:

#define ARM_pc		uregs[15]

    所以我們在arch\arm\kernel\irq.c文件中的總中斷函數asm_do_IRQ中加打印語句。(由於不同CPU的中斷處理函數可能不一樣所以我們的總中斷處理函數爲asm_do_IRQ,而其他的CPU的函數並不一定爲他)。

    我們加入的代碼爲:

       static pid_t pre_pid;
       static int cnt=0;
        //時鐘中斷的中斷號是30
        if(irq==30){
        	if(pre_pid==current->pid){
            		cnt++;
       	         }else{
            		cnt=0;
            		pre_pid=current->pid;
        	}

        	if(cnt==10*HZ){
            		cnt=0;
            		printk("s3c2410_timer_interrupt : pid = %d, task_name = %s\n",current->pid,current->comm);
            		printk("pc = %08x\n",regs->ARM_pc);//打印pc值
       	        }
        }
    這次我們再測試上面的測試程序就打印出了PC值。而我們根據PC值並結合上篇文章:嵌入式Linux——oops:根據oops信息,找到錯誤的產生位置以及函數的調用關係中所介紹的方法就可以找出出錯的位置了。

    這裏我簡要的說明一下這個方法:

一   先通過PC/IP值判斷這個錯誤是內核函數中的錯誤還是使用insmod加載的驅動程序的錯誤:

二   假設是加載驅動程序引起的錯誤

    2.1  是加載驅動程序引起的錯誤,那麼就要確定是哪個驅動程序引起的錯誤。

    2.2  確定是哪個驅動引起的錯誤之後我們就要反彙編這個驅動模塊的ko文件,得到dis文件。

    2.3  分析反彙編得到的dis文件,來確定引起錯誤的語句。

三   假設是內核函數引起的錯誤

    3.1   是內核函數引起的錯誤,那麼反彙編內核文件,得到dis文件

    3.2   在內核的反彙編文件中以PC/IP值進行搜索,得到出錯的函數和出錯的語句。

    這裏我需要強調的一點是在中斷中保存的PC值爲當前的指令加4,所以我們得到的PC值要減去4才能得出真正發生僵死的語句。

    雖然這裏我們找到了僵死位置,但是實際上僵死往往發生在某一段代碼上,所以根據中斷時保存的PC可以找到的是這個段的大概位置,要想發現具體的問題,我們對反彙編要有一定程度的瞭解

參考文獻:

驅動調試之修改系統時鐘中斷定位系統僵死問題
把握linux內核設計思想(六):內核時鐘中斷


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章