oz1010's blog

记录生活或技能

基本信息

OpenOCD(Open On-Chip Debugger)

提供依赖JTAG接口的分层架构的片上编程和调试支持和TAP支持,包括:

  • (X)SVF回放,便于自动边界扫描和FPGA/CPLD编程;
  • 调试目标支持(如ARM、MIPS):单步调试、断点/监控点、gprof性能分析等;
  • Flash芯片驱动(如CFI、NAND、内部闪存);
  • 内嵌TCL解释器,方便脚本编写;

OpenOCD支持众多网络接口与之交互:包括:telnet、TCL和GDB。GDB server可以使得OpenOCD作为“远程目标”,使用标准的GNU GDB程序(和其他使用GDB协议的工具,如IDA Pro)源码级调试嵌入式系统。

官方手册

OpenOCD用户手册

OpenOCD开发手册

工程编译

源码解析

自动生成配置

配置命令

Linux环境中,执行./configure xxx配置后会返回

1
2
3
4
5
6
7
8
9
$ ./configure --prefix=/usr/local/OpenOCD --datarootdir=/usr/local/OpenOCD --enable-ftdi=no --enable-remote-bitbang=no --enable-ep93xx=no --enable-amtjtagaccel=no --enable-jtag_dpi=no --enable-vdebug=no --enable-jtag_vpi=no --enable-buspirate=no --enable-openjtag=no --enable-presto=no --enable-stlink=no --enable-ti-icdi=no --enable-ulink=no --enable-usb-blaster-2=no --enable-ft232r=no --enable-vsllink=no --enable-xds110=no --enable-osbdm=no --enable-opendous=no --enable-armjtagew=no --enable-rlink=no --enable-usbprog=no --enable-esp-usb-jtag=no --enable-nulink=no --enable-kitprog=no --enable-usb-blaster=no --enable-parport-giveio=no --disable-werror --enable-dummy=yes

OpenOCD configuration summary
--------------------------------------------------
Linux GPIO bitbang through libgpiod no
SEGGER J-Link Programmer yes (auto)
Dummy Adapter yes
RISC-V Link Adapter yes
Use Capstone disassembly framework no

新增rvlink适配器

configure.ac中新增RVLINK_ADAPTER配置项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
m4_define([DUMMY_ADAPTER],
[[[dummy], [Dummy Adapter], [DUMMY]]])

m4_define([RVLINK_ADAPTER],
[[[rvlink], [RISC-V Link Adapter], [RVLINK]]])
...
AC_ARG_ADAPTERS([DUMMY_ADAPTER],[no])

AC_ARG_ADAPTERS([RVLINK_ADAPTER],[yes])
...
PROCESS_ADAPTERS([DUMMY_ADAPTER], [true], [unused])
PROCESS_ADAPTERS([RVLINK_ADAPTER], [true], [unused])
...
m4_foreach([adapter], [USB1_ADAPTERS,
HIDAPI_ADAPTERS, HIDAPI_USB1_ADAPTERS, LIBFTDI_ADAPTERS,
LIBFTDI_USB1_ADAPTERS,
LIBGPIOD_ADAPTERS,
LIBJAYLINK_ADAPTERS, PCIE_ADAPTERS, SERIAL_PORT_ADAPTERS,
DUMMY_ADAPTER,
RVLINK_ADAPTER,
...

例如,对于rvlink适配器,其名称是RISC-V Link Adapter,编译宏为BUILD_RVLINK

JTAG驱动编控制文件src/jtag/drivers/Makefile.am中可以使用编译宏RVLINK控制编译的代码。

1
2
3
4
5
6
if DUMMY
DRIVERFILES += %D%/dummy.c
endif
if RVLINK
DRIVERFILES += %D%/rvlink.c
endif

Linux环境中编译会包含rvlink.c文件。

src/jtag/interfaces.c文件适配器驱动列表新增适配器。

1
2
3
4
5
6
7
8
struct adapter_driver *adapter_drivers[] = {
#if BUILD_DUMMY == 1
&dummy_adapter_driver,
#endif
#if BUILD_RVLINK == 1
&rvlink_adapter_driver,
#endif
};

数据结构

调试目标target

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// src/target/target.h
struct target {
struct target_type *type; // 包含目标名称和访问函数
struct jtag_tap *tap; // jtag扫描链
bool examined; // 检查目标标记
enum target_debug_reason debug_reason; // 目标进入调试状态原因枚举
enum target_state state; // 当前目标状态
struct reg_cache *reg_cache; // 寄存器缓存
struct breakpoint *breakpoints; // bp断点列表
struct watchpoint *watchpoints; // wp断点列表
struct trace *trace_info; // 跟踪信息
void *arch_info; // 架构信息,常用于target与架构互转
void *private_config; // 配置信息
struct rtos *rtos; // RTOS信息
struct list_head *smp_targets; // SMP列表节点
struct gdb_service *gdb_service; // SMP场景,所有调试目标使用相同的GDB
};

调试目标列表

1
2
3
4
5
6
// src/target/target.c
static struct target_type *target_types[] = {
&stm8_target,
&riscv_target,
NULL,
};

对于ARM架构处理器,可以通过container_of(target->arch_info, struct cortex_a_common, armv7a_common.arm),将target转换为cortex_a_common,然后可以获取ADI版本版本。

1
2
3
4
// struct adiv5_dap->adi_version
struct cortex_a_common *cortex_a = target_to_cortex_a(target);
struct armv7a_common *armv7a = &cortex_a->armv7a_common;
armv7a->debug_ap->dap->adi_version == 5; /** Indicates ADI version (5, 6 or 0 for unknown) being used */

适配器adapter

1
2
3
4
5
6
7
8
9
10
// icd_openocd/src/jtag/interface.h
struct adapter_driver {
const char * const *transports; // 传输列表
const struct command_registration *commands; // 命令列表
// JTAG设备操作接口
int (*init)(void); // 初始化资源
int (*reset)(int srst, int trst); // 控制设备SRST和TRST信号

struct jtag_interface *jtag_ops; // 低等级JTAG接口
};

JTAG接口

1
2
3
4
5
// icd_openocd/src/jtag/interface.h
struct jtag_interface {
unsigned supported; // 驱动支持能力的位表
int (*execute_queue)(struct jtag_command *cmd_queue); // 执行队列中的命令
};

服务service

服务service,主要用于管理服务。在创建服务时会复制对应的服务驱动接口。

1
2
3
4
5
6
7
8
// src/server/server.h
struct service {
char *name;
struct connection *connections;
int (*new_connection)(struct connection *connection);
int (*input)(struct connection *connection);
void (*keep_client_alive)(struct connection *connection);
};

服务驱动service_driver,主要定义基本结构

1
2
3
4
5
6
7
// src/server/server.h
struct service_driver {
const char *name;
int (*new_connection_handler)(struct connection *connection);
int (*input_handler)(struct connection *connection);
void (*keep_client_alive_handler)(struct connection *connection);
};

GDB服务驱动

1
2
3
4
5
6
7
8
9
// src/server/gdb_server.c
static const struct service_driver gdb_service_driver = {
.name = "gdb",
.new_connection_during_keep_alive_handler = NULL,
.new_connection_handler = gdb_new_connection,
.input_handler = gdb_input,
.connection_closed_handler = gdb_connection_closed,
.keep_client_alive_handler = gdb_keep_client_alive,
};

连接connection

1
2
3
4
5
6
7
8
9
10
11
// src/server/server.h
struct connection {
int fd;
int fd_out; /* When using pipes we're writing to a different fd */
struct sockaddr_in sin;
struct command_context *cmd_ctx;
struct service *service;
bool input_pending;
void *priv;
struct connection *next;
};

启动OpenOCD

运行命令./src/openocd -s tcl -f tcl/interface/cmsis-dap.cfg -f tcl/target/xxxx.cfg启动OpenOCD。

配置文件解析

适配器驱动处理,各tcl配置文件

1
2
3
4
5
6
7
8
9
10
// 适配器驱动文件 tcl/interface/cmsis-dap.cfg
adapter driver cmsis-dap
transport select jtag
adapter speed 2000

// 目标配置文件 tcl/target/xxxx.cfg
telnet_port 4444
gdb_port 3333
jtag newtap $_CHIPNAME cpu -enable -irlen 32
target create $_TARGETNAME riscv -chain-position $_TARGETNAME -coreid $_HARTID

解析tcl配置文件主流程

1
2
3
4
5
6
7
8
// 主流程 src/openocd.c
openocd_main
setup_command_handler // 注册命令处理函数
adapter_register_commands // 注册adapter命令处理服务
openocd_thread
parse_config_file // 解析配置文件
// 处理 tcl/interface/cmsis-dap.cfg 配置文件
// 处理 tcl/target/xxxx.cfg

adapter命令匹配"cmsis-dap"流程

1
2
3
4
// adapter命令回调,适配器配置解析匹配名称 src/jtag/adapter.c
interface_command_handlers // 匹配命令adapter
adapter_command_handlers // 匹配命令driver
handle_adapter_driver_command // 若驱动名称匹配,则切换adapter_driver,注册驱动的命令列表并更新allowed_transports列表

适配器列表

1
2
3
4
5
6
7
8
9
10
// 适配器驱动列表 src/jtag/interfaces.c
struct adapter_driver *adapter_drivers[] = {
#if BUILD_CMSIS_DAP_USB == 1 || BUILD_CMSIS_DAP_HID == 1
&cmsis_dap_adapter_driver,
#endif
};
// "cmsis-dap"适配器驱动
struct adapter_driver cmsis_dap_adapter_driver = {
.name = "cmsis-dap",
};

其他命令处理

1
2
3
4
// 解析目标配置文件,处理文件中各行命令
// 处理命令telnet_port,handle_telnet_port_command设置telnet_port
// 处理命令gdb_port,handle_gdb_port_command设置gdb_port
// 处理命令transport select,handle_transport_select在allowed_transports列表中匹配传输

主函数流程概览

openocd_main函数初始化逻辑

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
// src/openocd.c
int openocd_main(int argc, char *argv[])
{
...
// 注册各命令的处理函数
struct command_context *cmd_ctx;
cmd_ctx = setup_command_handler(NULL);
...
ret = openocd_thread(argc, argv, cmd_ctx);
...
}

// src/openocd.c
static int openocd_thread(int argc, char *argv[], struct command_context *cmd_ctx)
{
...
// 解析命令行参数
parse_cmdline_args(cmd_ctx, argc, argv);
...
// 解析配置文件,对调试器进行配置
ret = parse_config_file(cmd_ctx);
...
ret = server_init(cmd_ctx);
...
ret = command_run_line(cmd_ctx, "init");
...
ret = server_loop(cmd_ctx);
}

server_init函数初始化逻辑

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
// src/server/server.c
int server_init(struct command_context *cmd_ctx)
{
int ret = tcl_init();
ret = telnet_init("Open On-Chip Debugger");
}

// src/server/tcl_server.c
int tcl_init(void)
{
return add_service(&tcl_service_driver, tcl_port, CONNECTION_LIMIT_UNLIMITED, NULL);
}
static const struct service_driver tcl_service_driver = {
.name = "tcl",
.new_connection_during_keep_alive_handler = NULL,
.new_connection_handler = tcl_new_connection,
.input_handler = tcl_input,
.connection_closed_handler = tcl_closed,
.keep_client_alive_handler = NULL,
};

// src/server/telnet_server.c
int telnet_init(char *banner)
{
int ret = add_service(&telnet_service_driver, telnet_port, CONNECTION_LIMIT_UNLIMITED,
telnet_service);
}
static const struct service_driver telnet_service_driver = {
.name = "telnet",
.new_connection_during_keep_alive_handler = NULL,
.new_connection_handler = telnet_new_connection,
.input_handler = telnet_input,
.connection_closed_handler = telnet_connection_closed,
.keep_client_alive_handler = NULL,
};

server_loop主循环

1
2
3
4
5
6
7
8
9
10
11
12
13
// src/server/server.c
int server_loop(struct command_context *command_context)
{
// 循环处理services中所有服务的连接列表
while(shutdown_openocd == CONTINUE_MAIN_LOOP)
{
// socket_select多路复用监听fd
// 轮询监听间隔为100ms
// 若服务有数据(service->fd),则接受连接并创建新的连接
// 若连接有数据(c->fd),则调用service->input进行处理
...
}
}

RISC-V架构目标示例

NEMU是RISC-V架构处理器硬件模拟器,OpenOCD通过TCP/51234接口连接它。

启动命令:./src/openocd -s tcl -f tcl/interface/rvlink.cfg -f tcl/target/simu/nemu.cfg

TCL配置文件

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
// tcl/interface/rvlink.cfg
adapter driver RV-LINK
transport select jtag
rvlink connect 127.0.0.1 51234

// tcl/target/simu/nemu.cfg
if { ![info exists _SMP_MODE] } {
set _SMP_MODE 1
}
if {![info exists _HARTID]} {
set _HARTID 0x00
}
if {![info exists _CHIPNAME]} {
set _CHIPNAME nemu
}
set _TARGETNAME $_CHIPNAME.cpu
adapter speed 2000
telnet_port 4444
gdb_port 3333
jtag newtap $_CHIPNAME cpu -enable -irlen 32
set _SMP_GROUP ""
target create $_TARGETNAME riscv -chain-position $_TARGETNAME -coreid $_HARTID
set _SMP_GROUP "$::_SMP_GROUP $_TARGETNAME"
if { $_SMP_MODE == 1 } {
eval "target smp $_SMP_GROUP"
}

成功启动并连接时的输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ ./src/openocd -s tcl -f tcl/interface/rvlink.cfg -f tcl/target/simu/nemu.cfg
Open On-Chip Debugger 0.12.0+dev-03889-g5fefbc2da-dirty (2024-09-10-10:57)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : Connected to server 127.0.0.1:51234
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : Note: The adapter "RV-LINK" doesn't support configurable speed
Info : JTAG tap: nemu.cpu tap/device found: 0x3ba0184d (mfg: 0x426 (Google Inc), part: 0xba01, ver: 0x3)
Info : [nemu.cpu] datacount=1 progbufsize=0
Warn : [nemu.cpu] We won't be able to execute fence instructions on this target. Memory may not always appear consistent. (progbufsize=0, impebreak=0)
Info : [nemu.cpu] Vector support with vlenb=0
Info : [nemu.cpu] S?aia detected with IMSIC
Info : [nemu.cpu] Core 0 made part of halt group 1.
Info : [nemu.cpu] Examined RISC-V core
Info : [nemu.cpu] XLEN=32, misa=0x40000000
[nemu.cpu] Target successfully examined.
Info : [nemu.cpu] Examination succeed
Info : [nemu.cpu] starting gdb server on 3333
Info : Listening on port 3333 for gdb connections

rvlink.cfg文件

adapter命令匹配"RV-LINK"流程

1
2
3
4
// adapter命令回调,适配器配置解析匹配名称 src/jtag/adapter.c
interface_command_handlers // 匹配命令adapter
adapter_command_handlers // 匹配命令driver
handle_adapter_driver_command // 若驱动名称匹配,则切换adapter_driver,注册驱动的命令列表并更新allowed_transports列表

适配器列表

1
2
3
4
5
6
7
8
9
10
// 适配器驱动列表 src/jtag/interfaces.c
struct adapter_driver *adapter_drivers[] = {
#if BUILD_RVLINK == 1
&rvlink_adapter_driver,
#endif
};
// "RV-LINK"适配器驱动 src/jtag/drivers/rvlink.c
struct adapter_driver rvlink_adapter_driver = {
.name = "RV-LINK",
};

transport命令选择jtag流程

1
2
3
4
5
// transport命令回调 src/transport/transport.c
transport_group // 匹配命令transport
transport_commands // 匹配命令select
handle_transport_select // 若名称匹配,则更新transport
transport_select // 调用select回调函数,并更新session

内置jtag传输

1
2
3
4
5
6
// src/jtag/core.c
static struct transport jtag_transport = {
.name = "jtag",
.select = jtag_select,
.init = jtag_init,
};

rvlink命令执行流程

1
2
3
4
// src/jtag/drivers/rvlink.c
rvlink_commands // 匹配命令rvlink
rvlink_subcommands // 匹配命令connect
rvlink_connect // 为目标建立TCP连接

nemu.cfg文件

telnet_port命令修改telnet端口

1
2
3
4
// src/server/telnet_server.c
telnet_command_handlers // 匹配命令telnet_port
handle_telnet_port_command // 直接调用函数
server_pipe_command // 修改telnet_port字符串的值

gdb_port命令修改gdb远程连接端口

1
2
3
// src/server/gdb_server.c
gdb_command_handlers // 匹配gdb_port
handle_gdb_port_command // 修改gdb_port字符串的值,并同步更新gdb_port_next

jtag命令创建新的tap

1
2
3
4
5
6
7
// jtag newtap nemu cpu -enable -irlen 32
// src/jtag/tcl.c
jtag_command_handlers // 匹配jtag
jtag_subcommand_handlers // 匹配newtap
handle_jtag_newtap // 创建nemu.cpu的tap对象
handle_jtag_newtap_args // 初始化tap对象,名称、ir配置
jtag_tap_init // 配置tap并将其加入tap列表__jtag_all_taps

target命令创建连接目标

1
2
3
4
5
6
// target create nemu.cpu riscv -chain-position nemu.cpu -coreid 0x00
// src/target/target.c
target_command_handlers // 匹配target
target_subcommand_handlers // 匹配create
jim_target_create // 调整参数并调用函数
target_create // 创建并匹配"riscv"调试目标,注册调试命令,更新all_targets

target命令设置smp

1
2
3
4
5
// target smp nemu.cpu
// src/target/target.c
target_command_handlers // 匹配target
target_subcommand_handlers // 匹配smp
handle_target_smp // 将所有target加入到smp列表中,每个组的smp编号从1开始

初始化命令

init命令初始化openocd,将会通过command_run_line函数触发一系列初始化函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// src/openocd.c
openocd_command_handlers // 匹配init
handle_init_command // 执行初始化函数。执行过程中jtag_poll_en为false,执行完成后恢复。
// target init命令
adapter_init // 适配器初始化 src/jtag/adapter.c
adapter_driver_gpios_init // 适配器驱动通用IO口初始化
// 特定(示例的rvlink)适配器初始化接口
// transport init命令
// dap init命令
target_examine // 循环检查调试目标列表;
// 延时检查
target_examine_one // 单次检查
target->type->examine(target) // 调用特定(示例为riscv)架构对应的检查接口
// flash init命令
// nand init命令
// pld init命令
// tpiu init命令
gdb_target_add_all // 循环调试目标列表,启动对应的GDB server
// _run_post_init_commands命令

target命令初始化所有调试目标

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
// target init 
// src/target/target.c
target_command_handlers // 匹配target
target_subcommand_handlers // 匹配init
handle_target_init_command // 直接调用startup.tcl脚本命令 init_targets init_target_events init_board

// init_targets 初始化函数为空
// src/target/startup.tcl
proc init_targets {} {
}

// init_target_events // 循环遍历目标列表,通过set_default_target_event为所有目标设置默认事件命令
// src/target/startup.tcl
proc init_target_events {} {
set targets [target names] // 获取到所有调试目标名称
foreach t $targets {
set_default_target_event $t gdb-flash-erase-start "reset init"
set_default_target_event $t gdb-flash-write-end "reset halt"
set_default_target_event $t gdb-attach "halt 1000"
}
}

// init_board 初始化函数为空
// src/target/startup.tcl
proc init_board {} {
}

transport传输初始化

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
// transport init
// src/transport/transport.c
transport_group // 匹配transport
transport_commands // 匹配init
handle_transport_init // 校验是否设置session变量,并调用特定(示例为jtag)的初始化接口

// jtag适配器初始化
// src/jtag/core.c
jtag_init // 初始化jtag适配器并执行jtag_init命令

// jtag_init
// src/jtag/startup.tcl
proc jtag_init {} {
if {[catch {jtag arp_init} err]!=0} {
# try resetting additionally
init_reset startup
}
}

// jtag arp_init
// src/jtag/tcl.c
jtag_command_handlers // 匹配jtag
jtag_subcommand_handlers // 匹配arp_init
handle_jtag_arp_init // 直接调用内部函数
jtag_init_inner // 执行jtag tap等初始化

dap初始化处理逻辑

1
2
3
4
5
6
// dap init
// src/target/arm_dap.c
dap_commands // 匹配dap
dap_subcommand_handlers // 匹配init
handle_dap_init // 直接调用函数
dap_init_all // 初始化所有dap对象

简单命令:flash init nand init pld init tpiu init

_run_post_init_commands初始化结束脚本命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/helper/startup.tcl
// 循环处理post_init_commands列表
proc _run_post_init_commands {} {
if {[info exists ::post_init_commands]} {
foreach cmd $::post_init_commands {
eval $cmd
}
}
}

// src/target/startup.tcl
// 将以下脚本函数加入到post_init_commands列表
lappend post_init_commands _post_init_target_array_mem
lappend post_init_commands _post_init_target_cortex_a_cache_auto

GDB交互

GDB连接处理

server_loop主循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/server/server.c
int server_loop(struct command_context *command_context)
{
// 循环处理services中所有服务的连接列表
while(shutdown_openocd == CONTINUE_MAIN_LOOP)
{
socket_select(fd_max + 1, &read_fds, NULL, NULL, &tv); // select处理所有服务连接,默认超时polling_period为100ms
target_call_timer_callbacks(); // 调试目标计时器回调;会调用keep_alive和server_keep_clients_alive处理各服务的保活逻辑
process_jim_events(command_context); // jim事件处理函数

// 循环遍历所有服务
for (service = services; service; service = service->next) {
// 若服务连接有数据,则创建连接
if (FD_ISSET(service->fd, &read_fds)) {
add_connection(service, command_context);
}
// 循环遍历服务下所有客户端连接,调用服务输入处理接口
for (c = service->connections; c; ) {
service->input(c);
}
}
}
}

创建连接

服务创建连接,并将其加入到服务连接队列中

1
2
3
4
5
6
// src/server/server.c
add_connection
// 创建connection对象
accept(service->fd, (struct sockaddr *)&service->sin, &address_size);
service->new_connection // gdb服务创建连接回调
// connection对象加入到service->connections尾部

GDB创建连接

1
2
3
4
5
6
7
8
// src/server/gdb_server.c
// service->new_connection
gdb_service_driver
gdb_new_connection // 初始化gdb连接目标
// 创建gdb连接对象
breakpoint_clear_target(target);
watchpoint_clear_target(target);
target_call_event_callbacks(target, TARGET_EVENT_GDB_ATTACH);

输入处理

GDB输入处理

1
2
3
4
5
6
7
8
9
10
11
12
13
// src/server/gdb_server.c
// service->input
gdb_service_driver
gdb_input // gdb输入处理回调函数,直接调用函数
gdb_input_inner // gdb输入处理函数
gdb_get_packet // 从连接获取协议包
gdb_get_packet_inner // 从连接中读取数据包,若成功则回复ACK('+')
gdb_get_char // 读取数据包,返回第一个字符
fetch_packet // 解析剩余数据,并计算校验和;当切换到noack模式,则不会校验校验和,并跳过回复ACK('+')
/*
根据packet[0]第一个字符,执行对应的处理函数
一般小写为读操作函数,大写为写操作函数
*/

GDB远程协议

具体协议需要参考GDB远程协议章节(Appendix E gdb Remote Serial Protocol)。

连接OpenOCD

GDB执行命令连接OpenOCD。执行GDB命令set debug remote 1打开远程调试后,再连接远程目标可以看到通信日志。

1
2
(gdb) target remote :3333
0x80000004 in ?? ()

qSupported

探测OpenOCD支持哪些特性

1
2
3
4
5
6
7
8
9
  [remote] Sending packet: $qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;memory-tagging+;xmlRegisters=i386#77
[remote] Received Ack
[remote] Packet received: PacketSize=4000;qXfer:memory-map:read-;qXfer:features:read+;qXfer:threads:read+;QStartNoAckMode+;vContSupported+
[remote] packet_ok: Packet qSupported (supported-packets) is supported

// src/server/gdb_server.c
gdb_query_packet // 处理q/Q消息
xml_printf // xml格式的回复消息
gdb_put_packet // 回复消息

vMustReplyEmpty

1
2
3
4
5
6
7
8
  [remote] Sending packet: $vMustReplyEmpty#3a
[remote] Received Ack
[remote] Packet received:

// src/server/gdb_server.c
gdb_v_packet // 处理v消息
// 无法匹配中任何命令
gdb_put_packet(connection, "", 0) // 直接回复空消息

QStartNoAckMode

设置不回复ACK模式

1
2
3
4
5
6
7
  [remote] Sending packet: $QStartNoAckMode#b0
[remote] Received Ack
[remote] Packet received: OK

// src/server/gdb_server.c
gdb_query_packet // 处理q/Q消息
gdb_put_packet // 回复OK

Hgxxx

1
2
3
4
5
6
7
8
  [remote] Sending packet: $Hg0#df
[remote] Packet received: OK

// src/server/gdb_server.c
gdb_thread_packet // 处理H消息
rtos_thread_packet // rtos线程包处理
// 若调试目标存在rtos,则设置当前线程
gdb_put_packet // 回复OK

qXfer:features:read

qXfer:features:read:target.xml:<offset,length>命令分页读取xml格式的架构描述文件,详细信息见riscv描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  [remote] Sending packet: $qXfer:features:read:target.xml:0,1000#0c
[remote] Packet received: m<?xml version="1.0"?>\n<!DOCTYPE target SYSTEM "gdb-target.dtd">\n<target version="1.0">\n<architecture>riscv:rv32</architecture>\n<feature name="org.gnu.gdb.riscv.cpu">\n<reg name="zero" bitsize="32" regnum="0" save-restore="yes" type="int" group="general"/>\n<reg name="ra" bitsize="32" regnum="1" save-restore="yes" type="int" group="general"/>\n<reg name="sp" bitsize="32" regnum="2" save-restore="yes" type="int" group="general"/>\n<reg name="gp" bitsize="32" regnum="3" save-restore="yes" type="int" group="genera [3585 bytes omitted]
...
[remote] Sending packet: $qXfer:features:read:target.xml:9000,1000#a5
[remote] Packet received: l type="int" group="csr"/>\n<reg name="hpmcounter27h" bitsize="32" regnum="3292" save-restore="no" type="int" group="csr"/>\n<reg name="hpmcounter28h" bitsize="32" regnum="3293" save-restore="no" type="int" group="csr"/>\n<reg name="hpmcounter29h" bitsize="32" regnum="3294" save-restore="no" type="int" group="csr"/>\n<reg name="hpmcounter30h" bitsize="32" regnum="3295" save-restore="no" type="int" group="csr"/>\n<reg name="hpmcounter31h" bitsize="32" regnum="3296" save-restore="no" type="int" group="csr"/>\n<reg [882 bytes omitted]

// src/server/gdb_server.c
gdb_query_packet // 处理q/Q消息
decode_xfer_read // 解析qXfer命令
gdb_get_target_description_chunk // 架构描述文件分块处理
gdb_generate_target_description // xml格式打印完整的架构描述文件
smp_reg_list_noread // 读取架构完整的寄存器列表
get_reg_features_list // 获取架构寄存器特性列表
xml_printf // 循环打印xml格式的描述文件
gdb_put_packet(connection, xml, strlen(xml)) // 回复架构描述文件xml数据;第一个字符为'm'表示后面有分页;第一个字符为'l'表示最后一页;

qTStatus

1
2
3
4
5
6
7
  [remote] Sending packet: $qTStatus#49
[remote] Packet received:

// src/server/gdb_server.c
gdb_query_packet // 处理q/Q消息
// 无法匹配中任何命令
gdb_put_packet(connection, "", 0) // 直接回复空消息

?

获取进入调试模式的原因对应的信号量

1
2
3
4
5
6
7
  [remote] Sending packet: $?#3f
[remote] Packet received: S00

// src/server/gdb_server.c
gdb_last_signal_packet 处理?消息
gdb_last_signal // 获取上次收到gdb的信号量(根据进入调试模式的原因转换)
gdb_put_packet(connection, sig_reply, 3); // 回复对应的信号量消息

qXfer:threads:read

获取线程列表

1
2
3
4
5
6
7
8
9
  [remote] Sending packet: $qXfer:threads:read::0,1000#92
[remote] Packet received: l<?xml version="1.0"?>\n<threads>\n</threads>\n

// src/server/gdb_server.c
gdb_query_packet // 处理q/Q消息
decode_xfer_read // 解析qXfer命令
gdb_get_thread_list_chunk // 线程列表线程信息分块处理
gdb_generate_thread_list // xml格式打印完整的线程列表信息
gdb_put_packet(connection, xml, strlen(xml)); // 回复线程列表信息xml数据;第一个字符为'm'表示后面有分页;第一个字符为'l'表示最后一页;

Hc-xxx

1
2
3
4
5
6
7
  [remote] Sending packet: $Hc-1#09
[remote] Packet received: OK

// src/server/gdb_server.c
gdb_thread_packet // 处理H消息
rtos_thread_packet // rtos线程包处理
gdb_put_packet // 回复OK

qC

1
2
3
4
5
6
  [remote] Sending packet: $qC#b4
[remote] Packet received: QC0

// src/server/gdb_server.c
rtos_thread_packet // 处理q消息
gdb_put_packet(connection, "QC0", 3); // 无rtos,直接回复QC0

qAttached

1
2
3
4
5
6
7
  [remote] Sending packet: $qAttached#8f
[remote] Packet received: 1
[remote] packet_ok: Packet qAttached (query-attached) is supported

// src/server/gdb_server.c
rtos_thread_packet // 处理q消息
gdb_put_packet(connection, "1", 1); // 直接回复"1"表示支持

g

获取通用寄存器列表

1
2
3
4
5
6
7
8
9
  [remote] Sending packet: $g#67
[remote] Packet received: 000000000000000000000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000080

// src/server/gdb_server.c
gdb_get_registers_packet // 处理g消息
target_get_gdb_reg_list // 获取特定架构寄存器列表
gdb_get_reg_value_as_str // 循环遍历所有寄存器,将寄存器的值格式化为字符串
gdb_str_to_target // 格式化寄存器的值,每个字节格式化为十六进制2位字符
gdb_put_packet(connection, reg_packet, reg_packet_size); // 返回寄存器值字符化后的数据

mxxx

m<address>,<length>读取指定地址的内存数据。默认分别读取当前PC和上一次PC对应的指令。

1
2
3
4
5
6
7
8
9
  [remote] Sending packet: $m80000004,4#59
[remote] Packet received: 93051005
[remote] Sending packet: $m80000000,4#55
[remote] Packet received: 13050005

// src/server/gdb_server.c
gdb_read_memory_packet // 处理m消息
target_read_buffer // 读取特定架构内存数据
gdb_put_packet // 返回内存数据字符化后的数据

附件

GDB通信日志

GDB远程连接日志

执行GDB命令set debug remote 1打开远程调试后,再连接远程目标可以看到通信日志

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
(gdb) target remote :3333
Remote debugging using :3333
[remote] start_remote_1: enter
[remote] Sending packet: $qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;memory-tagging+;xmlRegisters=i386#77
[remote] Received Ack
[remote] Packet received: PacketSize=4000;qXfer:memory-map:read-;qXfer:features:read+;qXfer:threads:read+;QStartNoAckMode+;vContSupported+
[remote] packet_ok: Packet qSupported (supported-packets) is supported
[remote] Sending packet: $vMustReplyEmpty#3a
[remote] Received Ack
[remote] Packet received:
[remote] Sending packet: $QStartNoAckMode#b0
[remote] Received Ack
[remote] Packet received: OK
[remote] Sending packet: $Hg0#df
[remote] Packet received: OK
[remote] Sending packet: $qXfer:features:read:target.xml:0,1000#0c
[remote] Packet received: m<?xml version="1.0"?>\n<!DOCTYPE target SYSTEM "gdb-target.dtd">\n<target version="1.0">\n<architecture>riscv:rv32</architecture>\n<feature name="org.gnu.gdb.riscv.cpu">\n<reg name="zero" bitsize="32" regnum="0" save-restore="yes" type="int" group="general"/>\n<reg name="ra" bitsize="32" regnum="1" save-restore="yes" type="int" group="general"/>\n<reg name="sp" bitsize="32" regnum="2" save-restore="yes" type="int" group="general"/>\n<reg name="gp" bitsize="32" regnum="3" save-restore="yes" type="int" group="genera [3585 bytes omitted]
[remote] Sending packet: $qXfer:features:read:target.xml:1000,1000#9d
[remote] Packet received: m="sstateen0" bitsize="32" regnum="333" save-restore="no" type="int" group="csr"/>\n<reg name="sstateen1" bitsize="32" regnum="334" save-restore="no" type="int" group="csr"/>\n<reg name="sstateen2" bitsize="32" regnum="335" save-restore="no" type="int" group="csr"/>\n<reg name="sstateen3" bitsize="32" regnum="336" save-restore="no" type="int" group="csr"/>\n<reg name="snxti" bitsize="32" regnum="390" save-restore="no" type="int" group="csr"/>\n<reg name="sintstatus" bitsize="32" regnum="391" save-restore="no" ty [3585 bytes omitted]
[remote] Sending packet: $qXfer:features:read:target.xml:2000,1000#9e
[remote] Packet received: mno" type="int" group="csr"/>\n<reg name="mhpmevent8" bitsize="32" regnum="873" save-restore="no" type="int" group="csr"/>\n<reg name="mhpmevent9" bitsize="32" regnum="874" save-restore="no" type="int" group="csr"/>\n<reg name="mhpmevent10" bitsize="32" regnum="875" save-restore="no" type="int" group="csr"/>\n<reg name="mhpmevent11" bitsize="32" regnum="876" save-restore="no" type="int" group="csr"/>\n<reg name="mhpmevent12" bitsize="32" regnum="877" save-restore="no" type="int" group="csr"/>\n<reg name="mhpmeven [3585 bytes omitted]
[remote] Sending packet: $qXfer:features:read:target.xml:3000,1000#9f
[remote] Packet received: m type="int" group="csr"/>\n<reg name="pmpcfg6" bitsize="32" regnum="999" save-restore="no" type="int" group="csr"/>\n<reg name="pmpcfg7" bitsize="32" regnum="1000" save-restore="no" type="int" group="csr"/>\n<reg name="pmpcfg8" bitsize="32" regnum="1001" save-restore="no" type="int" group="csr"/>\n<reg name="pmpcfg9" bitsize="32" regnum="1002" save-restore="no" type="int" group="csr"/>\n<reg name="pmpcfg10" bitsize="32" regnum="1003" save-restore="no" type="int" group="csr"/>\n<reg name="pmpcfg11" bitsize="32" r [3585 bytes omitted]
[remote] Sending packet: $qXfer:features:read:target.xml:4000,1000#a0
[remote] Packet received: m" save-restore="no" type="int" group="csr"/>\n<reg name="pmpaddr35" bitsize="32" regnum="1044" save-restore="no" type="int" group="csr"/>\n<reg name="pmpaddr36" bitsize="32" regnum="1045" save-restore="no" type="int" group="csr"/>\n<reg name="pmpaddr37" bitsize="32" regnum="1046" save-restore="no" type="int" group="csr"/>\n<reg name="pmpaddr38" bitsize="32" regnum="1047" save-restore="no" type="int" group="csr"/>\n<reg name="pmpaddr39" bitsize="32" regnum="1048" save-restore="no" type="int" group="csr"/>\n<reg n [3585 bytes omitted]
[remote] Sending packet: $qXfer:features:read:target.xml:5000,1000#a1
[remote] Packet received: m0h" bitsize="32" regnum="1629" save-restore="no" type="int" group="csr"/>\n<reg name="hstateen1h" bitsize="32" regnum="1630" save-restore="no" type="int" group="csr"/>\n<reg name="hstateen2h" bitsize="32" regnum="1631" save-restore="no" type="int" group="csr"/>\n<reg name="hstateen3h" bitsize="32" regnum="1632" save-restore="no" type="int" group="csr"/>\n<reg name="htval" bitsize="32" regnum="1668" save-restore="no" type="int" group="csr"/>\n<reg name="hip" bitsize="32" regnum="1669" save-restore="no" type="int [3585 bytes omitted]
[remote] Sending packet: $qXfer:features:read:target.xml:6000,1000#a2
[remote] Packet received: m" bitsize="32" regnum="2020" save-restore="no" type="int" group="csr"/>\n<reg name="tinfo" bitsize="32" regnum="2021" save-restore="no" type="int" group="csr"/>\n<reg name="tcontrol" bitsize="32" regnum="2022" save-restore="no" type="int" group="csr"/>\n<reg name="mcontext" bitsize="32" regnum="2025" save-restore="no" type="int" group="csr"/>\n<reg name="mscontext" bitsize="32" regnum="2027" save-restore="no" type="int" group="csr"/>\n<reg name="dcsr" bitsize="32" regnum="2033" save-restore="no" type="int" grou [3585 bytes omitted]
[remote] Sending packet: $qXfer:features:read:target.xml:7000,1000#a3
[remote] Packet received: m type="int" group="csr"/>\n<reg name="mhpmcounter5h" bitsize="32" regnum="3014" save-restore="no" type="int" group="csr"/>\n<reg name="mhpmcounter6h" bitsize="32" regnum="3015" save-restore="no" type="int" group="csr"/>\n<reg name="mhpmcounter7h" bitsize="32" regnum="3016" save-restore="no" type="int" group="csr"/>\n<reg name="mhpmcounter8h" bitsize="32" regnum="3017" save-restore="no" type="int" group="csr"/>\n<reg name="mhpmcounter9h" bitsize="32" regnum="3018" save-restore="no" type="int" group="csr"/>\n<reg [3585 bytes omitted]
[remote] Sending packet: $qXfer:features:read:target.xml:8000,1000#a4
[remote] Packet received: mestore="no" type="int" group="csr"/>\n<reg name="hpmcounter16" bitsize="32" regnum="3153" save-restore="no" type="int" group="csr"/>\n<reg name="hpmcounter17" bitsize="32" regnum="3154" save-restore="no" type="int" group="csr"/>\n<reg name="hpmcounter18" bitsize="32" regnum="3155" save-restore="no" type="int" group="csr"/>\n<reg name="hpmcounter19" bitsize="32" regnum="3156" save-restore="no" type="int" group="csr"/>\n<reg name="hpmcounter20" bitsize="32" regnum="3157" save-restore="no" type="int" group="csr"/> [3585 bytes omitted]
[remote] Sending packet: $qXfer:features:read:target.xml:9000,1000#a5
[remote] Packet received: l type="int" group="csr"/>\n<reg name="hpmcounter27h" bitsize="32" regnum="3292" save-restore="no" type="int" group="csr"/>\n<reg name="hpmcounter28h" bitsize="32" regnum="3293" save-restore="no" type="int" group="csr"/>\n<reg name="hpmcounter29h" bitsize="32" regnum="3294" save-restore="no" type="int" group="csr"/>\n<reg name="hpmcounter30h" bitsize="32" regnum="3295" save-restore="no" type="int" group="csr"/>\n<reg name="hpmcounter31h" bitsize="32" regnum="3296" save-restore="no" type="int" group="csr"/>\n<reg [882 bytes omitted]
[remote] Sending packet: $qTStatus#49
[remote] Packet received:
[remote] packet_ok: Packet qTStatus (trace-status) is NOT supported
[remote] Sending packet: $?#3f
[remote] Packet received: S00
[remote] Sending packet: $qXfer:threads:read::0,1000#92
[remote] Packet received: l<?xml version="1.0"?>\n<threads>\n</threads>\n
[remote] Sending packet: $Hc-1#09
[remote] Packet received: OK
[remote] Sending packet: $qC#b4
[remote] Packet received: QC0
[remote] Sending packet: $qAttached#8f
[remote] Packet received: 1
[remote] packet_ok: Packet qAttached (query-attached) is supported
warning: No executable has been specified and target does not support
determining executable automatically. Try using the "file" command.
[remote] wait: enter
[remote] select_thread_for_ambiguous_stop_reply: enter
[remote] select_thread_for_ambiguous_stop_reply: process_wide_stop = 0
[remote] select_thread_for_ambiguous_stop_reply: first resumed thread is Remote target
[remote] select_thread_for_ambiguous_stop_reply: is this guess ambiguous? = 0
[remote] select_thread_for_ambiguous_stop_reply: exit
[remote] wait: exit
[remote] Sending packet: $g#67
[remote] Packet received: 000000000000000000000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000080
[remote] Sending packet: $qXfer:threads:read::0,1000#92
[remote] Packet received: l<?xml version="1.0"?>\n<threads>\n</threads>\n
[remote] Sending packet: $m80000004,4#59
[remote] Packet received: 93051005
[remote] Sending packet: $m80000000,4#55
[remote] Packet received: 13050005
0x80000000 in ?? ()
[remote] start_remote_1: exit

架构描述文件

riscv描述文件

通信协议日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[remote] Sending packet: $qXfer:features:read:target.xml:0,1000#0c
[remote] Packet received: m<?xml version="1.0"?>\n<!DOCTYPE target SYSTEM "gdb-target.dtd">\n<target version="1.0">\n<architecture>riscv:rv32</architecture>\n<feature name="org.gnu.gdb.riscv.cpu">\n<reg name="zero" bitsize="32" regnum="0" save-restore="yes" type="int" group="general"/>\n<reg name="ra" bitsize="32" regnum="1" save-restore="yes" type="int" group="general"/>\n<reg name="sp" bitsize="32" regnum="2" save-restore="yes" type="int" group="general"/>\n<reg name="gp" bitsize="32" regnum="3" save-restore="yes" type="int" group="genera [3585 bytes omitted]
[remote] Sending packet: $qXfer:features:read:target.xml:1000,1000#9d
[remote] Packet received: m="sstateen0" bitsize="32" regnum="333" save-restore="no" type="int" group="csr"/>\n<reg name="sstateen1" bitsize="32" regnum="334" save-restore="no" type="int" group="csr"/>\n<reg name="sstateen2" bitsize="32" regnum="335" save-restore="no" type="int" group="csr"/>\n<reg name="sstateen3" bitsize="32" regnum="336" save-restore="no" type="int" group="csr"/>\n<reg name="snxti" bitsize="32" regnum="390" save-restore="no" type="int" group="csr"/>\n<reg name="sintstatus" bitsize="32" regnum="391" save-restore="no" ty [3585 bytes omitted]
[remote] Sending packet: $qXfer:features:read:target.xml:2000,1000#9e
[remote] Packet received: mno" type="int" group="csr"/>\n<reg name="mhpmevent8" bitsize="32" regnum="873" save-restore="no" type="int" group="csr"/>\n<reg name="mhpmevent9" bitsize="32" regnum="874" save-restore="no" type="int" group="csr"/>\n<reg name="mhpmevent10" bitsize="32" regnum="875" save-restore="no" type="int" group="csr"/>\n<reg name="mhpmevent11" bitsize="32" regnum="876" save-restore="no" type="int" group="csr"/>\n<reg name="mhpmevent12" bitsize="32" regnum="877" save-restore="no" type="int" group="csr"/>\n<reg name="mhpmeven [3585 bytes omitted]
[remote] Sending packet: $qXfer:features:read:target.xml:3000,1000#9f
[remote] Packet received: m type="int" group="csr"/>\n<reg name="pmpcfg6" bitsize="32" regnum="999" save-restore="no" type="int" group="csr"/>\n<reg name="pmpcfg7" bitsize="32" regnum="1000" save-restore="no" type="int" group="csr"/>\n<reg name="pmpcfg8" bitsize="32" regnum="1001" save-restore="no" type="int" group="csr"/>\n<reg name="pmpcfg9" bitsize="32" regnum="1002" save-restore="no" type="int" group="csr"/>\n<reg name="pmpcfg10" bitsize="32" regnum="1003" save-restore="no" type="int" group="csr"/>\n<reg name="pmpcfg11" bitsize="32" r [3585 bytes omitted]
[remote] Sending packet: $qXfer:features:read:target.xml:4000,1000#a0
[remote] Packet received: m" save-restore="no" type="int" group="csr"/>\n<reg name="pmpaddr35" bitsize="32" regnum="1044" save-restore="no" type="int" group="csr"/>\n<reg name="pmpaddr36" bitsize="32" regnum="1045" save-restore="no" type="int" group="csr"/>\n<reg name="pmpaddr37" bitsize="32" regnum="1046" save-restore="no" type="int" group="csr"/>\n<reg name="pmpaddr38" bitsize="32" regnum="1047" save-restore="no" type="int" group="csr"/>\n<reg name="pmpaddr39" bitsize="32" regnum="1048" save-restore="no" type="int" group="csr"/>\n<reg n [3585 bytes omitted]
[remote] Sending packet: $qXfer:features:read:target.xml:5000,1000#a1
[remote] Packet received: m0h" bitsize="32" regnum="1629" save-restore="no" type="int" group="csr"/>\n<reg name="hstateen1h" bitsize="32" regnum="1630" save-restore="no" type="int" group="csr"/>\n<reg name="hstateen2h" bitsize="32" regnum="1631" save-restore="no" type="int" group="csr"/>\n<reg name="hstateen3h" bitsize="32" regnum="1632" save-restore="no" type="int" group="csr"/>\n<reg name="htval" bitsize="32" regnum="1668" save-restore="no" type="int" group="csr"/>\n<reg name="hip" bitsize="32" regnum="1669" save-restore="no" type="int [3585 bytes omitted]
[remote] Sending packet: $qXfer:features:read:target.xml:6000,1000#a2
[remote] Packet received: m" bitsize="32" regnum="2020" save-restore="no" type="int" group="csr"/>\n<reg name="tinfo" bitsize="32" regnum="2021" save-restore="no" type="int" group="csr"/>\n<reg name="tcontrol" bitsize="32" regnum="2022" save-restore="no" type="int" group="csr"/>\n<reg name="mcontext" bitsize="32" regnum="2025" save-restore="no" type="int" group="csr"/>\n<reg name="mscontext" bitsize="32" regnum="2027" save-restore="no" type="int" group="csr"/>\n<reg name="dcsr" bitsize="32" regnum="2033" save-restore="no" type="int" grou [3585 bytes omitted]
[remote] Sending packet: $qXfer:features:read:target.xml:7000,1000#a3
[remote] Packet received: m type="int" group="csr"/>\n<reg name="mhpmcounter5h" bitsize="32" regnum="3014" save-restore="no" type="int" group="csr"/>\n<reg name="mhpmcounter6h" bitsize="32" regnum="3015" save-restore="no" type="int" group="csr"/>\n<reg name="mhpmcounter7h" bitsize="32" regnum="3016" save-restore="no" type="int" group="csr"/>\n<reg name="mhpmcounter8h" bitsize="32" regnum="3017" save-restore="no" type="int" group="csr"/>\n<reg name="mhpmcounter9h" bitsize="32" regnum="3018" save-restore="no" type="int" group="csr"/>\n<reg [3585 bytes omitted]
[remote] Sending packet: $qXfer:features:read:target.xml:8000,1000#a4
[remote] Packet received: mestore="no" type="int" group="csr"/>\n<reg name="hpmcounter16" bitsize="32" regnum="3153" save-restore="no" type="int" group="csr"/>\n<reg name="hpmcounter17" bitsize="32" regnum="3154" save-restore="no" type="int" group="csr"/>\n<reg name="hpmcounter18" bitsize="32" regnum="3155" save-restore="no" type="int" group="csr"/>\n<reg name="hpmcounter19" bitsize="32" regnum="3156" save-restore="no" type="int" group="csr"/>\n<reg name="hpmcounter20" bitsize="32" regnum="3157" save-restore="no" type="int" group="csr"/> [3585 bytes omitted]
[remote] Sending packet: $qXfer:features:read:target.xml:9000,1000#a5
[remote] Packet received: l type="int" group="csr"/>\n<reg name="hpmcounter27h" bitsize="32" regnum="3292" save-restore="no" type="int" group="csr"/>\n<reg name="hpmcounter28h" bitsize="32" regnum="3293" save-restore="no" type="int" group="csr"/>\n<reg name="hpmcounter29h" bitsize="32" regnum="3294" save-restore="no" type="int" group="csr"/>\n<reg name="hpmcounter30h" bitsize="32" regnum="3295" save-restore="no" type="int" group="csr"/>\n<reg name="hpmcounter31h" bitsize="32" regnum="3296" save-restore="no" type="int" group="csr"/>\n<reg [882 bytes omitted]

xml格式描述文件

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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
<?xml version="1.0"?>
<!DOCTYPE target SYSTEM "gdb-target.dtd">
<target version="1.0">
<architecture>riscv:rv32</architecture>
<feature name="org.gnu.gdb.riscv.cpu">
<reg name="zero" bitsize="32" regnum="0" save-restore="yes" type="int" group="general"/>
<reg name="ra" bitsize="32" regnum="1" save-restore="yes" type="int" group="general"/>
<reg name="sp" bitsize="32" regnum="2" save-restore="yes" type="int" group="general"/>
<reg name="gp" bitsize="32" regnum="3" save-restore="yes" type="int" group="general"/>
<reg name="tp" bitsize="32" regnum="4" save-restore="yes" type="int" group="general"/>
<reg name="t0" bitsize="32" regnum="5" save-restore="yes" type="int" group="general"/>
<reg name="t1" bitsize="32" regnum="6" save-restore="yes" type="int" group="general"/>
<reg name="t2" bitsize="32" regnum="7" save-restore="yes" type="int" group="general"/>
<reg name="fp" bitsize="32" regnum="8" save-restore="yes" type="int" group="general"/>
<reg name="s1" bitsize="32" regnum="9" save-restore="yes" type="int" group="general"/>
<reg name="a0" bitsize="32" regnum="10" save-restore="yes" type="int" group="general"/>
<reg name="a1" bitsize="32" regnum="11" save-restore="yes" type="int" group="general"/>
<reg name="a2" bitsize="32" regnum="12" save-restore="yes" type="int" group="general"/>
<reg name="a3" bitsize="32" regnum="13" save-restore="yes" type="int" group="general"/>
<reg name="a4" bitsize="32" regnum="14" save-restore="yes" type="int" group="general"/>
<reg name="a5" bitsize="32" regnum="15" save-restore="yes" type="int" group="general"/>
<reg name="a6" bitsize="32" regnum="16" save-restore="yes" type="int" group="general"/>
<reg name="a7" bitsize="32" regnum="17" save-restore="yes" type="int" group="general"/>
<reg name="s2" bitsize="32" regnum="18" save-restore="yes" type="int" group="general"/>
<reg name="s3" bitsize="32" regnum="19" save-restore="yes" type="int" group="general"/>
<reg name="s4" bitsize="32" regnum="20" save-restore="yes" type="int" group="general"/>
<reg name="s5" bitsize="32" regnum="21" save-restore="yes" type="int" group="general"/>
<reg name="s6" bitsize="32" regnum="22" save-restore="yes" type="int" group="general"/>
<reg name="s7" bitsize="32" regnum="23" save-restore="yes" type="int" group="general"/>
<reg name="s8" bitsize="32" regnum="24" save-restore="yes" type="int" group="general"/>
<reg name="s9" bitsize="32" regnum="25" save-restore="yes" type="int" group="general"/>
<reg name="s10" bitsize="32" regnum="26" save-restore="yes" type="int" group="general"/>
<reg name="s11" bitsize="32" regnum="27" save-restore="yes" type="int" group="general"/>
<reg name="t3" bitsize="32" regnum="28" save-restore="yes" type="int" group="general"/>
<reg name="t4" bitsize="32" regnum="29" save-restore="yes" type="int" group="general"/>
<reg name="t5" bitsize="32" regnum="30" save-restore="yes" type="int" group="general"/>
<reg name="t6" bitsize="32" regnum="31" save-restore="yes" type="int" group="general"/>
<reg name="pc" bitsize="32" regnum="32" save-restore="yes" type="int" group="general"/>
</feature>
<feature name="org.gnu.gdb.riscv.csr">
<reg name="utvt" bitsize="32" regnum="72" save-restore="no" type="int" group="csr"/>
<reg name="seed" bitsize="32" regnum="86" save-restore="no" type="int" group="csr"/>
<reg name="jvt" bitsize="32" regnum="88" save-restore="no" type="int" group="csr"/>
<reg name="unxti" bitsize="32" regnum="134" save-restore="no" type="int" group="csr"/>
<reg name="uintstatus" bitsize="32" regnum="135" save-restore="no" type="int" group="csr"/>
<reg name="uscratchcsw" bitsize="32" regnum="137" save-restore="no" type="int" group="csr"/>
<reg name="uscratchcswl" bitsize="32" regnum="138" save-restore="no" type="int" group="csr"/>
<reg name="sedeleg" bitsize="32" regnum="323" save-restore="no" type="int" group="csr"/>
<reg name="sideleg" bitsize="32" regnum="324" save-restore="no" type="int" group="csr"/>
<reg name="stvt" bitsize="32" regnum="328" save-restore="no" type="int" group="csr"/>
<reg name="senvcfg" bitsize="32" regnum="331" save-restore="no" type="int" group="csr"/>
<reg name="sstateen0" bitsize="32" regnum="333" save-restore="no" type="int" group="csr"/>
<reg name="sstateen1" bitsize="32" regnum="334" save-restore="no" type="int" group="csr"/>
<reg name="sstateen2" bitsize="32" regnum="335" save-restore="no" type="int" group="csr"/>
<reg name="sstateen3" bitsize="32" regnum="336" save-restore="no" type="int" group="csr"/>
<reg name="snxti" bitsize="32" regnum="390" save-restore="no" type="int" group="csr"/>
<reg name="sintstatus" bitsize="32" regnum="391" save-restore="no" type="int" group="csr"/>
<reg name="sscratchcsw" bitsize="32" regnum="393" save-restore="no" type="int" group="csr"/>
<reg name="sscratchcswl" bitsize="32" regnum="394" save-restore="no" type="int" group="csr"/>
<reg name="stimecmp" bitsize="32" regnum="398" save-restore="no" type="int" group="csr"/>
<reg name="stimecmph" bitsize="32" regnum="414" save-restore="no" type="int" group="csr"/>
<reg name="vsstatus" bitsize="32" regnum="577" save-restore="no" type="int" group="csr"/>
<reg name="vsie" bitsize="32" regnum="581" save-restore="no" type="int" group="csr"/>
<reg name="vstvec" bitsize="32" regnum="582" save-restore="no" type="int" group="csr"/>
<reg name="vsscratch" bitsize="32" regnum="641" save-restore="no" type="int" group="csr"/>
<reg name="vsepc" bitsize="32" regnum="642" save-restore="no" type="int" group="csr"/>
<reg name="vscause" bitsize="32" regnum="643" save-restore="no" type="int" group="csr"/>
<reg name="vstval" bitsize="32" regnum="644" save-restore="no" type="int" group="csr"/>
<reg name="vsip" bitsize="32" regnum="645" save-restore="no" type="int" group="csr"/>
<reg name="vstimecmp" bitsize="32" regnum="654" save-restore="no" type="int" group="csr"/>
<reg name="vstimecmph" bitsize="32" regnum="670" save-restore="no" type="int" group="csr"/>
<reg name="vsatp" bitsize="32" regnum="705" save-restore="no" type="int" group="csr"/>
<reg name="mstatus" bitsize="32" regnum="833" save-restore="no" type="int" group="csr"/>
<reg name="misa" bitsize="32" regnum="834" save-restore="no" type="int" group="csr"/>
<reg name="mie" bitsize="32" regnum="837" save-restore="no" type="int" group="csr"/>
<reg name="mtvec" bitsize="32" regnum="838" save-restore="no" type="int" group="csr"/>
<reg name="mtvt" bitsize="32" regnum="840" save-restore="no" type="int" group="csr"/>
<reg name="mvien" bitsize="32" regnum="841" save-restore="no" type="int" group="csr"/>
<reg name="mvip" bitsize="32" regnum="842" save-restore="no" type="int" group="csr"/>
<reg name="menvcfg" bitsize="32" regnum="843" save-restore="no" type="int" group="csr"/>
<reg name="mstateen0" bitsize="32" regnum="845" save-restore="no" type="int" group="csr"/>
<reg name="mstateen1" bitsize="32" regnum="846" save-restore="no" type="int" group="csr"/>
<reg name="mstateen2" bitsize="32" regnum="847" save-restore="no" type="int" group="csr"/>
<reg name="mstateen3" bitsize="32" regnum="848" save-restore="no" type="int" group="csr"/>
<reg name="mstatush" bitsize="32" regnum="849" save-restore="no" type="int" group="csr"/>
<reg name="mieh" bitsize="32" regnum="853" save-restore="no" type="int" group="csr"/>
<reg name="menvcfgh" bitsize="32" regnum="859" save-restore="no" type="int" group="csr"/>
<reg name="mstateen0h" bitsize="32" regnum="861" save-restore="no" type="int" group="csr"/>
<reg name="mstateen1h" bitsize="32" regnum="862" save-restore="no" type="int" group="csr"/>
<reg name="mstateen2h" bitsize="32" regnum="863" save-restore="no" type="int" group="csr"/>
<reg name="mstateen3h" bitsize="32" regnum="864" save-restore="no" type="int" group="csr"/>
<reg name="mcountinhibit" bitsize="32" regnum="865" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent3" bitsize="32" regnum="868" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent4" bitsize="32" regnum="869" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent5" bitsize="32" regnum="870" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent6" bitsize="32" regnum="871" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent7" bitsize="32" regnum="872" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent8" bitsize="32" regnum="873" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent9" bitsize="32" regnum="874" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent10" bitsize="32" regnum="875" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent11" bitsize="32" regnum="876" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent12" bitsize="32" regnum="877" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent13" bitsize="32" regnum="878" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent14" bitsize="32" regnum="879" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent15" bitsize="32" regnum="880" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent16" bitsize="32" regnum="881" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent17" bitsize="32" regnum="882" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent18" bitsize="32" regnum="883" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent19" bitsize="32" regnum="884" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent20" bitsize="32" regnum="885" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent21" bitsize="32" regnum="886" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent22" bitsize="32" regnum="887" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent23" bitsize="32" regnum="888" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent24" bitsize="32" regnum="889" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent25" bitsize="32" regnum="890" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent26" bitsize="32" regnum="891" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent27" bitsize="32" regnum="892" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent28" bitsize="32" regnum="893" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent29" bitsize="32" regnum="894" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent30" bitsize="32" regnum="895" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent31" bitsize="32" regnum="896" save-restore="no" type="int" group="csr"/>
<reg name="mscratch" bitsize="32" regnum="897" save-restore="no" type="int" group="csr"/>
<reg name="mepc" bitsize="32" regnum="898" save-restore="no" type="int" group="csr"/>
<reg name="mcause" bitsize="32" regnum="899" save-restore="no" type="int" group="csr"/>
<reg name="mtval" bitsize="32" regnum="900" save-restore="no" type="int" group="csr"/>
<reg name="mip" bitsize="32" regnum="901" save-restore="no" type="int" group="csr"/>
<reg name="mnxti" bitsize="32" regnum="902" save-restore="no" type="int" group="csr"/>
<reg name="mintstatus" bitsize="32" regnum="903" save-restore="no" type="int" group="csr"/>
<reg name="mscratchcsw" bitsize="32" regnum="905" save-restore="no" type="int" group="csr"/>
<reg name="mscratchcswl" bitsize="32" regnum="906" save-restore="no" type="int" group="csr"/>
<reg name="mtinst" bitsize="32" regnum="907" save-restore="no" type="int" group="csr"/>
<reg name="mtval2" bitsize="32" regnum="908" save-restore="no" type="int" group="csr"/>
<reg name="miselect" bitsize="32" regnum="913" save-restore="no" type="int" group="csr"/>
<reg name="mireg" bitsize="32" regnum="914" save-restore="no" type="int" group="csr"/>
<reg name="miph" bitsize="32" regnum="917" save-restore="no" type="int" group="csr"/>
<reg name="mtopei" bitsize="32" regnum="925" save-restore="no" type="int" group="csr"/>
<reg name="pmpcfg0" bitsize="32" regnum="993" save-restore="no" type="int" group="csr"/>
<reg name="pmpcfg1" bitsize="32" regnum="994" save-restore="no" type="int" group="csr"/>
<reg name="pmpcfg2" bitsize="32" regnum="995" save-restore="no" type="int" group="csr"/>
<reg name="pmpcfg3" bitsize="32" regnum="996" save-restore="no" type="int" group="csr"/>
<reg name="pmpcfg4" bitsize="32" regnum="997" save-restore="no" type="int" group="csr"/>
<reg name="pmpcfg5" bitsize="32" regnum="998" save-restore="no" type="int" group="csr"/>
<reg name="pmpcfg6" bitsize="32" regnum="999" save-restore="no" type="int" group="csr"/>
<reg name="pmpcfg7" bitsize="32" regnum="1000" save-restore="no" type="int" group="csr"/>
<reg name="pmpcfg8" bitsize="32" regnum="1001" save-restore="no" type="int" group="csr"/>
<reg name="pmpcfg9" bitsize="32" regnum="1002" save-restore="no" type="int" group="csr"/>
<reg name="pmpcfg10" bitsize="32" regnum="1003" save-restore="no" type="int" group="csr"/>
<reg name="pmpcfg11" bitsize="32" regnum="1004" save-restore="no" type="int" group="csr"/>
<reg name="pmpcfg12" bitsize="32" regnum="1005" save-restore="no" type="int" group="csr"/>
<reg name="pmpcfg13" bitsize="32" regnum="1006" save-restore="no" type="int" group="csr"/>
<reg name="pmpcfg14" bitsize="32" regnum="1007" save-restore="no" type="int" group="csr"/>
<reg name="pmpcfg15" bitsize="32" regnum="1008" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr0" bitsize="32" regnum="1009" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr1" bitsize="32" regnum="1010" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr2" bitsize="32" regnum="1011" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr3" bitsize="32" regnum="1012" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr4" bitsize="32" regnum="1013" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr5" bitsize="32" regnum="1014" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr6" bitsize="32" regnum="1015" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr7" bitsize="32" regnum="1016" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr8" bitsize="32" regnum="1017" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr9" bitsize="32" regnum="1018" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr10" bitsize="32" regnum="1019" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr11" bitsize="32" regnum="1020" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr12" bitsize="32" regnum="1021" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr13" bitsize="32" regnum="1022" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr14" bitsize="32" regnum="1023" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr15" bitsize="32" regnum="1024" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr16" bitsize="32" regnum="1025" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr17" bitsize="32" regnum="1026" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr18" bitsize="32" regnum="1027" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr19" bitsize="32" regnum="1028" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr20" bitsize="32" regnum="1029" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr21" bitsize="32" regnum="1030" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr22" bitsize="32" regnum="1031" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr23" bitsize="32" regnum="1032" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr24" bitsize="32" regnum="1033" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr25" bitsize="32" regnum="1034" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr26" bitsize="32" regnum="1035" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr27" bitsize="32" regnum="1036" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr28" bitsize="32" regnum="1037" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr29" bitsize="32" regnum="1038" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr30" bitsize="32" regnum="1039" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr31" bitsize="32" regnum="1040" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr32" bitsize="32" regnum="1041" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr33" bitsize="32" regnum="1042" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr34" bitsize="32" regnum="1043" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr35" bitsize="32" regnum="1044" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr36" bitsize="32" regnum="1045" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr37" bitsize="32" regnum="1046" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr38" bitsize="32" regnum="1047" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr39" bitsize="32" regnum="1048" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr40" bitsize="32" regnum="1049" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr41" bitsize="32" regnum="1050" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr42" bitsize="32" regnum="1051" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr43" bitsize="32" regnum="1052" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr44" bitsize="32" regnum="1053" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr45" bitsize="32" regnum="1054" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr46" bitsize="32" regnum="1055" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr47" bitsize="32" regnum="1056" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr48" bitsize="32" regnum="1057" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr49" bitsize="32" regnum="1058" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr50" bitsize="32" regnum="1059" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr51" bitsize="32" regnum="1060" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr52" bitsize="32" regnum="1061" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr53" bitsize="32" regnum="1062" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr54" bitsize="32" regnum="1063" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr55" bitsize="32" regnum="1064" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr56" bitsize="32" regnum="1065" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr57" bitsize="32" regnum="1066" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr58" bitsize="32" regnum="1067" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr59" bitsize="32" regnum="1068" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr60" bitsize="32" regnum="1069" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr61" bitsize="32" regnum="1070" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr62" bitsize="32" regnum="1071" save-restore="no" type="int" group="csr"/>
<reg name="pmpaddr63" bitsize="32" regnum="1072" save-restore="no" type="int" group="csr"/>
<reg name="scontext" bitsize="32" regnum="1513" save-restore="no" type="int" group="csr"/>
<reg name="hstatus" bitsize="32" regnum="1601" save-restore="no" type="int" group="csr"/>
<reg name="hedeleg" bitsize="32" regnum="1603" save-restore="no" type="int" group="csr"/>
<reg name="hideleg" bitsize="32" regnum="1604" save-restore="no" type="int" group="csr"/>
<reg name="hie" bitsize="32" regnum="1605" save-restore="no" type="int" group="csr"/>
<reg name="htimedelta" bitsize="32" regnum="1606" save-restore="no" type="int" group="csr"/>
<reg name="hcounteren" bitsize="32" regnum="1607" save-restore="no" type="int" group="csr"/>
<reg name="hgeie" bitsize="32" regnum="1608" save-restore="no" type="int" group="csr"/>
<reg name="henvcfg" bitsize="32" regnum="1611" save-restore="no" type="int" group="csr"/>
<reg name="hstateen0" bitsize="32" regnum="1613" save-restore="no" type="int" group="csr"/>
<reg name="hstateen1" bitsize="32" regnum="1614" save-restore="no" type="int" group="csr"/>
<reg name="hstateen2" bitsize="32" regnum="1615" save-restore="no" type="int" group="csr"/>
<reg name="hstateen3" bitsize="32" regnum="1616" save-restore="no" type="int" group="csr"/>
<reg name="htimedeltah" bitsize="32" regnum="1622" save-restore="no" type="int" group="csr"/>
<reg name="henvcfgh" bitsize="32" regnum="1627" save-restore="no" type="int" group="csr"/>
<reg name="hstateen0h" bitsize="32" regnum="1629" save-restore="no" type="int" group="csr"/>
<reg name="hstateen1h" bitsize="32" regnum="1630" save-restore="no" type="int" group="csr"/>
<reg name="hstateen2h" bitsize="32" regnum="1631" save-restore="no" type="int" group="csr"/>
<reg name="hstateen3h" bitsize="32" regnum="1632" save-restore="no" type="int" group="csr"/>
<reg name="htval" bitsize="32" regnum="1668" save-restore="no" type="int" group="csr"/>
<reg name="hip" bitsize="32" regnum="1669" save-restore="no" type="int" group="csr"/>
<reg name="hvip" bitsize="32" regnum="1670" save-restore="no" type="int" group="csr"/>
<reg name="htinst" bitsize="32" regnum="1675" save-restore="no" type="int" group="csr"/>
<reg name="hgatp" bitsize="32" regnum="1729" save-restore="no" type="int" group="csr"/>
<reg name="hcontext" bitsize="32" regnum="1769" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent3h" bitsize="32" regnum="1892" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent4h" bitsize="32" regnum="1893" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent5h" bitsize="32" regnum="1894" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent6h" bitsize="32" regnum="1895" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent7h" bitsize="32" regnum="1896" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent8h" bitsize="32" regnum="1897" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent9h" bitsize="32" regnum="1898" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent10h" bitsize="32" regnum="1899" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent11h" bitsize="32" regnum="1900" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent12h" bitsize="32" regnum="1901" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent13h" bitsize="32" regnum="1902" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent14h" bitsize="32" regnum="1903" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent15h" bitsize="32" regnum="1904" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent16h" bitsize="32" regnum="1905" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent17h" bitsize="32" regnum="1906" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent18h" bitsize="32" regnum="1907" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent19h" bitsize="32" regnum="1908" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent20h" bitsize="32" regnum="1909" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent21h" bitsize="32" regnum="1910" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent22h" bitsize="32" regnum="1911" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent23h" bitsize="32" regnum="1912" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent24h" bitsize="32" regnum="1913" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent25h" bitsize="32" regnum="1914" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent26h" bitsize="32" regnum="1915" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent27h" bitsize="32" regnum="1916" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent28h" bitsize="32" regnum="1917" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent29h" bitsize="32" regnum="1918" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent30h" bitsize="32" regnum="1919" save-restore="no" type="int" group="csr"/>
<reg name="mhpmevent31h" bitsize="32" regnum="1920" save-restore="no" type="int" group="csr"/>
<reg name="mseccfg" bitsize="32" regnum="1928" save-restore="no" type="int" group="csr"/>
<reg name="mseccfgh" bitsize="32" regnum="1944" save-restore="no" type="int" group="csr"/>
<reg name="tselect" bitsize="32" regnum="2017" save-restore="no" type="int" group="csr"/>
<reg name="tdata1" bitsize="32" regnum="2018" save-restore="no" type="int" group="csr"/>
<reg name="tdata2" bitsize="32" regnum="2019" save-restore="no" type="int" group="csr"/>
<reg name="tdata3" bitsize="32" regnum="2020" save-restore="no" type="int" group="csr"/>
<reg name="tinfo" bitsize="32" regnum="2021" save-restore="no" type="int" group="csr"/>
<reg name="tcontrol" bitsize="32" regnum="2022" save-restore="no" type="int" group="csr"/>
<reg name="mcontext" bitsize="32" regnum="2025" save-restore="no" type="int" group="csr"/>
<reg name="mscontext" bitsize="32" regnum="2027" save-restore="no" type="int" group="csr"/>
<reg name="dcsr" bitsize="32" regnum="2033" save-restore="no" type="int" group="csr"/>
<reg name="dpc" bitsize="32" regnum="2034" save-restore="no" type="int" group="csr"/>
<reg name="dscratch0" bitsize="32" regnum="2035" save-restore="no" type="int" group="csr"/>
<reg name="dscratch1" bitsize="32" regnum="2036" save-restore="no" type="int" group="csr"/>
<reg name="mcycle" bitsize="32" regnum="2881" save-restore="no" type="int" group="csr"/>
<reg name="minstret" bitsize="32" regnum="2883" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter3" bitsize="32" regnum="2884" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter4" bitsize="32" regnum="2885" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter5" bitsize="32" regnum="2886" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter6" bitsize="32" regnum="2887" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter7" bitsize="32" regnum="2888" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter8" bitsize="32" regnum="2889" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter9" bitsize="32" regnum="2890" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter10" bitsize="32" regnum="2891" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter11" bitsize="32" regnum="2892" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter12" bitsize="32" regnum="2893" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter13" bitsize="32" regnum="2894" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter14" bitsize="32" regnum="2895" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter15" bitsize="32" regnum="2896" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter16" bitsize="32" regnum="2897" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter17" bitsize="32" regnum="2898" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter18" bitsize="32" regnum="2899" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter19" bitsize="32" regnum="2900" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter20" bitsize="32" regnum="2901" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter21" bitsize="32" regnum="2902" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter22" bitsize="32" regnum="2903" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter23" bitsize="32" regnum="2904" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter24" bitsize="32" regnum="2905" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter25" bitsize="32" regnum="2906" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter26" bitsize="32" regnum="2907" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter27" bitsize="32" regnum="2908" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter28" bitsize="32" regnum="2909" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter29" bitsize="32" regnum="2910" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter30" bitsize="32" regnum="2911" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter31" bitsize="32" regnum="2912" save-restore="no" type="int" group="csr"/>
<reg name="mcycleh" bitsize="32" regnum="3009" save-restore="no" type="int" group="csr"/>
<reg name="minstreth" bitsize="32" regnum="3011" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter3h" bitsize="32" regnum="3012" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter4h" bitsize="32" regnum="3013" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter5h" bitsize="32" regnum="3014" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter6h" bitsize="32" regnum="3015" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter7h" bitsize="32" regnum="3016" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter8h" bitsize="32" regnum="3017" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter9h" bitsize="32" regnum="3018" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter10h" bitsize="32" regnum="3019" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter11h" bitsize="32" regnum="3020" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter12h" bitsize="32" regnum="3021" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter13h" bitsize="32" regnum="3022" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter14h" bitsize="32" regnum="3023" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter15h" bitsize="32" regnum="3024" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter16h" bitsize="32" regnum="3025" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter17h" bitsize="32" regnum="3026" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter18h" bitsize="32" regnum="3027" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter19h" bitsize="32" regnum="3028" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter20h" bitsize="32" regnum="3029" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter21h" bitsize="32" regnum="3030" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter22h" bitsize="32" regnum="3031" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter23h" bitsize="32" regnum="3032" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter24h" bitsize="32" regnum="3033" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter25h" bitsize="32" regnum="3034" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter26h" bitsize="32" regnum="3035" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter27h" bitsize="32" regnum="3036" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter28h" bitsize="32" regnum="3037" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter29h" bitsize="32" regnum="3038" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter30h" bitsize="32" regnum="3039" save-restore="no" type="int" group="csr"/>
<reg name="mhpmcounter31h" bitsize="32" regnum="3040" save-restore="no" type="int" group="csr"/>
<reg name="cycle" bitsize="32" regnum="3137" save-restore="no" type="int" group="csr"/>
<reg name="time" bitsize="32" regnum="3138" save-restore="no" type="int" group="csr"/>
<reg name="instret" bitsize="32" regnum="3139" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter3" bitsize="32" regnum="3140" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter4" bitsize="32" regnum="3141" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter5" bitsize="32" regnum="3142" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter6" bitsize="32" regnum="3143" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter7" bitsize="32" regnum="3144" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter8" bitsize="32" regnum="3145" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter9" bitsize="32" regnum="3146" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter10" bitsize="32" regnum="3147" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter11" bitsize="32" regnum="3148" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter12" bitsize="32" regnum="3149" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter13" bitsize="32" regnum="3150" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter14" bitsize="32" regnum="3151" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter15" bitsize="32" regnum="3152" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter16" bitsize="32" regnum="3153" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter17" bitsize="32" regnum="3154" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter18" bitsize="32" regnum="3155" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter19" bitsize="32" regnum="3156" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter20" bitsize="32" regnum="3157" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter21" bitsize="32" regnum="3158" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter22" bitsize="32" regnum="3159" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter23" bitsize="32" regnum="3160" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter24" bitsize="32" regnum="3161" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter25" bitsize="32" regnum="3162" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter26" bitsize="32" regnum="3163" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter27" bitsize="32" regnum="3164" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter28" bitsize="32" regnum="3165" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter29" bitsize="32" regnum="3166" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter30" bitsize="32" regnum="3167" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter31" bitsize="32" regnum="3168" save-restore="no" type="int" group="csr"/>
<reg name="cycleh" bitsize="32" regnum="3265" save-restore="no" type="int" group="csr"/>
<reg name="timeh" bitsize="32" regnum="3266" save-restore="no" type="int" group="csr"/>
<reg name="instreth" bitsize="32" regnum="3267" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter3h" bitsize="32" regnum="3268" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter4h" bitsize="32" regnum="3269" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter5h" bitsize="32" regnum="3270" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter6h" bitsize="32" regnum="3271" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter7h" bitsize="32" regnum="3272" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter8h" bitsize="32" regnum="3273" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter9h" bitsize="32" regnum="3274" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter10h" bitsize="32" regnum="3275" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter11h" bitsize="32" regnum="3276" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter12h" bitsize="32" regnum="3277" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter13h" bitsize="32" regnum="3278" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter14h" bitsize="32" regnum="3279" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter15h" bitsize="32" regnum="3280" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter16h" bitsize="32" regnum="3281" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter17h" bitsize="32" regnum="3282" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter18h" bitsize="32" regnum="3283" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter19h" bitsize="32" regnum="3284" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter20h" bitsize="32" regnum="3285" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter21h" bitsize="32" regnum="3286" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter22h" bitsize="32" regnum="3287" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter23h" bitsize="32" regnum="3288" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter24h" bitsize="32" regnum="3289" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter25h" bitsize="32" regnum="3290" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter26h" bitsize="32" regnum="3291" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter27h" bitsize="32" regnum="3292" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter28h" bitsize="32" regnum="3293" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter29h" bitsize="32" regnum="3294" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter30h" bitsize="32" regnum="3295" save-restore="no" type="int" group="csr"/>
<reg name="hpmcounter31h" bitsize="32" regnum="3296" save-restore="no" type="int" group="csr"/>
<reg name="scountovf" bitsize="32" regnum="3553" save-restore="no" type="int" group="csr"/>
<reg name="hgeip" bitsize="32" regnum="3667" save-restore="no" type="int" group="csr"/>
<reg name="mvendorid" bitsize="32" regnum="3922" save-restore="no" type="int" group="csr"/>
<reg name="marchid" bitsize="32" regnum="3923" save-restore="no" type="int" group="csr"/>
<reg name="mimpid" bitsize="32" regnum="3924" save-restore="no" type="int" group="csr"/>
<reg name="mhartid" bitsize="32" regnum="3925" save-restore="no" type="int" group="csr"/>
<reg name="mconfigptr" bitsize="32" regnum="3926" save-restore="no" type="int" group="csr"/>
<reg name="mtopi" bitsize="32" regnum="4081" save-restore="no" type="int" group="csr"/>
</feature>
<feature name="org.gnu.gdb.riscv.virtual">
<reg name="priv" bitsize="8" regnum="4161" save-restore="no" type="int" group="general"/>
</feature>
</target>

注意事项

指令差异

ARM处理器具有ARMThumb两种指令,相互切换一般采用b类指令进行跳转,否则处理器会返回异常。

分布式基础理论

CAP理论基本概念

  • 数据一致性(consistency):若系统更新操作成功,则它之后的读请求都必须读到这个新数据;若失败,则所有读请求都不能读到这个新数据,对调用者而言数据具有强一致性(strong consistency)
  • 服务可用性(availability):所有请求在一定时间内得到响应,可终止,不会一直等待
  • 分区容错性(partition-tolerance):在网络分区的情况下,被分隔的节点仍能正常对外服务

CAP三者不空兼得

若放弃P,即系统称为传统的单服务器模式;而分布式必然不能放弃P;

实现P就允许网络存在异常。若实现CP,向一个分区的更新操作因一致性要求,必然导致网络恢复前,其他分区不能满足可用性。若实现AP,存在不同分区因网络异常,无法保证强一致性。

因此,分布式系统理论上不可能选择CA架构,只能选择CP或AP架构。

Base理论

CAP理论的一种妥协,由于CAP只能二取一,Base理论降低了发生分区容错时对可用性和一致性的要求。

  1. 基本可用:允许可用性降低(可能响应延长、可能服务降级)
  2. 软状态:指允许系统中的数据存在中间状态,并任务该中间状态不会影响系统整体可用性(比如支付完成前的支付中)
  3. 最终一致性:节点数据同步可能存在时延,但在一定的期限后必须达成数据的一致,状态变为最终状态

数据一致性模型

强一致性:当更新操作完成后,任何多个后续进程的访问都会返回更新后的数据。根据CAP理论,这种实现需要牺牲可用性。

弱一致性:当更新操作完成后,不承诺立即可以读到更新后的数据,而这段时间存在“不一致性窗口”。

最终一致性:是弱一致性的特例,强调的是所有的数据副本,在经过一定时间的同步后,最终能够达到一个一致的状态。达到最终一致性的时间,就是不一致窗口时间。在没有故障发生的前提下,不一致窗口的时间主要受通信延迟,系统负载和复制副本的个数影响。

最终一致性模型根据其提供的不同保证可以划分为因果一致性和会话一致性等等模型。

因果一致性:要求有因果关系的操作顺序要得到保证,非因果关系的操作顺序则无所谓。

会话一致性:将对系统数据的访问过程框定在了一个会话当中,约定了系统保证在同一个有效的会话中实现“读己之所写”的一致性。

Quorum、WARO机制

WARO:一种简单的副本控制协议,写操作时,只有当所有副本都成功更新之后,这次写操作才算成功,否则视为失败。优先保证读、任何节点读到的数据都是最新数据,牺牲了更新服务的可用性。但只要有一个节点存活,任然能够提供读服务。

选举算法Quorum机制:如,10个副本,一次成功更新了三个,那么需要读取八个副本的数据,可以保证读到最新数据。无法保证强一致性,也就是无法实现任何时刻或节点都可以读到最近一次成功提交的副本数据。需要配合一个获取最新成功提交的版本号的metadata服务,这样可以确定最新成功提交的版本号,然后从已经读到的数据中就可以确认最新写入的数据。

模拟vexpress-a9

使用QEMU模拟vexpress-a9开发板,网络上教程比较多,可以快速搭建好开发环境。

基本环境

项目信息
操作系统Ubuntu 22.04
内核linux-4.14.334
根文件系统busybox-1.36.1
引导程序u-boot-2022.10-rc5

安装依赖

1
sudo apt-get install -y make gcc bc libncurses5-dev bison flex libssl-dev u-boot-tools gcc-arm-linux-gnueabi g++-arm-linux-gnueabi qemu-system-arm

Linux内核

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 使用vexpress默认配置编译
cd linux-4.14.334
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- vexpress_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- menuconfig
# 开启NFS4支持。
# 位置:File System -> Network File Systems->NFS client support for NFS version 4 (相关的四项全勾上)

# 编译内核
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- zImage -j$(nproc)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- LOADADDR=0x60003000 uImage -j$(nproc)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- modules -j$(nproc)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- dtbs -j$(nproc)
cd ..

# 启动模拟器
qemu-system-arm -M vexpress-a9 -m 128M -kernel linux-4.14.334/arch/arm/boot/zImage -dtb linux-4.14.334/arch/arm/boot/dts/vexpress-v2p-ca9.dtb -nographic -append "console=ttyAMA0"
# 这里注意文件的路径,是在linux-4.14.334源码目录执行的。
# -M vexpress-a9 模拟vexpress-a9单板,你能够使用-M ?參数来获取该qemu版本号支持的全部单板
# -m 128M 单板执行物理内存128M
# -kernel xx/zImage 告诉qemu单板执行内核镜像路径
# -dtb xx/vexpress-v2p-ca9.dtb 告诉qemu单板的设备树(必须加入)
# -nographic 不使用图形化界面,仅仅使用串口
# -append "console=ttyAMA0" 内核启动參数。这里告诉内核vexpress单板执行。串口设备是哪个tty。

提示没有根文件系统,接下来制作它。

根文件系统

配置编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cd busybox-1.36.1

# 配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- clean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- menuconfig
# 配置页面选择
# Settings->Build Options->Build static binary

# 编译
# 默认安装在_install路径中
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- -j$(nproc)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- install

cd ..

补充根文件系统

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
88
89
90
# 拷贝基本文件系统
mkdir rootfs-arm32
cd rootfs-arm32
cp -rfd ../busybox-1.36.1/_install/* .

# 创建设备节点
mkdir dev
sudo mknod -m 666 dev/tty1 c 4 1
sudo mknod -m 666 dev/tty2 c 4 2
sudo mknod -m 666 dev/tty3 c 4 3
sudo mknod -m 666 dev/tty4 c 4 4
sudo mknod -m 666 dev/console c 5 1
sudo mknod -m 666 dev/null c 1 3

# 安装动态链接库
mkdir lib
cp -d /usr/arm-linux-gnueabi/lib/*.so* ./lib

# 配置初始化进程rcS
mkdir -p etc/init.d
touch etc/init.d/rcS
chmod 777 etc/init.d/rcS
cat > etc/init.d/rcS <<EOF
#!/bin/sh
PATH=/bin:/sbin:/usr/bin:/usr/sbin
export LD_LIBRARY_PATH=/lib:/usr/lib
/bin/mount -n -t ramfs ramfs /var
/bin/mount -n -t ramfs ramfs /tmp
/bin/mount -n -t sysfs none /sys
/bin/mount -n -t ramfs none /dev
/bin/mkdir /var/tmp
/bin/mkdir /var/modules
/bin/mkdir /var/run
/bin/mkdir /var/log
/bin/mkdir -p /dev/pts
/bin/mkdir -p /dev/shm
/sbin/mdev -s
/bin/mount -a
echo "-----------------------------------"
echo "*****welcome to vexpress board*****"
echo "-----------------------------------"
EOF

# 配置文件系统fstab
cat > etc/fstab <<EOF
proc /proc proc defaults 0 0
none /dev/pts devpts mode=0622 0 0
mdev /dev ramfs defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /dev/shm tmpfs defaults 0 0
tmpfs /dev tmpfs defaults 0 0
tmpfs /mnt tmpfs defaults 0 0
var /dev tmpfs defaults 0 0
ramfs /dev ramfs defaults 0 0
EOF

# 配置初始化脚本
cat > etc/inittab <<EOF
::sysinit:/etc/init.d/rcS
::askfirst:-/bin/sh
::restart:/sbin/init
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
EOF

# 配置环境变量,'EOF'可以防止内容被解析
cat > etc/profile <<'EOF'
#!/bin/sh
USER="root"
LOGNAME=$USER
# export HOSTNAME=vexpress-a9
export HOSTNAME=`cat /etc/sysconfig/HOSTNAME`
export USER=root
export HOME=root
export PS1="[$USER@$HOSTNAME:\w]\#"
PATH=/bin:/sbin:/usr/bin:/usr/sbin
LD_LIBRARY_PATH=/lib:/usr/lib:$LD_LIBRARY_PATH
export PATH LD_LIBRARY_PATH
EOF

# 增加主机名
mkdir etc/sysconfig
cat > etc/sysconfig/HOSTNAME <<EOF
vexpress-a9
EOF

# 创建其他文件夹
mkdir mnt proc root sys tmp var

cd ..

接下来需要制作根文件系统,有两种方式

方式一:模拟SD卡方式启动

这种方式非常简单,但不适合需要经常修改文件系统中的文件的场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 创建SD卡根文件系统镜像
sudo mkdir /mnt/rootfs
sudo chmod 777 /mnt/rootfs
dd if=/dev/zero of=rootfs-arm32.ext3 bs=1M count=64
mkfs.ext3 rootfs-arm32.ext3
sudo mount -t ext3 rootfs-arm32.ext3 /mnt/rootfs -o loop
sudo cp -rf rootfs-arm32/* /mnt/rootfs/
sudo umount /mnt/rootfs/

# 启动模拟器
qemu-system-arm -M vexpress-a9 \
-m 512M \
-kernel linux-4.14.334/arch/arm/boot/zImage \
-dtb linux-4.14.334/arch/arm/boot/dts/vexpress-v2p-ca9.dtb \
-nographic \
-append "root=/dev/mmcblk0 rw console=ttyAMA0" \
-sd rootfs-arm32.ext3

方式二:通过NFS挂载根文件系统

首先,宿主机需要安装配置nfs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 安装依赖
sudo apt-get install -y nfs-kernel-server

# 配置nfs
sudo mkdir -p /sync/rootfs
sudo chmod 777 -R /home/johnny/project/ldd4/rootfs-arm32
sudo cat >>/etc/exports <<EOF
/home/johnny/project/ldd4/rootfs-arm32 *(rw,sync,no_root_squash,no_subtree_check)
EOF
# 为解决Linux内核与NFS服务器的兼容问题
# 设置Ubuntu20.04的NFS,使之兼容NFS-V2和NFS-V3并增加调试功能。
sudo sed -i 's/\(^RPCSVCGSSDOPTS="\).*/\1--nfs-version 2,3,4 --debug --syslog"/g' /etc/default/nfs-kernel-server

# 重启nfs服务
sudo systemctl restart rpcbind
sudo systemctl restart nfs-kernel-server

配置主机网络

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 安装依赖
sudo apt-get install -y uml-utilities bridge-utils

# 配置网桥
cat > /etc/network/interfaces <<EOF
auto lo
iface lo inet loopback

auto eth0

auto br0
iface br0 inet dhcp
bridge_ports eth0
EOF

# 创建tap0网卡,用于连接qemu虚拟开发板
sudo tunctl -u root -t tap0
sudo ifconfig tap0 172.16.16.10 promisc up

启动模拟器

1
2
3
4
5
6
7
8
9
sudo qemu-system-arm \
-M vexpress-a9 \
-m 512M \
-kernel linux-4.14.334/arch/arm/boot/zImage \
-dtb linux-4.14.334/arch/arm/boot/dts/vexpress-v2p-ca9.dtb \
-net tap,ifname=tap0,script=no,downscript=no,id=net0 \
-net nic,macaddr=00:16:3e:00:00:01 \
-nographic \
-append "root=/dev/nfs rw nfsroot=172.16.16.10:/home/johnny/project/ldd4/rootfs-arm32,proto=tcp,nfsvers=3,nolock init=/linuxrc console=ttyAMA0 ip=172.16.16.20"

100ask-qemu

目前只支持ubuntu 16.04和18.04,需要拥有桌面环境。

已经支持模拟网卡、LCD显示、LED灯、按键、AT24CXX I2C存储芯片的功能

具体参考百问网嵌入式Linux

Linux驱动开发

与通用主机的差异是使用的内核与交叉编译工具不一样,详见Makefile文件

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
ifeq ($(KERNELRELEASE),)

ifeq ($(ARCH),arm)
KERNELDIR ?= /home/johnny/project/ldd4/linux-4.14.334
OBJECTDIR ?= /home/johnny/project/ldd4/objects/vexpress-v2p-ca9
ROOTFS ?= /home/johnny/project/ldd4/rootfs-arm32
CROSS_COMPILE ?= arm-linux-gnueabi-
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)

modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) CROSS_COMPILE=$(CROSS_COMPILE) O=$(OBJECTDIR) modules

modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) CROSS_COMPILE=$(CROSS_COMPILE) O=$(OBJECTDIR) modules INSTALL_MOD_PATH=$(ROOTFS) modules_install

clean:
rm -rf *.o *.ko .*.cmd *.mod* modules.order Module.symvers .tmp_versions

else
obj-m += globalfifo.o

endif

环境搭建

Linux驱动开发依赖驱动运行内核的版本和对应的编译工具。内核版本查看使用命令uname -r

对于嵌入式来说,一般都是源码编译,编译驱动时需要指定目标系统的内核源码。此外,需要设置交叉编译工具变量CROSS_COMPILE为指定的工具。

对于通用内核来说,可以使用包管理工具直接下载内核,例如在Ubuntu系统中使用命令sudo apt-get install -y linux-headers-$(uname -r)。默认内核安装路径在/lib/modules/$(uname -r)。编译驱动时,需要将内核路径指定到/lib/modules/$(uname -r)/build。对应的头文件在路径/usr/src/linux-headers-$(uname -r)/include中。

对于wsl来说,与通用内核类似,只是内核需要单独从微软wsl2内核仓库下载。然后手动进行编译安装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 安装编译wsl特供内核依赖
sudo apt-get install -y libelf-dev build-essential pkg-config bison build-essential flex libssl-dev libelf-dev bc dwarves

# 解压进入到目录
tar -xvf WSL2-Linux-Kernel-linux-msft-wsl-5.15.90.1.tar.gz
cd WSL2-Linux-Kernel-linux-msft-wsl-5.15.90.1
cp Microsoft/config-wsl .config

# 编译安装
sudo make scripts
sudo make modules -j$(nproc)
sudo make modules_install

cd ..

驱动简介

Linux驱动可以使用多种方式加载到系统中。

基本框架

hello.c文件的内容如下

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
#include <linux/module.h> // 包含内核编程最常用的函数声明,如printk
#include <linux/kernel.h> // 包含模块编程相关的宏定义,如:MODULE_LICENSE

/*
init初始化函数在模块被插入进内核时调用,主要作用为驱动功能做好预备工作被称为模块的入口函数

__init的作用 :
1. 一个宏,展开后为:__attribute__ ((__section__ (".init.text"))) 实际是gcc的一个特殊链接标记
2. 指示链接器将该函数放置在 .init.text区段
3. 在模块插入时方便内核从ko文件指定位置读取入口函数的指令到特定内存位置
*/
int __init hello_init(void)
{
// 具体初始化逻辑
printk("hello module init.\n");
return 0;
}

/*
module_init 宏
1. 用法:module_init(模块入口函数名)
2. 动态加载模块,对应函数被调用
3. 静态加载模块,内核启动过程中对应函数被调用
4. 对于静态加载的模块其本质是定义一个全局函数指针,并将其赋值为指定函数,链接时将地址放到特殊区段(.initcall段),方便系统初始化统一调用。
5. 对于动态加载的模块,由于内核模块的默认入口函数名是init_module,用该宏可以给对应模块入口函数起别名
*/
module_init(hello_init);

/*
exit退出函数在模块从内核中被移除时调用,主要作用做些init函数的反操作被称为模块的出口函数

__exit的作用:
1. 一个宏,展开后为:__attribute__ ((__section__ (".exit.text"))) 实际也是gcc的一个特殊链接标记
2. 指示链接器将该函数放置在 .exit.text区段
3. 在模块插入时方便内核从ko文件指定位置读取出口函数的指令到另一个特定内存位置
*/
void __exit hello_exit(void)
{
// 具体反初始化逻辑
printk("hello module exit.\n");
}

/*
module_exit宏
1. 用法:module_exit(模块出口函数名)
2. 动态加载的模块在卸载时,对应函数被调用
3. 静态加载的模块可以认为在系统退出时,对应函数被调用,实际上对应函数被忽略
4. 对于静态加载的模块其本质是定义一个全局函数指针,并将其赋值为指定函数,链接时将地址放到特殊区段(.exitcall段),方便系统必要时统一调用,实际上该宏在静态加载时没有意义,因为静态编译的驱动无法卸载。
5. 对于动态加载的模块,由于内核模块的默认出口函数名是cleanup_module,用该宏可以给对应模块出口函数起别名
*/
module_exit(hello_exit);

/*
MODULE_LICENSE(字符串常量);
字符串常量内容为源码的许可证协议 可以是"GPL" "GPL v2" "GPL and additional rights" "Dual BSD/GPL" "Dual MIT/GPL" "Dual MPL/GPL"等, "GPL"最常用
其本质也是一个宏,宏体也是一个特殊链接标记,指示链接器在ko文件指定位置说明本模块源码遵循的许可证
在模块插入到内核时,内核会检查新模块的许可证是不是也遵循GPL协议,如果发现不遵循GPL,则在插入模块时打印抱怨信息:
myhello:module license 'unspecified' taints kernel
Disabling lock debugging due to kernel taint
也会导致新模块没法使用一些内核其它模块提供的高级功能
*/
MODULE_LICENSE("GPL");

Makefile文件的内容如下

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
ifeq ($(KERNELRELEASE),)

ifeq ($(ARCH),arm)
KERNELDIR ?= /root/ldd4/linux-4.14.334
# OBJECTDIR ?= /root/ldd4/objects/vexpress-v2p-ca9
ROOTFS ?= /root/ldd4/rootfs-arm32
CROSS_COMPILE ?= arm-linux-gnueabi-
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)

modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) CROSS_COMPILE=$(CROSS_COMPILE) O=$(OBJECTDIR) modules

modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) CROSS_COMPILE=$(CROSS_COMPILE) O=$(OBJECTDIR) modules INSTALL_MOD_PATH=$(ROOTFS) modules_install

clean:
rm -rf *.o *.ko .*.cmd *.mod* modules.order Module.symvers .tmp_versions

else
obj-m += hello.o

endif

驱动编译成功后,使用附录中的常用命令进行测试。

字符设备驱动

Linux字符设备会涉及到关键数据结构cdevfile_operations结构体的操作方法。

cdev定义在文件include/linux/cdev.h中,主要描述设备驱动基本信息。

file_operations定义在文件include/linux/fs.h中,主要描述设备驱动提供的基本接口函数,比如open、read、write、llseek、unlocked_ioctl等基本操作函数。

关键函数

初始化基本流程为:注册设备号->申请驱动内存->初始化与新增字符设备描述符

注销基本流程为:删除字符设备描述符->释放驱动内存->删除设备号

设备号

1
2
3
4
5
6
7
typedef u32 dev_t;
// 生成设备号
#define MKDEV(ma,minor) (((ma) << 20) | (mi))
// 获取主设备号
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
// 获取次设备号
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))

dev_t为设备编号类型,为32位无符号整数。

  • ma为主设备号major
  • mi为次设备号minor

在创建设备节点时,通过设备号来绑定驱动模块。

1
2
3
4
5
6
// 静态注册设备号
int register_chrdev_region(dev_t dev, unsigned count, const char *name);
// 动态申请设备号
int alloc_chrdev_region(dev_t *dev, unsigned minor, unsigned count, const char *name);
// 注销设备号
void unregister_chrdev_region(dev_t dev, unsigned count);
  • dev为设备编号,动态申请时通过指针传值方式返回
  • minor为需要分配的起始次设备号
  • count为需要分配的设备数量,主设备号一样,次设备号依次累加
  • name为设备驱动名称,可以在/proc/devices中查看

驱动内存

值得注意的是,设备驱动运行在内核空间,因此内存需要使用内核内存管理函数。

1
2
3
4
// 申请内存并置零。
void *kzalloc(size_t size, gfp_t flags);
// 内存释放
void kfree(const void *p)
  • size申请内存大小
  • flags内存标志位,这里使用GFP_KERNEL
  • p内存指针指向待释放的内存

字符设备

1
2
3
4
5
6
// 初始化字符设备描述符
void cdev_init(struct cdev *cdev, const struct file_operations *ops);
// 新增字符设备描述符
int cdev_add(struct cdev *cdev, dev_t dev, unsigned count);
// 删除字符设备描述符
void cdev_del(struct cdev *cdev);
  • cdev字符设备描述符
  • ops文件操作描述符
  • dev设备编号

其他宏

名称作用必选
module_init导出设备驱动的初始化函数
module_exit导出设备驱动的退出函数
module_param导出设备驱动参数
MODULE_LICENSE声明许可信息
MODULE_AUTHOR声明作者信息
MODULE_DESCRIPTION声明描述信息

简单的模拟缓存设备

支持最多创建10个设备节点,每个节点可以读取或写入数据。

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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>

#define GLOBALMEM_SIZE 0x1000
#define GLOBALMEM_MAJOR 230
#define GLOBALMEM_MINOR 0
#define DEVICE_NUM 10

// 为避免多个设备命令污染,Linux推荐使用_IO _IOR _IOW _IOWR来定义ioctl的命令
// 已经定义的设备类型可以见内核文档Documentation/ioctl/ioctl-number.txt
// 内核预定义的控制命令不会被设备驱动处理,这些定义在include/uapi/asm-generic/ioctls.h
#define GLOBALMEM_MAGIC 'g'
#define MEM_CLEAR _IO(GLOBALMEM_MAGIC, 0)

static int globalmem_major = GLOBALMEM_MAJOR;
module_param(globalmem_major, int, S_IRUGO);

typedef struct globalmem_dev
{
// 设备号
dev_t id;
// 字符设备
struct cdev cd;
// 模拟的设备内存
unsigned char mem[GLOBALMEM_SIZE];
} globalmem_dev_t;

globalmem_dev_t *globalmem_devp;

// 设备驱动的打开函数
static int globalmem_open(struct inode *inode, struct file *filp)
{
globalmem_dev_t *dev = container_of(inode->i_cdev, globalmem_dev_t, cd);
filp->private_data = dev;
return 0;
}

// 设备驱动的释放函数
static int globalmem_release(struct inode *, struct file *)
{
return 0;
}

// 设备驱动的I/O控制函数
static long globalmem_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
globalmem_dev_t *dev = filp->private_data;

switch (cmd)
{
case MEM_CLEAR:
memset(dev->mem, 0, GLOBALMEM_SIZE);
printk(KERN_INFO "globalmem is set to zero\n");
break;

default:
return -EINVAL;
}

return 0;
}

// 设备驱动的读操作。*ppos是要读的位置相对于内存开头的偏移,如果大于或等于GLOBALMEM_SIZE,则会返回0(EOF)
static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
globalmem_dev_t *dev = filp->private_data;

if (p >= GLOBALMEM_SIZE)
return 0;
if (count > GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;

if (copy_to_user(buf, dev->mem + p, count))
{
ret = -EFAULT;
}
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "read %u bytes(s) from %lu\n", count, p);
}

return ret;
}
// 驱动设备的写操作
static ssize_t globalmem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
globalmem_dev_t *dev = filp->private_data;

if (p > GLOBALMEM_SIZE)
return 0;
if (count > GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;

if (copy_from_user(dev->mem + p, buf, count))
ret = -EFAULT;
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "written %u bytes(s) from %lu\n", count, p);
}

return ret;
}

// 设备驱动的定位操作。
static loff_t globalmem_llseek_impl(struct file *filp, loff_t offset, int orig)
{
loff_t ret = 0;
loff_t f_pos = 0;

switch (orig)
{
case 1: /* 从内存当前位置开始seek */
f_pos = filp->f_pos;
case 0: /* 从内存开头位置seek */
if ((f_pos + offset) < 0)
{
ret = -EINVAL;
break;
}
if ((f_pos + offset) > GLOBALMEM_SIZE)
{
ret = -EINVAL;
break;
}
filp->f_pos = f_pos;
ret = filp->f_pos;
break;

default:
ret = -EINVAL;
break;
}

return ret;
}

// 设备驱动的文件操作结构体
static const struct file_operations globalmem_fops = {
.owner = THIS_MODULE,
.llseek = globalmem_llseek_impl,
.read = globalmem_read,
.write = globalmem_write,
.unlocked_ioctl = globalmem_ioctl,
.open = globalmem_open,
.release = globalmem_release,
};

// cdev的初始化和添加
static void globalmem_setup_cdev(globalmem_dev_t *dev)
{
int err;
cdev_init(&dev->cd, &globalmem_fops);
dev->cd.owner = THIS_MODULE;
err = cdev_add(&dev->cd, dev->id, 1);
if (err)
printk(KERN_NOTICE "Error %d adding globalmem", err);
}

// 设备驱动的初始化函数
static int __init globalmem_init(void)
{
int ret;
int i;

// 设备号的申请
dev_t id = MKDEV(globalmem_major, GLOBALMEM_MINOR);
if (globalmem_major)
// 1. 静态申请设备号
ret = register_chrdev_region(id, DEVICE_NUM, "globalmemND");
else
{
// 2. 动态申请设备号
ret = alloc_chrdev_region(&id, 0, DEVICE_NUM, "globalmemND");
globalmem_major = MAJOR(id);
}
if (ret < 0)
return ret;

// 从内核中申请一份globalmem_dev的内存并清零
globalmem_devp = kzalloc(sizeof(globalmem_dev_t) * DEVICE_NUM, GFP_KERNEL);
if (!globalmem_devp)
{
ret = -ENOMEM;
goto fail_malloc;
}

for (i = 0; i < DEVICE_NUM; ++i)
{
(globalmem_devp + i)->id = MKDEV(globalmem_major, i);
globalmem_setup_cdev(globalmem_devp + i);
}
return 0;

fail_malloc:
unregister_chrdev_region(id, DEVICE_NUM);
return ret;
}
// 导出设备驱动的初始化函数
module_init(globalmem_init);

// 设备驱动的退出函数
static void __exit globalmem_exit(void)
{
int i;
for (i = 0; i < DEVICE_NUM; ++i)
cdev_del(&(globalmem_devp + i)->cd);
kfree(globalmem_devp);
unregister_chrdev_region(globalmem_devp->id, DEVICE_NUM);
}
// 导出设备驱动的退出函数
module_exit(globalmem_exit);

// 作者版权声明
MODULE_AUTHOR("johnny <johnny@gmail.com>");
MODULE_LICENSE("GPL v2");

阻塞和非阻塞

异步通知和异步I/O

异步通知

异步通知使用Linux信号机制。

设备驱动中使用异步通知,主要用到一个数据结构和两个函数。

1
2
3
4
5
6
7
// 异步通知数据结构
struct fasync_struct;
// 处理标志变更
int fasync_helper(int fd, struct file *filp, int mode, struct fasync_struct **fa);
// 释放信号函数
void kill_fasync(struct fasync_struct **fa, int sig, int band);

异步I/O

Linux内核AIO。

AIO无法解决系统调用问题,已经被摒弃,使用io_uring替代AIO机制。一文图解原理|Linux I/O 神器之 io_uring

中断与时钟

Linux将中断处理程序分解为两个半部:顶半部(Top Half)和底半部(Bottom Half)。

ARM Linux默认情况下,中断都是在CPU0上产生的,需要通过接口irq_set_affinity把中断irq设定到CPU i上去。

顶半部用于完成尽量少的比较紧急的功能,它往往只是简单地读取寄存器中的中断状态,并在清除中断标志后就进行“登记中断”的工作。

中断处理工作的重心就落在了底半部的头上,需用它来完成中断事件的绝大多数任务。

关键函数

顶半部

1
2
3
4
5
6
7
8
9
10
11
12
typedef irqreturn_t (*irq_handler_t)(int, void *);
typedef int irqreturn_t;
// 申请irq
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev);
// 申请irq。区别是devm_开头的API申请的是内核“managed”的资源,一般不需要在出错处理和remove()接口里再显式的释放。
int devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id);
// 释放irq
void free_irq(unsigned int irq,void *dev_id);
// 屏蔽使能中断源
void disable_irq(int irq);
void disable_irq_nosync(int irq);
void enable_irq(int irq);

中断共享需要在申请时,增加IRQF_SHARED标志。

底半部

Linux实现底半部的机制主要有tasklet、工作队列、软中断和线程化irq。

内核定时器

定时器

timer_list

工作队列

内核延时

短延时

1
2
3
4
5
6
7
8
9
10
// 忙等待
void ndelay(unsigned long nsecs);
void udelay(unsigned long usecs);
void mdelay(unsigned long msecs);
// 睡眠
void msleep(unsigned int millisecs);
unsigned long msleep_interruptible(unsigned int millisecs);
void ssleep(unsigned int seconds);
// 睡着延时
schedule_timeout

内存与I/O访问

x86处理器中存在I/O空间的概念,而大多数嵌入式微处理器中并不提供I/O空间。

Linux内存管理

在Linux系统中,进程的4GB内存空间被分为两个部分——用户空间与内核空间。用户空间的地址一般分布为03GB(即PAGE_OFFSET,在0x86中它等于0xC0000000),而34GB为内核空间。用户进程通常只能访问用户空间的虚拟地址,不能访问内核空间的虚拟地址。用户进程只能通过系统调用等方式才可以访问到内核空间。

内核地址空间又被划分为物理内存映射区、虚拟内存分配区、高端页面映射区、专用页面映射区和系统保留映射区这几个区域。

对于x86系统而言,一般情况下,物理内存映射区最大长度为896MB。当系统物理内存大于896MB时,超过物理内存映射区的那部分内存称为高端内存。

内核空间最顶部FIXADDR_TOP~4GB的区域作为保留区。

紧接着最顶端的保留区以下的一段区域为专用页面映射区(FIXADDR_START~FIXADDR_TOP)。

virt_to_phys()和phys_to_virt()方法仅适用于DMA和常规区域,高端内存的虚拟地址与物理地址之间不存在如此简单的换算关系。

内存存取

用户空间动态申请

1
2
malloc
free

内核空间动态申请

1
2
3
4
5
6
7
8
9
// 依赖底层__get_free_pages()来实现,分配标志的前缀GFP正好是这个底层函数的缩写。
// 最常用的分配标志是GFP_KERNEL,其含义是在内核空间的进程中申请内存。
// 使用GFP_KERNEL标志申请内存时,若暂时不能满足,则进程会睡眠等待页,即会引起阻塞,因此不能在中断上下文或持有自旋锁的时候使用GFP_KERNE申请内存。
// 使用GFP_ATOMIC标志申请内存时,若不存在空闲页,则不等待,直接返回。
void *kmalloc(size_t size, int flags);
kfree
// 一般只为存在于软件中(没有对应的硬件意义)的较大的顺序缓冲区分配内存
void *vmalloc(unsigned long size);
void vfree(void * addr);

slab缓存

完全使用页为单元申请和释放内存容易导致浪费(如果要申请少量字节,也需要用1页);另一方面,在操作系统的运作过程中,经常会涉及大量对象的重复生成、使用和释放内存问题。如果我们能够用合适的方法使得对象在前后两次被使用时分配在同一块内存或同一类内存空间且保留了基本的数据结构,就可以大大提高效率。slab算法就是针对上述特点设计的。

1
2
3
4
5
6
7
// 创建slab缓存
struct kmem_cache *kmem_cache_create(const char *name, size_t size,size_t align, unsigned long flags,void (*ctor)(void*, struct kmem_cache *, unsigned long),void (*dtor)(void*, struct kmem_cache *, unsigned long));
// 分配和释放slab缓存
void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags);
void kmem_cache_free(struct kmem_cache *cachep, void *objp);
// 回收slab缓存
int kmem_cache_destroy(struct kmem_cache *cachep);

内存池

内存池技术也是一种非常经典的用于分配大量小对象的后备缓存技术。

1
2
3
4
5
6
7
8
9
// 创建内存池
mempool_t *mempool_create(int min_nr, mempool_alloc_t *alloc_fn,mempool_free_t *free_fn, void *pool_data);
typedef void *(mempool_alloc_t)(int gfp_mask, void *pool_data); // 标准对象分配的函数指针
typedef void (mempool_free_t)(void *element, void *pool_data); // 标准对象回收的函数指针
// 内存池中分配和回收对象
void *mempool_alloc(mempool_t *pool, int gfp_mask);
void mempool_free(void *element, mempool_t *pool);
// 回收内存池
void mempool_destroy(mempool_t *pool);

I/O端口和I/O内存

当位于I/O空间时,通常被称为I/O端口;当位于内存空间时,对应的内存空间被称为I/O内存。

I/O端口访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 读写字节端口(8位宽)
unsigned inb(unsigned port);
void outb(unsigned char byte, unsigned port);
// 读写字端口(16位宽)
unsigned inw(unsigned port);
void outw(unsigned short word, unsigned port);
// 读写长字端口(32位宽)
unsigned inl(unsigned port);
void outl(unsigned longword, unsigned port);
// 读写一串字节
void insb(unsigned port, void *addr, unsigned long count);
void outsb(unsigned port, void *addr, unsigned long count);
// 读写一串字
void insw(unsigned port, void *addr, unsigned long count);
void outsw(unsigned port, void *addr, unsigned long count);
// 读写一串长字
void insl(unsigned port, void *addr, unsigned long count);
void outsl(unsigned port, void *addr, unsigned long count);

I/O内存访问

在内核中访问I/O内存(通常是芯片内部的各个I2C、SPI、USB等控制器的寄存器或者外部内存总线上的设备)之前,需首先使用ioremap()函数将设备所处的物理地址映射到虚拟地址上。

1
2
3
4
5
6
// 返回一个特殊的虚拟地址,该地址可用来存取特定的物理地址范围,这个虚拟地址位于vmalloc映射区域。
// 通过devm_ioremap进行的映射通常不需要在驱动退出和出错处理的时候进行iounmap
void *ioremap(unsigned long offset, unsigned long size);
void __iomem *devm_ioremap(struct device *dev, resource_size_t offset,unsigned long size);
// 释放ioremap映射的地址
void iounmap(void * addr);

是Linux内核推荐用一组标准的API来完成设备内存映射的虚拟地址的读写。

没有_relaxed后缀的版本与有_relaxed后缀的版本的区别是前者包含一个内存屏障。

以下分别是读写8bit、16bit、32bit的寄存器的版本。

1
2
3
4
5
6
7
8
// 读寄存器
#define readb(c) ({ u8 __v = readb_relaxed(c); __iormb(); __v; })
#define readw(c) ({ u16__v = readw_relaxed(c); __iormb(); __v; })
#define readl(c) ({ u32 __v = readl_relaxed(c); __iormb(); __v; })
// 写寄存器
#define writeb(v,c) ({ __iowmb(); writeb_relaxed(v,c); })
#define writew(v,c) ({ __iowmb(); writew_relaxed(v,c); })
#define writel(v,c) ({ __iowmb(); writel_relaxed(v,c); })

申请释放I/O端口和I/O内存

Linux内核提供了一组函数以申请和释放I/O端口,表明该驱动要访问这片区域。

1
2
3
4
5
// 申请I/O端口
// 变体devm_request_region
struct resource *request_region(unsigned long first, unsigned long n, const char *name);
// 归还I/O端口
void release_region(unsigned long start, unsigned long n);

Linux内核也提供了一组函数以申请和释放I/O内存的范围。此处的“申请”表明该驱动要访问这片区域,它不会做任何内存映射的动作,更多的是类似于“reservation”的概念。

1
2
3
4
5
// 申请I/O内存
// 变体devm_request_mem_region
struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);
// 归还I/O内存
void release_mem_region(unsigned long start, unsigned long len);

I/O端口和I/O内存访问流程

I/O端口访问的一种途径是直接使用I/O端口操作函数:在设备打开或驱动模块被加载时申请I/O端口区域,之后使用inb()、outb()等进行端口访问,最后,在设备关闭或驱动被卸载时释放I/O端口范围。

I/O内存的访问步骤,首先是调用request_mem_region()申请资源,接着将寄存器地址通过ioremap()映射到内核空间虚拟地址,之后就可以通过Linux设备访问编程接口访问这些设备的寄存器了。访问完成后,应对ioremap()申请的虚拟地址进行释放,并释放release_mem_region()申请的I/O内存资源。

设备地址映射到用户空间

一般情况下,用户空间是不可能也不应该直接访问设备的,但是,设备驱动程序中可实现mmap()函数,这个函数可使得用户空间能直接访问设备的物理地址。

mmap()必须以PAGE_SIZE为单位进行映射。

1
2
// 驱动中mmap原型
int(*mmap)(struct file *, struct vm_area_struct*);

vm_operations_struct结构体的实体会在file_operations的mmap()成员函数里被赋值给相应的vma->vm_ops。一般open()函数也通常在mmap()里调用,close()函数会在用户调用munmap()的时候被调用到。

1
2
// 创建页表项
int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,unsigned long pfn, unsigned long size, pgprot_t prot);

I/O内存被映射时需要是nocache的,这时候,我们应该对vma->vm_page_prot设置nocache标志之后再映射。

当访问的页不在内存里,即发生缺页异常时,fault()会被内核自动调用,而fault()的具体行为可以自定义。

I/O内存静态映射

在将Linux移植到目标电路板的过程中,有得会建立外设I/O内存物理地址到虚拟地址的静态映射,这个映射通过在与电路板对应的map_desc结构体数组中添加新的成员来完成。

1
2
3
4
5
6
7
8
struct map_desc
{
unsigned long virtual; /* 虚拟地址 */
unsigned long pfn; /* __phys_to_pfn(phy_addr) */
unsigned long length; /* 大小 */
unsigned int type; /* 类型 */
};
// 然后通过函数iotable_init(struct map_desc&, size_t)建立映射。

驱动工程师可以对非常规内存区域的I/O内存(外设控制器寄存器、MCU内部集成的外设控制器寄存器等)依照电路板的资源使用情况添加到map_desc数组中,但是目前该方法已经不值得推荐。

DMA

DMA是一种无须CPU的参与就可以让外设与系统内存之间进行双向数据传输的硬件机制。

DMA方式的数据传输由DMA控制器(DMAC)控制,在传输期间,CPU可以并发地执行其他任务。当DMA结束后,DMAC通过中断通知CPU数据传输已经结束,然后由CPU执行相应的中断服务程序进行后处理。

DMA与Cache一致性

如果DMA的目的地址与Cache所缓存的内存地址访问有重叠,经过DMA操作,与Cache缓存对应的内存中的数据已经被修改,而CPU本身并不知道,它仍然认为Cache中的数据就是内存中的数据。这样就会发生Cache与内存之间数据“不一致性”的错误。

Cache的不一致性问题并不是只发生在DMA的情况下,实际上,它还存在于Cache使能和关闭的时刻。

Linux下的DMA编程

申请DMA缓冲区时应使用GFP_DMA标志,这样能保证获得的内存位于DMA区域中,并具备DMA能力。

在内核中定义了__get_free_pages()针对DMA的“快捷方式”__get_dma_pages(),它在申请标志中添加了GFP_DMA。

如果不想使用log2size(即order)为参数申请DMA内存,则可以使用另一个函数dma_mem_alloc()。

内核提供了如下函数以进行简单的虚拟地址/总线地址转换

1
2
unsigned long virt_to_bus(volatile void *address);
void *bus_to_virt(unsigned long address);

在使用IOMMU或反弹缓冲区的情况下,上述函数一般不会正常工作。而且,这两个函数并不建议使用。

设备并不一定能在所有的内存地址上执行DMA操作,在这种情况下应该通过下列函数执行DMA地址掩码

1
int dma_set_mask(struct device *dev, u64 mask);

内核中提供了如下函数以分配一个DMA一致性的内存区域

1
2
3
4
5
6
7
8
9
10
11
12
13
// 申请Cache一致的DMA缓冲区
void * dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *handle,gfp_t gfp);
// 释放Cache一致的DMA缓冲区
void dma_free_coherent(struct device *dev, size_t size, void *cpu_addr,dma_addr_t handle);

// 申请写合并的DMA缓冲区
void * dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t*handle, gfp_t gfp);
#define dma_free_writecombine(dev,size,cpu_addr,handle) \
dma_free_coherent(dev,size,cpu_addr,handle)

// PCI设备申请DMA缓冲区
void * pci_alloc_consistent(struct pci_dev *pdev, size_t size, dma_addr_t *dma_addrp);
void pci_free_consistent(struct pci_dev *pdev, size_t size, void *cpu_addr,dma_addr_t dma_addr);

缓冲区来自内核的较上层(如网卡驱动中的网络报文、块设备驱动中要写入设备的数据等),上层很可能用普通的kmalloc()、__get_free_pages()等方法申请,这时候就要使用流式DMA映射。

对于单个已经分配的缓冲区而言,使用dma_map_single()可实现流式DMA映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// DMA映射。第4个参数为DMA的方向,可能的值包括DMA_TO_DEVICE、DMA_FROM_DEVICE、DMA_BIDIRECTIONAL和DMA_NONE
dma_addr_t dma_map_single(struct device *dev, void *buffer, size_t size,enum dma_data_direction direction);
// DMA反映射
void dma_unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size,enum dma_data_direction direction);

// 获得DMA缓冲区的拥有权
void dma_sync_single_for_cpu(struct device *dev, dma_handle_t bus_addr,size_t size, enum dma_data_direction direction);
// 将其所有权返还给设备
void dma_sync_single_for_device(struct device *dev, dma_handle_t bus_addr,size_t size, enum dma_data_direction direction);

// 映射SG
int dma_map_sg(struct device *dev, struct scatterlist *sg, int nents,enum dma_data_direction direction);
// 去除映射SG
void dma_unmap_sg(struct device *dev, struct scatterlist *list,int nents, enum dma_data_direction direction);
// 返回scatterlist对应的缓冲区的总线地址和缓冲区的长度
dma_addr_t sg_dma_address(struct scatterlist *sg);
unsigned int sg_dma_len(struct scatterlist *sg);

// 获得DMA缓冲区的拥有权
void dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg,int nents, enum dma_data_direction direction);
// 将其所有权返还给设备
void dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg,int nents, enum dma_data_direction direction);

Linux内核目前推荐使用dmaengine的驱动架构来编写DMA控制器的驱动,同时外设的驱动使用标准的dmaengine API进行DMA的准备、发起和完成时的回调工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 申请DMA通道
struct dma_chan *dma_request_slave_channel(struct device *dev, const char *name);
struct dma_chan *__dma_request_channel(const dma_cap_mask_t *mask,dma_filter_fn fn, void *fn_param);
// 释放DMA通道
void dma_release_channel(struct dma_chan *chan);

// DMA完成回调函数原型:void (dma_fini_callback)(void*)

// 申请DMA描述符,然后填充callback和callback_param参数
dmaengine_prep_slave_single
// 把描述符插入队列
dmaengine_submit
// 发起DMA动作
dma_async_issue_pending

Linux设备驱动的软件架构思想

让驱动以某种标准方法拿到这些平台信息呢Linux总线、设备和驱动模型实际上可以做到这一点,驱动只管驱动,设备只管设备,总线则负责匹配设备和驱动,而驱动则以标准途径拿到板级信息。

一个现实的Linux设备和驱动通常都需要挂接在一种总线上,对于本身依附于PCI、USB、I2C、SPI等的设备而言,这自然不是问题。在SoC系统中集成的独立外设控制器、挂接在SoC内存空间的外设等却不依附于此类总线。Linux发明了一种虚拟的总线,称为platform总线,相应的设备称为platform_device,而驱动成为platform_driver。

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
struct platform_device
{
const char *name;
int id;
bool id_auto;
struct devicedev;
u32 num_resources;
struct resource *resource;

const struct platform_device_id *id_entry;
char *driver_override; /* Driver name to force a match */

/* MFD cell pointer */
struct mfd_cell *mfd_cell;

/* arch specific additions */
struct pdev_archdata archdata;
};

struct platform_driver
{
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device * pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};

struct device_driver
{
const char *name;
struct bus_type *bus;

struct module *owner;
const char *mod_name; /* used for built-in modules */

bool suppress_bind_attrs; /* disables bind/unbind via sysfs */

const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;

int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct attribute_group **groups;

const struct dev_pm_ops *pm;

struct driver_private *p;
};

与platform_driver地位对等的i2c_driver、spi_driver、usb_driver、pci_driver中都包含了device_driver结构体实例成员。它其实描述了各种xxx_driver(xxx是总线名)在驱动意义上的一些共性。

资源本身由resource结构体描述

1
2
3
4
5
6
7
8
struct resource
{
resource__size_t start;
resource_size_t end;
const char *name;
unsigned long flags; // 值可以为IORESOURCE_IO、IORESOURCE_MEM、IORESOURCE_IRQ、IORE-SOURCE_DMA等
struct resource *parent, *sibling, *child;
};

对resource的定义也通常在BSP的板文件中进行,而在具体的设备驱动中通过platform_get_resource()这样的API来获取

1
2
3
4
// 获取资源通用接口
struct resource *platform_get_resource(struct platform_device *, unsigned int,unsigned int);
// 获取IRQ资源封装接口,相当于platform_get_resource(dev, IORESOURCE_IRQ, num);
int platform_get_irq(struct platform_device *dev, unsigned int num);

platform也提供了platform_data的支持,platform_data的形式是由每个驱动自定义的

将globalfifo作为platform设备

globalfifo驱动挂接到platform总线上,这要完成两个工作:

  • 将globalfifo移植为platform驱动
  • 在板文件中添加globalfifo这个platform设备

移植时需要屏蔽module_init和module_exit宏定义的入口。

为了完成在板文件中添加globalfifo这个platform设备的工作,需要在板文件arch/arm/mach-<soc名>/mach-<板名>.c中添加相应的代码

1
2
3
4
static struct platform_device globalfifo_device = {
.name = "globalfifo",
.id = -1,
};

设备驱动分层思想

非常推荐使用misc类型设备驱动框架,编写字符类设备。

可以额外编写驱动触发设备驱动xxx_probe函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static int __init globalfifodev_init(void)
{
int ret;

globalfifo_pdev = platform_device_alloc("globalfifo", -1);
if (!globalfifo_pdev)
return -ENOMEM;

ret = platform_device_add(globalfifo_pdev);
if (ret)
{
platform_device_put(globalfifo_pdev);
return ret;
}
return ret;
}
module_init(globalfifodev_init);

Linux块设备驱动

块设备是与字符设备并列的概念,这两类设备在Linux中的驱动结构有较大差异,总体而言,块设备驱动比字符设备驱动要复杂得多。缓冲、I/O调度、请求队列等都是与块设备驱动相关的概念。

Linux块设备驱动结构

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
// 块设备操作描述符
struct block_device_operations
{
// 打开和释放
int (*open)(struct block_device *, fmode_t);
void (*release)(struct gendisk *, fmode_t);

// I/O控制
int (*ioctl)(struct block_device *, fmode_t, unsigned, unsigned long);
int (*compat_ioctl)(struct block_device *, fmode_t, unsigned, unsigned long);

// 介质改变,以后会被check_events取代
int (*media_changed)(struct gendisk *);

// 使介质有效
int (*revalidate_disk)(struct gendisk *);

// 获取驱动器信息
int (*getgeo)(struct block_device *, struct hd_geometry *);

// 模块指针,通常指向THIS_MODULE
struct module *owner;

// 其他
int (*rw_page)(struct block_device *, sector_t, struct page *, int rw);
int (*direct_access)(struct block_device *, sector_t, void **, unsigned long *);
unsigned int (*check_events)(struct gendisk *disk, unsigned int clearing);
void (*unlock_native_capacity)(struct gendisk *);
void (*swap_slot_free_notify)(struct block_device *, unsigned long);
};

gendisk

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
// 磁盘描述符
struct gendisk
{
// major、first_minor和minors共同表征了磁盘的主、次设备号,同一个磁盘的各个分区共享一个主设备号,而次设备号则不同
int major;
int first_minor;
int minors;

// 描述的块设备操作集合
const struct block_device_operations *fops;
// 管理这个设备的I/O请求队列的指针
struct request_queue *queue;
// 指向磁盘的任何私有数据,与字符设备驱动的private_data类似
void *private_data;
// 表示一个分区
struct hd_struct part0;
// 容纳分区表。与part0的关系:disk->part_tbl->part[0] = &disk->part0;
struct disk_part_tbl __rcu *part_tbl;

char disk_name[DISK_NAME_LEN]; /* name of major driver */
char *(*devnode)(struct gendisk *gd, umode_t *mode);

unsigned int events; /* supported events */
unsigned int async_events; /* async events, subset of all */

int flags;
struct device *driverfs_dev; // FIXME: remove
struct kobject *slave_dir;

struct timer_rand_state *random;
atomic_t sync_io; /* RAID */
struct disk_events *ev;
#ifdef CONFIG_BLK_DEV_INTEGRITY
struct blk_integrity *integrity;
#endif
int node_id;
};

操作函数

1
2
3
4
5
6
7
8
9
// 分配gendisk
struct gendisk *alloc_disk(int minors);
// 增加gendisk
void add_disk(struct gendisk *disk);
// 释放gendisk
void del_gendisk(struct gendisk *gp);
// gendisk引用计数
struct kobject *get_disk(struct gendisk *disk);
void put_disk(struct gendisk *disk);

bio、request和request_queue

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
struct bvec_iter
{
sector_t bi_sector; /* device address in 512byte sectors */
unsigned int bi_size; /* residual I/O count */
unsigned int bi_idx; /* current index into bvl_vec */
unsigned int bi_bvec_done; /* number of bytes completed in current bvec */
};

struct bio
{
struct bio *bi_next; /* request queue link */
struct block_device *bi_bdev;
unsigned long bi_flags; /* status, command, etc */
unsigned long bi_rw; /* bottom bits READ/WRITE,
* top bits priority
*/
struct bvec_iter bi_iter;
/* Number of segments in this BIO after
* physical address coalescing is performed.
*/
unsigned int bi_phys_segments;
...

struct bio_vec *bi_io_vec; /* the actual vec list */
struct bio_set *bi_pool;
/*
* We can inline a number of vecs at the end of the bio, to avoid
* double allocations for a small number of bio_vecs. This member
* MUST obviously be kept at the very end of the bio.
*/
struct bio_vec bi_inline_vecs[0];
};

与bio对应的数据每次存放的内存不一定是连续的,因此需要一个向量。向量中的每个元素实际是一个[page,offset,len],我们一般也称它为一个片段。

1
2
3
4
5
6
struct bio_vec
{
struct page *bv_page;
unsigned int bv_len;
unsigned int bv_offset;
};

I/O调度算法可将连续的bio合并成一个请求。请求是bio经由I/O调度进行调整后的结果,这是请求和bio的区别。

每个块设备或者块设备的分区都对应有自身的request_queue,从I/O调度器合并和排序出来的请求会被分发(Dispatch)到设备级的request_queue。

主要API

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
// 初始化请求队列
request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock);
// 清除请求队列
void blk_cleanup_queue(request_queue_t * q);
// 分配请求队列
request_queue_t *blk_alloc_queue(int gfp_mask);
// 对于RAMDISK这种完全随机访问的非机械设备,并不需要进行复杂的I/O调度。
// 使用如下函数来绑定请求队列和“制造请求”函数
void blk_queue_make_request(request_queue_t * q, make_request_fn * mfn);
// 提取请求
struct request * blk_peek_request(struct request_queue *q);
// 启动请求
void blk_start_request(struct request *req);
// 报告完成
void __blk_end_request_all(struct request *rq, int error);
void blk_end_request_all(struct request *rq, int error);
// 用blk_queue_make_request()绕开I/O调度,但是在bio处理完成后应该使用bio_endio
void bio_endio(struct bio *bio, int error);

// 如果是I/O操作故障,可以调用快捷函数bio_io_error()
#define bio_io_error(bio) bio_endio((bio), -EIO)
// 遍历一个请求的所有bio
#define __rq_for_each_bio(_bio, rq) \
if ((rq->bio)) \
for (_bio = (rq)->bio; _bio; _bio = _bio->bi_next)
// 遍历一个bio的所有bio_vec
#define __bio_for_each_segment(bvl, bio, iter, start) \
for (iter = (start); \
(iter).bi_size && \
((bvl = bio_iter_iovec((bio), (iter))), 1); \
bio_advance_iter((bio), &(iter), (bvl).bv_len))
#define bio_for_each_segment(bvl, bio, iter) \
__bio_for_each_segment(bvl, bio, iter, (bio)->bi_iter)
// 迭代遍历一个请求所有bio中的所有segment
#define rq_for_each_segment(bvl, _rq, _iter) \
__rq_for_each_bio(_iter.bio, _rq) \
bio_for_each_segment(bvl, _iter.bio, _iter.iter)

I/O调度器

Linux 2.6以后的内核包含4个I/O调度器,它们分别是Noop I/O调度器(适合Flash)、Anticipatory I/O调度器、Deadline I/O调度器(适合读取多的场景,数据库)与CFQ I/O调度器(适合多媒体应用)。其中,Anticipatory I/O调度器算法已经在2010年从内核中去掉了。

可以通过给内核添加启动参数,选择所使用的I/O调度算法

1
kernel elevator=deadline

通过类似如下的命令,改变一个设备的调度器

1
$ echo SCHEDULER > /sys/block/DEVICE/queue/scheduler

Linux块设备驱动初始化

1
2
3
4
5
6
// 注册设备
// major参数是块设备要使用的主设备号,name为设备名,它会显示在/proc/devices中。
// 如果major为0,内核会自动分配一个新的主设备号,register_blkdev()函数的返回值就是这个主设备号。
int register_blkdev(unsigned int major, const char *name);
// 注销设备
int unregister_blkdev(unsigned int major, const char *name);

块设备的打开释放

块设备驱动的open()函数和其字符设备驱动的对等体不太相似,前者不以相关的inode和file结构体指针作为参数。

1
2
int (*open)(struct block_device *bdev, fmode_t mode);
void (*release)(struct gendisk *disk, fmode_t mode);

块设备ioctl函数

与字符设备驱动一样,块设备可以包含一个ioctl()函数以提供对设备的I/O控制能力。高层的块设备层代码处理了绝大多数I/O控制。例如,drivers/block/floppy.c实现了与软驱相关的命令,drivers/mmc/card/block.c实现了MMC子系统的命令处理。

块设备驱动的I/O请求处理

使用请求队列的源码见drivers/memstick/core/ms_block.c

使用请求队列对于一个机械磁盘设备而言的确有助于提高系统的性能,但是对于RAMDISK、ZRAM(Compressed RAM Block Device)等完全可真正随机访问的设备而言,无法从高级的请求队列逻辑中获益。源码见drivers/block/zram/zram_drv.c

实例:vmem_disk驱动

vmem_disk硬件原理

vmem_disk是一种模拟磁盘,其数据实际上存储在RAM中。它使用通过vmalloc()分配出来的内存空间来模拟出一个磁盘,以块设备的方式来访问这片内存。该驱动是对字符设备驱动章节中globalmem驱动的块方式改造。

加载vmem_disk.ko后,在使用默认模块参数的情况下,系统会增加4个块设备节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ ls -l /dev/vmem_disk*
brw-rw---- 1 root disk 252, 0 2月 25 14:00 /dev/vmem_diska
brw-rw---- 1 root disk 252, 16 2月 25 14:00 /dev/vmem_diskb
brw-rw---- 1 root disk 252, 32 2月 25 14:00 /dev/vmem_diskc
brw-rw---- 1 root disk 252, 48 2月 25 14:00 /dev/vmem_diskd

$ sudo mkfs.ext2 /dev/vmem_diska
mke2fs 1.42.9 (4-Feb-2014)
Filesystem label=
OS type: Linux
Block size=1024 (log=0)
Fragment size=1024 (log=0)
Stride=0 blocks, Stripe width=0blocks
64 inodes, 512 blocks
25 blocks (4.88%) reserved for the super user
First data block=1
Maximum filesystem blocks=524288
1 block group
8192 blocks per group, 8192fragments per group
64 inodes per group
Allocating group tables: done
Writing inode tables: done
Writing superblocks and filesystem accounting information: done

驱动开发常用项

驱动属性项

linux/device.h

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
// 设备属性处理函数
static ssize_t xxx1_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t n)
{
int input;
if (kstrtoint(buf, 10, &input))
return -EINVAL;
...
schedule_work(&work);
return n;
}
// 声明设备属性结构体dev_attr_xxx1,并赋值xxx1_store为对应的处理函数
static DEVICE_ATTR_WO(xxx1);
static struct attribute *xxx_attrs[] = {
&dev_attr_xxx1.attr,
&dev_attr_xxx2.attr,
NULL,
};
static const struct attribute_group xxx_group = {
.attrs = xxx_attrs,
};

// 在进行初始化时进行,例如probe中
// 注册设备属性
sysfs_create_group(&dev->kobj, &xxx_group);
// 移除设备属性
sysfs_remove_group(&dev->kobj, &xxx_group);

工作队列

内核驱动处理时,遇到比较费时类型的任务时,可以将任务放到工作队列,稍后在合适的时候进行处理。

1
2
3
4
5
6
7
8
// 工作队列结构体
struct work_struct{}
// 工作队列处理函数
void xxx_handle(struct work_struct *work);
// 初始化工作队列
INIT_WORK(&xxx_work, xxx_handle);
// 调度工作队列
schedule_work(&work);

附录

常用命令

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
# 加载驱动
$ sudo insmod globalmem.ko
# 卸载驱动
$ sudo rmmod globalmem

# 只查看最新的内核打印消息
$ dmesg -W

# 查看驱动主设备号
$ cat /proc/devices | grep globalmem
230 globalmem
# 查看驱动设备节点信息
$ ls -l /dev/globalmem
crwxrwxrwx 1 root root 230, 0 Dec 27 17:40 /dev/globalmem
# 查看platform驱动
ls /sys/devices/platform/globalfifo -l
total 0
lrwxrwxrwx 1 root root 0 1月 2 17:00 driver -> ../../../bus/platform/drivers/globalfifo
-rw-r--r-- 1 root root 4096 1月 2 17:03 driver_override
-r--r--r-- 1 root root 4096 1月 2 17:03 modalias
drwxr-xr-x 2 root root 0 1月 2 17:03 power
lrwxrwxrwx 1 root root 0 1月 2 17:03 subsystem -> ../../../bus/platform
-rw-r--r-- 1 root root 4096 1月 2 17:00 uevent

# 创建设备节点
$ sudo mknod /dev/globalmem c 230 0
# 删除设备节点
$ sudo unlink /dev/globalmem
# 创建支持多设备的节点
$ sudo mknod /dev/globalmem0 c 230 0
$ sudo mknod /dev/globalmem1 c 230 1

# 创建的设备普通用户没有写入权限,需要增加写入权限
# 或者给予全权限
$ sudo chmod 777 /dev/globalmem*

# 向设备写入数据
$ echo "hello world 0" >> /dev/globalmem0
# 读取设备中的数据
$ cat /dev/globalmem0
hello world 0

参考

Linux设备驱动开发详解:基于最新的Linux4.0内核.pdf

嵌入式王道长-Linux内核驱动开发

参考原文

Chapter 1 系统接口

Unix utilities实验

实验说明

启动系统

sleep

重点:使用user/user.h的sleep接口实现,单位为jiffies(1/10)。

pingpong

重点:使用pip接口通信。

primes

目的:主进程准备好2-35的数字写入管道。

从管道中读取数字n(此数字为素数),创建一个子进程,并将剩余的非n的倍数的数写入子管道中。然后进程等待子进程的退出。子进程会重复父进程的动作,直到读取的数字到达35,则不再创建子进程。

find

重点:熟悉文件属性读取,和路径拼接。

xargs

重点:使用exec接口实现,并需要构建新的参数数组。

Chapter 2 系统结构

RISC-V has three modes in which the CPU can execute instructions: machine mode, supervisor mode, and user mode.

An application can execute only user-mode instructions and is said to be running in user space, while the software in supervisor mode can also execute privileged instructions and is said to be running in kernel space.

CPUs provide a special instruction ( RISC-V provides the ecall instruction ) that switches the CPU from user mode to supervisor mode and enters the kernel at an entry point specified by the kernel.

the entire operating system resides in the kernel, so that the implementations of all system calls run in supervisor mode. This organization is called a monolithic kernel.

OS designers can minimize the amount of operating system code that runs in supervisor mode, and execute the bulk of the operating system in user mode. This kernel organization is called a microkernel.

源码文件功能描述如下

文件名功能描述
bio.cDisk block cache for the file system.
console.cConnect to the user keyboard and screen.
entry.SVery first boot instructions.
exec.cexec() system call.
file.cFile descriptor support.
fs.cFile system.
kalloc.cPhysical page allocator.
kernelvec.SHandle traps from kernel, and timer interrupts.
log.cFile system logging and crash recovery.
main.cControl initialization of other modules during boot.
pipe.cPipes.
plic.cRISC-V interrupt controller.
printf.cFormatted output to the console.
proc.cProcesses and scheduling.
sleeplock.cLocks that yield the CPU.
spinlock.cLocks that don’t yield the CPU.
start.cEarly machine-mode boot code.
string.cC string and byte-array library.
swtch.SThread switching.
syscall.cDispatch system calls to handling function.
sysfile.cFile-related system calls.
sysproc.cProcess-related system calls.
trampoline.SAssembly code to switch between user and kernel.
trap.cC code to handle and return from traps and interrupts.
uart.cSerial-port console device driver.
virtio_disk.cDisk device driver.
vm.cManage page tables and address spaces.

进程虚拟空间分布

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
地址    功能域

MAXVA <--------->
trampoline
<--------->
trapframe
<--------->
heap
<--------->
user stack
<--------->
user text
and data
(followed by global variables)
(Instructions come first)
0 <--------->

risc-v指针宽度为64位,但硬件只使用低39位用于在页表中寻找虚拟地址,而xv6系统中只使用了38位。因此最大地址为2^38 - 1=0x3f,ffff,ffff MAXVA(kernel/riscv.h:363)

进程重要信息存储在struct proc(kernel/proc.h:85)

RISC-V ecall instruction raises the hardware privilege level and changes the program counter to a kernel-defined entry point.

When the system call completes, the kernel switches back to the user stack and returns to user space by calling the sret instruction, which lowers the hardware privilege level and resumes executing user instructions just after the system call instruction.

启动流程:

  • boot loader将kernel加载到内存0x8000 0000
  • 在machine mode下,跳转到_entry(kernel/entry.S:7),设置堆栈,并跳转运行C代码
  • 在C代码入口函数start(kernel/start.c:21)中,切换到supervisor mode,配置时钟中断,并跳转到主函数。
  • 在主函数main(kernel/main.c:11)中,初始化设备和子系统,创建第一个进程
  • 在初始化进程userinit(kernel/proc.c:233)中,寄存器a7装载SYS_EXEC(kernel/syscall.h:8)后再次进入内核
  • 在内核系统调用处理函数syscall(kernel/syscall.c:132)中,启动/init进程
  • 系统调用完成后,返回进程init(user/init.c),创建一个新console设备文件,并打开文件描述符0,1,2。

it sets the previous privilege mode to supervisor in the register mstatus, it sets the return address to main by writing main’s address into the register mepc, disables virtual address translation in supervisor mode by writing 0 into the page-table register satp, and delegates all interrupts and exceptions to supervisor mode.

system call实验

System call tracing

新增一个trace系统调用,它接收一个整数参数,它表明哪些系统调用被标记。当被标记的系统调用返回时,需要打印<pid>: syscall <call name> -> <return value>。此标记对子进程和forks都有效,但对其他进程无效。

关键点

  • user/trace.c中设置调用trace(x)后需要再trace(0)清空进程标记;
  • user/user.h中增加用户调用系统函数int trace(int sys_mask);user/usys.pl增加trace生成相关汇编代码;
  • kernel/syscall.h新增宏编号#define SYS_trace 22kernel/sysproc.c新增标记实现函数uint64 sys_trace(void)
  • syscall增加标记打印逻辑,需要注意allocproc中共用struct proc proc[NPROC];,申请后要清空之前的标记;

Sysinfo

新增一个sysinfo系统调用,它会收集系统空闲内存字节大小freemem和正在使用的进程数量nproc。需要提供用户测试程序sysinfotest调用这个接口,若整个调用没有问题,则打印"sysinfotest: OK"

关键点

  • 需要使用接口copyout将内核空间的数据拷贝到用户空间中

练习

  1. 增加一个系统调用,返回系统剩余可用内存大小

Chapter 3 页表

创建地址空间

核心的数据结构pagetable_t kernel_pagetable(kernel/vm.c:1)中,核心的功能函数是walk,用于查找虚拟地址对应的PTE。

main中调用kvminit(kernel/vm.c:54)创建内核页表,再调用kvmmake,最终通过kvmmapmappageswalk完成物理地址虚拟地址映射。

main中调用kvminithart(kernel/vm.c:62)安装内核页表。主要讲根页表的地址设置到satp寄存器中,设置前后需要刷新TLB缓存。

The RISC-V has an instruction sfence.vma that flushes the current CPU’s TLB. Xv6 executes sfence.vma in kvminithart after reloading the satp register

物理内存分配

分配器定义在kalloc.c(kernel/kalloc.c:1)。每个空闲页的列表元素都是一个struct run

main中调用kinit(kernel/kalloc.c:27)来初始化分配器。它将初始化空闲列表kmem->freelist(kernel/kalloc.c:21)用于保存内核结束位置到PHYSTOP区间的每一页。

进程地址空间

每个进程都有独立的页表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
MAXVA   <--------->
trampoline RX--
<--------->
trapframe R-W-
<--------->
unused
<--------->
heap R-WU
<--------->
stack R-WU
<--------->
guard page
<--------->
data R-WU
<--------->
unused
<--------->
text RX-U
0 <--------->

一个进程的用户内存从虚拟地址零开始,可以增长到MAXVA(kernel/riscv.h:360),允许最大使用256GB内存。

零地址放置的text代码,没有写入权限,当异常的程序试图向零地址写入数据,会出发page fault

sbrk

sbrk是进程为调整内存时的系统调用。它由函数growproc(kernel/proc.c:260)实现。

exec

exec是一个系统调用,它可以用从文件读取的数据替换进程用户空间数据,这样的文件被称为二进制或可行性文件。函数exec(kernel/exec.c:23)会读取并解析ELF格式的文件,它包含struct elfhdrELF文件头部和一系列struct proghdr程序区域头部。每个程序区域头部描述程序必须加载到内存的位置。

/init程序区域头部像下面这样

1
2
3
4
5
6
7
8
9
10
11
12
13
$ objdump -p user/_init 

user/_init: file format elf64-little

Program Header:
0x70000003 off 0x0000000000006bac vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**0
filesz 0x0000000000000033 memsz 0x0000000000000000 flags r--
LOAD off 0x0000000000001000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**12
filesz 0x0000000000001000 memsz 0x0000000000001000 flags r-x
LOAD off 0x0000000000002000 vaddr 0x0000000000001000 paddr 0x0000000000001000 align 2**12
filesz 0x0000000000000010 memsz 0x0000000000000030 flags rw-
STACK off 0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**4
filesz 0x0000000000000000 memsz 0x0000000000000000 flags rw-

值得注意的是,头部信息中filesz可能会小于memsz,那时因为这些变量值为0,文件中无需存储,但加载时需要申请memsz大小的空间并清零。

然后,函数需要拷贝参数列表,并将堆栈和PC设置好。最后将释放旧页表,使用新页表。

练习

  1. 解析riscv的设备树,找出总共拥有多少物理内存
  2. 写一个用户程序调用sbrk(1),观察调用前后页表的变化。内核申请了多少空间?新内存的PTE包含哪些数据?
  3. 修改源码让内核使用超级页
  4. 在Unix系统中,若exec处理的可执行文件以#!开头,则会使用第一行剩余部分替换程序作为解释文件执行。修改源码让内核支持这个特性。
  5. 实现一个地址空间随机分布的内核。

Chapter 4 traps和系统调用

riscv的trap机制

Each RISC-V CPU has a set of control registers that the kernel writes to tell the CPU how to handle traps, and that the kernel can read to find out about a trap that has occurred.

在文件riscv.h(kernel/riscv.h:1)中包含系统用到的所有描述。这里列举最重要的寄存器

  • stvec: 内核写入trap处理程序的地址
  • sepc: 当trap发生时,处理器会保存pc;当从trap返回时,调用sret会从此寄存器恢复pc
  • scause: 处理器会存放一个数字描述trap的原因
  • sscratch: trap处理程序使用sscratch来避免改写用户寄存器
  • sstatus: SIE位控制设备中断是否使能;SPP位指示trap触发前是user mode还是supervisor mode;

xv6只将它们使用在计时器中断的特殊场景

硬件处理所有类型trap流程:

  • sstatus的SIE位被清零,则后面的步骤略过
  • sstatus的SIE位清零
  • 拷贝pc值到sepc
  • 保存当前模式到sstatus的SPP位
  • 设置scause
  • 模式切换为supervisor mode
  • 拷贝stvecpc
  • 继续从pc处开始执行

Note that the CPU doesn’t switch to the kernel page table, doesn’t switch to a stack in the kernel, and doesn’t save any registers other than the pc.

用户空间的traps流程

用户空间trap路径是:uservec(kernel/trampoline.S:21)->usertrap(kernel/trap.c:37)-return->usertrapret(kernel/trap.c:90)->userret(kernel/trampoline.S:101)

xv6使用trampoline页来存储stvec,它页包含uservec。trampoline页会被每个进程映射到TRAMPOLINE的地址处。

uservec函数会将32个用户寄存器存储到TRAPFRAME地址所在的trapframe结构中,然后将寄存器satp切换为内核页表,再调用usertrap函数。

usertrap(kernel/trap.c:37)函数会检测trap的原因并处理它。首先将设置stveckernelvec,以便处理内核trap。保存sepc寄存器。如果trap是系统调用,则调用syscall处理它;若是设备中断,devintr处理它;否则,是一种异常场景,调用内核终止异常的进程。若是系统调用,则在函数结束时会调用usertrapret

返回用户空间的第一步是调用usertrapret(kernel/trap.c:90)。它会将stvec设置回uservecuservec的映射地址可以通过TRAMPOLINE、trampoline和uservec计算出来(注意内核中这些是物理地址)。然后恢复pc,最后调用userret函数并将a0设置为用户页表。

userret(kernel/trampoline.S:101)函数将切换satp为用户页表,并恢复32个用户寄存器。最后调用sret返回用户空间。

系统调用流程

以initcode.S中第一个系统调用exec为例。

initcode.S在寄存器a0a1存放着exec的参数,并将系统调用编号存放在a7中。根据系统调用编号在syscalls(kernel/syscall.c:107)数组中匹配到处理函数。指令ecall会触发trap切换到内核中并引发uservecusertrapsyscall执行。

系统调用参数列表

内核trap代码将用户寄存器放在当前进程的trap frame上,可以通过内核函数argintargaddrargfd返回第n个参数,它们通过调用argraw实现(kernel/syscall.c:34)。

有些参数通过用户地址传递,fechstr(kernel/syscall.c:25)函数能够拷贝用户传递的字符串。

内核空间的traps流程

内核空间trap路径是:kernelvec(kernel/kernelvec.S:12)->kerneltrap(kernel/trap.c:135)->kernelvec(kernel/kernelvec.S:12)

若trap不是设备中断,则异常会直接导致xv6内核出panic。

当处理器遇到trap进入内核空间时,总会禁用中断,直到设置stvec后。

缺页异常

若发生在用户空间,内核将终止相关进程。若发生在内核空间,则会直接panic。

许多内核利用缺页异常来实现写时复制(copy-on-write, COW)机制。COW fork的基本方法是,为父进程和子进程初始化共享所有物理页,但将他们的全部设置为只读。当进程向某页写入数据时,会触发store page faults。此时内核需要重新申请新的一页将数据拷贝过来,并再次进行映射。一个重要的优化项是,对于缺页发生在仅从该进程中引用的,无需进行拷贝。

另一个广泛使用的特性是惰性分配(lazy allocation)。当一个应用调用sbrk请求更多内存,内核只调整其使用大小,但不会申请物理内存和创建PTEs。当这些新地址被访问时,才会申请对应的内存页并完成映射。

还有一个广泛使用的特性是需求分页(demand paging)。当大型程序启动时,内核无需将所有数据加载到内存中,而仅仅配置足够的用户地址空间,并将其设置为无效。当发生页错误时,内核将页的内容读入并映射到用户空间。

为应对程序运行时需要的空间比硬件RAM大的场景,操作系统可以实现磁盘映射(paging to disk)。

练习

  1. 配置内核页表,让内核可以直接使用用户空间地址;
  2. 实现惰性内存分配机制;
  3. 实现COW fork;
  4. 是否有方法消除TRAPFRAME页映射到每个用户地址空间?比如,修改uservec函数将32个用户寄存器存入内核栈,或将其存入proc结构中?
  5. 是否有方法消除TRAMPOLINE页映射?

中断和设备驱动

在xv6中,内核trap会处理和识别设备中断,最终交于devintr(kernel/trap.c:178)。

Many device drivers execute code in two contexts: a top half that runs in a process’s kernel thread, and a bottom half that executes at interrupt time.

终端输入

终端驱动(kernel/console.c)是一个简单的驱动框架的示例。

The UART hardware that the driver talks to is a 16550 chip [13] emulated by QEMU. On a real computer, a 16550 would manage an RS232 serial link connecting to a terminal or other computer. When running QEMU, it’s connected to your keyboard and display.

UART的基地址是0x10000000(UART0, kernel/memlayout.h:21),UART0各寄存器定义在文件(kernel/uart.c:22)中。

xv6的main调用consoleinit(kernel/console.c:182)函数,然后再调用uartinit(kernel/uart.c:53)函数来初始化UART硬件。

在init.c(user/init.c:19)打开的文件描述符,它能够读取xv6的命令。调用read系统调用,通过内核来调用consoleread(kernel/console.c:80)。它会一直等待中断并缓存数据到cons.buf中,直到整个行输入完成,会将缓存中数据拷贝给用户最终返回到用户空间。

当用户输入一个字符,UART硬件设备会想处理器产生一个中断,它会激活xv6的trap处理程序。设备中断最后会调用devintr(kernel/trap.c:178)进行处理。接着,通过PLIC硬件单元来分辨是哪个设备中断,如果是UART设备devintr会调用uartintr

uartintr(kernel/uart.c:176)会从UART硬件读取任意输入字符,并交于consoleintr(kernel/console.c:136)进行处理;consoleintr的任务就是将输入放到cons.buf,直到整行输入完成,立即唤醒consoleread

终端输出

在一个连接到终端的文件描述符的write系统调用,最终会调用uartputc(kernel/uart.c:87)。对于每个字符,会调用uartstart来开启设备发送。

驱动的并发

These calls acquire a lock, which protects the console driver’s data structures from concurrent access.

计时器中断

Xv6 uses timer interrupts to maintain its clock and to enable it to switch among compute-bound processes; the yield calls in usertrap and kerneltrap cause this switching.

RISC-V requires that timer interrupts be taken in machine mode, not supervisor mode.

代码在start.c配置接收计时器中断(kernel/start.c:63)。部分工作的目的是编写CLINT硬件(core-local interruptor),在特定延时后产生一个中断。最终,start配置mtvectimervec并使能计时器中断。

A timer interrupt can occur at any point when user or kernel code is executing; there’s no way for the kernel to disable timer interrupts during critical operations.

The basic strategy is for the handler to ask the RISC-V to raise a “software interrupt” and immediately return.

在machine mode的中断处理程序是timervec(kernel/kernelvec.S:95),它主要配置CLINT的MTIMECMP寄存器,并设置sip为2后立刻返回。

练习

  1. 修改uart.c完全不使用中断,同时也需要修改console.c
  2. 增加一个以太网卡驱动

Chapter 6 锁

Xv6 uses a number of concurrency control techniques, depending on the situation; many more are possible. This chapter focuses on a widely used technique: the lock.

竞争

On the RISC-V this instruction is amoswap r, a. amoswap reads the value at the memory address a, writes the contents of register r to that address, and puts the value it read into r.

It performs this sequence atomically, using special hardware to prevent any other CPU from using the memory address between the read and the write.

Xv6’s acquire (kernel/spinlock.c:22) uses the portable C library call __sync_lock_test_and_set, which boils down to the amoswap instruction; the return value is the old (swapped) contents of lk->locked.

acquire(kernel/spinlock.c:22)利用riscv处理器的指令amoswap.w.aq a0, a0, (s1)实现。当获取锁成功时lk->locked为1。

release(kernel/spinlock.c:47)利用riscv处理器的指令amoswap.w zero, zero, (s1)实现。当释放锁成功时lk->locked为0。

使用锁

A hard part about using locks is deciding how many locks to use and which data and invariants each lock should protect.

再入锁

It might appear that some deadlocks and lock-ordering challenges could be avoided by using re-entrant locks, which are also called recursive locks. The idea is that if the lock is held by a process and if that process attempts to acquire the lock again, then the kernel could just allow this (since the process already has the lock), instead of calling panic, as the xv6 kernel does.

锁和中断处理程序

Some xv6 spinlocks protect data that is used by both threads and interrupt handlers.

To avoid this situation, if a spinlock is used by an interrupt handler, a CPU must never hold that lock with interrupts enabled.

例如,clockintr计时器中断处理会增加ticks(kernel/trap.c:164),同时内核线程sys_sleep(kernel/sysproc.c:59)会读取ticks的值。锁tickslock会让两次访问串行化。

指令和内存顺序

It is natural to think of programs executing in the order in which source code statements appear. That’s a reasonable mental model for single-threaded code, but is incorrect when multiple threads interact through shared memory.

To tell the hardware and compiler not to re-order, xv6 uses __sync_synchronize() in both acquire (kernel/spinlock.c:22) and release (kernel/spinlock.c:47). __sync_synchronize() is a memory barrier: it tells the compiler and CPU to not reorder loads or stores across the barrier.

睡眠锁

Xv6 provides such locks in the form of sleep-locks. acquiresleep (kernel/sleeplock.c:22) yields the CPU while waiting

练习

  1. 若屏蔽kalloc(kernel/kalloc.c:69)acquirerelease的调用,会出现哪些问题?若没有看到问题,原因是什么?
  2. kfree中屏蔽锁(恢复kalloc中的锁),会出现哪些问题?
  3. 修改kalloc.c源码让内存申请支持并发,CPU不用相互等待。
  4. 使用POSIX线程进行编码。例如,实现一个并行哈希表并测试puts/gets数据量是否随着核数量的增加而增加。
  5. 在xv6中实现pthreads的子集。实现一个用户级的线程库,这样一个用户进程就可以有多个线程,并安排这些线程运行在不同的cpu上并行运行。想出一个设计,正确地处理一个线程进行阻塞系统调用,并改变其共享地址空间。

调度

复用

Xv6 multiplexes by switching each CPU from one process to another in two situations. First, xv6’s sleep and wakeup mechanism switches when a process waits for device or pipe I/O to complete, or waits for a child to exit, or waits in the sleep system call. Second, xv6 periodically forces a switch to cope with processes that compute for long periods without sleeping.

上下文切换

it just saves and restores sets of 32 RISC-V registers, called contexts.

当进程想放弃CPU时,内核线程会调用swtch来保存它的上下文并返回调度器上下文。每个上下文都包含在struct context(kernel/proc.h:2),进程的struct proc和CPU的struct cpu都包含上下文。

swtch(kernel/swtch.S:3)只保存被调用者保存的寄存器。C编译器会生成代码保存调用者保存的寄存器到栈上。

When swtch returns, it returns to the instructions pointed to by the restored ra register, that is, the instruction from which the new thread previously called swtch.

调度

当调用函数swtch后,会切换到调度器的栈。调度器会继续在循环中查找可以切换的进程,并再次调用swtch进行切换。

We just saw that xv6 holds p->lock across calls to swtch: the caller of swtch must already hold the lock, and control of the lock passes to the switched-to code.

mycpu和myproc

Xv6 maintains a struct cpu for each CPU (kernel/proc.h:22), which records the process currently running on that CPU (if any), saved registers for the CPU’s scheduler thread, and the count of nested spinlocks needed to manage interrupt disabling.

RISC-V numbers its CPUs, giving each a hartid. Xv6 ensures that each CPU’s hartid is stored in that CPU’s tp register while in the kernel. This allows mycpu to use tp to index an array of cpu structures to find the right one.

It would be more convenient if xv6 could ask the RISC-V hardware for the current hartid whenever needed, but RISC-V allows that only in machine mode, not in supervisor mode.

The return value of myproc is safe to use even if interrupts are enabled: if a timer interrupt moves the calling process to a different CPU, its struct proc pointer will stay the same.

睡眠和唤醒

Sleep and wakeup are often called sequence coordination or conditional synchronization mechanisms.

管道

A more complex example that uses sleep and wakeup to synchronize producers and consumers is xv6’s implementation of pipes. Each pipe is represented by a struct pipe, which contains a lock and a data buffer.

Let’s suppose that calls to piperead and pipewrite happen simultaneously on two different CPUs.

进程锁

The lock associated with each process (p->lock) is the most complex lock in xv6. A simple way to think about p->lock is that it must be held while reading or writing any of the following struct proc fields: p->state, p->chan, p->killed, p->xstate, and p->pid.

练习

文件系统

概述

The xv6 file system implementation is organized in seven layers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<---------------->
File descriptor
<---------------->
Pathname
<---------------->
Directory
<---------------->
Inode
<---------------->
Logging
<---------------->
Buffer cache
<---------------->
Disk
<---------------->

Disk hardware traditionally presents the data on the disk as a numbered sequence of 512-byte blocks (also called sectors): sector 0 is the first 512 bytes, sector 1 is the next, and so on.

The file system does not use block 0 (it holds the boot sector). Block 1 is called the superblock; it contains metadata about the file system (the file system size in blocks, the number of data blocks, the number of inodes, and the number of blocks in the log). Blocks starting at 2 hold the log. After the log are the inodes, with multiple inodes per block. After those come bitmap blocks tracking which data blocks are in use. The remaining blocks are data blocks

xv6文件系统结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
0       <---------------->
boot
1 <---------------->
super
2 <---------------->
log

<---------------->
inodes


<---------------->
bit map
<---------------->
data

....

data
<---------------->

缓存层

代码在bio.c中,缓存层有两个任务

  • 同步访问硬盘块block。确保只有一个块的副本在内存中,且同一时间只有一个内核线程在使用它。
  • 缓存热门数据块block。

The main interface exported by the buffer cache consists of bread and bwrite. A kernel thread must release a buffer by calling brelse when it is done with it.

bread (kernel/bio.c:93) calls bget to get a buffer for the given sector (kernel/bio.c:97).

When the caller is done with a buffer, it must call brelse to release it.

日志层

One of the most interesting problems in file system design is crash recovery.

Xv6 solves the problem of crashes during file-system operations with a simple form of logging.

Once the system call has logged all of its writes, it writes a special commit record to the disk indicating that the log contains a complete operation. At that point the system call copies the writes to the on-disk file system data structures. After those writes have completed, the system call erases the log on disk.

block块分配器

File and directory content is stored in disk blocks, which must be allocated from a free pool. Xv6’s block allocator maintains a free bitmap on disk, with one bit per block.

inode层

It might refer to the on-disk data structure containing a file’s size and list of data block numbers. Or “inode” might refer to an in-memory inode

磁盘上数据展现形式

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
dinode
|----------------|
type
|----------------|
major
|----------------|
minor
|----------------|
nlink
|----------------|
size
|----------------|
address 1 --> data0_1
|----------------|
...
|----------------|
address 12 --> data0_12
|----------------|
indirect --> indirect block
|----------------| |----------------|
address 1 --> data1_1
|----------------|
...
|----------------|
address 256 --> data1_256
|----------------|

文件夹层

A directory is implemented internally much like a file.

目录名称

文件描述符层

All the open files in the system are kept in a global file table, the ftable.

The functions sys_link and sys_unlink edit directories, creating or removing references to inodes. They are another good example of the power of using transactions.

练习

附录

CH.1 Unix utilities实验代码

sleep

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "kernel/types.h"
#include "user/user.h"

int main(int argc, char *argv[])
{
int seconds;

if (argc <= 1)
{
fprintf(2, "sleep: need one arg\n");
exit(0);
}

// ticks = 1/10 seconds
seconds = 10 * atoi(argv[1]);
if (seconds < 0)
{
seconds = 0;
}

sleep(seconds);
exit(0);
}

pingpong

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
#include "kernel/types.h"
#include "user/user.h"

int main(int argc, char *argv[])
{
int p1[2]; // parent->child
int p2[2]; // child->parent
pipe(p1);
pipe(p2);
if (fork() == 0)
{
char buffer[5];
close(p1[1]);
close(p2[0]);
read(p1[0], buffer, sizeof(buffer));
printf("%d: received %s\n", getpid(), buffer);
write(p2[1], "pong", 5);
close(p1[0]);
close(p2[1]);
}
else
{
char buffer[5];
close(p1[0]);
close(p2[1]);
write(p1[1], "ping", 5);
read(p2[0], buffer, sizeof(buffer));
printf("%d: received %s\n", getpid(), buffer);
close(p1[1]);
close(p2[0]);
}
exit(0);
}

primes

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
#include "kernel/types.h"
#include "user/user.h"

#define MAX_NUMBER 35

int main()
{
int p0[2]; // parent->child
int n, prime;
pipe(p0);

// feeds the numbers 2 through 35
for (int i = 2; i <= MAX_NUMBER; ++i)
write(p0[1], &i, sizeof(i));

while (read(p0[0], &n, sizeof(n)))
{
printf("%d prime %d\n", getpid(), n);
prime = n;
int p1[2]; // child -> grandchild
pipe(p1);

if (fork() == 0)
{
// child
close(p0[0]);
close(p0[1]);
p0[0] = p1[0];
p0[1] = p1[1];
continue;
}
else
{
// parent
while (n < MAX_NUMBER && read(p0[0], &n, sizeof(n)))
if (n % prime != 0)
write(p1[1], &n, sizeof(n));

// close all resources
close(p0[0]);
close(p0[1]);
close(p1[0]);
close(p1[1]);
// wait children
if (n < MAX_NUMBER)
wait(0);
exit(0);
}
}

wait(0);
printf("%d exit\n", getpid());
exit(0);
}

find

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
#include "kernel/types.h"
#include "user/user.h"
#include "kernel/fs.h"
#include "kernel/stat.h"

void cmp_file(char *path, char *name)
{
char *str1, *str2;
for (str1 = path + strlen(path), str2 = name + strlen(name); str1 >= path && str2 >= name && *str1 == *str2; --str1, --str2)
; // printf("%c %c\n", *str1, *str2);

if ((int)(str2 - name) == -1)
printf("%s\n", path);
}

void find(char *path, char *patern)
{
int fd;
struct dirent de;
struct stat st;
char buf[512], *p;

if ((fd = open(path, 0)) < 0)
{
fprintf(2, "find: connot open %s\n", path);
return;
}

if (fstat(fd, &st) < 0)
{
fprintf(2, "find: connot stat %s\n", path);
close(fd);
return;
}

switch (st.type)
{
case T_DEVICE:
case T_FILE:
cmp_file(path, patern);
break;

case T_DIR:
if (strlen(path) + 1 + DIRSIZ + 1 > sizeof(buf))
{
printf("find: path too long\n");
break;
}

strcpy(buf, path);
p = buf + strlen(buf);
if (*(p - 1) != '/')
*p++ = '/';
while (read(fd, &de, sizeof(de)) == sizeof(de))
{
if (de.inum == 0 || strcmp(".", de.name) == 0 || strcmp("..", de.name) == 0)
continue;
memmove(p, de.name, DIRSIZ);
p[DIRSIZ] = 0;
find(buf, patern);
}
break;
}

close(fd);
}

int main(int argc, char *argv[])
{
if (argc <= 2)
{
fprintf(2, "find: need more args\n");
exit(-1);
}
find(argv[1], argv[2]);
exit(0);
}

xargs

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
#include "kernel/types.h"
#include "kernel/param.h"
#include "user/user.h"

int main(int argc, char *argv[])
{
char buffer[512];
char *newarg[MAXARG];
int i;
int new_idx;
for (i = 1, new_idx = 0; i < argc; ++i)
if (argv[i][0] == '-')
i += 1;
else
newarg[new_idx++] = argv[i];
while (*gets(buffer, sizeof(buffer)) != '\0')
{
buffer[strlen(buffer) - 1] = '\0';
newarg[new_idx] = buffer;

if (fork() == 0)
{
exec(newarg[0], newarg);
exit(0);
}
wait(0);
}

exit(0);
}

CH.2 system calls实验代码

GDB-常用命令

打开GDB调试开关命令:set debug remote 1

GDB-远程序列化协议

简介

所有GDB命令和响应都是以包(packet)形式进行送。一个包的基本结构:$包数据#2字节校验和

1
$packet-data#checksum

2字节校验和是计算$#之间的所有字符数值和,并取模256后的两位十六进制数。

GDB 5.0之前的协议需要包含两字节序列编号

1
$sequence-id:packet-data#checksum

当主机或目标机接收到第一个包,预期的一个响应是确认:可以是+(表明包接收正确)或-(请求重传)

1
2
-> $packet-data#checksum
<- +

连接一旦建立,可以禁用+/-确认。

主机(GDB)发送命令,而目标机(调试目标)发送响应。

包数据不能包含非法字符#$

若包数据中可以使用, ;:作为分隔符。除特别说明,所有数字均使用十六进制表示。

GDB 5.0之前的协议不能使用:作为分隔符(与sequence-id的分隔符冲突)。

在许多包中二进制数据都以两位十六进制字符表示。

二进制数据7d(ASCII字符})作为转义字符。任何转义字符传输时需要跟一个与0x20异或后的结果。例如,单字节0x7d在传输时会转为两个字节0x7d 0x5d。常见的转义字符有0x23 # 0x24 $ 0x7d } 0x2a *

响应数据可以使用运行长度编号来节省空间。例如:编号后'0* ' 表示编码字符串'0000'*后面的空格表示重复0字符32-29=3次。

对于可打印字符#$或数值超过126的都不可使用。对于7次重复(字符$)可以使用5次(字符")来分开表示。例如,'00000000'编码后为'0*"00'

对于不支持的命令,会返回空响应$#00

在最小场景中,目标机必须支持?命令来告诉GDB停止的原因,gG命令用于寄存器访问,mM命令用于内存访问。对于单线程目标要实现c命令,并支持单步调试命令s。多线程目标需要支持vCont命令。其他所有命令都是可选的。

下表中,命令中的空格表示语义分隔,但实际数据包不需要发送。

命令说明
!使能扩展模式
?当连接第一次建立后,会被询问目标机停止的原因。回复与step和continue一样。
A arglen,argnum,arg,…初始化argv[]参数数组传递给程序。其中arglen是十六进制编码后的字节流参数arg的长度。
b baud(不推荐使用)改变串行通信速率
B addr,mode(不推荐使用,使用Z和z包替代)设置(mode是S)或清除(mode是C)在addr处的断点。
bc向后继续执行。目标系统反向执行。
bs向后单步执行。反向执行一条执行。
c [addr]从addr处继续执行;addr省略时,从当前地址继续。
C sig[;addr]与c命令一样,额外可携带一个信号量sig(十六进制数)
d(不推荐使用)其他调试标志
D
D;pid
通知远端目标机GDB断开连接。第二种包含进程编号,只对特定的进程生效。
F RC,EE,CF;XX文件I/O扩展协议命令
g读取通用寄存器
回复:‘XX…’
寄存器每个字节由两个十六进制表示。当目标寄存器不可用时,会使用字符x进行占位。
G XX…写通用寄存器
z0,addr,kind
Z0,addr,kind
[;con_list…]
[;cmds:persis,cmd_list]
插入(Z0)或移除(z0)在addr处kind类型的一个软件断点
z1,addr,kind
Z1,addr,kind
[;con_list…]
[;cmds:persis,cmd_list]
插入(Z1)或移除(z1)在addr处kind类型的一个硬件断点
z2,addr,kind
Z2,addr,kind
插入(Z2)或移除(z2)在addr处的一个写监控点。监控的字节数由kind指定
z3,addr,kind
Z3,addr,kind
插入(Z3)或移除(z3)在addr处的一个读监控点。监控的字节数由kind指定
z4,addr,kind
Z4,addr,kind
插入(Z4)或移除(z4)在addr处的一个访问监控点。监控的字节数由kind指定

停止回复包

通用包

q开头的是通用询问包;以Q开头的是通用设置包;

参考

详见官方文档 Appendix E gdb Remote Serial Protocol章节

语言简介

Verilog在线学习HDLBits主页

基本语句

常量

1
2
3
3'b101 	// binary 101
4'hf // binary 1111
4'd10 // binary 1010

线wire

物理线路没有方向,但建模上需要指定方向。

创造一个模型,有三个输入和四个输出

1
2
3
4
a -> w
b -> x
b -> y
c -> z

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module top_module( 
input a,b,c,
output w,x,y,z );

assign w=a;
assign x=b;
assign y=b;
assign z=c;

// If we're certain about the width of each signal, using
// the concatenation operator is equivalent and shorter:
// assign {w,x,y,z} = {a,b,b,c};

endmodule

非门Inverter

电路中产生一个非门

1
2
3
4
5
module top_module( input in, output out );
assign out = !in;
// similar
// assign out = ~in;
endmodule

与门AND gate

电路中产生一个与门

1
2
3
4
5
6
7
8
module top_module( 
input a,
input b,
output out );

assign out = a & b;

endmodule

或非门Nor gate

电路中产生一个或非门

1
2
3
4
5
6
7
8
module top_module( 
input a,
input b,
output out );

assign out = ~(a | b);
endmodule

异或非门XNOR gate

电路中产生一个异或非门

1
2
3
4
5
6
7
8
module top_module( 
input a,
input b,
output out );

assign out = ~(a ^ b);
endmodule

声明线Declaring wires

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
`default_nettype none
module top_module(
input a,
input b,
input c,
input d,
output out,
output out_n );

wire w1, w2, w3;
assign out = w3;
assign out_n = ~w3;

assign w3 = w1 | w2;
assign w1 = a & b;
assign w2 = c & d;
endmodule

7458芯片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module top_module ( 
input p1a, p1b, p1c, p1d, p1e, p1f,
output p1y,
input p2a, p2b, p2c, p2d,
output p2y );

wire wp11,wp12;
assign p1y = wp11 | wp12;
assign wp11 = p1a & p1b & p1c;
assign wp12 = p1f & p1e & p1d;

wire wp21,wp22;
assign p2y = wp21 | wp22;
assign wp21 = p2a & p2b;
assign wp22 = p2c & p2d;
endmodule

向量Vector

基本使用

声明语法:

1
type [upper:lower] vector_name;

类型可以是wire, reg等等

1
2
3
4
5
6
wire [7:0] w;         // 8-bit wire
reg [4:1] x; // 4-bit reg
output reg [0:0] y; // 1-bit reg that is also an output port (this is still a vector)
input wire [3:-2] z; // 6-bit wire input (negative ranges are allowed)
output [3:0] a; // 4-bit output wire. Type is 'wire' unless specified otherwise.
wire [0:7] b; // 8-bit wire where b[0] is the most-significant bit.

隐含声明可能导致意想不到的结果

1
2
3
4
5
6
wire [2:0] a, c;   // Two vectors
assign a = 3'b101; // a = 101
assign b = a; // b = 1 implicitly-created wire
assign c = b; // c = 001 <-- bug
my_module i1 (d,e); // d and e are implicitly one-bit wide if not declared.
// This could be a bug if the port was intended to be a

unpacked数组维度跟在名称后,packed数组维度放在名称前

1
2
reg [7:0] mem [255:0];   // 256 unpacked elements, each of which is a 8-bit packed vector of reg.
reg mem2 [28:0]; // 29 unpacked elements, each of which is a 1-bit reg.

描绘如下电路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module top_module ( 
input wire [2:0] vec,
output wire [2:0] outv,
output wire o2,
output wire o1,
output wire o0 ); // Module body starts after module declaration

assign {o2,o1,o0} = vec[2:0];
assign outv = vec;

// This is ok too
//assign o0 = vec[0];
//assign o1 = vec[1];
//assign o2 = vec[2];
endmodule

将字数据(2字节)按高低位分离

1
2
3
4
5
6
7
8
9
10
11
12
`default_nettype none     // Disable implicit nets. Reduces some types of bugs.
module top_module(
input wire [15:0] in,
output wire [7:0] out_hi,
output wire [7:0] out_lo );

assign out_hi = in[15:8];
assign out_lo = in[7:0];

// Concatenation operator also works: assign {out_hi, out_lo} = in;
endmodule

向量门

按位与&和逻辑与&对于N-bit位输入来说,会产生不同结果。前者会产生N-bit位输出,而后者只产生1-bit位输出(作为布尔值,非0值为true,0值为false)。

描绘如下电路,out_not高5-3位为b的非,其余为a的非。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module top_module( 
input [2:0] a,
input [2:0] b,
output [2:0] out_or_bitwise,
output out_or_logical,
output [5:0] out_not
);

assign out_or_bitwise = a | b;
assign out_or_logical = a || b;
// ! is logical inverse
assign out_not = {~b, ~a};
endmodule

颠倒8位

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
module top_module (
input [7:0] in,
output [7:0] out
);
assign out = {in[0],in[1],in[2],in[3],in[4],in[5],in[6],in[7]};
//assign {out[0],out[1],out[2],out[3],out[4],out[5],out[6],out[7]} = in;

/*
// I know you're dying to know how to use a loop to do this:

// Create a combinational always block. This creates combinational logic that computes the same result
// as sequential code. for-loops describe circuit *behaviour*, not *structure*, so they can only be used
// inside procedural blocks (e.g., always block).
// The circuit created (wires and gates) does NOT do any iteration: It only produces the same result
// AS IF the iteration occurred. In reality, a logic synthesizer will do the iteration at compile time to
// figure out what circuit to produce. (In contrast, a Verilog simulator will execute the loop sequentially
// during simulation.)
always @(*) begin
for (int i=0; i<8; i++) // int is a SystemVerilog type. Use integer for pure Verilog.
out[i] = in[8-i-1];
end


// It is also possible to do this with a generate-for loop. Generate loops look like procedural for loops,
// but are quite different in concept, and not easy to understand. Generate loops are used to make instantiations
// of "things" (Unlike procedural loops, it doesn't describe actions). These "things" are assign statements,
// module instantiations, net/variable declarations, and procedural blocks (things you can create when NOT inside
// a procedure). Generate loops (and genvars) are evaluated entirely at compile time. You can think of generate
// blocks as a form of preprocessing to generate more code, which is then run though the logic synthesizer.
// In the example below, the generate-for loop first creates 8 assign statements at compile time, which is then
// synthesized.
// Note that because of its intended usage (generating code at compile time), there are some restrictions
// on how you use them. Examples: 1. Quartus requires a generate-for loop to have a named begin-end block
// attached (in this example, named "my_block_name"). 2. Inside the loop body, genvars are read only.
generate
genvar i;
for (i=0; i<8; i = i+1) begin: my_block_name
assign out[i] = in[8-i-1];
end
endgenerate
*/

endmodule

重复操作

声明语法:

1
{num{vector}}

示例

1
2
3
4
{5{1'b1}}           // 5'b11111 (or 5'd31 or 5'h1f)
{2{a,b,c}} // The same as {a,b,c,a,b,c}
{3'd5, {2{3'd6}}} // 9'b101_110_110. It's a concatenation of 101 with
// the second vector, which is two copies of 3'b110.

将8位数据扩展为32位,高24位为符号位(bit[7]),余下8位为输入

1
2
3
4
5
6
7
8
9
10
11
module top_module (
input [7:0] in,
output [31:0] out
);

// Concatenate two things together:
// 1: {in[7]} repeated 24 times (24 bits)
// 2: in[7:0] (8 bits)
assign out = { {24{in[7]}}, in };

endmodule

描述电路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module top_module (
input a, b, c, d, e,
output [24:0] out
);

wire [24:0] top, bottom;
assign top = { {5{a}}, {5{b}}, {5{c}}, {5{d}}, {5{e}} };
assign bottom = {5{a,b,c,d,e}};
assign out = ~top ^ bottom; // Bitwise XNOR

// This could be done on one line:
// assign out = ~{ {5{a}}, {5{b}}, {5{c}}, {5{d}}, {5{e}} } ^ {5{a,b,c,d,e}};

endmodule

模块Module

基本使用

基本语法

1
2
3
module mod_a ( input in1, input in2, output out );
// Module body
endmodule

线与模块端口连接有两种方式:通过位置和通过名称

通过位置连接,模块实例instance1三个端口分别连接线wa, wb, wc

1
mod_a instance1 ( wa, wb, wc );

通过名称连接,模块实例instance2三个端口名称与对应线连接:

1
mod_a instance2 ( .out(wc), .in1(wa), .in2(wb) );

描述电路,其中模块mod_a在其他地方描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
module top_module (
input a,
input b,
output out
);

// Create an instance of "mod_a" named "inst1", and connect ports by name:
mod_a inst1 (
.in1(a), // Port"in1"connects to wire "a"
.in2(b), // Port "in2" connects to wire "b"
.out(out) // Port "out" connects to wire "out"
// (Note: mod_a's port "out" is not related to top_module's wire "out".
// It is simply coincidence that they have the same name)
);

/*
// Create an instance of "mod_a" named "inst2", and connect ports by position:
mod_a inst2 ( a, b, out ); // The three wires are connected to ports in1, in2, and out, respectively.
*/

endmodule

移位模块

给已经实现的D触发器(D flip-flop)模块module my_dff ( input clk, input d, output q );,按下图描述电路

1
2
3
4
5
6
7
8
module top_module ( input clk, input d, output q );
wire q1,q2;

my_dff d1 (clk, d, q1);
my_dff d2 (clk, q1, q2);
my_dff d3 (clk, q2, q);
endmodule

8位移位器

描绘电路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
module top_module (
input clk,
input [7:0] d,
input [1:0] sel,
output reg [7:0] q
);

wire [7:0] o1, o2, o3; // output of each my_dff8

// Instantiate three my_dff8s
my_dff8 d1 ( clk, d, o1 );
my_dff8 d2 ( clk, o1, o2 );
my_dff8 d3 ( clk, o2, o3 );

// This is one way to make a 4-to-1 multiplexer
always @(*) begin // Combinational always block
case(sel)
2'h0: q = d;
2'h1: q = o1;
2'h2: q = o2;
2'h3: q = o3;
endcase
end
endmodule

全加器

add16全加器已经定义module add16 ( input[15:0] **a**, input[15:0] **b**, input **cin**, output[15:0] **sum**, output **cout** );,它包含16个add1全加器,描述电路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module top_module (
input [31:0] a,
input [31:0] b,
output [31:0] sum
);
wire o1,o2;

add16 inst1(a[15:0],b[15:0],0,sum[15:0],o1);
add16 inst2(a[31:16],b[31:16],o1,sum[31:16],o2);
endmodule

module add1 ( input a, input b, input cin, output sum, output cout );
// Full adder module here
assign sum = cin ? ~(a^b) : a^b;
assign cout = cin ? a|b : a&b;
endmodule

加减器

减法器由全加器变化而来,只需将对第二个操作数取补码即可(反码加一)。等效的电路可以有两种效果:a+b+0a+~b+1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module top_module(
input [31:0] a,
input [31:0] b,
input sub,
output [31:0] sum
);

wire o1,o2;
wire [31:0] b1 = b^{32{sub}};

add16 inst1(a[15:0],b1[15:0],sub,sum[15:0],o1);
add16 inst2(a[31:16],b1[31:16],o1,sum[31:16],o2);
endmodule

过程Procedure

对于综合硬件,有两种相关always块:

  • 组合型:always @(*)
  • 时序型:always @(posedge clk)

块语句需要使用beginend标记出来,若语句只有“单句”时,块标记可以省略。

Always块

对于简单的场景,组合型always块等价于assign语句。

1
2
assign out1 = a & b | c ^ d;
always @(*) out2 = a & b | c ^ d;

assign语句左边符号对应的类型是net(例如:wire),而过程赋值对应的类型是variable(例如:reg)。两者的差异不会对综合的电路产生任何影响,只是一种不成文的约定。

在VHDL中有三种赋值类型:

  • 持续赋值assign x = y;
  • 过程阻塞赋值,在过程中使用x = y;
  • 过程非阻塞赋值,在过程中使用x <= y;

在组合alsways块中,使用阻塞赋值。在时序always块中,使用非阻塞赋值。

使用三种方式,用异或门构建电路。值得注意的是,第三种方式产生的结果会因为有触发器而产生延时。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// synthesis verilog_input_version verilog_2001
module top_module(
input clk,
input a,
input b,
output wire out_assign,
output reg out_always_comb,
output reg out_always_ff );

assign out_assign = a^b;
always @(*) out_always_comb = a^b;
always @(posedge clk) out_always_ff <= a^b;
endmodule

if语句

描绘一个2-1选择器

对应的值表

sel_b1sel_b2out_assign out_always
00a
01a
10a
11b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// synthesis verilog_input_version verilog_2001
module top_module(
input a,
input b,
input sel_b1,
input sel_b2,
output wire out_assign,
output reg out_always );

assign out_assign = sel_b1&sel_b2 ? b : a;

always @(*) begin
if (sel_b1&sel_b2) begin
out_always = b;
end
else begin
out_always = a;
end
end
endmodule

if语句锁存

修复下图错误,当cpu_overheated时,产生关闭信号(shut_off_computer设置为1)。

修改前,错误的示意图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// synthesis verilog_input_version verilog_2001
module top_module (
input cpu_overheated,
output reg shut_off_computer,
input arrived,
input gas_tank_empty,
output reg keep_driving ); //

always @(*) begin
if (cpu_overheated)
shut_off_computer = 1;
end

always @(*) begin
if (~arrived)
keep_driving = ~gas_tank_empty;
end

endmodule

修改后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// synthesis verilog_input_version verilog_2001
module top_module (
input cpu_overheated,
output reg shut_off_computer,
input arrived,
input gas_tank_empty,
output reg keep_driving ); //

always @(*) begin
if (cpu_overheated)
shut_off_computer = 1;
else
shut_off_computer = 0;
end

always @(*) begin
if (~arrived)
keep_driving = ~gas_tank_empty;
else
keep_driving = 0;
end

endmodule

case语句

基本语法

1
2
3
4
5
6
case (cond)
2'h0: statments0;
2'h1: statments1;
...
default: statmentsx;
endcase

建立一个6-1选择器,否则输出0

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
// synthesis verilog_input_version verilog_2001
module top_module (
input [2:0] sel,
input [3:0] data0,
input [3:0] data1,
input [3:0] data2,
input [3:0] data3,
input [3:0] data4,
input [3:0] data5,
output reg [3:0] out );//

always@(*) begin // This is a combinational circuit
case(sel)
4'h0: out = data0;
4'h1: out = data1;
4'h2: out = data2;
4'h3: out = data3;
4'h4: out = data4;
4'h5: out = data5;
default: out = 0;
endcase
end

endmodule

构建一个4bits的优先编码器,输出第一个1所在的比特位。

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
module top_module (
input [3:0] in,
output reg [1:0] pos
);

always @(*) begin // Combinational always block
case (in)
4'h0: pos = 2'h0; // I like hexadecimal because it saves typing.
4'h1: pos = 2'h0;
4'h2: pos = 2'h1;
4'h3: pos = 2'h0;
4'h4: pos = 2'h2;
4'h5: pos = 2'h0;
4'h6: pos = 2'h1;
4'h7: pos = 2'h0;
4'h8: pos = 2'h3;
4'h9: pos = 2'h0;
4'ha: pos = 2'h1;
4'hb: pos = 2'h0;
4'hc: pos = 2'h2;
4'hd: pos = 2'h0;
4'he: pos = 2'h1;
4'hf: pos = 2'h0;
default: pos = 2'b0; // Default case is not strictly necessary because all 16 combinations are covered.
endcase
end

// There is an easier way to code this. See the next problem (always_casez).

endmodule

casez

当部分比特位不关心时,可以使用casez。符号z?在一下场景都是等价的。与casez类似的是casex,后者使用符号x

1
2
3
4
5
6
7
8
9
always @(*) begin
casez (in[3:0])
4'bzzz1: out = 0; // in[3:1] can be anything
4'bzz1z: out = 1;
4'b?1??: out = 2;
4'b1???: out = 3;
default: out = 0;
endcase
end

构建8bits优先编码器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// synthesis verilog_input_version verilog_2001
module top_module (
input [7:0] in,
output reg [2:0] pos );

always @(*) begin
casez (in[7:0])
8'h0: pos = 0;
8'bzzzz_???1: pos = 0;
8'bzzzz_zz10: pos = 1;
8'bzzzz_z100: pos = 2;
8'bzzzz_1000: pos = 3;
8'bzzz1_0000: pos = 4;
8'bzz10_0000: pos = 5;
8'bz100_0000: pos = 6;
8'b1000_0000: pos = 7;
default: pos = 3'bzzz;
endcase
end
endmodule

键盘扫描码

处理如下四个按键

Scancode [15:0]Arrow key
16'he06bleft arrow
16'he072down arrow
16'he074right arrow
16'he075up arrow
Anything elsenone
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// synthesis verilog_input_version verilog_2001
module top_module (
input [15:0] scancode,
output reg left,
output reg down,
output reg right,
output reg up );

always @(*) begin
left = 0; down = 0; right = 0; up = 0;
case (scancode)
16'he06b: left = 1;
16'he072: down = 1;
16'he074: right = 1;
16'he075: up = 1;
default: ;
endcase
end
endmodule

更多特性

条件

三元条件操作符?:

1
(condition ? if_true : if_false)

逻辑操作简化

若需要对向量所有位进行门操作时,正常书写比较冗余,可以对与、或和异或操作简化

1
2
3
& a[3:0]     // AND: a[3]&a[2]&a[1]&a[0]. Equivalent to (a[3:0] == 4'hf)
| b[3:0] // OR: b[3]|b[2]|b[1]|b[0]. Equivalent to (b[3:0] != 4'h0)
^ c[2:0] // XOR: c[2]^c[1]^c[0]

实现奇偶校验位算法

1
2
3
4
5
6
7
module top_module (
input [7:0] in,
output parity);

assign parity = ^in[7:0];
endmodule

100bits翻转

1
2
3
4
5
6
7
8
9
10
11
module top_module (
input [99:0] in,
output reg [99:0] out
);

always @(*) begin
for (int i=0;i<$bits(out);i++) // $bits() is a system function that returns the width of a signal.
out[i] = in[$bits(out)-i-1]; // $bits(out) is 100 because out is 100 bits wide.
end

endmodule

比特1计数

1
2
3
4
5
6
7
8
9
10
11
module top_module( 
input [254:0] in,
output [7:0] out );

always @(*) begin // Combinational always block
out = 0;
for (int i=0; i<$bits(in); i++)
out += in[i];
end
endmodule

100bits全加器

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
module top_module( 
input [99:0] a, b,
input cin,
output [99:0] cout,
output [99:0] sum );

genvar i;
generate
for (i=0; i<$bits(a); ++i) begin: adder_gen
adder_1bit u_adder (
.a(a[i]),
.b(b[i]),
.cin(i==0?cin:cout[i-1]),
.sum(sum[i]),
.cout(cout[i])
);
end
endgenerate

endmodule

module adder_1bit (
input a,b,
input cin,
output cout,
output sum
);
assign {cout,sum} = a+b+cin;
// assign sum = cin ? ~(a^b) : a^b;
// assign cout = cin ? a|b : a&b;
endmodule

100位BCD码全加器

已经定义1位BCD码全加器

1
2
3
4
5
6
module bcd_fadd (
input [3:0] a,
input [3:0] b,
input cin,
output cout,
output [3:0] sum );
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
module top_module( 
input [399:0] a, b,
input cin,
output cout,
output [399:0] sum );

wire [100:0] c;
assign cout = c[100];
assign c[0] = cin;

genvar i;
generate
for(i=0; i<100; ++i) begin: bcd_gen
bcd_fadd u_bcd (
.a( a[(4*i+3):(4*i)] ),
.b( b[(4*i+3):(4*i)] ),
.cin( c[i] ),
.cout( c[i+1] ),
.sum( sum[(4*i+3):(4*i)] )
);
end
endgenerate

endmodule

仿真

timescale

定义仿真中时间单位的比例。它的作用是指定时序仿真中时间单位的大小,以便仿真器可以正确地模拟设计中的时序行为。

基本语法

1
`timescale unit / precision

其中,unit是时间单位,可以是1ns1ps1us等,表示一个时钟周期的时间长度;precision是时间精度,表示仿真的最小时间单位;

#num

等待num个时间单位后,执行后面的语句。

基本语法

1
#num statement

task

Verilog语言中具有类似C语言函数的结构有task和function,他们可以增加代码可读性和重复使用性。Function用来描述组合逻辑,只能有一个返回值,function的内部不能包含时序控制。Task类似procedure,执行一段verilog代码,task中可以有任意数量的输入和输出,task也可以包含时序控制。

基本语法

1
2
task TASK_NAME;
endtask

PS/2键盘设备仿真

当用户按键或松开时,键盘以每帧11位的格式串行传送数据给主机,同时在PS2_CLK时钟信号上传输对应的时钟(一般为10.0–16.7kHz)。第一位是开始位(逻辑0),后面跟8位数据位(低位在前),一个奇偶校验位(奇校验)和一位停止位(逻辑1)。每位都在时钟的 下降沿 有效,下图显示了键盘传送一字节数据的时序。在下降沿有效的主要原因是下降沿正好在数据位的中间,因此可以让数据位从开始变化到接收采样时能有一段信号建立时间。

键盘输出数据时序图

键盘通过PS2_DAT引脚发送的信息称为扫描码,每个扫描码可以由单个数据帧或连续多个数据帧构成。当按键被按下时送出的扫描码被称为 通码(Make Code) ,当按键被释放时送出的扫描码称为 断码(Break Code) 。以 W 键为例, W 键的通码是1Dh,如果 W 键被按下,则PS2_DAT引脚将输出一帧数据,其中的8位数据位为1Dh,如果 W 键一直没有释放,则不断输出扫描码1Dh 1Dh … 1Dh,直到有其他键按下或者 W 键被放开。某按键的断码是F0h加此按键的通码,如释放 W 键时输出的断码为F0h 1Dh,分两帧传输。

多个键被同时按下时,将逐个输出扫描码,如:先按左 Shift 键(扫描码为12h)、再按 W 键、放开 W 键、再放开左 Shift 键,则此过程送出的全部扫描码为:12h 1Dh F0h 1Dh F0h 12h。

键盘扫描码

每个键都有唯一的通码和断码。键盘所有键的扫描码组成的集合称为扫描码集。共有三套标准的扫描码集,所有现代的键盘默认使用第二套扫描码。下图显示了键盘各键的扫描码(以十六进制表示),如Caps键的扫描码是58h。 下图可以看出,键盘上各按键的扫描码是随机排列的,如果想迅速的将键盘扫描码转换为ASCII码,一个最简单的方法就是利用查找表 LookUp Table, LUT ,扫描码到ASCII码的转换表格请读者自己生成。

键盘扫描码
扩展键盘和数字键盘的扫描码

键盘控制器

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
module ps2_keyboard(clk,clrn,ps2_clk,ps2_data,data,
ready,nextdata_n,overflow);
input clk,clrn,ps2_clk,ps2_data;
input nextdata_n;
output [7:0] data;
output reg ready;
output reg overflow; // fifo overflow
// internal signal, for test
reg [9:0] buffer; // ps2_data bits
reg [7:0] fifo[7:0]; // data fifo
reg [2:0] w_ptr,r_ptr; // fifo write and read pointers
reg [3:0] count; // count ps2_data bits
// detect falling edge of ps2_clk
reg [2:0] ps2_clk_sync;

always @(posedge clk) begin
ps2_clk_sync <= {ps2_clk_sync[1:0],ps2_clk};
end

wire sampling = ps2_clk_sync[2] & ~ps2_clk_sync[1];

always @(posedge clk) begin
if (clrn == 0) begin // reset
count <= 0; w_ptr <= 0; r_ptr <= 0; overflow <= 0; ready<= 0;
end
else begin
if ( ready ) begin // read to output next data
if(nextdata_n == 1'b0) //read next data
begin
r_ptr <= r_ptr + 3'b1;
if(w_ptr==(r_ptr+1'b1)) //empty
ready <= 1'b0;
end
end
if (sampling) begin
if (count == 4'd10) begin
if ((buffer[0] == 0) && // start bit
(ps2_data) && // stop bit
(^buffer[9:1])) begin // odd parity
fifo[w_ptr] <= buffer[8:1]; // kbd scan code
w_ptr <= w_ptr+3'b1;
ready <= 1'b1;
overflow <= overflow | (r_ptr == (w_ptr + 3'b1));
end
count <= 0; // for next
end else begin
buffer[count] <= ps2_data; // store ps2_data
count <= count + 3'b1;
end
end
end
end
assign data = fifo[r_ptr]; //always set output data

endmodule

键盘仿真模型

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
`timescale 1ns / 1ps
module ps2_keyboard_model(
output reg ps2_clk,
output reg ps2_data
);
parameter [31:0] kbd_clk_period = 60;
initial ps2_clk = 1'b1;

task kbd_sendcode;
input [7:0] code; // key to be sent
integer i;

reg[10:0] send_buffer;
begin
send_buffer[0] = 1'b0; // start bit
send_buffer[8:1] = code; // code
send_buffer[9] = ~(^code); // odd parity bit
send_buffer[10] = 1'b1; // stop bit
i = 0;
while( i < 11) begin
// set kbd_data
ps2_data = send_buffer[i];
#(kbd_clk_period/2) ps2_clk = 1'b0;
#(kbd_clk_period/2) ps2_clk = 1'b1;
i = i + 1;
end
end
endtask

endmodule

键盘测试代码

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
`timescale 1ns / 1ps
module keyboard_sim;

/* parameter */
parameter [31:0] clock_period = 10;

/* ps2_keyboard interface signals */
reg clk,clrn;
wire [7:0] data;
wire ready,overflow;
wire kbd_clk, kbd_data;
reg nextdata_n;

ps2_keyboard_model model(
.ps2_clk(kbd_clk),
.ps2_data(kbd_data)
);

ps2_keyboard inst(
.clk(clk),
.clrn(clrn),
.ps2_clk(kbd_clk),
.ps2_data(kbd_data),
.data(data),
.ready(ready),
.nextdata_n(nextdata_n),
.overflow(overflow)
);

initial begin /* clock driver */
clk = 0;
forever
#(clock_period/2) clk = ~clk;
end

initial begin
clrn = 1'b0; #20;
clrn = 1'b1; #20;
model.kbd_sendcode(8'h1C); // press 'A'
#20 nextdata_n =1'b0; #20 nextdata_n =1'b1;//read data
model.kbd_sendcode(8'hF0); // break code
#20 nextdata_n =1'b0; #20 nextdata_n =1'b1; //read data
model.kbd_sendcode(8'h1C); // release 'A'
#20 nextdata_n =1'b0; #20 nextdata_n =1'b1; //read data
model.kbd_sendcode(8'h1B); // press 'S'
#20 model.kbd_sendcode(8'h1B); // keep pressing 'S'
#20 model.kbd_sendcode(8'h1B); // keep pressing 'S'
model.kbd_sendcode(8'hF0); // break code
model.kbd_sendcode(8'h1B); // release 'S'
#20;
$stop;
end

endmodule

电路

组合逻辑

基本门

GND

1
assign out = 1'b0;

NOR

1
assign out = ~(in1|in2);

多路选择器

9路选择器

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
module top_module( 
input [15:0] a, b, c, d, e, f, g, h, i,
input [3:0] sel,
output [15:0] out );

// Case statements can only be used inside procedural blocks (always block)
// This is a combinational circuit, so use a combinational always @(*) block.
always @(*) begin
out = '1; // '1 is a special literal syntax for a number with all bits set to 1.
// '0, 'x, and 'z are also valid.
// I prefer to assign a default value to 'out' instead of using a
// default case.
case (sel)
4'h0: out = a;
4'h1: out = b;
4'h2: out = c;
4'h3: out = d;
4'h4: out = e;
4'h5: out = f;
4'h6: out = g;
4'h7: out = h;
4'h8: out = i;
endcase
end
endmodule

4位256路选择器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module top_module (
input [1023:0] in,
input [7:0] sel,
output [3:0] out
);

// We can't part-select multiple bits without an error, but we can select one bit at a time,
// four times, then concatenate them together.
assign out = {in[sel*4+3], in[sel*4+2], in[sel*4+1], in[sel*4+0]};

// Alternatively, "indexed vector part select" works better, but has an unfamiliar syntax:
// assign out = in[sel*4 +: 4]; // Select starting at index "sel*4", then select a total width of 4 bits with increasing (+:) index number.
// assign out = in[sel*4+3 -: 4]; // Select starting at index "sel*4+3", then select a total width of 4 bits with decreasing (-:) index number.
// Note: The width (4 in this case) must be constant.

endmodule

使用示例

南京大学电路教学项目-NVboard,可以图形化显示过程状态和数据,同时也支持verilator。

双控开关

Makefile

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
VERILATOR_FLAGS := --build -j 0 -cc --exe -x-assign fast -Wall

C_SRC := $(shell ls csrc/*.cpp)
V_SRC := $(shell ls vsrc/*.v)
OBJ_DIR := obj_dir
TARGET := obj_dir/Vtop
#TARGET_ARGS := +trace

.PHONY: run clean

ifdef TRACE
VERILATOR_FLAGS += --trace
endif

all: ${TARGET}

run: ${TARGET}
@rm -rf logs
@${TARGET} ${TARGET_ARGS}

${TARGET}: ${V_SRC} ${C_SRC}
#@verilator --build -j 0 -cc --exe -x-assign fast -Wall --trace $^
@verilator ${VERILATOR_FLAGS} $^

sim:
$(call git_commit, "sim RTL") # DO NOT REMOVE THIS LINE!!!
@echo "Write this Makefile by your self."

include ../Makefile

clean:
@rm -rf logs
@rm -rf ${OBJ_DIR}

verilog代码文件top.v

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module top
(
input a,
input b,
output f
);

assign f = a ^ b;

initial begin
$display("[%0t] Model running...\n", $time);
end

endmodule

主体仿真文件sim_main.cpp

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
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdint.h>
#include <memory>
#include "Vtop.h"
#include "verilated.h"

#if VM_TRACE_VCD
#include "verilated_vcd_c.h"
#endif

int main(int argc, char** argv)
{
const std::unique_ptr<VerilatedContext> contextp{new VerilatedContext};
contextp->debug(0); // Set debug level, 0 is off, 9 is highest
contextp->randReset(2); // Randomization reset policy
contextp->commandArgs(argc, argv); // Pass arguments so Verilated code can see them

// "TOP" will be hierarchical name of the module.
const std::unique_ptr<Vtop> top{new Vtop{contextp.get(), "TOP"}};

#if VM_TRACE_VCD
Verilated::mkdir("logs");
contextp->traceEverOn(true); // Verilator must compute traced signals
const std::unique_ptr<VerilatedVcdC>tfp{new VerilatedVcdC};
top->trace(tfp.get(), 99); // Trace 99 levels of hierarchy (or see below)
tfp->open("logs/simu_top.vcd");
printf("Start trace ...\n");
#endif

uint64_t sim_time = 10000;
while(contextp->time()<sim_time && !contextp->gotFinish())
{
contextp->timeInc(1);

int a = rand() & 1;
int b = rand() & 1;
top->a = a;
top->b = b;
top->eval();
// printf("a = %d, b = %d, f = %d, time = %lu\n", a, b, top->f, contextp->time());
assert(top->f == (a ^ b));

#if VM_TRACE_VCD
tfp->dump(contextp->time());
#endif
}

top->final();

// Coverage analysis
#if VM_COVERAGE
Verilated::mkdir("logs");
contextp->converagep()->write("logs/coverage.dat");
#endif

// Final simulation summary
contextp->statsPrintSummary();

return 0;
}

Linux命令

常用命令

cd

ls

awk

sed

ps

df

fdisk

wc

find

grep/egrep

top

vi/vim

diff/vimdiff

man

查看内置帮助手册,不仅可以查看命令手册,还有系统调用、库函数、异常码、宏等等信息。

1
2
3
4
5
6
7
8
# 学习如何RTFM
$ man man
# 学习如何使用库函数
$ man 3 getopt
# 检索含有关键词xxx的命令
$ man -k xxx
$ man readline
$ man bash

xargs

strace

system call trace, 记录程序运行过程中的系统调用信息

netstat

ip

ifconfig

ssh

telnet

sort

history

!n再次执行编号n命令

!xxx再次执行以xxx开头的最近一条命令

yes

不断重复输出y

watch

在前台定时执行命令

1
2
3
4
5
6
$ watch -t -n 1 "echo -n '第六期一生一芯 | 周六 15:00~17:00 | '; \
date; echo '课程主页 https://ysyx.oscc.cc/docs/'"
第六期一生一芯 | 周六 15:00~17:00 | Thu Apr 4 16:58:59 CST 2024
课程主页 https://ysyx.oscc.cc/docs/


crontab

在后台定时执行命令,需要编写对应的配置文件

timeout

执行特定时间,返回超时或命令自身的返回值

  • 0:命令成功执行并完成。
  • 124:命令因超时而终止。
  • 其他:命令执行失败
1
$ timeout 6 mkdir -p /mnt/dev0

脚本编写

细节介绍参考man bash

基本语法

简介

脚本的本质就是将众多Linux小工具按照一定的逻辑顺序依次执行处理,脚本文件内容就是这些命令的集合。

程序运行时都会打开3个文件

  • 0号文件,标准输入
  • 1号文件,标准输出
  • 2号文件,标准错误输出

可以通过lsof -p <PID>查看进程打开的文件。

>重定向标准输出,>>追加方式重定向标准输出,2>&1将标准错误重定向到标准输出。

内置变量有

  • $0 $1 ... $n:脚本名称(有时会包含相对或绝对路径)、第1个参数……第n个参数
  • $?:最后一条命令或函数的返回值
  • $@:传递$0之外的所有参数,可以用数组保存其值:ALL_ARGS=("$@")
  • $#:传递给脚本或函数的参数个数

内置命令

shift

基本格式:shift [n]

将参数左移n位,默认值为1

变量用法

基本用法

使用示例

1
2
3
4
# 变量赋值
var="/root/project/abc"
# 变量使用
echo $var ${var}

变量展开

又名shell参数展开,基本格式

1
2
3
4
${变量##模式}
${变量#模式}
${变量%%模式}
${变量%模式}

模式支持的符号:

  • * 匹配任意字符串
  • ? 匹配任意单个字符
  • [...] 匹配字符集,比如:范围[0-9a-z],取反[!abc][^abc]
  • \ 匹配转义字符,比如:*

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ var="/root/project/abc"
# 从左开始,最小匹配模式删除匹配字符串
$ echo ${var#*/}
root/project/abc

# 从左开始,最大匹配模式删除匹配字符串
$ echo ${var##*/}
abc

# 从右开始,最小匹配模式删除匹配字符串
$ echo ${var%/*}
/root/project
# 从右开始,最大匹配模式删除匹配字符串
$ echo ${var%%/*}

交互界面

基本操作

  • Tab键自动补全
  • 上下方向键遍历历史命令

任务管理

  • Ctrl+Z最小化,或运行时命令末尾添加&(但用户退出后后台任务也会退出,可以使用nohup命令避免)
  • jobs任务栏
  • bg后台执行任务,将指定任务2切换到后台执行bg %2
  • fg前台执行任务,将指定任务2切换到前台执行fg %2

shell可以通过快捷键操作光标快速移动,更多快捷键可以阅读man readlineCommands for Moving部分

  • Ctrl+B光标前移一个字符
  • Ctrl+F光标后移一个字符
  • Ctrl+A光标移到首字符处
  • Ctrl+E光标移到首字符处

基本单元

shell都是基于文本进行处理,其中只有数字字符串在特定场景可以进行数学运算。

通配符

  • *任意长度的字符串
  • ?任意一个字符
  • [xxx]集合中任意一个字符

示例

1
2
$ echo Hello-{a,bb,ccc}-{1,2}!
Hello-a-1! Hello-a-2! Hello-bb-1! Hello-bb-2! Hello-ccc-1! Hello-ccc-2!

数组

示例

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
# 创建一个数组
array=()
declare -a array

# 添加元素
array+=("item0")
array+=("item1")
array+=("item2")

# 遍历一个数组
for item in $array; do echo item is $item; done
for i in {1..${#array[@]}}; do item=${array[$i]}; echo item is $item; done
for item in ${array[@]}; do echo item is $item; done

# 删除指定元素
unset 'array[1]' # 删除item0,但是索引依然保留
# 重新创建一个新数组,通过过滤方式
new_array=()
for item in ${array[@]}; do
if [ $item != "item0" ]; then
new_array+=("$item")
fi
done

# 删除整个数组
unset array
array=() # 清空整个数组

判断逻辑

if

1
$ if mkdir -p /mnt/dev0; then echo ok; else echo error; fi

命令执行成功则打印ok,否则打印失败

case

循环逻辑

while

1
2
3
$ while [[ `seq 1 10 | shuf | head -n 1` != "1" ]]; do echo "retry"; done
retry
retry

for

捕获信号

trap 命令用于捕获和处理信号或错误。当特定信号发生时,trap 可以执行指定的命令。trap 可以捕获多种信号,包括系统信号和错误。

一些常见的信号量包括:

  • SIGINT (2): 中断信号,通常由 Ctrl+C 触发。
  • SIGTERM (15): 终止信号,通常由 kill 命令发送,用于请求进程终止。
  • SIGKILL (9): 强制终止信号,无法被捕获或忽略,通常用于强制停止进程。
  • SIGQUIT (3): 退出信号,通常由 Ctrl+ 触发,用于生成 core dump。
  • SIGSTOP (19): 停止信号,无法被捕获或忽略,通常用于挂起进程。
  • SIGCONT (18): 恢复信号,用于恢复一个被 SIGSTOP 停止的进程。
  • SIGHUP (1): 挂起信号,通常是终端关闭时发送的。
  • SIGUSR1SIGUSR2: 用户自定义信号,可以用来传递特定信息。
  • SIGSEGV (11): 段错误信号,通常在程序访问非法内存时触发。
  • SIGPIPE (13): 管道破裂信号,通常当向一个没有读取者的管道写数据时触发。

常见的错误有:

  • EXIT:捕获脚本或命令退出时的状态(返回码)。例如,你可以捕获脚本退出时的状态码,或者通过 trap 在脚本退出时执行清理工作。
  • ERR:捕获任何命令的非零退出状态(发生错误时触发)。通过 trap 'command' ERR,你可以在发生错误时执行指定的命令。
1
2
3
4
5
6
7
8
#!/bin/bash

exit_handler() {
# 具体处理逻辑
}

trap exit_handler SIGINT SIGTERM EXIT ERR

输入输出

经典示例

输入参数解析

1
2
3
4
5
6
输入的参数可以包含以下几种情况的任意组合,<xxx>表示参数后必须跟对应的值:
-n <num>
--number <num>
-v
--version
-h

case手动解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/bash

# 手动解析参数
while [ $# -gt 0 ]; do
case "$1" in
-n|--number)
# 确保后面有值
if [ -n "$2" ] && [[ "$2" != -* ]]; then
number="$2"
shift 2
else
echo "错误: -n 或 --number 需要一个参数"
exit 1
fi
;;
-v|--version) show_version=true; shift; ;;
-h|--help) show_help=true; shift; ;;
*)
echo "忽略非选项参数: $1"
shift
;;
esac
done

自制CPU主频监视器

1
$ watch -n 1 "cat /proc/cpuinfo | grep MHz | awk '{print \$1 NR \$3 \$4 \$2}'"

打包特定文件并上传到远端

1
$ find . -name "*.pdf" | xargs tar cj | ssh yzh@192.168.1.1 'cd ysyx; > pdf.tar.bz2'

统计工具类型分布

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ echo $PATH | tr -t : '\n' | xargs -I{} find {} -maxdepth 1 -type f -executable | \
xargs file -b -e elf | sort | uniq -c | sort -nr

835 ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV)
129 Perl script text executable
113 POSIX shell script, ASCII text executable
34 Python script, ASCII text executable
24 ELF 64-bit LSB pie executable, x86-64, version 1 (GNU/Linux)
20 Bourne-Again shell script, ASCII text executable
15 setuid ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV)
12 setgid ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV)
10 ELF 64-bit LSB executable, x86-64, version 1 (SYSV)
9 POSIX shell script, Unicode text, UTF-8 text executable
3 ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux)
2 Python script, Unicode text, UTF-8 text executable
2 PHP script, ASCII text executable
1 setuid, setgid ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV)
1 Python script, ISO-8859 text executable
1 POSIX shell script, ASCII text executable, with very long lines (459)
1 PHP phar archive with SHA1 signature
1 Paul Falstad's zsh script, ASCII text executable
1 JavaScript source, ASCII text
0%