内核bug导致Java进程CPU升高
内核SLES10SP3的老版本内核2.6.16.60-0.54.5也是使用了很长时间了,虽然之前也有一些bug,不过都是组内单个patch自己编译内核搞定问题的,虽然内核用的是SLES,但补丁却不是同步的,没办法,业务模式不同,卖出去的服务器又不能像windows一样,开个后门远程打补丁,只能慢慢积攒影响一般的补丁,最后搞一次大的,把内核版本升级上来,到2.6.16.60-0.83.2,从版本号上看,步子迈得小了不会扯着蛋,但仍然会出现很多神奇的问题。
Linux内核升级上来之后与产品进行联合调试,当时产品便转来一个当时看比较奇葩的问题:内核升级后,产品的进程CPU占用率从200升到了400.接手问题一看,产品是基于java开发的,自己对于JVM一窍不通呀,java程序的CPU升高请产品先分析一下JVM吧,排查自己代码问题。最后确是产品的哥们用小学学到的对比法证明了是内核的问题:JVM及上层代码保持不变,只更新内核vmlinuz等相关文件,问题复现。
对于类似的问题陷入盲目,看不懂产品的代码(java都忘光了),更不了解JVM的具体实现,为什么一个单独的java进程CPU会升高这么多?最终还是东少给了建议:忽略上层,先看内核函数CPU分布热点。
于是便引进了oprofile工具,用来分析CPU占用率。最终发现系统使用的30%的CPU属于mark_offset_pmtmr函数占用,对比前后代码看,函数内容并没有变化,mark_offset_pmtmr属于定时器的内容,被pmtmr定时器唯一引用
static struct timer_opts timer_pmtmr = { .name = "pmtmr", .mark_offset = mark_offset_pmtmr, .get_offset = get_offset_pmtmr, .monotonic_clock = monotonic_clock_pmtmr, .delay = delay_pmtmr, .read_timer = read_timer_tsc, .resume = pmtmr_resume, };
代码未动便是系统对定时器的选择做了变动,查看系统启动日志boot.msg发现系统更换TSC定时器
Found Intel CPU with TSC timer issues, marking it unstable
发现新内核在unsynchronized_tsc函数下做了CPU的判别定义
static __init int unsynchronized_tsc(void) { #ifdef CONFIG_SMP if (oem_force_hpet_timer()) return 1; /* Intel systems are normally all synchronized. Exceptions are handled in the OEM check above. */ if (boot_cpu_data.x86_vendor == X86_VENDOR_INTEL) { #ifdef CONFIG_ACPI /* TSC always running, use TSC as time source */ if ((cpuid_eax(0x80000000) >= 0x80000007) && (cpuid_edx(0x80000007) & (1<<8))) return 0; /* TSC doesn't tick in C3 so don't use it there */ if (acpi_gbl_FADT && acpi_gbl_FADT->length > 0 && acpi_gbl_FADT->plvl3_lat < 1000) return 1; #endif #if defined (CONFIG_X86_32) && defined (CONFIG_SMP) if (boot_cpu_data.x86 == 6 && (boot_cpu_data.x86_model == 23 || boot_cpu_data.x86_model == 15)) { printk(KERN_INFO "Found Intel CPU with TSC timer" " issues, marking it unstablen"); return 1; } #endif return 0; } #endif /* Assume multi socket systems are not synchronized */return num_possible_cpus() > 1;
内核根据CPU model值判定TSC不稳定,返回给init_tsc使其初始化失败,然后使用pmtmr定时器。
查看CPU model
oen@oen ~ $ cat /proc/cpuinfo | grep model model : 23
确实在CPU型号在问题范围内。于是便咨询李义SUSE这个改动的原因为何,同时看了一下kernel最新代码是否有这个问题,令人意外的是新代码中unsynchronized_tsc函数删除了对CPU modle的判定,后来得到答复:补丁人员代码从高版本改动后,push到低版本时忘了,忘了.....
平台版本赶着过线,SUSE出正式补丁估计要一段时间了,只好自己写了,unsynchronized_tsc函数耦合还是比较强的,改了4个文件才基本OK,同时编译替换产品的内核,CPU升高的问题解决。
SP4的正式补丁参考:
http://kernel.opensuse.org/cgit/kernel-source/commit/?id=1a06c258106e7b293f3e9784d3907c5048f7574f
内核bug导致Java进程CPU升高来自于OenHan
链接为:https://oenhan.com/kernel-bug-double-java-cpu