回到start_kernel,559行,boot_cpu_init函数,跟start_kernel位于同一文件:

494static void __init boot_cpu_init(void)

495{

496        int cpu = smp_processor_id();

497        /* Mark the boot cpu "present", "online" etc for SMP and UP case */

498        set_cpu_online(cpu, true);

499        set_cpu_active(cpu, true);

500        set_cpu_present(cpu, true);

501        set_cpu_possible(cpu, true);

502}

496行,第一次见到smp_processor_id宏。由于没有配置CONFIG_DEBUG_PREEMPT,所以其等价于调用宏raw_smp_processor_id,其意义在于SMP的情况下,获得当前CPU的ID。如果不是SMP,那么就返回0。那么在CONFIG_X86_32_SMP的情况下:

#define raw_smp_processor_id() (percpu_read(cpu_number))

千万要注意,这里cpu_number来自arch/x86/kernel/setup_percpu.c的30行:

30DEFINE_PER_CPU(int, cpu_number);

31EXPORT_PER_CPU_SYMBOL(cpu_number);

这个东西不像是c语言全局变量,而是通过两个宏来定义的。要读懂这两个宏,必须对每CPU变量这个概念非常了解,如果还不是很清楚的同学请查阅一下博客“每CPU变量”http://blog.csdn.net/yunsongice/archive/2010/05/18/5605239.aspx。

其中DEFINE_PER_CPU来自文件include/linux/percpu-defs.h:

#define DEFINE_PER_CPU(type, name)                                /

DEFINE_PER_CPU_SECTION(type, name, "")

该宏静态分配一个每CPU数组,数组名为name,结构类型为type。由于我们没有设置CONFIG_DEBUG_FORCE_WEAK_PER_CPU编译选项,所以DEFINE_PER_CPU_SECTION又被定义为:

#define DEFINE_PER_CPU_SECTION(type, name, sec)                       /

__PCPU_ATTRS(sec) PER_CPU_DEF_ATTRIBUTES                    /

__typeof__(type) name

其中,__PCPU_ATTRS(sec)在include/linux/percpu-defs.h中定义:

#define __PCPU_ATTRS(sec)                                          /

__percpu __attribute__((section(PER_CPU_BASE_SECTION sec))) /

PER_CPU_ATTRIBUTES

__percpu是个编译扩展类型,大家可以去看看include/linux/compile.h这个文件,里面的__percpu是空的。而传进来的sec也是空的,PER_CPU_ATTRIBUTES也是空的,而上面PER_CPU_DEF_ATTRIBUTES还是空代码,可能都是留给将来内核代码扩展之用的吧,所以DEFINE_PER_CPU(int, cpu_number)展开就是:

__attribute__((section(PER_CPU_BASE_SECTION)))       /

__typeof__(int) cpu_number

所以现在只关注PER_CPU_BASE_SECTION,来自include/asm-generic/percpu.h

#ifdef CONFIG_SMP

#define PER_CPU_BASE_SECTION ".data.percpu"

#else

#define PER_CPU_BASE_SECTION ".data"

#endif

好了,介绍一下GCC的一些扩展,首先如何使用typeof来支持一些“genericprogramming”。gcc支持一种叫做类型识别的技术,通过typeof(x)关键字,获得x的数据类型。而如果是在一个要被一些c文件包含的头文件中获得变量的数据类型,就需要用__typeof__而不是typeof关键字了,比如说我们这里。最后,这里就是声明一个int类型的cpu_number指针,编译的时候把他指向.data.percpu段的开始位置。

当然,我们自己写程序的时候没有这么地generic programming,所以没必要这么做,不过想要成为一个内核达人,掌握这些技术还是很有必要的。DEFINE_PER_CPU_SECTION宏又是什么意思呢?定义在arch/x86/kernel/percpu-defs.h的147行:

#define EXPORT_PER_CPU_SYMBOL(var) EXPORT_SYMBOL(var)

EXPORT_SYMBOL是什么东西啊?还记得我们在编译内核时有个kallsyms目标么?这里就用到了。所以我说过,内核的编译过程很重要,请大家务必掌握!这个对象对应于“/proc/kallsyms”文件,该文件对应着内核符号表,记录了符号以及符号所在的内存地址。模块可以使用如下宏导出符号到内核符号表:

EXPORT_SYMBOL(符号名);

EXPORT_SYMBOL_GPL(符号名)

导出的符号可以被其他模块使用,不过使用之前一定要声明一下。EXPORT_SYMBOL_GPL()只适用于包含GPL许可权的模块。所以,EXPORT_SYMBOL(cpu_number)就是把cpu_number变量声明出去。接下来重点介绍一下percpu_read这个宏,来自arch/x86/include/asm/percpu.h:

#define percpu_read(var)             percpu_from_op("mov", var, "m" (var))

看到mov,我们就知道可能要调用汇编语言了,所以这个宏调用percpu_from_op("mov", cpu_number, "m" (cpu_number)),这个宏位于同一个文件:

164#define percpu_from_op(op, var, constraint)             /

165({                                                      /

166        typeof(var) pfo_ret__;                          /

167        switch (sizeof(var)) {                          /

168        case 1:                                         /

169                asm(op "b "__percpu_arg(1)",%0"         /

170                    : "=q" (pfo_ret__)                  /

171                    : constraint);                      /

172                break;                                  /

173        case 2:                                         /

174                asm(op "w "__percpu_arg(1)",%0"         /

175                    : "=r" (pfo_ret__)                  /

176                    : constraint);                      /

177                break;                                  /

178        case 4:                                         /

179                asm(op "l "__percpu_arg(1)",%0"         /

180                    : "=r" (pfo_ret__)                  /

181                    : constraint);                      /

182                break;                                  /

183        case 8:                                         /

184                asm(op "q "__percpu_arg(1)",%0"         /

185                    : "=r" (pfo_ret__)                  /

186                    : constraint);                      /

187                break;                                  /

188        default: __bad_percpu_size();                   /

189        }                                               /

190        pfo_ret__;                                      /

191})

当然,我们为了获得CPU号,cpu_number用一个字节就够了,所以上面代码翻译过来就是:

asm("movb "__percpu_arg(1)",%0"         /

: "=q" (pfo_ret__)                  /

: constraint);

其中,__percpu_arg(1)是这样定义的:

#ifdef CONFIG_SMP

#define __percpu_arg(x)              "%%"__stringify(__percpu_seg)":%P" #x

#ifdef CONFIG_X86_64

#define __percpu_seg           gs

#define __percpu_mov_op           movq

#else

#define __percpu_seg           fs

#define __percpu_mov_op           movl

#endif

所以__percpu_arg(x)翻译过来就是:

"%%"__stringify(fs)":%P" #x

请注意,这是大家第一次遇到这样的定义方式,跟我们传统的C语言宏不一样了。不错,这里要学习新知识了,首先讲讲关于#和##的知识。

在C语言的宏中,#的功能是将其后面的宏参数进行字符串化操作(Stringfication),简单说就是在对它所引用的宏变量通过替换后在其左右各加上一个双引号。比如__percpu_arg宏:

#define __percpu_arg(x)              "%%"__stringify(__percpu_seg)":%P" #x

而x,我们传入的是1;__percpu_seg,我们传入的是fs,所以__percpu_arg展开:

%%"__stringify(fs)":%P" "1"

而##,虽然这里没有用到,但是我还是一并讲讲,供大家扩充c语言知识。##被称为连接符(concatenator),用来将两个Token连接为一个Token。注意这里连接的对象是Token就行,而不一定是宏的变量。比如:

#define LINK_MULTIPLE(a,b,c,d) a##_##b##_##c##_##d

typedef struct record_type LINK_MULTIPLE(name,company,position,salary);

这里这个语句将展开为:

typedef struct record_type name_company_position_salary;

再来一个新知识,可变参数宏,于1999年的ISO C标准中定义,也就十年前而已。我们看到这里用到了__stringify宏,其定义是这样的:

#define __stringify(x...) __stringify_1(x)

其中…为可变参数,什么意思?就是这个宏除x以外,还可以有额外的多个参数。另外,如果是可变参数宏,那么可变参数x可能会展开为多个参数,那么比如上面也可定义为:

#define __stringify(...)   my__stringify(y,z)  0

不过如果指定了参数名x,则后者必须包含一个x:

#define __stringify(x...) my__stringify(x,y,z)

那么我要问了,如果我定义成上面三个参数的形式,但是给的却又只有两个参数怎么办?比如执行一个__stringify(a,b)语句,会不会出错?当然会!因为既然是可变参数的宏,那么传递进去一个空参数也是对的啊,只不过你得有多余的逗号啊,也就是__stringify(,a,b)这样。

不过GCC还有一个东西可以避免这样的“bug”,那就是##符号。如果我们定义成:

#define __stringify(x...) my__stringify(##x,y,z)

这时,##这个连接符号充当的作用就是当x为空的时候,消除前面的那个逗号,这样就不会有上面的bug了。

所以宏:

#define __stringify_1(x...)    #x

我们就可以看懂了吧,

所以__percpu_arg(1)最终翻译过来就是:

"%%" "fs:%P" "1"

因此上边的汇编代码翻译过来就是:

asm("movb %%fs:%P1, %0"         /

: "=q" (pfo_ret__)                  /

: "m" (var));

其中pfo_ret__是输出部%0,q,表示寄存器eax、ebx、ecx或edx中的一个,并且变量pfo_ret__存放在这个寄存器中。var就是刚才我们建立的那个临时的汇编变量cpu_number,作为输入部%1。

还记得“加载全局/中断描述符表”中把__KERNEL_PERCPU段选择子赋给了fs了吗,不错,fs: cpu_number就获得了当前存放在__KERNEL_PERCPU段中cpu_number偏移的内存中,最后把结果返回给pfo_ret__。所以这个宏最后的结果就是pfo_ret__的值,其返回的是CPU的编号,把它赋给boot_cpu_init函数的内部变量cpu。

那么这个偏移cpu_number到底是多少呢?众里寻他千百度,猛回首,这个偏移在生成的vmlinux.lds文件的504行被我发现了:

__per_cpu_load = .; .data.percpu 0

不错,刚才.data.percpu处被编译成0,所以内部cpu的值就是0。我为什么把这一部分讲得这么详细呢,因为这部分内容包含了很多内核代码的细节,以后遇到同样的问题我们就照这样的方法来分析,举一反三。随后的四个函数set_cpu_online、set_cpu_active、set_cpu_present和set_cpu_possible我不想多说了,就是激活当前CPU的cpu_present_bits中四个标志位online、active、present和possible,感兴趣的同学可以通过上面学到的方法去详细分析。

from: http://blog.csdn.net/yunsongice/article/details/6130032

激活第一个CPU的更多相关文章

  1. Android 隐式意图激活另外一个Actitity

    上篇文章<Android 显示意图激活另外一个Actitity>最后谈到显示意图激活另外一个Actitity会有一些局限性和弊端 本文介绍另一种方法:隐式意图激活另外一个Actitity ...

  2. 无废话Android之smartimageview使用、android多线程下载、显式意图激活另外一个activity,检查网络是否可用定位到网络的位置、隐式意图激活另外一个activity、隐式意图的配置,自定义隐式意图、在不同activity之间数据传递(5)

    1.smartimageview使用 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android&q ...

  3. 虚拟机评估——如何确定一个CPU核上部署的虚拟机数量?

    最近研究虚拟化技术,不可避免遇到一个问题:如何评估物理主机上虚拟主机的容量?下面这篇文章的思路有一定的启发性,转发一下. 如何确定一个CPU核上部署的虚拟机数量? 摘要:本文说明一个CPU核上部署虚拟 ...

  4. 使用performance monitor 查看 每一个cpu core的cpu time

    使用performance monitor 查看 每一个cpu core的cpu time: 打开performance monitor,添加 counter 如下 运行一段cpu bound 的代码 ...

  5. 一个 CPU 核 开多少个 线程 比较合适 ?

    一个 CPU 核 开多少个 线程 比较合适 ? 这是一个 线程池 的 问题 . 我之前也 反对 过 线程池, 因为我认为 线程池 影响了 对 用户 的 实时响应性 . 我也认为, 分时 (对 CPU ...

  6. 我是一个CPU:这个世界慢!死!了!

    最近小编看到一篇十分有意思的文章,多方位.无死角的讲解了CPU关于处理速度的理解,看完之后真是豁然开朗.IOT时代,随着科技的发展CPU芯片的处理能力越来越强,强大的程度已经超乎了我们的想象.今天就把 ...

  7. 大话一个CPU(沙子是如何影响未来的)

    大话一个CPU(沙子是如何影响未来的) CPU是个啥? 先大体上了解一下 中央处理器 (英语:Central Processing Unit,缩写:CPU),是计算机的主要设备之一,功能主要是解释计算 ...

  8. Android 显示意图激活另外一个Actitity

    1.跳转到一个新的Actitity 新建项目, 新建一个java类OtherScreenActivity 继承自 Activity类 package com.wuyudong.twoactivity; ...

  9. [android] 显示意图激活另外一个activity

    可以使用跳转的方式类似javaweb来实现界面转换 显示意图就是必须要指定开启组件的具体信息,包名,组件名,组件的class 新建一个类TwoActivity ,继承Activity类,重写onCre ...

随机推荐

  1. Maven 本地仓库明明有jar包,pom文件还是报错解决办法

    方法一: 找到出错的jar包文件位置,删掉_maven.repositories文件 方法二: maven中的本地仓库的index索引没有更新导致 解决方案: 在eclipse中打开菜单 window ...

  2. Multipath在OpenStack中的faulty device的成因及解决(part 1)

    | 版权:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接.如有问题,可以邮件:wangxu198709@gmail.com 简介: Multip ...

  3. [LeetCode] Minimum Genetic Mutation 最小基因变化

    A gene string can be represented by an 8-character long string, with choices from "A", &qu ...

  4. Django框架之虚拟环境搭建

    创建虚拟环境篇 今天小编就来讲一下在Ubantu下如何搭建Django环境,希望能帮助那些不会搭建的童鞋^o^ 0.首先要先安装好Python环境,至于安装过程,小编就不讲了,百度一下,你懂得.. 1 ...

  5. 推送本地项目至Github遇到的问题以及解决办法记录

    在把本地新项目推送至GitHub仓库时的大致流程和步骤,首先现在GitHub上面新建一个项目,复制该项目的 带.git 后缀的地址,比如 git@github.com:XXX/XXX.git 然后在本 ...

  6. [AHOI 2005]COMMON 约数研究

    Description Input 只有一行一个整数 N(0 < N < 1000000). Output 只有一行输出,为整数M,即f(1)到f(N)的累加和. Sample Input ...

  7. Codeforces 429E Points and Segments

    Description 题面 题目大意:有 \(n\) 个区间 \([L_i,R_i]\) ,你要给每一个区间染红蓝,使得每一个位置被红色染过的次数与被蓝色染过的次数差的绝对值不大于\(1\) Sol ...

  8. ●BZOJ 4566 [Haoi2016]找相同字符

    题链: http://www.lydsy.com/JudgeOnline/problem.php?id=4566题解: 广义后缀自动机 对两个串同时建立一个广义后缀自动机. 同时统计出每个状态对两个串 ...

  9. ●BZOJ 1492 [NOI2007]货币兑换Cash

    题链: http://www.lydsy.com/JudgeOnline/problem.php?id=1492 题解: 斜率优化DP,CDQ分治 定义$DP[i]$为第i天结束后的最大收益. 由于题 ...

  10. 2015 多校联赛 ——HDU5350(huffman)

    Problem Description MZL is a mysterious mathematician, and he proposed a mysterious function at his ...