嵌入式Linux——驱动调试:修改内核代码来定位系统僵死问题

2019-07-12 14:58发布

简介:     在驱动运行时可能会碰到系统因为某些不明原因而僵死的问题。而本文主要就是介绍通过修改内核代码来将僵死的位置找出。    Linux内核:linux-2.6.22.6  所用开发板:JZ2440 V3(S3C2440A) 声明:     本文是看完韦东山老师视频后所做的课后总结。文中主要内容还是老师视频中所讲的。但有部分内容是看其他网友博客总结。 内核时钟中断:     关于内核时钟内容转载自:把握linux内核设计思想(六):内核时钟中断     内核中很多函数是基于时间驱动的,其中有些函数需要周期或者定期执行。比如有的每秒钟执行100次,有的在等待一个相对时间之后执行。除此之外,内核还必须管理系统的时间日期。     周期性产生的时间都是由系统定时器驱动的,系统定时器是一种可编程硬件芯片。他可以以固定频率产生中断,该中断就是所谓的定时器中断,其所对应的中断处理函数负责更新系统时间,也负责执行需要周期性运行的任务。        系统定时器以某种频率自行触发时钟中断,该频率可以通过编程预定,称作节拍率。当时钟中断发生时,内核就通过一种特殊的中断处理器对其进行处理。内核知道连续两次时钟中断的间隔时间,该间隔时间就称为节拍。内核就是靠这种已知的时钟中断间隔来计算实际时间和系统运行时间的。内核通过控制时钟中断维护实际时间,另外内核也为用户提供一组系统调用获取实际日期和实际时间。时钟中断对操作系统的管理来说十分重要,系统更新运行时间、更新实际时间、均衡调度程序中个处理器上运行队列、检查进程是否用尽时间片等工作都利用时钟中断来周期执行。     系统定时器频率是通过静态预定义的,也就是HZ,体系结构不同,HZ的值也不同。内核在driversmd aid6.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表示中断号。我们从archarmplat-s3c24xx ime.c中的函数: static void __init s3c2410_timer_init (void) { s3c2410_timer_setup(); setup_irq(IRQ_TIMER4, &s3c2410_timer_irq); }     可以看出IRQ_TIMER4就是这个时钟处理函数的中断号。而IRQ_TIMER4在includeasm-armarch-s3c2410irqs.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 . ",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]     所以我们在archarmkernelirq.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 ",current->pid,current->comm); printk("pc = %08x ",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内核设计思想(六):内核时钟中断