0%

1
2
3
4
5
mkdir riscv_build
export CROSS_COMPILE=riscv64-mti-linux-gnu- ARCH=riscv
make defconfig O=riscv_build
make -j12
make install O=riscv_build

上述默认为动态编译

如果需要静态编译

1
2
3
4
5
6
make menuconfig O=riscv_build

Location:
-> Busybox Settings
-> Build Options
[*] Build BusyBox as a static binary (no shared libs)

融合动态库

1
2
3
4
5
6
7
8
9
10
11
#riscv_build/_install 为 busybox 等生成的目录
rsync -rl <toolchain_path>/sysroot/riscv/ _install/ #融合系统目录

# 生成 init
cd _install
ln -s bin/busybox init

# 删除 _install 下的所有 *.o *.a 文件, 缩小体积
cd _install
find . -type f -name "*. O" | xargs rm -f
find . -type f -name "*. A" | xargs rm -f

生成 cpiogz 最终文件

1
2
把 install.sh 拷过来放到 riscv_build下
执行 ./install.sh

install.sh 脚本内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#!/bin/bash
sudo rm -rf rootfs
mkdir rootfs
cd rootfs

cp -r ../_install/* .
mkdir dev usr bin sbin lib etc proc tmp sys var root mnt
cd etc
cat > inittab <<- EOF
::sysinit:/etc/init.d/rcS
::respawn:-/bin/login
::restart:/sbin/init
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
::shutdown:/sbin/swapoff -a
EOF

cat > profile <<- EOF
# /etc/profile: system-wide .profile file for the Bourne shells
echo
# echo -n "Processing /etc/profile..."
# no-op
# Set search library path
# echo "Set search library path in /etc/profile"
export LD_LIBRARY_PATH=/lib:/usr/lib
# Set user path
# echo "Set user path in /etc/profile"
PATH=/bin:/sbin:/usr/bin:/usr/sbin
export PATH
# Set PS1
# Note: In addition to the SHELL variable, ash supports \u, \h, \W, \$, \!, \n, \w, \nnn (octal numbers corresponding to ASCII characters)
# And \e[xx;xxm (color effects), etc.
# Also add an extra '\' in front of it!
# echo "Set PS1 in /etc/profile"
export PS1="\\e[00;32m[$USER@\\w\\a]\\$\\e[00;34m"
# echo "Done"
alias ll='ls -al'
EOF


cat > fstab <<- EOF
proc /proc proc defaults 0 0
none /tmp tmpfs defaults 0 0
mdev /dev tmpfs defaults 0 0
sysfs /sys sysfs defaults 0 0
EOF

cat > passwd <<- EOF
root:x:0:0:root:/root:/bin/sh
EOF

cat > group <<- EOF
root:x:0:root
EOF

cat > shadow <<- EOF
root:4rs6NCNMjYULk:19366:0:99999:7:::
EOF

mkdir init.d
cd init.d

cat > rcS <<- EOF
#! /bin/sh
#echo "----------mount all"
/bin/mount -a
#echo "----------Starting mdev......"
#/bin/echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
echo "********************************************************"
echo " mini Rootfs"
echo "********************************************************"
EOF

cd ../../dev
sudo mknod -m 666 console c 5 1
sudo mknod -m 666 null c 1 3
cd ../

sudo chmod 777 etc/init.d/rcS

ln -s bin/busybox init
cd ../
sudo chown -R root:root rootfs
cd rootfs
find .| cpio -o -H newc | gzip > ../busybox_rootfs.cpio.gz

阅读全文 »

https://github.com/mit-pdos/xv6-riscv/
一个简单,类UNIX的 MIT(麻省理工) 教学用操作系统

用户态 syscall

Xv6内核提供了Unix内核传统上提供的服务和系统调用的子集

系统调用 描述
int fork() 创建一个进程,返回子进程的PID
int exit(int status) 终止当前进程,并将状态报告给wait()函数。无返回
int wait(int *status) 等待一个子进程退出; 将退出状态存入*status; 返回子进程PID。
int kill(int pid) 终止对应PID的进程,返回0,或返回-1表示错误
int getpid() 返回当前进程的PID
int sleep(int n) 暂停n个时钟节拍
int exec(char *file, char *argv[]) 加载一个文件并使用参数执行它; 只有在出错时才返回
char *sbrk(int n) 按n 字节增长进程的内存。返回新内存的开始
int open(char *file, int flags) 打开一个文件;flags表示read/write;返回一个fd(文件描述符)
int write(int fd, char *buf, int n) 从buf 写n 个字节到文件描述符fd; 返回n
int read(int fd, char *buf, int n) 将n 个字节读入buf;返回读取的字节数;如果文件结束,返回0
int close(int fd) 释放打开的文件fd
int dup(int fd) 返回一个新的文件描述符,指向与fd 相同的文件
int pipe(int p[]) 创建一个管道,把read/write文件描述符放在p[0]和p[1]中
int chdir(char *dir) 改变当前的工作目录
int mkdir(char *dir) 创建一个新目录
int mknod(char *file, int, int) 创建一个设备文件
int fstat(int fd, struct stat *st) 将打开文件fd的信息放入*st
int stat(char *file, struct stat *st) 将指定名称的文件信息放入*st
int link(char *file1, char *file2) 为文件file1创建另一个名称(file2)
int unlink(char *file) 删除一个文件

用户态的基础接口

  • 文件操作 open read write close link unlink stat fstat mkdir chdir
  • 进程相关 fork dup wait pipe getpid exit kill
  • log相关 printf
  • 内存 malloc free
  • 时间相关 uptime(获取当前的tick) sleep

文件系统

提供了一个精简版的文件系统, 支持ramdisk, 支持文件读写

可改code, 将用户态程序打包进文件系统, 将文件系统镜像融入到 kernel, 使之成为 kernel .rodata段的内容, 作为ramdisk使用

不依赖加载器, 可以将用户态应用程序和 little kernel 打包到一起

阅读全文 »

本文由 简悦 SimpRead 转码, 原文地址 zhuanlan.zhihu.com

经过下面文章的介绍,我们已经知道cache的基本工作原理。
Cache的基本原理 - 知乎

但是,我们一直避开了一个关键问题。我们都知道cache控制器根据地址查找判断是否命中,这里的地址究竟是虚拟地址(virtual address,VA)还是物理地址(physical address,PA)?我们应该清楚CPU发出对某个地址的数据访问,这个地址其实是虚拟地址,虚拟地址经过MMU转换成物理地址,最终从这个物理地址读取数据。因此cache的硬件设计既可以采用虚拟地址也可以采用物理地址甚至是取两者地址部分组合作为查找cache的依据。

虚拟高速缓存(VIVT)

我们首先介绍的是虚拟高速缓存,这种cache硬件设计简单。在cache诞生之初,大部分的处理器都使用这种方式。虚拟高速缓存以虚拟地址作为查找对象。如下图所示。

虚拟地址直接送到cache控制器,如果cache hit。直接从cache中返回数据给CPU。如果cache miss,则把虚拟地址发往MMU,经过MMU转换成物理地址,根据物理地址从主存(main memory)读取数据。由于我们根据虚拟地址查找高速缓存,所以我们是用虚拟地址中部分位域作为索引(index),找到对应的的cacheline。然后根据虚拟地址中部分位域作为标记(tag)来判断cache是否命中。因此,我们针对这种index和tag都取自虚拟地址的高速缓存称为虚拟高速缓存,简称VIVT(Virtually Indexed Virtually Tagged)。另外,我们复习下cache控制器查找数据以及判断是否命中的规则:通过index查找对应的cacheline,通过tag判断是否命中cache。 虚拟高速缓存的优点是不需要每次读取或者写入操作的时候把虚拟地址经过MMU转换为物理地址,这在一定的程度上提升了访问cache的速度,毕竟MMU转换虚拟地址需要时间。同时硬件设计也更加简单。但是,正是使用了虚拟地址作为tag,所以引入很多软件使用上的问题。 操作系统在管理高速缓存正确工作的过程中,主要会面临两个问题。歧义(ambiguity)和别名(alias)。为了保证系统的正确工作,操作系统负责避免出现歧义和别名。

歧义(ambiguity)

歧义是指不同的数据在cache中具有相同的tag和index。cache控制器判断是否命中cache的依据就是tag和index,因此这种情况下,cache控制器根本没办法区分不同的数据。这就产生了歧义。什么情况下发生歧义呢?我们知道不同的物理地址存储不同的数据,只要相同的虚拟地址映射不同的物理地址就会出现歧义。例如两个互不相干的进程,就可能出现相同的虚拟地址映射不同的物理地址。假设A进程虚拟地址0x4000映射物理地址0x2000。B进程虚拟地址0x4000映射物理地址0x3000。当A进程运行时,访问0x4000地址会将物理地址0x2000的数据加载到cacheline中。当A进程切换到B进程的时候,B进程访问0x4000会怎样?当然是会cache hit,此时B进程就访问了错误的数据,B进程本来想得到物理地址0x3000对应的数据,但是却由于cache hit得到了物理地址0x2000的数据。操作系统如何避免歧义的发生呢?当我们切换进程的时候,可以选择flush所有的cache。
flush cache操作有两种:

  • 使主存储器有效。针对write back高速缓存,首先应该使主存储器有效,保证已经修改数据的cacheline写回主存储器,避免修改的数据丢失。
  • 使高速缓存无效。保证切换后的进程不会错误的命中上一个进程的缓存数据。
阅读全文 »

本文由 简悦 SimpRead 转码, 原文地址 zhuanlan.zhihu.com

对于没有接触过底层技术的朋友来说,或许从未听说过cache。毕竟cache的存在对程序员来说是透明的。在接触cache之前,先为你准备段code分析。

1
2
3
4
5
int arr[10][128];

for (i = 0; i < 10; i++)
for (j = 0; j < 128; j++)
arr[i][j] = 1;

如果你曾经学习过C/C++语言,这段code自然不会陌生。如此简单的将arr数组所有元素置1。 你有没有想过这段code还有下面的一种写法。

1
2
3
4
5
int arr[10][128];

for (i = 0; i < 128; i++)
for (j = 0; j < 10; j++)
arr[j][i] = 1;

功能完全一样,但是我们一直在重复着第一种写法(或许很多的书中也是建议这么编码),你是否想过这其中的缘由?文章的主角是cache,所以你一定猜到了答案。那么cache是如何影响这2段code的呢?

为什么需要cache

在思考为什么需要cache之前,我们首先先来思考另一个问题:我们的程序是如何运行起来的?

我们应该知道程序是运行在 RAM之中,RAM 就是我们常说的DDR(例如: DDR3、DDR4等)。我们称之为main memory(主存)。当我们需要运行一个进程的时候,首先会从磁盘设备(例如,eMMC、UFS、SSD等)中将可执行程序load到主存中,然后开始执行。在CPU内部存在一堆的通用寄存器(register)。如果CPU需要将一个变量(假设地址是A)加1,一般分为以下3个步骤:

  1. CPU 从主存中读取地址A的数据到内部通用寄存器 x0(ARM64架构的通用寄存器之一)。
  2. 通用寄存器 x0 加1。
  3. CPU 将通用寄存器 x0 的值写入主存。
阅读全文 »

  • [[#introduction|introduction]]
  • [[#sign secure boot|sign secure boot]]
  • [[#KEK and DKEY|KEK and DKEY]]
    • [[#KEK and DKEY#如何使用DKEK (了解)|如何使用DKEK (了解)]]
  • [[#OTP|OTP]]
  • [[#keyWriter|keyWriter]]

main domain

加密硬件加速器 – 带 ECC 的 PKA、AES、SHA、 RNG、DES 和 3DES
Asymmetrische Kryptografie: RSA und ECC-Funktionen
• Hash-Funktionen: Message Digest Algorithm (MD5), SHA1 und SHA2-224/256/384/512
• Symmetrische Kryptografie-Funktionen: AES-128/192/256
• Hardware-TRNG-Modul mit Nachbearbeitung für einen deterministischen Zufallsbitgenerator (DRBG)

wakeup domain DMSC

Main components of the DMSC are:
•Arm Cortex-M3 processor core (ARMv7-M architecture profile)
•160 KB ROM to allow boot sequence, authentication and provide security service (M3 accessible only)

  • Two separate local memory banks for Instruction code (I-code) and Data space (D-code) with single error correction and double error detection
  • Firewall enabled 32-bit VBUSP CBASS interconnect
  • Interrupt Aggregator with support of up to 80 interrupt inputs to the DMSC
  • Four dual-mode 32-bit timers
  • DMSC control module - contains various control, configuration and status MMRs for power management functions
  • Security Manager module for device security management, device type control (GP, EMU, HS), emulation and JTAG control, and key management
  • AES engine with 128, 192 and 256-bits support and DPA/EMA countermeasures

TIFS world 安全通信能力

SoC中的每个物理处理器都有能力在不同的模式下运行,如特权和非特权,安全或不安全。主机的定义超出了物理处理器的范围,也区分了处理器的操作模式。

阅读全文 »

qemu 启动虚拟机时需要指定Machine 即 -M 参数指定对应的机型.
从我们之前分析的demo看, 默认以virt 启动, 因riscv vcpu 只能运行在S/U mode 下, 所以不需要关注opensbi的部分, 只需要关注u-boot 和 kernel的部分.
这里只说kernel的部分, kernel 在编译时也要按对应的机型编译, 如virt. 还有dtb的部分, kernel 和 qemu 中也是对应的.

新增Machine 只是对于没有开发板时的妥协,便于开发人员在没有开发板时有一个板子的虚拟环境进行测试验证, 所以可以看到该新增Machine的模拟硬件同开发板基本上是一致的;
对于虚拟化来说只有virt就可以了, virt上的feature也是最全的, 最适合跑guest os, 没有必要用其他Machine 跑guest os.

dtb 信息

在qemu 中一般会以代码的形式写dtb的相关属性, 可以用 qemu 导出相应机型的dtb信息

1
2
3
4
qemu-system-riscv64 -machine virt,dumpdtb=virt.dtb -smp 1 -m 2G -nographic
dtc virt.dtb > virt.dts
qemu-system-riscv64 -machine sifive_u,dumpdtb=sifive_u.dtb -smp 2 -m 2G -nographic
dtc sifive_u.dtb > sifive_u.dts

为了宏观的观察新增一个Machine 需要添加哪些部分, 可以先对比下 virt.dts 和 新增Machine 如sifive_u.dts的内容.

对于dtb 中描述的硬件信息, qemu都需要模拟出来对应的硬件.

qemu 中 dtb 信息的创建

qemu 对 libfdt 的接口做了二次封装,部分接口举例如下:

  1. create_device_tree:初始化一颗设备树
  2. qemu_fdt_setprop_cell:设置设备树的节点的属性,值为数字
  3. qemu_fdt_setprop_string:设置设备树的节点的属性,值为字符串
  4. qemu_fdt_add_subnode:添加一个子节点
阅读全文 »

本文由 简悦 SimpRead 转码, 原文地址 zhuanlan.zhihu.com

TLB是translation lookaside buffer的简称。首先,我们知道MMU的作用是把虚拟地址转换成物理地址。虚拟地址和物理地址的映射关系存储在页表中,而现在页表又是分级的。64位系统一般都是3~5级。常见的配置是4级页表,就以4级页表为例说明。分别是PGD、PUD、PMD、PTE四级页表。在硬件上会有一个叫做页表基地址寄存器,它存储PGD页表的首地址。MMU就是根据页表基地址寄存器从PGD页表一路查到PTE,最终找到物理地址(PTE页表中存储物理地址)。这就像在地图上显示你的家在哪一样,我为了找到你家的地址,先确定你是中国,再确定你是某个省,继续往下某个市,最后找到你家是一样的原理。一级一级找下去。这个过程你也看到了,非常繁琐。如果第一次查到你家的具体位置,我如果记下来你的姓名和你家的地址。下次查找时,是不是只需要跟我说你的姓名是什么,我就直接能够告诉你地址,而不需要一级一级查找。四级页表查找过程需要四次内存访问。延时可想而知,非常影响性能。页表查找过程的示例如下图所示。以后有机会详细展开,这里了解下即可。

TLB的本质是什么

TLB其实就是一块高速缓存。数据cache缓存地址(虚拟地址或者物理地址)和数据。TLB缓存虚拟地址和其映射的物理地址。TLB根据虚拟地址查找cache,它没得选,只能根据虚拟地址查找。所以TLB是一个虚拟高速缓存。硬件存在TLB后,虚拟地址到物理地址的转换过程发生了变化。虚拟地址首先发往TLB确认是否命中cache,如果cache hit直接可以得到物理地址。否则,一级一级查找页表获取物理地址。并将虚拟地址和物理地址的映射关系缓存到TLB中。既然TLB是虚拟高速缓存(VIVT),是否存在别名和歧义问题呢?如果存在,软件和硬件是如何配合解决这些问题呢?

TLB的特殊

虚拟地址映射物理地址的最小单位是4KB。所以TLB其实不需要存储虚拟地址和物理地址的低12位(因为低12位是一样的,根本没必要存储)。另外,我们如果命中cache,肯定是一次性从cache中拿出整个数据。所以虚拟地址不需要offset域。index域是否需要呢?这取决于cache的组织形式。如果是全相连高速缓存。那么就不需要index。如果使用多路组相连高速缓存,依然需要index。下图就是一个四路组相连TLB的例子。现如今64位CPU寻址范围并没有扩大到64位。64位地址空间很大,现如今还用不到那么大。因此硬件为了设计简单或者解决成本,实际虚拟地址位数只使用了一部分。这里以48位地址总线为了例说明。

TLB的别名问题

我先来思考第一个问题,别名是否存在。我们知道PIPT的数据cache不存在别名问题。物理地址是唯一的,一个物理地址一定对应一个数据。但是不同的物理地址可能存储相同的数据。也就是说,物理地址对应数据是一对一关系,反过来是多对一关系。由于TLB的特殊性,存储的是虚拟地址和物理地址的对应关系。因此,对于单个进程来说,同一时间一个虚拟地址对应一个物理地址,一个物理地址可以被多个虚拟地址映射。将PIPT数据cache类比TLB,我们可以知道TLB不存在别名问题。而VIVT Cache存在别名问题,原因是VA需要转换成PA,PA里面才存储着数据。中间多经传一手,所以引入了些问题。

阅读全文 »

l1/l2 cache 结构

image-20240416112815996

cache控制器是如何判断数据是否在cache中命中呢?所以cache肯定是只能缓存主存中极小一部分数据。我们如何根据地址在有限大小的cache中查找数据呢?现在硬件采取的做法是对地址进行散列(可以理解成地址取模操作)

image-20240416112819790

我们一共有8行cache line,cache line大小是8 Bytes。所以我们可以利用地址低3 bits(如上图地址蓝色部分)用来寻址8 bytes中某一字节,我们称这部分bit组合为offset。
同理,8行cache line,为了覆盖所有行。我们需要3 bits(如上图地址黄色部分)查找某一行,这部分地址部分称之为index

tag array和data array一一对应。每一个cache line都对应唯一一个tag,tag中保存的是整个地址位宽去掉index和offset使用的bit剩余部分(如上图地址绿色部分)。tag、index和offset三者组合就可以唯一确定一个地址了。因此,当我们根据地址中index位找到cache line后,取出当前cache line对应的tag,然后和地址中的tag进行比较,如果相等,这说明cache命中。如果不相等,说明当前cache line存储的是其他地址的数据,这就是cache缺失。

Directory Mapped

image-20240416112823674

0x00、0x40 地址中index部分是一样的。因此,这2个地址对应的cache line是同一个。所以,当我们访问0x00地址时,cache会缺失
, 然后数据会从主存中加载到cache中第0行cache line;
当我们访问0x40地址时,依然索引到cache中第0行cache line,由于此时cache line中存储的是地址0x00地址对应的数据,所以此时依然会cache缺失。然后从主存中加载0x40地址数据到第一行cache line中

访问0x40地址时,就会把0x00地址缓存的数据替换。这种现象叫做cache颠簸(cache thrashing)

阅读全文 »

以 sifive fu740 为例

编译流程

编译opensbi

1
2
3
4
git clone https://github.com/riscv/opensbi.git
cd opensbi
make PLATFORM=generic
export OPENSBI=<path to opensbi/build/platform/generic/firmware/fw_dynamic.bin>

生成 fw_dynamic.bin
编译uboot 和 spl

1
2
3
cd <U-Boot-dir>
make sifive_unmatched_defconfig
make

生成 spl/u-boot-spl.binu-boot.itb 文件

烧写

1
2
3
4
5
6
sudo sgdisk -g --clear -a 1 \
--new=1:34:2081 --change-name=1:spl --typecode=1:5B193300-FC78-40CD-8002-E86C45580B47 \
--new=2:2082:10273 --change-name=2:uboot --typecode=2:2E54B353-1271-4842-806F-E436D6AF6985 \
--new=3:16384:282623 --change-name=3:boot --typecode=3:0x0700 \
--new=4:286720:13918207 --change-name=4:root --typecode=4:0x8300 \
/dev/sdX

sdX 表示通配, sd 卡插到电脑上, sd 卡的节点可能是 sdb sdc 等, 这里以 sdX 表示

阅读全文 »

无卡启动

配置tftpd

1
2
3
4
5
6
7
8
9
10
sudo apt install tftpd-hpa
sudo vi /etc/default/tftpd-hpa #编辑 /etc/default/tftpd-hpa
# /etc/default/tftpd-hpa

TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/srv/tftp"
TFTP_ADDRESS=":69"
TFTP_OPTIONS="-s"

sudo chmod 777 -R /srv/tftp

配置完后, tftp 使用的目录为 /srv/tftp

将编出的 work/image.fit 拷贝到该文件夹中

本机测试

1
2
tftp localhost
> get image.fit

无错误代表没问题

开发板 u-boot tftp 下载

1
StarFive # setenv ipaddr 192.168.xx.xx;setenv serverip 192.168.xx.xx
阅读全文 »