回到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. python 函数“四剑客”的使用和介绍

    python函数四剑客:lambda.map.filter和reduce. 一.lambda(匿名函数) 1. 学习lambda要注意一下几点: lambda语句被用来创建新的函数对象,并且在运行的时 ...

  2. 连接mysql数据库报错java.sql.SQLException: The server time zone value '�й���׼ʱ��' is unrecognized...解决方法

    今天连接mysql数据库报错如下: java.sql.SQLException: The server time zone value '�й���׼ʱ��' is unrecognized or r ...

  3. Java动态代理模式浅析

    Java代理设计模式 - 静态代理 Java中的动态代理 - 调用处理器 代理设计模式的UML图: 我将首先介绍Java中的各种代理实现方法 Java代理设计模式 - 静态代理 这个例子非常简单,只有 ...

  4. 前端面试送命题-JS三座大山

    前言 本篇文章比较适合3年以上的前端工作者,JS三座大山分别指:原型与原型链,作用域及闭包,异步和单线程. 原型与原型链 说到原型,就不得不提一下构造函数,首先我们看下面一个简单的例子: functi ...

  5. 再谈angularJS数据绑定机制及背后原理—angularJS常见问题总结

    这篇是对angularJS的一些疑点回顾,是对目前angularJS开发的各种常见问题的整理汇总.如果对文中的题目全部了然于胸,觉得对整个angular框架应该掌握的七七八八了.希望志同道合的通知补充 ...

  6. 3.如何搭建Appium自动化测试环境

    整个APP自动化环境安装可以参照虫师博客安装 附以下链接: http://www.cnblogs.com/fnng/category/695788.html 下面介绍运用到工作中遇到的一些问题 1.如 ...

  7. 机器学习技法:09 Decision Tree

    Roadmap Decision Tree Hypothesis Decision Tree Algorithm Decision Tree Heuristics in C&RT Decisi ...

  8. [AHOI 2009]chess 中国象棋

    Description 题库链接 给你一张 \(N\times M\) 的棋盘.要求每行每列最多放两个棋子,问总方案数. \(1\leq N,M\leq 100\) Solution 记 \(f_{i ...

  9. [Codeforces 864E]Fire

    Description Polycarp is in really serious trouble — his house is on fire! It's time to save the most ...

  10. 51nod 1673 树有几多愁

    lyk有一棵树,它想给这棵树重标号. 重标号后,这棵树的所有叶子节点的值为它到根的路径上的编号最小的点的编号. 这棵树的烦恼值为所有叶子节点的值的乘积. lyk想让这棵树的烦恼值最大,你只需输出最大烦 ...