应用背景:
目前android系统的游戏朝着重型手游方法发展,部分apk游戏对硬件配置要求极高,甚至为求极致的用户体验不惜限制低配置手机用户的使用,当游戏检测到硬件配置不符合最低要求时直接闪退. apk应用检测手机硬件配置主要还是检测cpu核心数,内存大小,显存大小等信息.
android模拟器采用virtualbox作为虚拟器在windows等平台上运行android镜像, 而在主机端cpu大部分是4核的,为了实现多开模拟器(每开一个模拟器至少需要1个cpu核),将每个开启的virtualbox模拟器设置为单核(实际上virtualbox多核存在bug,即多核情况下vboxheadless这个进程存在高cpu占用率的问题). 而当模拟器设置为一核时,部分重型游戏检测配置为单核直接闪退或者采用低配置运行,影响模拟器兼容性和用户体验.
综上: 采用设置虚拟机单核来避免virtualbox的多核bug; 模拟器内部采用虚拟多个cpu核心的方法来反上层apk对cpu核心数的检测.
实现原理:
android系统采用linux内核,而上层apk检查cpu个数主要采用如下两个方法:
1> 扫描/sys/devices/system/cpu目录下cpuX文件夹的个数,每一个cpuX代表一个cpu,如cpu0 cpu1等文件夹
2> 读取/sys/devices/system/cpu/online文件值,此值代表目前激活的可用cpu的个数.经过实践发现,主要还是通过查看此属性值来最终确定cpu的核心数.
为了实现虚拟cpu核心数,我们需要虚拟出包含cpu0的四个cpuX文件夹,即cpu0-cpu3,并且修改/sys/devices/system/cpu/online文件的值为0-3. linux启动至少保证一个cpu在运行,所以cpu0在设置单核启动的时候就真实存在.实际要虚拟cpu1-cpu3这三个文件夹.
具体步骤:
linux在启动初始化阶段会对cpu的硬件信息进行初始化,主要通过sysfs文件系统提交给上层应用接口,如应用程序可以直接通过'cat /sys/devices/system/cpu/online'命令查看当前cpu个数. 这里问题就转为了修改sysfs文件系统建立cpu信息的函数了.
通过逆向或者对sysfs属性或者对cpu信息初始化流程熟悉的同学就幸福了,比较容易找到设置/sys/devices/system/cpu/online文件的值和虚拟cpuX文件夹.这里就不卖关子,马上给各位客观呈上我的处理方法.
在呈现实际修改内容的通过还需要说下cpu信息初始化的大概流程,以便大家深入看. 大致过程如下,如果有啥理解错误的地方,还请大家拍砖:
1> init/main.c: start_kernel()-->boot_cpu_init();此函数初始化cpu0,不作改动.
2> init/main.c: start_kernel()-->rest_init()--> kernel_init-->do_basic_setup()-->driver_init()-->cpu_dev_init()
这一系列的调用最终调用到cpu_dev_init(),此函数在drivers/base/cpu.c文件中. 在此函数中可以hotplug启动未启动的其他cpu,我们就在这个函数中来虚拟cpu1-cpu3文件夹
3> 显示/sys/devices/system/cpu/online的值的函数为show_cpus_attr, 如下,online,present,possible三个文件属性的值都是通过此函数来显示的,实际上也需要present,possible和值不比online小,所以这里假设都设置为0-3,表示有4个cpu核存在:
##################BEGIN#######################
#define _CPU_ATTR(name, map) \
{ __ATTR(name, 0444, show_cpus_attr, NULL), map }
/* Keep in sync with cpu_subsys_attrs */
static struct cpu_attr cpu_attrs[] = {
_CPU_ATTR(online, &cpu_online_mask),
_CPU_ATTR(possible, &cpu_possible_mask),
_CPU_ATTR(present, &cpu_present_mask),
};
###################END######################
实际修改如下: (改动出标记为红色)
show_cpus_attr函数:
##################BEGIN#######################
static ssize_t show_cpus_attr(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct cpu_attr *ca = container_of(attr, struct cpu_attr, attr);
int n = cpulist_scnprintf(buf, PAGE_SIZE-2, *(ca->map));
/* 新增编译宏控制编译,写死直接设置online值为0-3 */
#ifdef FAKE_CPUS_CORES
sprintf(buf, "0-3\n");
n = 4;
#else
buf[n++] = '\n';
buf[n] = '\0';
#endif
return n;
}
###################END######################
##################BEGIN#######################
void __init cpu_dev_init(void)
{
if (subsys_system_register(&cpu_subsys, cpu_root_attr_groups))
panic("Failed to register CPU subsystem");
cpu_dev_register_generic();
#if defined(CONFIG_SCHED_MC) || defined(CONFIG_SCHED_SMT)
sched_create_sysfs_power_savings_entries(cpu_subsys.dev_root);
#endif
/* 如下为增加部分 */
#ifdef FAKE_CPUS_CORES /*编译宏控制 */
int i;
int cpu_real_count = 0;
for_each_possible_cpu(i) { /*此循环查看已经存在的实际物理核心数 */
cpu_real_count++;
}
/*此函数把需要模拟的cpu文件夹全部链接到cpu0. register_cpu_fake函数根据register_cpu函数改造而来*/
register_cpu_fake(&per_cpu(cpu_devices_fake, 0), 0,cpu_real_count);
#endif
}
##################END#######################
新增的register_cpu_fake函数:
#################BEGIN########################
#ifdef FAKE_CPUS_CORES /* 编译宏控制 */
static DEFINE_PER_CPU(struct cpu, cpu_devices_fake);
/* register_cpu_fake有函数register_cpu函数改造而来,此函数标红部分为相对register_cpu函数的新增 */
int __cpuinit register_cpu_fake(struct cpu *cpu, int num, int cpu_real_count)
{
int error =0;
cpu->node_id = cpu_to_node(num);
memset(&cpu->dev, 0x00, sizeof(struct device));
cpu->dev.id = num;
cpu->dev.bus = &cpu_subsys;
cpu->dev.release = cpu_device_release;
#ifdef CONFIG_ARCH_HAS_CPU_AUTOPROBE
cpu->dev.bus->uevent = arch_cpu_uevent;
#endif
error = device_register(&cpu->dev);
if (!error && cpu->hotpluggable)
register_cpu_control(cpu);
if (!error)
per_cpu(cpu_sys_devices, num) = &cpu->dev;
if (!error)
register_cpu_under_node(num, cpu_to_node(num));
#ifdef CONFIG_KEXEC
if (!error)
error = device_create_file(&cpu->dev, &dev_attr_crash_notes);
#endif
/* 此处代码建立cpuX到cpu0的链接,单核时建立cpu1-cpu3到cpu0的链接,
*双核存在cpu0和cpu1,所以只建立cpu2,cpu3到cpu0的链接
*sysfs_create_link(&cpu_subsys.dev_root->kobj, &cpu->dev.kobj, buf);函数建立sysfs文件系统链接
*/
int cpu_num = cpu_real_count;
if(cpu_num < 4){
for(;cpu_num < 4;cpu_num++){
char buf[16];
sprintf(buf,"cpu%d",cpu_num);
sysfs_create_link(&cpu_subsys.dev_root->kobj, &cpu->dev.kobj, buf);
}
}
return error;
}
#endif
##################END#######################
经过上路改动,重新编译内核后启动,直接进入/sys/devices/system/cpu目录,cat online值发现时0-3,且有cpu0-cpu3四个文件夹,说明成功修改. andorid系统可以运行安兔兔来检测,检测得到的结果是4核. 至此,虚拟多个cpu内核个数的实践就完满了.