前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >go语言调度器源代码情景分析之二:CPU寄存器

go语言调度器源代码情景分析之二:CPU寄存器

作者头像
阿波张
发布于 2019-06-24 07:19:47
发布于 2019-06-24 07:19:47
1.2K00
代码可运行
举报
运行总次数:0
代码可运行

寄存器是CPU内部的存储单元,用于存放从内存读取而来的数据(包括指令)和CPU运算的中间结果,之所以要使用寄存器来临时存放数据而不是直接操作内存,一是因为CPU的工作原理决定了有些操作运算只能在CPU内部进行,二是因为CPU读写寄存器的速度比读写内存的速度快得多。

为了便于交流和使用汇编语言进行编程,CPU厂商为每个寄存器都取了一个名字,比如AMD64 CPU中的rax, rbx, rcx, rdx等等,这样程序员就可以很方便的在汇编代码中使用寄存器的名字来进行编程,为了对寄存器的使用有个直观的感受,我们用个例子来简单的说明一下。

假设有如下go语言编写的一行代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
c = a + b

在AMD64 Linux平台下,使用go编译器编译它可得到如下AT&T格式的汇编代码(如果对汇编代码不熟悉的话可以直接看每一条指令后面的注释,不影响我们理解):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mov    (%rsp),%rdx          //把变量a的值从内存中读取到寄存器rdx中
mov    0x8(%rsp),%rax   //把变量b的值从内存中读取到寄存器rax中
add    %rdx,%rax             //把寄存器rdx和rax中的值相加,并把结果放回rax寄存器中
mov    %rax,0x10(%rsp)  //把寄存器rax中的值写回变量c所在的内存

可以看到,上面的一行go语言代码被编译成了4条汇编指令,指令中出现的rax,rdx和rsp都是寄存器的名字(AT&T格式的汇编代码中所有寄存器名字前面都有一个%符号),虽然这里只有4条指令,但也从一个侧面说明汇编代码其实比较简单,它所做的工作不外乎就是把数据在内存和寄存器中搬来搬去或做一些基础的数学和逻辑运算。

不同体系结构的CPU,其内部寄存器的数量、种类以及名称可能大不相同,这里我们只介绍目前使用最为广泛的AMD64这种体系结构的CPU,这种CPU共有20多个可以直接在汇编代码中使用的寄存器,其中有几个寄存器在操作系统代码中才会见到,而应用层代码一般只会用到如下分为三类的19个寄存器。

  1. 通用寄存器:rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp, r8, r9, r10, r11, r12, r13, r14, r15寄存器。CPU对这16个通用寄存器的用途没有做特殊规定,程序员和编译器可以自定义其用途(下面会介绍,rsp/rbp寄存器其实是有特殊用途的);
  2. 程序计数寄存器(PC寄存器,有时也叫IP寄存器):rip寄存器。它用来存放下一条即将执行的指令的地址,这个寄存器决定了程序的执行流程;
  3. 段寄存器:fs和gs寄存器。一般用它来实现线程本地存储(TLS),比如AMD64 linux平台下go语言和pthread都使用fs寄存器来实现系统线程的TLS,在本章线程本地存储一节和第二章详细分析goroutine调度器的时候我们可以分别看到Linux平台下Pthread线程库和go是如何使用fs寄存器的。

上述这些寄存器除了fs和gs段寄存器是16位的,其它都是64位的,也就是8个字节,其中的16个通用寄存器还可以作为32/16/8位寄存器使用,只是使用时需要换一个名字,比如可以用eax这个名字来表示一个32位的寄存器,它使用的是rax寄存器的低32位。

为了便于查阅,下表列出这些64通用寄存器对应的32/16/8位寄存器的名字:

64位

32位

16位

8位

rax

eax

ax

al/ah

rbx

ebx

bx

bl/bh

rcx

ecx

cx

cl/ch

rdx

edx

dx

dl/dh

rsi

esi

si

-

rdi

edi

di

-

rbp

ebp

bp

-

rsp

esp

sp

-

r8~r15

r8d~r15d

r8w~r15w

r8b~r15b

通用寄存器的用法如前面我们所见,主要用于临时存放数据,后面的章节我们还会见到大量使用通用寄存器的例子,所以这里就不对其进行详细介绍了,但有三个比较特殊的寄存器值得在这里单独提出来做一下说明:

rip寄存器

rip寄存器里面存放的是CPU即将执行的下一条指令在内存中的地址。看如下汇编语言代码片段:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
0x0000000000400770: add    %rdx,%rax
0x0000000000400773: mov    $0x0,%ecx

假设当前CPU正在执行第一条指令,这条指令在内存中的地址是0x0000000000400770,紧接它后面的下一条指令的地址是0x0000000000400773,所以此时rip寄存器里面存放的值是0x0000000000400773。

这里需要牢记的就是rip寄存器的值不是正在被CPU执行的指令在内存中的地址,而是紧挨这条正在被执行的指令后面那一条指令的地址。

读者可能会有疑问,在前面的两个汇编指令片段中并没有指令修改rip寄存器的值,是怎么做到让它一直指向下一条即将执行的指令的呢?其实修改rip寄存器的值是CPU自动控制的,不需要我们用指令去修改,当然CPU也提供了几条可以间接修改rip寄存器的指令,在汇编语言一节中我们会详细介绍CPU自动修改以及用指令修改rip寄存器值的两种方式。

rsp 栈顶寄存器和rbp栈基址寄存器

这两个寄存器都跟函数调用栈有关,其中rsp寄存器一般用来存放函数调用栈的栈顶地址,而rbp寄存器通常用来存放函数的栈帧起始地址,编译器一般使用这两个寄存器加一定偏移的方式来访问函数局部变量或函数参数,比如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mov    0x8(%rsp),%rdx

这条指令把地址为 0x8(%rsp) 的内存中的值拷贝到rdx寄存器,这里的0x8(%rsp) 就利用了 rsp 寄存器加偏移 8 的方式来读取内存中的值。

寄存器的内容我们就先简单的介绍到这里,但这些并不是我们需要了解的有关寄存器的全部内容,有些内容需要等我们学习了汇编指令和函数调用栈之后才能更加深刻的理解,到时候我们再回头来继续介绍相关的知识。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-04-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 go语言核心编程技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
go语言调度器源代码情景分析之五:汇编指令
汇编语言是每位后端程序员都应该掌握的一门语言,因为学会了汇编语言,不管是对我们调试程序还是研究与理解计算机底层的一些运行原理都具有非常重要的作用,所以建议有兴趣的读者可以多花点时间把它学好。
阿波张
2019/06/24
1.2K0
go语言调度器源代码情景分析之五:汇编指令
Go语言调度器之主动调度(20)
本文是《Go语言调度器源代码情景分析》系列的第20篇,也是第五章《主动调度》的第1小节。
阿波张
2019/06/24
9180
go语言调度器源代码情景分析之三:内存
内存由大量内存单元组成,内存单元大小为1个字节(1字节包含8个二进制位), 每个内存单元都有一个编号,更专业的说法是每一个内存单元都有一个地址,我们在编写汇编代码或编译器把用高级语言所写的程序编译成汇编指令时,如果要读写内存,就必须在指令中指定内存地址,这样CPU才知道它要存取哪个或哪些内存单元。
阿波张
2019/06/24
8050
go语言调度器源代码情景分析之三:内存
协程原理:函数调用过程、参数和寄存器
并发编程是什么,进程?线程?其实还有协程,尤其是服务器并发。随着Go的普及,估计大伙儿都知道有协程这个玩意儿了,其实早就有了C里面叫Coroutine,SRS就是用的这个玩意儿。 上图借用的是Kotlin的图,不仅仅是Go,各种的语言都有协程的支持。 协程历史 还是放一张图出来,看看协程的发展历史。 中国文化中,由于历史悠久,所以特别强调继承,如果这个想法是来自远古时代,那才叫真宗。 各位朋友看呐,协程初祖Donald Knuth,60年前的神功秘籍,有啥好怀疑的,赶紧拥抱协程吧,哈哈哈。 SRS的协
Winlin
2022/03/18
6860
golang源码分析:go 汇编
AT&T格式的汇编代码中所有寄存器名字前面都有一个%符号,rsp代码sp寄存器,里面存的是栈顶指针。
golangLeetcode
2022/08/02
9410
golang源码分析:go 汇编
汇编学习(2),数据表示与寄存器
本篇介绍下数据在计算机中的表示形式以及常用的寄存器, 最后再学一个稍微复杂点的代码。
一只小虾米
2022/12/02
4790
汇编学习(2),数据表示与寄存器
Go语言调度器源代码情景分析之十:线程本地存储
本文是《go调度器源代码情景分析》系列 第一章 预备知识的第十小节,也是预备知识的最后一小节。
阿波张
2019/06/24
1.3K0
go语言调度器源代码情景分析之八:系统调用
我们将在最后一章讨论有关系统调用方面的抢占调度,所以这里有必要对系统调用有个基本的了解。
阿波张
2019/06/24
6780
Swift 汇编(一)Protocol Witness Table 初探
由于工作中接触到 Swift 汇编与逆向知识,所以整理了这篇博客。内容与顺序无关,第一篇文章并非入门,单纯只是第一篇文章。建议有一定汇编基础的读者学习。
酷酷的哀殿
2021/06/22
1.9K0
Swift 汇编(一)Protocol Witness Table 初探
go语言调度器源代码情景分析之六:go汇编语言
go语言runtime(包括调度器)源代码中有部分代码是用汇编语言编写的,不过这些汇编代码并非针对特定体系结构的汇编代码,而是go语言引入的一种伪汇编,它同样也需要经过汇编器转换成机器指令才能被CPU执行。需要注意的是,用go汇编语言编写的代码一旦经过汇编器转换成机器指令之后,再用调试工具反汇编出来的代码已经不是go语言汇编代码了,而是跟平台相关的汇编代码。
阿波张
2019/06/24
1.4K0
go语言调度器源代码情景分析之六:go汇编语言
CPU基本结构和运行原理
北桥:CPU和内存、显卡等部件进行数据交换的唯一桥梁,即CPU想和其他任何部分通信,须经过北桥。北桥芯片中通常集成的还有内存控制器等,控制与内存的通信。现在的主板上已经看不到北桥,它的功能已被集成到CPU当中。
JavaEdge
2023/01/10
1.2K0
CPU基本结构和运行原理
【CSAPP】BombLab
《CSAPP》是指计算机系统基础课程的经典教材《Computer Systems: A Programmer's Perspective》,由Randal E. Bryant和David R. O'Hallaron编写。该书的主要目标是帮助深入理解计算机系统的工作原理,包括硬件和软件的相互关系,其涵盖了计算机体系结构、汇编语言、操作系统、计算机网络等主题,旨在培养学生系统级编程和分析的能力。
SarPro
2024/02/20
2770
【CSAPP】BombLab
深入iOS系统底层之CPU寄存器介绍
计算机是一种数据处理设备,它由CPU和内存以及外部设备组成。CPU负责数据处理,内存负责存储,外部设备负责数据的输入和输出,它们之间通过总线连接在一起。CPU内部主要由控制器、运算器和寄存器组成。控制器负责指令的读取和调度,运算器负责指令的运算执行,寄存器负责数据的存储,它们之间通过CPU内的总线连接在一起。每个外部设备(例如:显示器、硬盘、键盘、鼠标、网卡等等)则是由外设控制器、I/O端口、和输入输出硬件组成。外设控制器负责设备的控制和操作,I/O端口负责数据的临时存储,输入输出硬件则负责具体的输入输出,它们间也通过外部设备内的总线连接在一起。
欧阳大哥2013
2018/08/22
1.5K0
深入iOS系统底层之CPU寄存器介绍
x64架构下Linux系统函数调用
push指令将数据压栈。具体就是将esp(stack pointer)寄存器减去压栈数据的大小,再将数据存储到esp寄存器所指向的地址。
Orlion
2024/09/02
1810
x64架构下Linux系统函数调用
golang 协程的实现原理
要理解协程的实现, 首先需要了解go中的三个非常重要的概念, 它们分别是G, M和P, 没有看过golang源代码的可能会对它们感到陌生, 这三项是协程最主要的组成部分, 它们在golang的源代码中无处不在.
golangLeetcode
2022/08/02
6430
golang  协程的实现原理
bthread源码剖析(三): 汇编语言实现的上下文切换
上回书说道,TaskGroup的run_main_task()有三大关键函数,剩余一个sched_to()没有展开详谈。那在今天的sched_to()源码探秘之旅开始之前呢,首先高能预警,本文会涉及到汇编语言,所以请大家坐稳扶好!
果冻虾仁
2021/12/08
9960
浅谈函数调用!
导语 |  在任意一门编程语言中,函数调用基本上都是非常常见的操作;我们都知道,函数是由调用栈实现的,不同的函数调用会切换上下文;但是,你是否好奇,对于一个函数调用而言,其底层到底是如何实现的呢?本文讲解了函数调用的底层逻辑实现。 一、汇编概述 既然要讲解函数调用的底层逻辑实现,那么汇编语言我们是绕不过的。 因此,首先来复习一下汇编相关的知识。 我们都知道,计算机只能读懂二进制指令,而汇编就是一组特定的字符,汇编的每一条语句都直接对应CPU的二进制指令,比如:mov rax,rdx就是我们常见的汇编指令。
腾讯云开发者
2022/08/26
1.8K0
浅谈函数调用!
go语言调度器源代码情景分析之七:函数调用过程
前面几节我们介绍了CPU寄存器、内存、汇编指令以及栈等基础知识,为了达到融会贯通加深理解的目的,这一节我们来综合运用一下所学知识,看看函数的执行和调用过程。
阿波张
2019/06/24
1.3K0
go语言调度器源代码情景分析之七:函数调用过程
汇编和内存
你已经开了汇编学习的旅程,并且在前几章中你已经学习了汇编调用的一些黑魔法,你现在知道了,当一个函数被调用,他的参数和返回值是如何传递的。但是您还没学到的是将代码加载到内存后如何执行代码。
molier
2022/11/03
1.3K0
汇编和内存
程序的机器级表示
call proc后, 这个过程会push main的调用地址的下一处,在proc里面也会push rbp, 通过打印内存的值,可以看到 rsp上 存储的变量信息, 选用的数字比较有规则,比如 0x12345678 , 0x 66666666 如下图:
changan
2020/11/04
3250
程序的机器级表示
相关推荐
go语言调度器源代码情景分析之五:汇编指令
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验