x86上 /proc/cpuinfo中的cpufreq和scaling_cur_freq怎么算

    前天我正在看代码的时候,一个同事跑过来问我,“/proc/cpuinfo中的CPU频率是怎么来的,我在跑performance测试的时候,通过/proc/cpuinfo 查看CPU频率只有1.2G,但是我的CPU最高频率可以到2.7G。”我让他去把cpufreq governor设置成performance之后,再去执行performance测试。虽然他在做出修改之后,performance测试没有问题了,不过他的第一个问题却让我心生疑问,就去查了一下相关代码。

在linux kernel中,可以通过以下三个接口获取CPU frequency信息

1) /proc/cpuinfo

2) /sys/devices/system/cpu/cpuX/cpufreq/scaling_cur_freq

3) /sys/devices/system/cpu/cpuX/cpufreq/cpuinfo_cur_freq

那么,这3个接口究竟反应的是什么样的频率信息呢?它们的值是否都相同呢?我们直接使用cat看看

在比较早期的kernel(3.16)版本中,设置当前频率为1.6G,通过以上3个接口查询的结果如下:

#cat /proc/cpuinfo |grep MHz

cpu MHz         : 1600.000
cpu MHz         : 1600.000
cpu MHz         : 1600.000
cpu MHz         : 1600.000
#cat  /sys/devices/system/cpu/cpuX/cpufreq/scaling_cur_freq

1600000
1600000
1600000
1600000

#cat  /sys/devices/system/cpu/cpuX/cpufreq/cpuinfo_cur_freq

1600000
1600000
1600000
1600000
从上面的结果可以看出,在3.16 kernel上,以上3个接口读出来的CPU frequency都是相同的,就是我们设定的1.6GHz。

在Ubuntu1804-自带的kernel4.15上,同样设置频率为1.6G,也通过以上3个接口查询,结果如下:

#cat /proc/cpuinfo |grep MHz

cpu MHz         : 1596.330
cpu MHz         : 1596.307
cpu MHz         : 1596.313
cpu MHz         : 1596.330

#cat  /sys/devices/system/cpu/cpuX/cpufreq/scaling_cur_freq

1596340
1596348
1596368
1596333

#cat  /sys/devices/system/cpu/cpuX/cpufreq/cpuinfo_cur_freq

1600000
1600000
1600000
1600000

以上测试结果表明:

1)只有cpuinfo_cur_freq的值跟我们的设定值一致,为1.6GHz;

2)scaling_cur_freq和/proc/cpuinfo 多次查询出来的结果都不太一样,而且跟设定值有一个小的误差;

以上测试说明了不同版本的kernel也会有不同,我们去kernel change log中找跟这些改动相关的patch,主要有:

1)跟scaling_cur_freq改动相关的patch:commit 4815d3c56d1e10449a44089a47544d9ba84fad0d

 cpufreq: x86: Make scaling_cur_freq behave more as expected

该patch通过APEF和MPERF计算CPU在一段时间内的平均频率,能够摒除CPU进入到idle state的影响。

使用APERF-MPERF的方式,不管CPU 频率是由HW-controlled,还是firmware-controlled,抑或是由linux cpufreq subsystem控制,甚至是没有frequency控制的情况下,user通过scaling_cur_freq sysfs接口都能获得有一个有意义的频率信息。

2)跟/proc/cpuinfo改动相关的patch:commit 7d5905dc14a87805a59f3c5bf70173aac2bb18f8

x86 / CPU: Always show current CPU frequency in /proc/cpuinfo

在这个patch之前,/proc/cpuinfo中的频率信息,只是一个象征意义,根据平台实现不同,可能是cpufreq governor之前写下去的requested cpufreq,并不表示CPU实际的运行频率。这个patch也是使用APERF和MPERF来计算CPU频率,像scaling_cur_freq一样能够反映CPU的实际频率信息。

最后,我们来看一下,有了这些patch(kernel 4.15)之后,以上这3个接口具体是如何实现的。

我以acpi_cpufreq driver为例进行分析,我们先来看一下跟这三个接口相关的,acpi_cpufreq driver的一些关键的callback是如何实现的:

1)频率切换是通过target/target_index还是setpolicy来完成呢?

acpi_cpufreq只能在fixed predefined frequency point中间调整,使用target_index callback完成实际的频率切换,setpolicy callback为NULL;

2)acpi_cpufreq_driver.get = get_cur_freq_on_cpu,通过读取MSR 0x199获取当前的requested frequency

/proc/cpuinfo 对应的code在arch/x86/kernel/cpu/proc.c中

show_cpuinfo
{
	if(cpu_has(c, X86_FEATURE_TSC))
	{
		unsigned int freq = aperfmperf_get_khz(cpu);			//1)先用aperf-mperf snapshot的方式,统计一段时间内CPU的average frequency
		if(!freq)
		{
			freq = cpufreq_quick_get(cpu);						//2)若1)失败,使用cpufreq_driver->get获取当前频率
			{
				if(cpufreq_driver && cpufreq_driver->setpolicy && cpufreq_driver->get)	//对于acpi_cpufreq而言,setpolicy callback为NULL
				{
					freq = cpufreq_driver->get(cpu);
					return freq;
				}
				policy = cpufreq_cpu_get(cpu);		//3)返回scaling governor上一次选中的target frequency;
				if(policy)
					return policy->cur;
			}
		}
		if(!freq)
			freq = cpu_khz;							//4)如果1、2、3都失败,返回TSC的cpu_khz信息;
		seq_printf(m, "cpu MHz\t\t: %u.%03u\n", freq / 1000, (freq % 1000));
	}	
}

/sys/devices/system/cpu/cpuX/cpufreq/cpuinfo_cur_freq和scaling_cur_freq都在drivers/cpufreq/cpufreq.c中实现

show_cpuinfo_cur_freq
{
	cur_freq = __cpufreq_get(policy->cpu);
	{
		cpufreq_driver->get(cpu);		
		//即get_cur_freq_on_cpu
	}
}
从MSR 0x199中获取该core的requestedFID-VID,再根据FID-VID去freq_table中查其相应的frequency
get_cur_freq_on_cpu(int cpu)
{
	struct cpufreq_policy *policy;
	struct acpi_cpufreq_data *data;
	u32 val;
	policy = cpufreq_cpu_get_raw(cpu);	
	data = policy->driver_data;
	
	//暂存的OS request frequency;
	cached_freq = policy->freq_table[to_perf_data(data)->state].frequency;

	//读MSR 0x199
	val = get_cur_val(cpumask_of(cpu), data);		
	{
		u32 val = drv_read(data, mask);			
		{
			struct acpi_processor_performance *perf = to_perf_data(data);
			struct drv_cmd cmd = 
			{
				.reg = &perf->control_register,		//注意这里读的是control register,即MSR 0x199
				.func.read = data->cpu_freq_read,
			};
			smp_call_function_any(mask, do_drv_read, &cmd,1);
		}
		return val;
	}
	//把FID-VID转换成frequency,去policy->freq_table中找MSR 199中FID-VID对应的pos,返回其frequency
	freq = extract_freq(policy, val);		
	{
		struct acpi_cpufreq_data *data = policy->driver_data;
		switch(data->cpu_feature)
		{
			case SYSTEM_INTEL_MSR_CAPABLE:
			case SYSTEM_AMD_MSR_CAPABLE:
				return extract_msr(policy,val);
				{
					struct acpi_processor_performance *perf;
					perf = to_perf_data(data);
					msr = val;
					cpufreq_for_each_entry(pos, policy->freq_table)
					{
						if(msr == perf->states[pos->driver_data].status)
							return pos->frequency;
					}
					return policy->freq_table[0].frequency;
				}
			case SYSTEM_IO_CAPABLE:
				return extract_io(policy,val);
			default:
				return 0;
		}
	}
}
show_scaling_cur_freq
{
	freq = arch_freq_get_on_cpu(policy->cpu);        //1)通过APERF-MPERF获取CPU频率
	{
		if(!boot_cpu_has(X86_FEATURE_APERFMPERF))
			return 0;
		
		if(aperfmperf_snapshot_cpu(cpu, ktime_get(),true))
			return per_cpu(samples.khz, cpu);
		
		msleep(APERFMPERF_REFRESH_DELAY_MS);
		smp_call_function_single(cpu, aperfmperf_snapshot_khz, NULL,1);
		
		return per_cpu(samples.khz, cpu);
	}
	
	if(!freq)        //2)如果1)失败,通过cpufreq_driver->get callback获取CPU频率信息
	{
		if(cpufreq_driver && cpufreq_driver->setpolicy && cpufreq_driver->get)    //2.1)对于acpi_cpufreq driver而言,setpolicy callback为空,从这里跳出去;
			freq = cpufreq_driver->get(cpu);
	}
	
	if(!freq)        //3)返回scaling governor上一次选择的target frequency作为CPU频率
		freq = policy->cur;
}

来highlight一下结论:

MSR 0x199是per-core的,每个core都可以根据自己的load,计算一个target frequency,通过MSR 0x199写下去;但是我们知道,多个core的情况下,如果只有一个VRM,需要对多个core 的target frequency进行处理,取一个合适的频率作为实际的频率。如果有APERF-MPERF的话,那么可以通过/proc/cpuinfo 和scaling_cur_freq接口获得CPU实际频率,否则就只能看到各个core的requested frequency,而实际的频率可能跟我们读到的不一样。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值