linux能运行在x86cpu上面吗,X86 Linux的SMP

SMP是对称多处理器的意思。Intel为SMP特地出台了一个MultiProcessor Specification,现在使用最多的就是1997年5月的1.4版。里面规范了如何设置中断控制器,BSP如何启动其他的AP,达到SMP。

X86多核系统启动的时候,硬件会选出一个BSP专门完成系统的引导工作,因此说BIOS是一般是运行在单核状态,grub也是。这个BSP在引导阶段要做的很重要的工作就是检查和配置系统的设备(特别是磁盘相关的HBA硬件),扫描并且初始化PCI树,初始化内存。因此当系统变得非常庞大、资源很多的时候,例如高端的服务器平台,这个初始化的时间就非常长了。

具体有多长呢?举一个EMC DD9XXX产品的例子吧,在512GB RAM,12个PCIe Gen2的卡全配的情况下,系统从按下power button到开到grub菜单,大约需要15-20分钟!也就是差不多1/4个小时就用来开机了。这也是为什么服务器平台通常不建议重启的原因,因为开关一次的代价实在是太大太大了。

上面提到的是硬件的SMP,就是系统当中存在同质的多个处理器。

在Intel的平台下,BSP在工作的时候,其他处理器(AP)处于wait startup IPI状态。BSP在初始化了基本硬件资源之后,通过APIC首先发送INIT IPI给其他的AP,然后等待10ms;接下来发送一个STARTUP IPI,再等待200us;最后再发送一个STARTUP IPI,再等待200us。之所以会有第二个STARTUP IPI,据说是为了fix一些CPU的bug。大部分情况下,第一个STARTUP IPI就足够了。BSP需要告知AP从哪里开始运行。

接下来说软件。Linux在2.6就开始支持多处理器SMP。那么这里说的软件的SMP,就是说同样的操作系统内核(Image)并发跑在多个物理处理器上。

Linux支持SMP是follow Intel的规范,包括上面提到的IPI中断。Linux的启动SMP的过程总结下来就是下面的过程。

do_boot_cpu() [ setup start_secondary() as entry point ]

|-> wakeup_xxxx_via_init_nmi() [ runs start_secondary in APs ]

|-> cpu_init() [ initializes AP ]

|-> wait_for_master_cpu() [ set xxx_initialized_mask, then pends on xxx_callout_mask ]

|-> BSP set xxx_callout_mask, to continue AP's cpu_init(), pends on xxx_callin_mask ]

也就是说这里定义了三个bitmap,其中每一位代表一个逻辑CPU(HT,或者core)。

BSP发送STARTUP IPI之后会等待AP置位cpu_initialized_mask。

AP启动之后会把自己对应的bit置为1,然后等待BSP设置cpu_callout_mask。

此时BSP知道该AP已经启动,然后设置cpu_callout_mask的相应位,之后等待AP设置cpu_callin_mask。

AP发现cpu_callout_mask被设置,于是进行剩余的启动工作。然后设置cpu_callin_mask的相应位。

BSP检测到cpu_callin_mask被设置之后,完成该AP的初始化,开始下一个AP的初始化操作。

这里面其实有个地方没有详细展开,那就是AP的启动的entry point。这个是由BSP设置的一段代码,由STARTUP IPI发送给AP。AP运行这段代码,完成从实模式开始的启动过程直到32bit保护模式/64bit长模式。这段代码在Linux里面叫做trampoline,中文是蹦床的意思。

(其实在前一篇IoT产品架构设计当中提到了一个独创性的在线升级,把代码从ROM搬移并跳转到RAM中运行,也可以说使用了一种特殊的蹦床的技术)。

关于Linux SMP的具体流程可以参考代码。trampoline是纯汇编,有兴趣的也可以去看看它的原理。

刚才讨论的是SMP的引导部分。在Linux运行过程中,有些时候也需要让某一个或者几个CPU完成一些特定的操作,这个时候也需要通过IPI。Linux内核为此做了封装,例如

/*

* Call a function on all other processors

*/

void smp_call_function(smp_call_func_t func, void *info, int wait);

void smp_call_function_many(const struct cpumask *mask,

smp_call_func_t func, void *info, bool wait);

int smp_call_function_any(const struct cpumask *mask,

smp_call_func_t func, void *info, int wait);

具体的用法网上大把大把的,这里不举例了。

但是需要注意的是,刚才提到了这些实际上是通过IPI来实现的,所以接收的CPU是在中断上下文运行的特定函数(Linux-4.4.30, Ubuntu-16.04 X86_64 server),例如。

void print_cpu_id(void * cpuid)

{

int cpu=smp_processor_id();

printk("Called: myid %d\n",cpu);

printk("Called: myid %d\n",cpu);

printk("Called: in_int=%d, in_irq=%d, in_softirq=%d\n",

in_interrupt(), in_irq(), in_softirq());

return;

}

static int __init hello_world_init(void)

{

int cpu=0;

flag=0;

printk("hello_world_init\n");

cpu=smp_processor_id();

printk("Caller: myid is %d\n",cpu);

smp_call_function(print_cpu_id, &cpu, 0);

。。。

}

[447099.229034] Called: myid 0

[447099.229037] Called: in_int=65536, in_irq=65536, in_softirq=0

因此需要记住中断上下文里的一些使用禁忌。

如果不能避免这样的禁忌,那就需要用别的方法来做,例如可以参考Linux中断处理的路子(top half和bottom half,softirq, tasklet, workqueue)。

我自己实现了类似的操作。

总结一下,硬件和操作系统(Linux)都为SMP的支持做了必要的准备,不管是boot up还是run-time。

仔细想想,好像还有一个什么地方有些奇怪。为什么SMP的初始化是由Linux完成的?服务器平台开机15-20分钟难道就没有什么好办法加速?

其实,这就是问为什么BIOS不用SMP来开机,而是UP。这个我也不知道确切的答案,或许是因为复杂性,或许是因为灵活性。这些有待专家给出来吧。

下一篇讲讲Linux X86的中断吧,应该会很短小。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值