如果用的是Snapgear的uClinux(2.4.x),板上内存不够大,而且要访问一个大容量文件系统,__getdents极有可能是整个系统的定时炸弹。
问题背景。 公司要开发便携式数字硬盘录像机(此处简称盒子),选定ARM内核,操作系统用Snapgear的uClinux(2.4.x)。因为产品必须做到抗震,所以存储设备没用硬盘而是用CF卡。接下当然就是写CF卡驱动,让文件系统能够在恰当时候调用到CF卡驱动中例程,七拼八凑总算可以以FAT32文件系统方式读写CF卡了。挂载,fopen,fwrite,fread,fclose,试下来还行,和Windows读写也成功。——自以为天下太平了,可这毕竟是自以为,没几天就发个问题:fclose关闭文件,切断盒子电源,拔出CF卡,插入USB读卡器,用Media Player播放刚录下的avi,发现快播放结束时Media Player提示文系已损坏!有问题了总得解决,幸好过去写了个avi文件查看工具,一看,才发现最后要写的一些数据被写入。好,既然知道现象就要解决问题,尝试几种方法。1)认为fwrite函数写过程是这样的:用户buf-->系统缓冲-->CF卡,那些数据只被写入系统缓冲而没被更新入CF,程序改为在调fclose前先调fflush;——不行;2)认为写CF卡要一定时间,也许fclose调用得太快了,那我在fclose之前先sleep(5),——5秒呐,对电子设备来说刚满月的小孩也要长到可以打酱油了,——不行。怎么办呢,怎么办呢?亏我耳听四方,发现个现象,如果在fclose后显示一下该目录下的所有文件,那文件居然就完整了!程序也就是调用一下ls -l /mnt(CF卡挂载在/mnt)。当然啦,这方法实用下来还是曾出现一两次有数据没写入,但真的居然可以解决问题了(用户拔电源最好晚点)。
青蛙才会自认为自已个天才,不过我找到那邪门歪招时还真出了点青蛙的感觉。可惜,现实总是没有结局的,没等我兴奋劲消退去又一个问题出来了,而且事后证明这问题比上一问题麻烦多了,它将困扰我将近十个月。那就是执行system("ls -l /mnt)时,瞧不准那一次,系统给你出个提示: fault-common.c(97): start_code=0xabb040, start_stack=0xac7438) 然后呢系统挂起,要靠5分钟的看门狗才能把它唤醒,总之一句话,执行system("ls -l /mnt")时,会随机出现统被挂起。
解决。 看到fault-common.c(97)提示,对于个程序上屡犯错的人,大概第一念头是:不好,栈溢出了!让函数栈大瘦身吧,这减肥一般针对较占内存变量,像字符缓冲区,数组,而方法无非两种:1)若使用频率不高,改为丛堆中动态分配;2)若使用频率较高,改为全局变量,一进入函数时先把全局变量置为初始值。针对以上ls -l /mnt,那当然是ls可执行程序了,翻到<.../snapger/user/fileutils/ls.c>,修改main中一些变量的存储位置,像fullname。我几乎是把main层变量全改为全局变量了(人慌就这样,统统外放,先确定是不是这些变量造成的),可fault-common.c(97)依旧存在。看来实在是没法了,只好采用万能调试工具:printf,每行语句执行后都加上个printf。——结果出了让人头疼的现象:1)提示fault-common.c(97)时并不是在一个固定位置;2)函数本是失败的,某条语句后一加printf,函数竟然变成功了,也就是说加不加printf居然对执行结果有影响。而调试,难办的就是此种随机错误,加之那时我还要忙于解决网络发送(盒子同时也是个网络视频服务器),找到个较少出现fault-common.c(97)的代码,先出个内核第一版。就这样一直到1.06版,这问题都一直存在,一直成了我的心病。
又要出新内核了,此次内核要解决的问题中一个是要能确信支持16G容量CF卡(客户有报说16G的有问题,那种CF卡,小小点玩意价格上却相当于一台台式机,所以我调试用的一直用1G)。插入CF卡,运行内核,开始录像,停止录像,好了,系统立即提示fault-common.c(97):,更严重的是它每次都出,次次不落(此时我其实很高兴,因为问题随机性消失了,对调试来说应该是好消息),基于此我下定决心,此次一定要广开洞深挖土,连根拔掉。依旧在ls.c中的main函数中加入printf,经过轮翻编译、运行,终于得出两点结论:1)ls -l /mnt时是一定报错,但ls -l <别目录>时并不报错,像ls -l /bin,而且是次次成功;2)问题是出在readdir函数,执行了该函数后,一些变量值,像listsize由256变成了个巨大数字,listused也由0变成了莫名数字。没办法了,乖乖跟踪readdir函数吧(以前我一直停留在main函数,总幻想着main层程序有问题)。
对readdir执行printf跟踪,弄清该函数大致流程: readdir <.../snapgear/uClibc/libc/misc/dirent/readdir.c> | |-->__getdents <.../snapgear/uClibc/libc/sysdeps/linux/common/getdents.c> user layer | | --------------------------------------------------- kernel layer | |-->__syscall_getdents <.../snapgear/linux-2.4.x/fs/readdir.c> | | 小时我怕蛇,现在也怕蛇,将来也怕蛇的,不过我对蛇一直念念不忘,极可能那一节一节肚纹吧,在程序上还把一种数据结构自称为蛇形缓冲串。举个存放学生姓名的例子,程序分配连续的1024字节,然后每隔一定字节存储一个值,这一定间隔自称为节,每一节存放一个学生姓名;为了程序上能够区分,每一节有两个字段:节长度,姓名字符串。搜索程序搜到一个新姓名就形成一个节添加入蛇形缓冲串,后续程序则根据节中长度字段一个姓名一个姓名地提取出来。由于使用时节长度也分固定长度和非固定长度,相应地名称上也有固定式蛇形缓冲串和非固定式蛇形缓冲串。
好了,继续说readdir。__syscall_getdents在内核层被执行,它的主要流程:1)丛相应文件系统读回那目录数据,2)目录数据主要是文件名,对每个文件名形成struct linux_dirent结构,把它作为一个节放入参数dirent指向的那个非固定式蛇形缓冲串,并在返回值中指出了缓冲串中有效字节数。
__getdents调用__syscall_getdents后,它的两个变量:kdp指出了被内核填充了的蛇形缓冲串开始地址,retval指出了蛇形级冲串中的有效字节数。按常理说,根据这个缓冲串,上层的readdir就可以根据它一个个地解析出指定目录下的文件名了。但也不知什么原因,readir要求节类型是struct dirent,致使__getdents另一主要工作便是在把这个内核返回的非固定式蛇形缓冲串(A)修正为readdir要求的非固定蛇形缓冲串(B),程序实现上也就是丛A提出一节,转为struct dirent(DIR)结构,存入B,这样一直循环(见while ((char *)kdp < (char *)skdp + retval)),直到处理完所有节。
至此readdir就好理解了,它调用__getdents得到个非固定式蛇形缓冲串,dir->dd_buf指出了蛇形缓冲串开始地址,bytes指出了蛇形级冲串中的有效字节数,然后一次次地向上返回一个个节,ls就便可以丛DIR结构指示的节中抽取出文件名。
流程是理解了,现在要找问题,依旧是万能的printf,但结果却让我大大的纳闷了:fault-common.c(97)出现在__getdents函数内的两个变量赋值语句中间,常理上看这两个变量是不可能无效的。就在我实在不理解时候,我想到了程序如果分配蛇形缓冲串,也终于注意到了一条语句:
skdp = kdp = alloca (red_nbytes);
依字面看就知道这是分配red_nbytes大小的内存,skdp和kdp指向这内存区。我丛没用过alloca函数,那时就主观认为它类似malloc,丛堆中分配内存,只不过有人可能不喜欢用malloc这个名称而改用了alloca(各人写程序都有它主观嗜好,都好理解)。而既有分配,上看下看__getdents也没有相对应的释放函数啊!而且sdkp/kdp这指针都是不向上传的,这不就内存泄漏!怀着这疑问敲入,搜索alloca,选中文(英文太差了,得用中文预热一下),首页就看到以下一段:
使用alloca函数,这个函数是在函数的调用栈上动态分配内存,函数返回以后,alloca申请的内存随着调用栈的释放被自动释放……
看到这里,天空放睛,一切都顺理成章了,问题根源还是出在栈溢出。由于uClinux内核所在文件系统它的扇区最大也不会超过512字节,所以red_nbytes小于512字节,丛栈中分配能分配出来;而对于16G文件系统,它的扇区大小是8192字节,red_nbytes就接近8192,致使栈溢出。
为解决以上问题程序上所要做改动都集中在<.../snapgear/uClibc/libc/sysdeps/linux/common/getdents.c>。 1、添加include ,声明malloc和free。 2、skdp = kdp = alloca (red_nbytes);改为skdp = kdp = malloc (red_nbytes); 3、在函数出口处添加语句:free(skdp)以释放申请的内存。
后话: 好了,啰哩啰嗦的写了几段,希望对一些人有用,程序本就共享的。不过在此我有个愿望,就是想知道哪里有能运行在uClinux-2.4.x下的FAT32格式化程序,要是有人知道或有的话希望发到告知一下,万分感谢。
转载
|