回到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. JS面向对象之原型链

      对象的原型链 只要是对象就有原型 原型也是对象 只要是对象就有原型, 并且原型也是对象, 因此只要定义了一个对象, 那么就可以找到他的原型, 如此反复, 就可以构成一个对象的序列, 这个结构就被成 ...

  2. Oracle中SQL调优(SQL TUNING)之最权威获取SQL执行计划大全

    该文档为根据相关资料整理.总结而成,主要讲解Oracle数据库中,获取SQL语句执行计划的最权威.最正确的方法.步骤,此外,还详细说明了每种方法中可选项的意义及使用方法,以方便大家和自己日常工作中查阅 ...

  3. Redis、Memcache与MongoDB的区别

    >>Memcached Memcached的优点:Memcached可以利用多核优势,单实例吞吐量极高,可以达到几十万QPS(取决于key.value的字节大小以及服务器硬件性能,日常环境 ...

  4. cogs 558 奇怪的函数

    提交地址:http://cojs.tk/cogs/problem/problem.php?pid=558 558. 奇怪的函数 ★☆   输入文件:xx.in   输出文件:xx.out   简单对比 ...

  5. [HAOI2007]覆盖问题

    题目描述 某人在山上种了N棵小树苗.冬天来了,温度急速下降,小树苗脆弱得不堪一击,于是树主人想用一些塑料薄膜把这些小树遮盖起来,经过一番长久的思考,他决定 用3个L*L的正方形塑料薄膜将小树遮起来.我 ...

  6. APIO 2016

    我好菜啊都不会 T1.boats 题目大意:给你N段区间,按顺序决定每段区间可以选一个数或不选,若选则选的这个数必须大于所有在这之前选的数,求有多少种方案.(N<=500,区间在[1,1e9]范 ...

  7. hdu5635 BestCoder Round #74 (div.2)

    LCP Array  Accepts: 131  Submissions: 1352  Time Limit: 4000/2000 MS (Java/Others)  Memory Limit: 13 ...

  8. bzoj3294[Cqoi2011]放棋子 dp+组合+容斥

    3294: [Cqoi2011]放棋子 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 755  Solved: 294[Submit][Status] ...

  9. Linux内核异常处理体系结构详解(一)【转】

    转自:http://www.techbulo.com/1841.html 2015年11月30日 ⁄ 基础知识 ⁄ 共 6653字 ⁄ 字号 小 中 大 ⁄ Linux内核异常处理体系结构详解(一)已 ...

  10. 使设备I/O的核心模块工作,有哪两种方式?

    设备处理进程方式.文件操作方式.