首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >riscv gcc工具链是如何被编译的

riscv gcc工具链是如何被编译的

作者头像
bigmagic
发布于 2022-01-10 00:10:09
发布于 2022-01-10 00:10:09
2.3K00
代码可运行
举报
文章被收录于专栏:嵌入式iot嵌入式iot
运行总次数:0
代码可运行

riscv gcc工具链是如何被编译的

  • 概述
  • 编译器编译原理
    • 历史背景
    • gcc工具链是如何工作的?
    • 工具链中有哪些组件?
  • 工具链的构建顺序
  • riscv gcc编译器的目录结构
  • riscv gcc编译器的构建
  • 编译最小支持RVB和RVV的riscv gcc

概述

gcc工具链是一个复杂而又巧妙的工程,随着riscv上层软件的逐渐完善,工具链和底层系统软件的开发也显得尤为重要。深入理解gcc的原理,能够更好的对计算机体系结构有一个完整的了解。

但是由于gcc的源码过于复杂,其诞生的年代也十分久远,入手gcc也相当棘手。而riscv是一个新的体系架构,在该架构上去理解gcc的开发和编译过程,不会有许多的历史包袱,这也是后面文章中主要进行分析的架构。

编译器编译原理

历史背景

GNU C编译器的早期作者是GNU项目的创始人Richard Stallman。

GNU项目最早开始于1984年,其目的是为了创建一个完全自由免费的类Unix的操作系统。由于每一个类Unix的操作系统都需要一个C编译器作为支持,但是当时并没有免费使用的编译器。所以GNU项目不得不从头开始进行编译器的开发工作。在1987年,第一个GCC正式版本诞生,这是一个免费发布并且可移植的编译器,此后GCC成为开发免费软件最重要的工具之一。

后来随着编译语言的增多,包括Fortran,ada,JavaObjective-C也被支持。

gcc工具链是如何工作的?

gcc工具链并不是一个单独的程序,而是一系列程序的合集,这些工具以一种串联的方式进行排列。

其中就包括预处理,编译,汇编,链接等过程。这种特性的特点就是上一个步骤的输出结果总是下一个过程的输入,最后生成了特定架构所需的可执行的文件。按照这种方式组合,形成了"工具链",当为不同的架构生成机器代码时,称为交叉编译工具链。

工具链中有哪些组件?

下图展示了riscv gcc编译完成后的组件。当然,最新发挥作用的是编译器gcc本身,将C文件转换成汇编代码。

汇编代码则由汇编器进行链接,生成特定的机器代码。

下面通过一个表格简单的描述一下

工具

功能

addr2line

可以将指令的地址转换成文件名,函数名和源代码行数的工具

ar

库管理器,创建静态库

as

汇编器,主要处理汇编代码

objcopy

将文件转换成另外一种格式,比如.bin转换成.elf,或者将.elf转换成.bin

objdump

反汇编

readelf

显示elf相关的信息

size

列出可执行文件的每一部分的尺寸,代码段,数据段等大小信息

通过上述一些列的工具,可以将C语言转换成可以执行的代码程序,但是现在还缺少在目标机器上运行程序时的C库,C库提供了一个标准的抽象层,可以执行基本的任务,包括内存分配、终端输出、文件访问等等。对于不同的系统,也有着不同的C库,比如针对Linux桌面环境,有glibc或者eglibc或者uClibc等等。对于嵌入式Linux,可以选择eglibc或者uClibc,对于没有任何操作系统或者RTOS来说,可以使用newlib,甚至可以不使用。还有一些小众的C库,针对特定的需要进行设计,比如针对ramdisk优化的klibc等等。

工具链的构建顺序

这些工具构建需要一定的顺序,这是一件有趣的事情。

  • 编译出的编译器需要C库
  • 编译出C库需要编译器

这就带来一个问题,A依赖B,B也依赖A。这就是典型的先有鸡,后有蛋的问题。

编译器首先会构建一个不需要C库就能构建出来的精简编译器,这部分我们称为引导程序、初始编译器或者核心编译器。

  • 最后的编译器需要C库
  • 编译出C库需要编译器
  • 编译器需要C库的头文件和引导程序

现在的问题变成了编译C库需要的头文件和引导文件。由于C库提供了"C runtime",也被称为CRT,但是这部分的编译还是需要依赖编译器。

  • 最后的编译器需要C库
  • 编译出C库需要编译器
  • 编译器需要C库的头文件和引导程序
  • 编译C库的引导程序

这样问题可能就变得简单一些了,我们只需要构建一个简单的编译器,他不需要C库头文件但是需要启动文件,该编译器同时也是C库的引导程序。我们称为这个简易编译器为pass1。最后完整的编译器为pass2。下面则变得清晰了起来:

  • 最后的编译器需要C库
  • 编译出C库需要编译器
  • 编译器需要C库的头文件和引导程序
  • 编译C库的引导程序
  • 我们需要一个简易的编译器

这样下来,我们就可以得到一个编译器所需要的C库了,然后完整的编译出最终的编译器。

上述只是一个基本的流程概述,实际上过程比这个更加的复杂。

下面来看riscv gcc编译后生成的文件夹

带有build前缀的都是编译器编译时的中间产物。可以看到build-gcc-newlib-stage1build-gcc-newlib-stage2

实际上gcc在编译过程中编译了三次:

  • 编译额外的C编译器(stage1)
  • 用stage1的编译器重新编译GCC编译器(stage2)
  • 用stage2的编译器再次编译GCC编译器(stage3)

stage2和stage3是为了更好的检查GCC编译的准确性,同时,也可以采用不同的优化等级对最后生成的gcc工具链进行优化。

其实stage2和stage3产生的应该是一样的结果,可以采用make compare来进行校验,由于这种严密的编译机制,可以使得最后的gcc生成的产物变得准确无误。

riscv gcc编译器的目录结构

在了解如何编译之前,首先看一下riscv gcc仓库有哪些东西。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
https://github.com/riscv-collab/riscv-gnu-toolchain
  • qemu

工具链仓库的qemu左右是为了测试使用,结合riscv gcc的dejagnu测试框架,测试功能

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
make report-linux SIM=qemu # Run with qemu
  • riscv-binutils

该目录用于binutils生成,比如ar,ld等二进制工具,工具集合,也是非常重要的一个仓库。

  • riscv-dejagnu

dejagnu测试框架是测试gcc和binutils重要的工具,是保证gcc和binutils功能正常的非常重要的测试框架。

  • riscv-gcc

gcc主要的程序

  • riscv-gdb

通过外设接口,可以通过gdb调试

  • riscv-glibc

支持编译的程序在Linux运行的glibc库

  • riscv-newlib

支持编译的程序在rtos或者baremetal上运行的的C库

riscv gcc编译器的构建

当前公认的riscv gcc主线在

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
https://github.com/riscv-collab/riscv-gnu-toolchain

该master分支是稳定的可以使用的riscv 版本。但是现在做riscv扩展指令集分析,这里选择

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
https://github.com/riscv-collab/riscv-gnu-toolchain/tree/basic-rvv

该分支实现了也就是riscv b扩展,v扩展的最小支持。该分支作为入手gcc分析是最好的选择。

下载代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
git clone https://github.com/riscv-collab/riscv-gnu-toolchain.git
cd riscv-gnu-toolchain
git checkout basic-rvv
git submodule init && git submodule update

在官方的readme.md中介绍了如何编译的流程。

大概介绍一下:

该编译器支持两种libc库,支持rtos和barematel的newlib库和支持Linux的glibc。

默认使用make时,链接的是newlib库,使用make linux时,链接的是glibc。

同时由于riscv有着非常多的arch组合,可以编译单独的arch,比如

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
./configure --prefix=/opt/riscv --with-arch=rv32gc --with-abi=ilp32d

那么只编译arch是rv32gc,abi为ilp32d的组合。

如果同时支持rv32和rv64的组合配置,可以选择--enable-multilib

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
./configure --prefix=/opt/riscv --enable-multilib

不同的组合有着不同的需求,如果只是针对不同的芯片编译出的编译器,那么只选择一个配置即可,若需要发布更多组合的arch支持编译器,可以选择multilib。

编译最小支持RVB和RVV的riscv gcc

可以选择下面的配置

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
./configure --prefix=$RISCV/learn/  --with-arch=rv64gcv_zba_zbb_zbc_zbs --with-abi=lp64d --with-cmodel=medany --with-multilib-generator="rv64gcv_zba_zbb_zbc_zbs-lp64d--"
make -j $(nproc)

这里的RVV可以直接采用gcv即可,而RVB则被拆分成各种子扩展。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Zba - address generation
Zbb - basic bit manipulation
Zbc - carryless multiplication
Zbs - single-bit instructions

最后生成的riscv gcc工具链可以做测试。

结合编译参数,开启O2优化。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
'-march=rv64gcv_zba_zbb_zbc_zbs' ,'-mabi=lp64d'

可以生成带有RVB扩展的格式的代码。

不难看出,当写出下面的函数时

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
long sh1add_test(long a, long b)
{
  return a + (b << 1);
}

而开启rvb扩展时的反汇编代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
long sh1add_test(long a, long b)
{
    800000a2:   1141                    addi    sp,sp,-16
    800000a4:   e422                    sd      s0,8(sp)
    800000a6:   0800                    addi    s0,sp,16
  return a + (b << 1);
}
    800000a8:   6422                    ld      s0,8(sp)
    800000aa:   20a5a533                sh1add  a0,a1,a0
    800000ae:   0141                    addi    sp,sp,16
    800000b0:   8082                    ret

不开启rvb扩展时的反汇编代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
long sh1add_test(long a, long b)
{
    800000a2: 1141                 addi sp,sp,-16
    800000a4: e422                 sd s0,8(sp)
    800000a6: 0800                 addi s0,sp,16
  return a + (b << 1);
}
    800000a8: 6422                 ld s0,8(sp)
  return a + (b << 1);
    800000aa: 0586                 slli a1,a1,0x1
}
    800000ac: 952e                 add a0,a0,a1
    800000ae: 0141                 addi sp,sp,16
    800000b0: 8082                 ret

由此可见开启rvb扩展优化后,代码的code size和性能均会提高。

那么这个优化在gcc中是如何实现的,后面文章中会慢慢的提及。

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

本文分享自 嵌入式IoT 微信公众号,前往查看

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

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Ingress-Nginx 服务暴露基础学习与实践
描述: 到目前为止我们了解kubernetes常用的三种暴露服务的方式:LoadBlancer Service、 NodePort Service、Ingress
全栈工程师修炼指南
2022/09/29
3.2K1
Ingress-Nginx 服务暴露基础学习与实践
Nginx Ingress的一些奇巧淫技
redirect主要用于域名重定向,比如访问a.com被重定向到b.com。 如下我们配置访问ng.coolops.com重定向到www.baidu.com
极客运维圈
2020/05/14
9.2K1
Ingress-Nginx 服务暴露基础学习与实践 (2)
更多学习笔记文章请关注 WeiyiGeek 公众账号,学习交流【邮箱联系: Master#weiyigeek.top】
全栈工程师修炼指南
2021/07/25
3.4K0
Ingress-Nginx 服务暴露基础学习与实践 (2)
ingress通过daemonSet,nodeSelector,hostNetwork方式部署
首先我们需要在k8s集群中准备边缘节点,用来部署ingress(需要对边缘节点打污点或者使用亲和性/反亲和性),如果是私有化部署,需要对ingress做高可用,如果资源充足,还可以再做一次负载均衡,这里在本地测试的话,直接在其中一个node上启动一个就可以了
dogfei
2020/08/19
4K0
kubernetes1.22安装使用ingress-nginx
我们已经了解了 Ingress 资源对象只是一个路由请求描述配置文件,要让其真正生效还需要对应的 Ingress 控制器才行,Ingress 控制器有很多,这里我们先介绍使用最多的 ingress-nginx,它是基于 Nginx 的 Ingress 控制器。
我是阳明
2021/12/27
3.4K0
kubernetes1.22安装使用ingress-nginx
【K8S专栏】Kubernetes应用访问管理
在Kubernetes中,提供了Service和Ingress两种对象来实现应用间访问或外部对集群应用访问,这两种对象在实际的工作中会时长使用,非常重要的对象。
没有故事的陈师傅
2022/12/06
1.7K0
【K8S专栏】Kubernetes应用访问管理
《做一个不背锅运维:一篇搞定K8s Ingress》
Ingress是一种Kubernetes资源,用于将外部流量路由到Kubernetes集群内的服务。与NodePort相比,它提供了更高级别的路由功能和负载平衡,可以根据HTTP请求的路径、主机名、HTTP方法等来路由流量。
不背锅运维
2023/03/30
1.9K0
《做一个不背锅运维:一篇搞定K8s Ingress》
二进制安装Kubernetes(k8s) v1.24.1 IPv4/IPv6双栈 --- Ubuntu版本 (下)
在新版的Kubernetes中系统资源的采集均使用Metrics-server,可以通过Metrics采集节点和Pod的内存、磁盘、CPU和网络的使用率
小陈运维
2022/06/15
9040
【K8s】Kubernetes 服务发现之 Ingress
Ingress 是 Kubernetes 提供的一种服务发现机制,主要作用是为集群外部访问集群内部服务提供访问入口,通过制定 Ingress 策略管理 HTTP 路由,将集群外部的访问请求反向代理到集群内部不同 Service 对应的 Endpoint(即 Pod)上。
行者Sun
2024/09/02
3290
【K8s】Kubernetes 服务发现之 Ingress
Ingress-Nginx 服务暴露基础学习与实践(1)
本章讲解通过服务发现的功能进行实现 , 由 Ingress controller 来提供路由信息的刷新, Ingress controller可以理解为一个监视器不断监听 kube-apiserver 实时感知service、Pod的变化
全栈工程师修炼指南
2021/07/25
3.3K0
云原生(三十一) | Kubernetes篇之平台基本预装资源
GitHub - kubernetes-sigs/metrics-server: Scalable and efficient source of container resource metrics for Kubernetes built-in autoscaling pipelines.
Lansonli
2022/07/13
9450
云原生(三十一) | Kubernetes篇之平台基本预装资源
Nginx-Ingress详解
k8s 中所有的资源都有对应的控制器在操控这个资源,管理资源的生命周期,实现”声明式“效果。Deployment、Service、Replicaset等资源的控制器封装在k8s内置的 controller-manager进程中。
kinnylee
2021/12/30
6.9K1
二进制安装Kubernetes(k8s) v1.23.6 ---(下)
1.23.3 和 1.23.4 和 1.23.5 和 1.23.6 文档以及安装包已生成。
小陈运维
2022/04/21
1.7K0
基于AWS EKS的K8S实践 - 打通外网对集群内服务的调用
service 通常用作集群内服务之前的通信,ingress 通常用于暴露给集群外的服务使用。
shysh95
2023/08/23
9121
基于AWS EKS的K8S实践 - 打通外网对集群内服务的调用
使用loki和grafana展示ingress-nginx的日志
在kubernetes中,对于日志的收集,使用最多的是FEK, 不过有时候,FEK在架构上会略显重, ES的查询及全文检索功能其实使用的不是很多.LoKi做为日志架构的新面孔, 由grafana开源, 使用了与Prometheus同样的label理念, 同时摒弃了全文检索的能力, 因此比较轻便, 非常具有潜力。
没有故事的陈师傅
2021/01/04
2.5K0
使用loki和grafana展示ingress-nginx的日志
K8S Ingress使用|常见问题列表
最近有部分同学咨询关于使用Ingress-nginx碰到的一系列问题,其实有部分问题,我也没有碰到过,都是在官网上找到的答案,验证后,进行了一个简单问题列表整理,希望能够帮助到需要的人。
用户5166556
2020/08/27
3.7K0
k8s nginx-ingress上的配置优化
自建K8s上,如果部署了Nginx-Ingress,通常一些默认的参数有些可能需要优化下以便提升它的性能(阿里云之类的云厂商提供的Ingress是优化过的)。
保持热爱奔赴山海
2022/01/11
1K0
在k8s(kubernetes) 上安装 ingress V1.1.0
Ingress 公开了从集群外部到集群内服务的 HTTP 和 HTTPS 路由。流量路由由 Ingress 资源上定义的规则控制。
小陈运维
2021/12/16
1.6K0
配置 ingress nginx 日志持久化和 ssl 证书
ingress-nginx 默认访问日志保存在 /var/log/nginx/ 目录下。
懒人的小脑
2020/12/01
2.1K0
配置 ingress nginx 日志持久化和 ssl 证书
k8s中负载均衡器【ingress-nginx】部署
在Kubernetes中,服务和Pod的IP地址仅可以在集群网络内部使用,对于集群外的应用是不可见的。为了使外部的应用能够访问集群内的服务,在Kubernetes 目前 提供了以下几种方案:
我的小碗汤
2019/07/30
4.7K0
推荐阅读
相关推荐
Ingress-Nginx 服务暴露基础学习与实践
更多 >
交个朋友
加入腾讯云官网粉丝站
蹲全网底价单品 享第一手活动信息
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档