内核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

发表回复