三国志13 攻城bug塔bug怎么解决

花了5天时间,终于解决了一个bug,心情非常愉快,憋了这么久,不吐不快。&事情是这样的,前面跟外地一家公司,开发一个二路RTSP音视频合成一路RTMP音视频的设备。设备在公司内运行是好好的,可到了现场,出现直播流畅,录制后点播卡顿的问题。由于设备在外地,调试不方便。只能这边写日志打印代码,那边烧程序调试,于是远程调试的恶梦开始了。远程操作画面卡不说,关键是慢,本来一个几分钟的事情,远程要搞几十分钟。长达5天的远程调试,真是对人的耐性的一种考验。&首先我怀疑的是时间戳不均匀。于是我将发送端的时间戳,接收端的时间戳分别日志成文件,统计,没有发现过大或过小的时间戳。也没有发现累计时间戳和累计到达时间偏差很大。这样能排除时间戳的问题。&其次我怀疑是数据格式的问题。我们这边RTSP的数据源设备和现场的不一样。于是我又写代码,将RTSP下拉的数据保存文件,去掉RTP头,添加SPS、PPS,保存为裸H264文件。数据用VLC播放这个裸H.264文件,结果可以流畅播放,说明视频数据是完整的。再写代码将H264文件分帧并用RTMP协议打包发送直播,FP能流畅直播,录制依然是卡的。开始怀疑我的分帧发送代码是否有问题,于是将我以前录制好的H.264文件拿来,用同样的方法测试,结果直播流畅,录制流畅。同样的代码不同H264文件有不同效果,那么可能是H264文件格式的不同。于是分析h264文件的NAL。NAL等于5的就是关键帧。录制流畅的h264每个关键帧之间的间隔是固定的32,而录制卡顿的H264文件,十几个关键帧连在一起。根据以往经验,这是变码率的H264数据。我的RTMP协议栈并没用支持这种格式。于是开始分析这种变码率的h264格式,在我自己的电脑里面搭建环境调试改写协议栈,轻车熟路,没过多久,我的RTMP协议栈能支持发送这种变码率的H264数据直播了。直播流畅,录制流畅。好像问题攻克了。于是带着高兴的心情,将程序更新到我的远程设备,运行。一看效果,刚开始直播和录制流畅,没过多久就开始卡顿了。和之前卡顿不同的是,卡顿频率降低了,而且FP会反复打印日志NetStream.Buffer.Empty。刚才高兴的心情一下子仿佛回到了解放前。&根据经验,这种情况一般是网络带宽不足,播放端缓存不足,或时间戳过小导致。于是我让在现场的工作的人员测试播放,结果他们在局域网看效果仍然卡顿,排除了带宽不足的问题。然后我增加播放器缓存到5秒,播放依然卡顿,又排除了缓存不足的问题。再然后我将发送端时间戳,接收端时间戳日志到文件,终于发现问题了。发送端时间戳正常,而接收端时间戳出现4000以上的大时间戳。按道理发送30帧每秒的视频,平滑处理后的时间戳应该是33-34。如果是FMS将我的时间戳修改增加了,那么会导致累计时间戳比累计时间大,但结果统计这二个值相差不大。我也没有发现有过小时间戳来中和这个大时间戳,那么累计时间戳是如何保持不变的呢,有一种可能性,丢包了。我将统计的帧数除以时间打印出来发现接收端只有20帧每秒。发送端打印的是30帧每秒。恩,可能是丢包了。我想看看是哪些数据丢了,于是将发送端的数据记录到文件,接收端接收的数据也保存到文件,对比,竟然发现数据总大小一模一样,说明没有丢包。于是我逐帧地对比发送端和接收端的数据,发现接收端有一包里面包含十多个帧的现象。而这种现象出现在接收到一个大关键帧的后面。FMS为什么会将大关键帧帧后面的小参考帧连起来做为一帧呢?这个问题我想了很久,也做了各种各样的实验。修改了多种打时间戳的方法和平滑时间戳的方法,也没有效果。最后,我猜测是否因为音频数据不足导致。因为我知道音频和视频播放不一样,它不会因为时间戳打得快就快放,它按照自己的频率计算时间匀速播放。如果音频数据不足或丢失,那么本来应该和它一起播放的视频帧会快进或跳过。于是我将发送音频部分的日志打印出来,果然发现存放问题,音频数据的环形缓存区满了,导致音频丢包。我为了防止重入,发送视频包的时候,音频不能发送。而且我们是1080P 的视频,视频关键帧有上百KB。我的音频环形缓存长度设置的10个。瞬间导致音频缓存满,然后就是音频数据丢失。于是我将音频环形缓冲长度改为30,日志显示环形缓冲最大不超过20个。小心地将最新的程序更新到设备,看效果,直播依然卡顿。我明明解决了一个BUG,竟然没有效果。神啊,救救我吧,我已经花了4天时间了,早已身心疲惫。&当然,神是不会理我的,这BUG还是要我们程序员自己解决。FP还是打印日志NetStream.Buffer.Empty。于是又来分析时间戳,统计,没有发现过大或过小的时间戳。也没有发现累计时间戳和累计到达时间偏差很大。但是发现累计时间和累计到达时间相比戳抖动比较大。说明时间戳没问题,只是有些包来晚了,然后后来又补上了。这样子好像是远程直播带宽不稳定导致。于是让在现场的工作人员测试直播,效果流畅。再让他们测测录制,也是流畅的。反复测试没出现卡顿,问题终于解决了。心情愉悦。&总结,1,找BUG需要沉下心来,找不到问题不要灰心,一定要充满斗志,否则容易中途放弃不前。 2,判断问题需要准确定位,在一个错误方向上努力完全是浪费时间。3,多做实验,写日志,用数据说话,不要凭空猜测。4,写代码的时候,日志不要多,但处理严重错误的时候还是需要日志一下,方便日后排除错误。不要像我缓冲满了也不printf一下。
发送端时间戳正常,而接收端时间戳出现4000以上的大时间戳。按道理发送30帧每秒的视频,平滑处理后的时间戳应该是33-34。如果是FMS将我的时间戳修改增加了,那么会导致累计时间戳比累计时间大,但结果统计这二个值相差不大
这个时间戳是H264的IBP帧的时间戳还是RTMP包的时间戳呢?如果是264的时间戳不是连续增加的吗?33-34是rtmp包的相对时间戳吗?rtmp包的时间戳有什么作用呢,接触不多,请赐教
1我指的是RTMP包的时间戳。 2发送的rtmp包时间戳是相对时间戳。 3rtmp时间戳是用来做音视频同步的。
你好,我将dm368编码的h264视频裸流保存成文件,但是vlc打开后发现播放速度是原来的两倍,请问知道是为什么吗?
ans:编码的时候,输入正确帧率就可以了。如果不输入帧率,编码后的裸H264 文件VLC会用默认帧率播放,默认好像是25还是30的。如果你实际压缩是15帧每秒,按照默认帧率播放就会快放了。
阅读(...) 评论()文明6攻城车使用方法 攻城车为什么不打_安游在线
大家好,在文明6中很多时候玩家都需要对敌人的城市发动进攻,一般单位面对城坚壁厚的城市时攻击效很低,这时候就需要使用攻城车,今天我们就来介绍文明6攻城车使用方法,告诉大家攻城……
编辑:胖子发布时间: 09:00
  大家好,在文明6中很多时候玩家都需要对敌人的城市发动进攻,一般单位面对城坚壁厚的城市时攻击效很低,这时候就需要使用攻城车,今天我们就来介绍文明6攻城车使用方法,告诉大家攻城车为什么不打?
  攻城锤无法攻城是因为在本代中攻城锤是辅助单位,需要和特定的兵种进行堆叠才能发挥作用。
  攻城锤:与该单位堆叠的近战单位能对城墙造成全额伤害,如果我们需要攻城就需要建造工程车,攻城车部署在战场后就会为附近一定范围内的士兵增加攻城BUFF,进攻城市的效率就会提高,而攻城车本身不会攻击。
  在文明6中,许多单位或机制需要到达特殊的条件才能解锁,您可以查看我们的攻略来了解有那些特殊条件。
秋季更新相关 常用工具下载 开局必看攻略 新手问题 游戏资料 玩法攻略 热门国家 地图种子 小编MOD推荐 中国专用MOD主题: 6749|排名: 3&
- [回帖奖励
&&半小时前
- [回帖奖励
&&昨天&17:36
前天&20:46发布
- [回帖奖励
&&昨天&15:59
7&天前发布
- [回帖奖励
&&昨天&14:07
前天&18:24发布
&&5&小时前
昨天&22:08发布
&&昨天&16:42
前天&22:55发布
&&昨天&13:10
前天&00:21发布
&&前天&20:06
3&天前发布
&&前天&20:01
前天&19:40发布
&&前天&19:27
前天&17:44发布
&&前天&19:26
前天&18:26发布
&&前天&19:24
&&前天&17:39
&&前天&17:17
&&前天&17:16
&&前天&17:09
&&前天&17:07
&&前天&16:51
前天&15:33发布
&&前天&16:49
前天&12:04发布
&&前天&14:10
&&前天&12:31
前天&01:02发布
&&前天&12:29
前天&09:12发布
1 / 338 页
&&官方技术交流群:
&&服务投诉邮箱:
云币排行榜
32023 30722 24969 18613 18077 15034 14315 12960 12853 11733 11440 11267 11101 11043 10674
扫描微信二维码随时获得最新资讯
@APICloud 欢迎关注
(C) 2014 APICloud2514人阅读
linux kernel(93)
公司做的网络视频监控产品正在做测试,这两天测试人员报告说多台设备出现奇怪的问题,现象如下:
(1)PC端接收不到设备端应用程序采集通过网络发送的图像
(2)PC端可以ping通设备端,telnet可以登录设备,设备ping PC端只能通一个数据包
于是我telnet登录异常设备,通过tftp http ftp上传下载文件到PC,发现都正常。
起初怀疑是设备网卡driver出现问题,但是细想网卡driver处于数据链路层,
上层(不管应用层是哪种协议)传下来的数据包对于driver来说是一样的。tftp ftp http能正常工作,说明网卡driver能正常收发数据, ping应该也能正常工作才对。
仔细看ping返回的结果,发现有一些不一样的地方,如下:
# ping 10.0.14.198
PING 10.0.14.198 (10.0.14.198): 56 data bytes
64 bytes from 10.0.14.198: seq=0 ttl=127 time=0.817 ms
--- 10.0.14.198 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.817/0.817/0.817 ms
ping通一个icmp包,之后就没有反应,ctrl+c程序退出显示没有丢包。
从现象上来看,并不是ping出现丢包,而是感觉ping好像在完成一次数据包通讯后阻塞。
想到这里,就不再怀疑是网卡driver的问题,想再找找看系统其他的异常现象。
ps查看系统进程时发现异常设备pid已经到了2万多,并且不再增加,而对比正常设备pid在1万多,pid还在增加。
于是想是不是异常设备的图像采集程序产生进程过多,系统进程数到达最大值,无法创建进程导致这个bug,
写一段测试代码如下:
#include &unistd.h&
#include &stdlib.h&
#include &stdio.h&
#include &string.h&
#define MAXPROCESS 65535
#define SLEEPTIME 1
void main (int argc , char ** argv)
int count = 0;
int maxprocess = MAXPROCESS;
if (argc == 2) {
maxprocess = atoi(argv[1]);
for (count = 0; count & count++)
pid = fork();
if (pid & 0) {
perror(&fork error&);
} else if (pid == 0) {
printf(&child %d start\n&, count);
sleep(SLEEPTIME);
printf(&child %d end\n&, count);
printf(&parent:create %d child\n&, count);
for (count = 0; count & MAXPROCESS; count++) {
创建指定个数子进程sleep 1s,父进程等待子进程退出后回收资源然后退出。
编译运行,结果如下:
# ./fork_test 1
parent:create 0 child
child 0 start
#发现程序不退出,根据打印发现是子进程sleep没有退出,咦,这是什么情况。
直接在console下执行sleep命令,发现也是阻塞不退出,只能ctrl+c退出。
这是该bug的另一个现象:sleep阻塞。
查看系统的pid限制,如下:
# cat /proc/sys/kernel/pid_max
32768观察正常设备pid,发现系统pid在达到32768后会从0-32768中再找已释放的pid使用。
所以也不再怀疑是系统进程数限制,还得再找其他线索。
date查看系统时间与hwclock获取的RTC时间对比,发现系统时间跟RTC时间差距较大,但是kernel启动加载RTCdriver后会同步系统时间和RTC时间,系统时间与RTC时间应该一致呀?
观察找到原因,发现又一个重要bug现象,系统时间走的是准的,晚于RTC时间的原因是,系统时间在某个时间值上往前走180s左右就会回跳回来再往前走,来回循环,导致系统时间晚于RTC时间!
到这里,关于这个bug已经发现4种现象:
(1)PC端接收不到设备端应用程序采集通过网络发送的图像
(2)PC端可以ping通设备端,telnet可以登录设备,设备ping PC端只能通一个数据包
(3)设备端sleep会阻塞
(4)设备端date系统时间走180s回跳
直觉感觉要从date这个现象入手,首先找date命令实现,嵌入式设备文件系统中使用的是busybox,其中有简化的date命令,也可以找glibc库来查看完整版本的date命令。这里不再详述date实现,date最终是调用gettimeofday来获取时间。
gettimeofday调用sys_gettimeofday,是kernel的系统调用,kernel产生软中断,进入内核态,根据系统调用号跳转到sys_gettimeofday,调用do_gettimeofday,如下:
void do_gettimeofday(struct timeval *tv)
getnstimeofday(&now);
tv-&tv_sec = now.tv_
tv-&tv_usec = now.tv_nsec/1000;
void getnstimeofday(struct timespec *ts)
WARN_ON(timekeeping_suspended);
seq = read_seqbegin(&timekeeper.lock);
*ts = timekeeper.
nsecs = timekeeping_get_ns();
/* If arch requires, add in gettimeoffset() */
nsecs += arch_gettimeoffset();
} while (read_seqretry(&timekeeper.lock, seq));
timespec_add_ns(ts, nsecs);
static inline s64 timekeeping_get_ns(void)
cycle_t cycle_now, cycle_
struct clocksource *
/* read clocksource: */
clock = timekeeper.
cycle_now = clock-&read(clock);
/* calculate the delta since the last update_wall_time: */
cycle_delta = (cycle_now - clock-&cycle_last) & clock-&
/* return delta convert to nanoseconds using ntp adjusted mult. */
return clocksource_cyc2ns(cycle_delta, timekeeper.mult,
timekeeper.shift);
}do_gettimeofday调用getnstimeofday,最关键的是timerkeeper.xtime,这是kernel的墙上时间,xtime的更新是在kernel下clockevent注册的时钟中断,只要kernel时钟中断正常,xtime时间就会不断被更新。
但是由于kernel一般是1/100s产生一次时钟中断(kernel配置默认为100HZ),当然对于tickless sysytem,时钟中断不固定,但是精度都不够高。
为了提高时钟精度,调用timekeeping_get_ns,使用已注册clocksource提供的read函数,来获取距上次update xtime的时间,来作为xtime的补充时间,提高精度。
kernel下xtime的更新和获取机制有时间还需要仔细研究下,这里先说这些。
kernel下xtime的操作流程如下:
gettimeofday &===获取=== xtime &===更新=== clockevent clocksource
现在date出现时间回跳,是哪一步出了问题呢,从上面这个流程我想到有3种可能:
(1)gettimeofday时获取xtime出错
(2)xtime存储出错
(3)更新xtime出错
如何排除,想到了二分法,如果能够直接获取xtime值,与gettimeofday获取的值对比,就可以确定到底是哪一步出了问题。
首先要说明下xtime,在kernel源码的kernel/time/timekeepering.c中定义了struct timekeepering结构体用来表征kernel下与时间相关内容,其中就有xtime成员,结构体定义如下:
struct timespec {
__kernel_time_t tv_
/* seconds */
/* nanoseconds */
};tv_sec和tv_nsec表示了从以来的时间。
但是xtime并没有留接口给系统调用等,无法从用户空间来直接获取xtime,并且该bug复现难,需要设备运行很长时间,因此也不能修改kernel后再重新启动。
那怎么办,想到了一个办法:driver module + application。
在timekeeping.c中也找到了kernel下来获取xtime的接口,如下:
unsigned long get_seconds(void)
return timekeeper.xtime.tv_
EXPORT_SYMBOL(get_seconds);
于是编写如下模块代码:
#include &linux/mm.h&
#include &linux/miscdevice.h&
#include &linux/slab.h&
#include &linux/vmalloc.h&
#include &linux/mman.h&
#include &linux/random.h&
#include &linux/init.h&
#include &linux/raw.h&
#include &linux/tty.h&
#include &linux/capability.h&
#include &linux/ptrace.h&
#include &linux/device.h&
#include &linux/highmem.h&
#include &linux/crash_dump.h&
#include &linux/backing-dev.h&
#include &linux/bootmem.h&
#include &linux/splice.h&
#include &linux/pfn.h&
#include &linux/export.h&
#include &asm/uaccess.h&
#include &asm/io.h&
#define GET_XTIME 0
static int dev_open(struct inode *inode, struct file *filp)
static ssize_t dev_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
static ssize_t dev_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
static long dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
int __user *argp = (int __user *)
unsigned long now = 0;
switch (cmd) {
case GET_XTIME :
now = get_seconds();
if (copy_to_user(argp, &now, 4))
return -EFAULT;
return -EFAULT;
static const struct file_operations dev_fops = {
= dev_read,
= dev_write,
= dev_open,
.unlocked_ioctl = dev_ioctl,
static struct cdev char_
static int __init char_dev_init(void)
rc = alloc_chrdev_region(&devid, 0, 1, &char_dev&);
if (rc != 0)
printk(&alloc chardev region failed\n&);
return -1;
major = MAJOR(devid);
cdev_init(&char_dev, &dev_fops);
cdev_add(&char_dev, devid, 1);
static void __exit char_dev_exit(void)
cdev_del(&char_dev);
unregister_chrdev_region(MKDEV(major,0), 1);
module_init(char_dev_init);
module_exit(char_dev_exit);
编译此模块,insmod插入kernel中,接着编写一个应用程序如下:
#include &stdio.h&
#include &sys/ioctl.h&
#include &sys/types.h&
#include &sys/stat.h&
#include &sys/time.h&
#include &fcntl.h&
#define GET_XTIME 0
void main(void)
unsigned long now = 0;
gettimeofday(&tv, &tz);
printf(&tv.tv_sec = %d\n&, tv.tv_sec);
printf(&tv.tv_usec = %d\n&, tv.tv_usec);
int fd = open(&/dev/char_dev&, O_RDWR);
if (fd & 0)
printf(&open failed\n&);
ioctl(fd, GET_XTIME, &now);
printf(&xtime.tv_sec = %d\n&, now);
close(fd);
}分别将gettimeofday获取的时间和kernel中xtime的时间打印出来。
编译程序,在kernel下运行3次,如下:
# ./dev_tool
tv.tv_sec =
tv.tv_usec = 617831
xtime.tv_sec =
# ./dev_tool
tv.tv_sec =
tv.tv_usec = 17649
xtime.tv_sec =
# ./dev_tool
tv.tv_sec =
tv.tv_usec = 281584
xtime.tv_sec = 很明显可以看出,xtime的时间是停止的,那为什么gettimeofday时间还会走呢?
上面分析过gettimeofday实现,为了提高精度,gettimeofday的时间 = xtime + 根据clocksource-&read获取的cycles换算出来的补充时间
那就来看下我们设备中注册的clocksource什么样,如下:
static cycle_t
timer_get_cycles( struct clocksource *cs )
return __raw_readl( IO_ADDRESS( REG_TIMER_TMR2DL ));
static struct clocksource timer_clocksource =
= MTAG_TIMER,
.rating = 300,
= timer_get_cycles,
= CLOCKSOURCE_MASK( 32 ),
= CLOCK_SOURCE_IS_CONTINUOUS,
static u32 notrace
update_sched_clock( void )
return __raw_readl(IO_ADDRESS( REG_TIMER_TMR2DL ));
static int __init
timer_clocksource_init( void )
u32 val = 0, mode = 0;
timer_stop( 2 );
__raw_writel( 0xffffffff, IO_ADDRESS( REG_TIMER_TMR2TGT ));
// free-running timer as clocksource
val = __raw_readl( IO_ADDRESS( REG_TIMER_TMRMODE ));
mode = ( val & ~( 0x0f && TIMER2_MODE_OFFSET )) | TIMER2_CONTINUOUS_MODE;
__raw_writel( mode, IO_ADDRESS( REG_TIMER_TMRMODE ));
timer_start( 2 );
setup_sched_clock( update_sched_clock, 32,
if(clocksource_register_hz( &timer_clocksource,
panic(&%s: can't register clocksource\n&, timer_clocksource.name);
可以看出根据该clcoksource-&read获取的最大cycles为0xffffffff,而timer的工作频率是24MHZ,因此换算成时间就是178.9s。
看到这个数字我一下就兴奋了,因为前面说过的一个bug现象,date时间走3分钟就跳转回来。这里就可以解释这个现象了:
kernel下的xtime时间停止了,但是由于clocksource的补充精度时间最大可以补充178.9s,
所以gettimeofday获取时间就在xtime基础上最多走178.9s,溢出后重新从0开始计数,时间又回到xtime重新开始!
那么问题来了,为什么xtime停止更新了呢?
xtime的更新是基于kernel时钟中断,具体函数还是在timekeepering.c中的update_wall_time,产生一次时钟中断就会将新增的时间加在xtime上。
难道是没有时钟中断了?
多次查看/proc/interrupts(涉及公司设备内容,这里就不贴了),发现timer的中断果然没有变化啊!
利用kernel下预留的操作寄存器命令查看timer模块寄存器,发现产生时钟中断的timer的状态寄存器是stop。
为了验证是timer中断没有了导致该bug,首先在出现该bug的设备直接再次start timer,发现设备恢复正常。而在正常设备上stop timer,就会复现该bug。这就说明无timer intr就是该bug的本质!
从最开始怀疑网卡driver有问题,经过一连串的推测验证后,终于确定了引起该bug的原因:timer interrupt没有了!
算是有一个阶段性小胜利,哈哈。
但是问题还没有最终解决,kernel代码中哪里导致了timer stop呢?
首先想到要让timer stop,软件只可能去置位stop寄存器。那只需要找出kernel中stop timer的接口,确定哪里会调用它,就可以缩小问题范围了。kernel中timer intr的代码如下:
static void
timer_set_mode( enum clock_event_mode mode, struct clock_event_device *evt )
u32 val = 0, timermode = 0;
val = __raw_readl( IO_ADDRESS( REG_TIMER_TMRMODE ));
switch( mode )
case CLOCK_EVT_MODE_PERIODIC:
timer_stop( 1 );
timermode = ( val & ~( 0x0f && TIMER1_MODE_OFFSET )) | TIMER1_PERIODICAL_MODE;
__raw_writel( TIMER1_TARGET, IO_ADDRESS( REG_TIMER_TMR1TGT ));
__raw_writel( timermode, IO_ADDRESS( REG_TIMER_TMRMODE ));
timer_start( 1 );
case CLOCK_EVT_MODE_ONESHOT:
timer_stop( 1 );
timermode = ( val & ~( 0x0f && TIMER1_MODE_OFFSET )) | TIMER1_ONE_SHOT_MODE;
__raw_writel( TIMER1_TARGET, IO_ADDRESS( REG_TIMER_TMR1TGT ));
__raw_writel( timermode, IO_ADDRESS( REG_TIMER_TMRMODE ));
timer_start( 1 );
case CLOCK_EVT_MODE_UNUSED:
case CLOCK_EVT_MODE_SHUTDOWN:
VLOGD( MTAG_TIMER, &time stop and clr src pnd. mode = %d&, mode );
timer_stop(1);
timer_clr_pnd(1);
VLOGD( MTAG_TIMER, &REG_TIMER_TMREN is %u; REG_TIMER_TMRPND is %u&, \
readl(IO_ADDRESS( REG_TIMER_TMREN )), readl(IO_ADDRESS( REG_TIMER_TMRPND )));
static int
timer_set_next_event( unsigned long cycles, struct clock_event_device *evt )
timer_stop( 1 );
__raw_writel( cycles, IO_ADDRESS( REG_TIMER_TMR1TGT ));
timer_start( 1 );
static struct clock_event_device timer_clockevent =
= MTAG_TIMER,
= CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
= timer_set_mode,
.set_next_event = timer_set_next_event,
static void __init
timer_clockevent_init( void )
clockevents_calc_mult_shift( &timer_clockevent, CLOCK_TICK_RATE, 4 );
timer_clockevent.max_delta_ns = clockevent_delta2ns( 0xffffffff, &timer_clockevent );
timer_clockevent.min_delta_ns = clockevent_delta2ns( CLOCKEVENT_MIN_DELTA, &timer_clockevent );
timer_clockevent.cpumask
= cpumask_of( 0 );
clockevents_register_device( &timer_clockevent );
static void
timer_clock_event_interrupt( void )
struct clock_event_device *evt = &timer_
timer_clr_pnd( 1 );
evt-&event_handler( evt );
static irqreturn_t
timer_interrupt( int irq, void *dev_id )
u32 srcpnd = 0;
struct clock_event_device *evt = &timer_
srcpnd = __raw_readl(IO_ADDRESS( REG_TIMER_TMRPND ));
if( srcpnd & TIMER1_EVENT )
timer_clock_event_interrupt();
__raw_writel( srcpnd, IO_ADDRESS( REG_TIMER_TMRPND ));
return IRQ_HANDLED;
static struct irqaction timer_irq =
= &timer&,
= IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
.handler = timer_interrupt,
这里只贴出clockevent和timer irq处理相关的部分代码,可以看出涉及到stop timer只有set_next_event和set_mode中,set_next_event会在timer_interrupt中的evt-&event_handler中调用,来设置下次触发intr的时间点,set_mode来设置timer的工作模式。
直觉感觉,set_mode应该只在timer初始化时使用,而set_next_event会在每次timer intr中使用。因此想在set_mode中加打印来看下哪里会调用set_mode(猜测set_mode调用少,set_next_event中不能加打印,因为timer intr太多)。
set_mode加打印后重新编译kernel,在一台设备上启动发现set_mode只会在kernel启动中调用,进入console后就不会调用了。
这样其实就排除了set_mode函数的可能性,因为根据观察timer intr停止的时间,都是在用户空间,并且kernel启动中printk打印的时间戳是正常的。
在kernel启动后用户空间发生timer intr停止,软件上来看,只可能是timer中断部分出现问题了。
但是看代码,timer_interrupt中也没有stop timer的操作啊。
不过还是想到了一种场景会导致stop timer现象:
timer_interrupt中调用timer_clock_event_interrupt,其中又调用clockevent-&event_handler,该函数会调用clockevent-&set_next_event,
在set_next_event中设置完下一次触发时间点后就start timer了,回到timer_interrupt中clear下timer intr就会从中断处理中退出。
如果设置的下次触发时间点足够短(kernel为tickless,每次设置的触发时间都不一样),在clear timer intr之前该次intr就产生了,但是接下来就clear掉了。
这样中断处理函数退出后就不会再次产生timer intr了。
但是有2个地方我觉得需要验证下:
(1)timer计数达到目标后,状态寄存器是否是stop状态
(2)如果是上述场景导致这次bug,那么延长start timer和clear intr之间时间,应该会让该bug更快复现
timer经过测试发现计数达到目标后,状态寄存器就会显示为stop状态。
在time_interrupt中的start timer 和clear intr中间加入一些没用代码做延时(不能用delay,因为现在timer有问题呢),进入console后很快就复现了bug。
所以这个bug的原因就是不应该在set_next_event后再次clear timer intr。于是将上面time_interrupt修改为:
static irqreturn_t
timer_interrupt( int irq, void *dev_id )
u32 srcpnd = 0;
struct clock_event_device *evt = &timer_
srcpnd = __raw_readl(IO_ADDRESS( REG_TIMER_TMRPND ));
if( srcpnd & TIMER1_EVENT )
timer_clr_pnd( 1 );
evt-&event_handler( evt );
return IRQ_HANDLED;
终于解决了这个bug,对于sleep ping的阻塞问题也就可以理解了:
sleep ping实现中都使用了定时器,定时器是由kernel的时钟中断和软中断结合实现的,由时钟中断来触发定时器软中断,在软中断中检查定时是否到了,所以没有了时钟中断,kernel的定时器机制不能工作。
根据kernel代码,kernel调度的时间片是使用ktime_get接口获取的,该函数获取的时间也是使用了clocksource提供的精度补充时间,时间是180s来回跳转,也是走的,所以kernel的调度还是正常的。
记录这次bug调试,并没有详细来说明一些知识的细节,而是重在说明思路,如何从最初怀疑网卡driver问题,一步步分析排查,直到最后彻底找到代码原因。
我想如果要成为一个系统级程序员,解决类似的bug,开阔的思路比知识更重要!
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:840408次
积分:8548
积分:8548
排名:第1776名
原创:131篇
转载:95篇
评论:270条
阅读:15608
阅读:38986
阅读:80430
(1)(1)(1)(1)(3)(2)(1)(1)(1)(2)(2)(3)(2)(1)(1)(4)(2)(1)(1)(4)(5)(2)(6)(2)(7)(7)(3)(10)(1)(2)(7)(3)(1)(7)(13)(13)(6)(7)(9)(10)(13)(11)(12)(7)(8)(17)(2)

我要回帖

更多关于 攻城掠地无限金币bug 的文章

 

随机推荐