声明:文章的来源是韦东山老师的第二期驱动调试一课关于修改内核来定位系统僵死的问题,作为学习笔记,记录下来作为以后复习回顾用,若有侵权的地方,请联系本人,会删除文章。
随便找一段程序,比如打开函数里面,故意引入一个错误,使其循环僵死:
static int first_drv_open(struct inode inode, struct file file)
{
//printk(“first_drv_open\n”);
/ 配置GPF4,5,6为输出 /
while(1); //引入的循环,也就是错误
gpfcon &= ~((0x3<<(42)) | (0x3<<(52)) | (0x3<<(62)));
gpfcon |= ((0x1<<(42)) | (0x1<<(52)) | (0x1<<(62)));
return 0;
}
当应用程序进入入口函数first_drv_init的时候就会在里面卡住,然后编译安装驱动程序:
make
cp first_drv.ko /work/nfs_root/first_fs/
insmod first_drv.ko
./firstdrvtest
然后开发板就死在这儿了,再也没有反应了。
类似于人体的心脏,对于一个操作系统也是一样的,系统的时钟中断始终是运行的,所以可以在系统的时钟中断加入打印语句来看看哪儿出问题了
用cat /proc/interrupts查看系统时钟的中断,可以看到下面的信息:
CPU0
30: 8990 s3c S3C2410 Timer Tick
在cat一次:
CPU0
30: 10977 s3c S3C2410 Timer Tick
可以看出系统的时钟中断是一直进行的,然后利用Timer Tick在内核中极性搜索:
Time.c (e:\linux-2.6.22.6\arch\arm\plat-s3c24xx): .name = “S3C2410 Timer Tick”,
进入这个函数:
static struct irqaction s3c2410_timer_irq = {
.name = “S3C2410 Timer Tick”,
.flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
.handler = s3c2410_timer_interrupt,
};
在第一期视频里有讲解中断里面都有一个irqaction结构体,里面有handler处理函数(惭愧,已经忘了)
s3c2410_timer_interrupt就是他的中断处理函数:
static irqreturn_t
s3c2410_timer_interrupt(int irq, void dev_id)
{
write_seqlock(&xtime_lock);
timer_tick();
write_sequnlock(&xtime_lock);
return IRQ_HANDLED;
}
系统虽然已经卡死了,但是中断是不断产生的,所以可以在里面加入打印函数,看看函数究竟卡死在什么地方:
/ 如果某个进程超过了十秒,就打印进程的信息,在内核中每个进程都有一个task_struct来表示,可以进去看看pid是怎么定义的 /
static pid_t pre_pid;
int cnt = 0; //这儿犯了一个错误,这个应该是静态变量,要不然每次中断cnt都是0,导致没有结果,加上static
if(pre_pid = current->pid)
{
cnt ++;
}
else
{
cnt = 0;
pre_pid = current->pid;
}
if(cnt == 3 * HZ) //经过测试,这个值最好大一点,否则可能来不及输入命令,设置10
{
cnt = 0;
/ 每一个进程都有一个task_struct,里面有一个成员comm用来表示当前的进程名称,这样可以知道是哪一个进程出问题了*/
printk(“s3c2410_timer_interrupt : pid = %d, task_name = %s\n”, current->pid, current->comm);
}
然后将修改过的timer.c拷贝到内核中,重新编译:
e:\linux-2.6.22.6\arch\arm\plat-s3c24xx\
/work/system/linux-2.6.22.6/arch/arm/plat-s3c24xx
然后重新编译,并且将内核拷贝到网络文件系统中以新的内核重新启动开发板
(注意一点,修改内核后一般出现错误的原因都是因为修改而出现的问题,看看错误信息,仔细监测修改的地方)
cd /work/system/linux-2.6.22.6/
make uImage
cp arch/arm/boot/uImage /work/nfs_root/uImage_timer
reboot
q
nfs 30000000 192.168.1.100:/work/nfs_root/uImage_timer
在不做任何事的情况下会打印下列信息:
s3c2410_timer_interrupt : pid = 0, task_name = swapper
这个可能只是内核的一种状态,因为在内核中没有pid = 0 的进程, ps:
PID Uid VSZ Stat Command
1 0 3092 S init
2 0 SW< [kthreadd]
3 0 SWN [ksoftirqd/0]
4 0 SW< [watchdog/0]
5 0 SW< [events/0]
6 0 SW< [khelper]
53 0 SW< [kblockd/0]
54 0 SW< [ksuspend_usbd]
57 0 SW< [khubd]
59 0 SW< [kseriod]
71 0 SW [pdflush]
72 0 SW [pdflush]
73 0 SW< [kswapd0]
74 0 SW< [aio/0]
708 0 SW< [mtdblockd]
723 0 SW< [kmmcd]
738 0 SW< [rpciod/0]
747 0 3096 S -sh
748 0 3096 R ps
安装驱动程序,并且对其进行测试:
insmod first_drv.ko
./firstdrvtest
然后可以看到打印信息:
s3c2410_timer_interrupt : pid = 751, task_name = firstdrvtest
这个说明是卡在进程firstdrvtest
但是这个不能体现具体卡在进程的具体位置,所以还要知道具体卡在进程的哪个地方了
原理: 系统中断的时候具体的执行方式是:保存现场 -> 执行中断 -> 恢复现场 -> 保存现场 -> 执行中断 -> 恢复现场
保存现场,保存的是各个寄存器的值,所以打印出各个寄存器的值,打印出PC的值就知道系统卡死在什么地方
PC寄存器所保存的值是应用程序或者驱动程序执行到哪个地方的时候被打断时保存的值
中断的时候,程序就会强行跳到0xffff0018的位置执行中断程序(嵌入式linux应用开发),入口函数是asm_do_irq(不同的内核可能入口函数不一样)
asmlinkage void asm_do_IRQ(int irq, struct pt_regs *regs)
struct pt_regs {long uregs[18];};
#define ARM_cpsr uregs[16]
#define ARM_pc uregs[15]
pt_regs的作用主要是存放保存现场存放的信息。uregs[15]里面存放的就是PC的值,将其打印出来就可以了。
在s3c2410_timer_init中可以看到中断号是14 + 16 = 30;
#define IRQ_TIMER4 S3C2410_IRQ(14)
#define S3C2410_IRQ(x) ((x) + S3C2410_CPUIRQ_OFFSET)
或者cat /proc/interrupts 可以看到中断号是 :
30: 8990 s3c S3C2410 Timer Tick
恢复之前的s3c2410_timer_interrupt函数
在中断的入口函数作如下修改:
asmlinkage void asm_do_IRQ(int irq, struct pt_regs *regs)
{
static pid_t pre_pid;
static int cnt = 0;
struct irqdesc *desc = irq_desc + irq;
if(irq == 30)
{
/* 如果10秒内都是在同一个进程,就打印 */
if(pre_pid == current->pid)
{
cnt ++;
}
else
{
cnt = 0;
pre_pid = current->pid;
}
if(cnt == 10 * HZ)
{
cnt = 0;
/* 每一个进程都有一个task_struct,里面有一个成员comm用来表示当前的进程名称,这样可以知道是哪一个进程出问题了*/
printk("asm_do_IRQ =>> s3c2410_timer_interrupt : pid = %d, task_name = %s\n", current->pid, current->comm);
printk("pc = %08x", regs->ARM_pc);
}
}
........
然后重新编译,以新的内核启动,具体的操作就不写了,和上面的一样。
e:\linux-2.6.22.6\arch\arm26\kernel\
/work/system/linux-2.6.22.6/arch/arm/plat-s3c24xx
经过编译后,发现是if语句少了一个},所以出现了一个很奇怪的提示:
arch/arm/kernel/irq.c:213: error: syntax error at end of input,213行根本就不在irq.c里面,后面没有修改,
所以应该是irq.c中出现了错误,检查后发现是少了一个}的原因。
下面是编译内核的时候出现的错误提示信息:
arch/arm/kernel/irq.c:145: warning: unsigned int format, long int arg (arg 2)
arch/arm/kernel/irq.c:199: warning: ISO C90 forbids mixed declarations and code
arch/arm/kernel/irq.c:213: error: syntax error at end of input
scripts/Makefile.build:202: recipe for target 'arch/arm/kernel/irq.o' failed
过程就不详述了,直接写出结果:
asm_do_IRQ =>> s3c2410_timer_interrupt : pid = 751, task_name = firstdrvtest
pc = bf00000c
这个就是输出信息。
后面怎么分析就非常简单了,在之前的根据PC值找出错误的地方已经说的非常清楚了,所以只是简单分析下就行了:
根据PC值确定是加载的模块出现了错误,僵死
重启开发板
nfs 30000000 192.168.1.100:/work/nfs_root/uImage_timer
bootm 30000000
insmod first_drv.ko
cat /proc/kallsyms > /kallsyms.txt
bf000000 t first_drv_open [first_drv]
arm-linux-objdump -D first_drv.ko > first_drv.dis
00000000 <first_drv_open>:
0: e1a0c00d mov ip, sp
4: e92dd800 stmdb sp!, {fp, ip, lr, pc}
8: e24cb004 sub fp, ip, #4 ; 0x4
c: ea000001 b 18 <first_drv_write+0x8>
pc = bf00000c
所以出错的就是: c: ea000001 b 18 <first_drv_write+0x8>
假设内核出错就简单了,可以这样:
vi System.map //查看是不是内核的范围
arm-linux-objdump -D vmlinux.ko > vmlinux.dis
然后就可以看看汇编知道哪儿出错了
这一课就到此为止,前面还有一课是关于自制寄存器工具,读取硬件寄存器的数据,原理很简单,就没做笔记了,直接写程序。感谢韦东山老师辛苦录制的视频,供我们学习,共同进步。