基于Linux 2.6.32内核进行分析,看本篇文章前,建议先看看percpu变量这篇文章

smp_processor_id()用来获取当前cpu的id,首先来看smp_processor_id的定义:

# define smp_processor_id() raw_smp_processor_id()

接下来:

#define raw_smp_processor_id() (percpu_read(cpu_number))

有必要解释一下这里的cpu_number的由来:

DEFINE_PER_CPU(int, cpu_number);//每个CPU的cpuid是放置在cpu_number这个percpu变量中

后面最终调用了:

percpu_from_op("mov", per_cpu__##var, "m" (per_cpu__##var))
// 这里的var为cpu_number

这个宏展开后,实质上等价于:

#define percpu_from_op(op, var, constraint)
({
typeof(per_cpu_cpu_number) ret__;
switch (sizeof(per_cpu_cpu_number)) {
case 4:
asm(“movl %%fs:%P1, %0" //在64位体系结构下为 movl %%gs:%P1, %0
: "=r" (ret__)
: "m" (per_cpu_cpu_number));
break;
...
})

这里fs寄存器里面是什么东西?为什么这里就可以获取到cpu的id?请看完后文后到这里进行回答:

smp_processor_id实际要去读取的是cpu_number这个percpu变量,因为在系统初始化的时候,把每个cpu的id都设置到了cpu_number这个percpu变量中,请看代码:

setup_per_cpu_areas()
{
for_each_possible_cpu(cpu) {
.....
per_cpu(cpu_number, cpu) = cpu;//看到没,在这里设置进去了,等下我们就要去取里面的内容
setup_percpu_segment(cpu);
.....
}
}

至于per_cpu的实现,请参考文章开头给出的那篇文章。总之这里就是把各个CPU的cpuid设置到了cpu_number这个percpu变量中。这里是为了给大家说明:我们不是要去cpu_number里面取cpuid吗?那什么时候放进去的呢。现在大家明白了吧。

接下来正真看fs寄存器里有什么东西:

//在/arch/x86/kernel/head_32.S中
movl $(__KERNEL_PERCPU), %eax
movl %eax,%fs //fs段寄存器指向GDT的27偏移项

那GDT的27偏移项描述符是谁呢?

static inline void setup_percpu_segment(int cpu)
{
#ifdef CONFIG_X86_32
struct desc_struct gdt; pack_descriptor(&gdt, per_cpu_offset(cpu), 0xFFFFF,
0x2 | DESCTYPE_S, 0x;
gdt.s = 1;
write_gdt_entry(get_cpu_gdt_table(cpu),
GDT_ENTRY_PERCPU, &gdt, DESCTYPE_S);
#endif
}

这里设置的在 GDT 表中的偏移 GDT_ENTRY_PERCPU 与 前面fs的__KERNEL_PERCPU一样都为27,所以fs段选择子对应的GDT描述符项基地址为per_cpu_offset(cpu)。也就是说每个CPU的fs段选择子都是一样的27,但是设置的段描述符的基地址每个CPU都是不一样的(因为base指定为per_cpu_offset(cpu))。

再回到最开始的嵌入式汇编:

asm("movb %%fs:%P1, %0"
:"=q"(ret__)
:"m"(per_cpu__cpu_number));

不难看出:per_cpu_offset[cpu] + &per_cpu__cpu_number 便得到了相应 cpu 的每 per_cpu__cpu_number变量的地址,与正常percpu变量存取方式不同,cpu_number的offset是通过GDT表项得到的。关于 per_cpu_offset数组的由来 请参考 setup_per_cpu_areas 函数。

整个smp_processor_id()取CPUid的示意图:

FQA:

在start_kernel的开始调用boot_cpu_init,且boot_cpu_init在setup_per_cpu_areas()之前调用,在这个函数里面,已经在使用smp_processor_id了!但是此时运行时per_cpu_areas还没初始化,这是怎么回事?

这是因为此时的fs引用的GDT描述符的base为0(此时GDT的GDT_ENTRY_PERCPU描述符base为0,参考arch/x86/kernel/cpu/common.c:DEFINE_PER_CPU_PAGE_ALIGNED),smp_processor_id()直接引用cpu_number的地址来获取cpuid,cpu_number在编译时分配到.data.percpu段,所以此时smp_processor_id返回为0。且此时 AP (应用处理器)还没有启动,BP(引导处理器)完成启动,所以就是 cpu 0。

2.6.24内核中运行时cpu_number的值是在哪里初始化的?percpu变量专用的GDT描述符项在哪里设置的?

/* Initialize the CPU's GDT.  This is either the boot CPU doing itself
(still using the master per-cpu area), or a CPU doing it for a
secondary which will soon come up. */
__cpuinit void init_gdt(int cpu)
{
struct desc_struct *gdt = get_cpu_gdt_table(cpu); pack_descriptor((u32 *)&gdt[GDT_ENTRY_PERCPU].a,//依然在27(GDT_ENTRY_PERCPU)偏移项
(u32 *)&gdt[GDT_ENTRY_PERCPU].b,
__per_cpu_offset[cpu], 0xFFFFF,
0x80 | DESCTYPE_S | 0x2, 0x8); per_cpu(this_cpu_off, cpu) = __per_cpu_offset[cpu];
per_cpu(cpu_number, cpu) = cpu;
}

参考整个系统GDT:

  • ——- start of kernel segments: *
  • 12 - kernel code segment <==== new cacheline
  • 13 - kernel data segment
  • 14 - default user CS
  • 15 - default user DS
  • 16 - TSS
  • 17 - LDT
  • 18 - PNPBIOS support (16->32 gate)
  • 19 - PNPBIOS support
  • 20 - PNPBIOS support
  • 21 - PNPBIOS support
  • 22 - PNPBIOS support
  • 23 - APM BIOS support
  • 24 - APM BIOS support
  • 25 - APM BIOS support *
  • 26 - ESPFIX small SS
  • 27 - per-cpu [ offset to per-cpu data area ]
  • 28 - stack_canary-20 [ for stack protector ]
  • 29 - unused
  • 30 - unused
  • 31 - TSS for double fault handler

smp_processor_id()获取当前执行cpu_id的更多相关文章

  1. .NET C#-- 利用BeginInvoke与EndInvoke完成异步委托方法并获取方法执行返回值示例

    //定义委托 delegate string MyDelegate(string name); //定义委托调用函数 public string Hello(string name) { Thread ...

  2. Selenium2学习-036-WebUI自动化实战实例-034-JavaScript 在 Selenium 自动化中的应用实例之六(获取 JS 执行结果返回值)

    Selenium 获取 JavaScript 返回值非常简单,只需要在 js 脚本中将需要返回的数据 return 就可以,然后通过方法返回 js 的执行结果,方法源码如下所示: /** * Get ...

  3. TaskTracker获取并执行map或reduce任务的过程1

    TaskTracker获取并执行map或reduce任务的过程(一) 我们知道TaskTracker在默认情况下,每个3秒就行JobTracker发送一个心跳包,也就是在这个心跳包中包含对任务的请求. ...

  4. 老李推荐:第5章7节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 循环获取并执行事件 - runMonkeyCycles

    老李推荐:第5章7节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 循环获取并执行事件 - runMonkeyCycles   poptest是国内唯一一家培养测试开 ...

  5. java 中多线程和锁的使用以及获取多线程执行结果

    多线程一:原生的写法   关键词 implements  实现  Runnable 类 run()  方法 注意点 : 创建类的实例 InterfaceController inter=new Int ...

  6. 获取 SharpSvn 执行 svn 操作的实时日志

    1 获取 SharpSvn 操作日志的方式 之前一篇随笔(使用 SharpSvn 执行 svn 操作)讲到可以通过声称一个绑定到一个 SvnClient 对象的 SvnClientReport 对象. ...

  7. Emacs Helm: 使用关键字搜索、获取、执行任何东西

    Helm 是一个emacs的软件包,定义了一个通用框架,交互式地.动态缩减式地使用关键字选择.获取.执行任何东西.比如: 执行emacs 命令 打开文件 查看man文档 执行grep操作 执行apt命 ...

  8. Java: 获取当前执行位置的文件名/类名/方法名/行号

    在 JAVA 程序有时需要获取当前代码位置, 于是就利用 Thread.currentThread().getStackTrace() 写了下面这个工具类, 用来获取当前执行位置处代码的文件名/类名/ ...

  9. java 获取 正在执行的方法名

    //获取调用该方法的方法名.... String method = Thread.currentThread().getStackTrace()[2].getMethodName(); //获取正在执 ...

随机推荐

  1. 【Linux】一些常用命令(待整理)

    一.关机重启命令 二.查询ip 三.查询杀死进程 四.CentOS7 关闭防火墙 五.vim常用 5.1 搜索 5.2 设置行号 剪切 替换 一.关机重启命令 shutdown -h 10 #计算机将 ...

  2. Android源码阅读技巧--查找开发者选项中显示触摸操作源码

    在开发者模式下,在开发者选项中,可以勾选“显示触摸操作”,然后只要点击屏幕就会在点击的位置有圈圈显示.如何找到绘制圈圈的代码部分,有什么技巧来阅读代码量这么大的android系统源码呢?以下请跟着小老 ...

  3. 深度剖析Vue中父给子、子给父、兄弟之间传值!

    本片文章将为您详细讲解在Vue中,父给子传值.子给父传值以及兄弟之间传值方式! 父传子:父组件 // template里面 <aa :info="name"/> // ...

  4. css 实现居中的五种方式

      一 .文字居中 1 固定容器高度的单行文本垂直居中:height=line-height         2 未知容器高度的文本垂直居中:设定padding,使上下的padding值相同即可   ...

  5. springcloud超简单的入门2--Eureka服务治理

    Eureka服务治理 下面请听第一个话题,母...咳咳,拿错书了. Eureka简介 eureka是什么呢? 简单来说呢,当我的微服务应用多了起来,一个一个写死再程序里是件很不优雅的事情,而且同一服务 ...

  6. 磁盘告警之---神奇的魔法(Sparse file)

      一.问题来源 半夜钉钉接到告警,某台机器的磁盘使用率少于20%,于是迷糊中爬起来,咔咔咔 find / -size +1G,咔咔咔,把几个只有4-5G的日志文件echo空值了一下,然后吓蒙了,刚刚 ...

  7. Netty源码分析 (十)----- 拆包器之LineBasedFrameDecoder

    Netty 自带多个粘包拆包解码器.今天介绍 LineBasedFrameDecoder,换行符解码器. 行拆包器 下面,以一个具体的例子来看看业netty自带的拆包器是如何来拆包的 这个类叫做 Li ...

  8. SpringAop实现公共字段填充

    一.说明 项目中经常会有一些放在缓存中的公共字段需要进行填充,我们知道mybatis-plus很方便地可以实现公共字段填充.在这里我定义了一个字段填充的注解,当我们需要进行数据填充的时候只要在方法上打 ...

  9. 使用 Docker 安装 showdoc

    一.简介 ShowDoc 是一个非常适合IT团队在线共享文档的工具,在线访问地址为:https://www.showDoc.cc 本来也可以直接 pull showdoc 镜像到本地,使用 docke ...

  10. [Pandas] 02 - Tutorial of NumPy

    Ref: NumPy 教程 这里主要是查缺补漏一些常用方法. 初步认识 矩阵常见知识点 矩阵操作 Ref: [Python] 01 - Number and Matrix[总结过一部分] 一.矩阵 ( ...