oz1010's blog

记录生活或技能

先插播广告

使用Vultr,充值就送美金,便宜还可免费换ip

  1. 最低3.5美金(2.5美金的仅支持ipv6访问),性能足够使用!
  2. 可以保存快照,随时迁移新的IP地址,而且免费!
  3. 可以使用支付宝和微信,相当方便!

服务器端的工作

安装脚本

运行以下脚本即可:

1
curl -Ls https://raw.githubusercontent.com/v2fly/fhs-install-v2ray/master/install-release.sh | sudo bash

里面可能会有些报错信息,暂时不管,完成之后就是配置了

获取用户ID

之前的脚本,不用手动配置脚本即可使用,现在使用以上脚本,需要自己配置config.json文件,首先获取用户ID:

运用指令:cat /proc/sys/kernel/random/uuid 创建一个用户 id ,并记住这个id号;

1
2
[root@vultr ~]# cat /proc/sys/kernel/random/uuid 
08ef6234-dcc0-45d1-9954-f9490cb2beb2

配置

配置文件路径为/usr/local/etc/v2ray/config.json

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
{
"inbounds": [{
"port": 12345,
"protocol": "vmess",
"settings": {
"clients": [
{
"id": "08ef6234-dcc0-45d1-9954-f9490cb2beb2",
"level": 1,
"alterId": 0
}
]
}
}],
"outbounds": [{
"protocol": "freedom",
"settings": {}
},{
"protocol": "blackhole",
"settings": {},
"tag": "blocked"
}],
"routing": {
"rules": [
{
"type": "field",
"ip": ["geoip:private"],
"outboundTag": "blocked"
}
]
}
}

直接复制我上面的配置即可使用,id就是上面第二步获取的用户id

启动V2Ray

在首次安装完成之后,V2Ray不会自动启动,需要手动运行上述启动命令。而在已经运行V2Ray的VPS上再次执行安装脚本,安装脚本会自动停止V2Ray 进程,升级V2Ray程序,然后自动运行V2Ray。在升级过程中,配置文件不会被修改。

1
2
3
4
5
6
7
8
# 启动
systemctl start v2ray
# 停止
systemctl stop v2ray
# 重启
systemctl restart v2ray
# 使能
systemctl enable v2ray

关于软件更新:更新 V2Ray 的方法是再次执行安装脚本!再次执行安装脚本!再次执行安装脚本!

打开防火墙

centos系统防火墙默认是开启的,上面第三步你用了一个端口,因此你需要打开这个端口,指令如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 添加开放端口
firewall-cmd --zone=public --add-port=12345/tcp --permanent
# 重载防火墙配置,不然查看开放端口都查不到,也不能用,重载配置后即可
firewall-cmd --reload
# 删除端口
firewall-cmd --zone=public --remove-port=123456/tcp --permanent
# 查看已开放端口
firewall-cmd --zone=public --list-ports
# 查看防火墙firewalld状态
systemctl status firewalld
# 开启防火墙
systemctl start firewalld
# 关闭防火墙
systemctl stop firewalld

如果哪一天发现怎么无法使用了,有可能是IP被屏蔽,也有肯能是端口被封,这个时候就需要换个端口,别忘记防火墙开启新端口,那旧端口就可以删除了

客户端下载和使用

Windows客户端下载

工具下载

v2ray-core发布地址

v2rayN发布地址

Android客户端下载

工具下载

v2rayNG发布地址

官方机器翻译版本

官方参考手册:

开发板整体布局AN385 - ARM Cortex-M3 SMM on V2M-MPS2

子系统相关Cortex-M System Design Kit Technical Reference Manual r1p0

代码为官方示例程序:FreeRTOSv202112.00/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC

系统结构

配置文件

FreeRTOSv202112.00/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC/FreeRTOSConfig.h工程下的系统配置文件

  • configSUPPORT_DYNAMIC_ALLOCATION 栈内存管理

关键组件

1
2
3
4
5
6
7
8
FreeRTOS
|-Source
|-tasks.c # 必备;提供任务管理
list.c # 必备;提供列表管理
queue.c # 基本必备;提供队列管理
timers.c # 可选;提供定时器管理
event_groups.c # 可选;提供事件组管理
croutine.c # 可选;提供协程管理

FreeRTOS/Source/portable/MemMang提供heap_1~5堆申请实现

头文件路径:

  • FreeRTOS/Source/include系统内核头文件
  • FreeRTOS/Source/portable/[compiler]/[architecture]BSP头文件
  • FreeRTOSConfig.h配置文件

BSP

FreeRTOS/Source/portable/[compiler]/[architecture]特定编译器和架构的文件

M3内核寄存器

r0~r12

sp

lr

pc

xpsr

初始化

重置入口Reset_Handler

初始化pc寄存器指向0x80(应该有默认引导指向这里)

mps2_m3.ld定义了内存分布和关键变量分布,ld是ARM的链接配置文件,在编译时有-T ./scripts/mps2_m3.ld选项指定。

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
// 文件FreeRTOSv202112.00/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC/scripts/mps2_m3.ld

MEMORY
{
FLASH (xr) : ORIGIN = 0x00000000, LENGTH = 4M /* to 0x00003FFF = 0x007FFFFF*/
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 4M /* to 0x21FFFFFF = 0xFFFFFF */
}
ENTRY(Reset_Handler)

_estack = ORIGIN(RAM) + LENGTH(RAM);

_sidata = LOADADDR(.data);

.data : /* AT ( _sidata ) */
{
. = ALIGN(4);
_sdata = .;
*(.data*)
. = ALIGN(4);
_edata = .;
} > RAM AT > FLASH

.bss :
{
. = ALIGN(4);
_sbss = .;
__bss_start__ = _sbss;
*(.bss*)
*(COMMON)
. = ALIGN(4);
_ebss = .;
__bss_end__ = _ebss;
} >RAM

ENTRY(Reset_Handler)指定汇编入口

这里_sidata变量为代码的.data起始处数据(地址为0x56e8)。_sdata_edata分别是4字节对齐(ARM处理器没对齐会出现异常)用于存放.data数据的内存起止数据处(地址为0x200001000x20000178)。

_sbss_ebss分别是内存.bss区域起止数据(地址为0x200001800x200064a0)。

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
// FreeRTOSv202112.00/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC/init/startup.c
/* Prevent optimization so gcc does not replace code with memcpy */
__attribute__((optimize("O0")))
__attribute__((naked)) void
Reset_Handler(void)
{
/* set stack pointer */
__asm volatile("ldr r0, =_estack");
__asm volatile("mov sp, r0");

/* copy .data section from flash to RAM */
for (uint32_t *src = &_sidata, *dest = &_sdata; dest < &_edata;)
{
*dest++ = *src++;
}

/* zero out .bss section */
for (uint32_t *dest = &_sbss; dest < &_ebss;)
{
*dest++ = 0;
}

/* jump to board initialisation */
void _start(void);
_start();
}

设置栈指针

入口函数__attribute__((optimize("O0"))) __attribute__((naked))定义避免优化,这两个是属于GNU C的编译属性控制编译过程。

__asm volatile("ldr r0, =_estack");__asm封装为asm,标注C内联汇编,volatile直接从Flash上读取_estack的值,这个值在就是0x20400000,即将栈指针指向内存最高处。

拷贝.data区域

将Flash的.data区域拷贝到内存中,内存中数据起止地址4字节对齐。

清空.bss区域

将内存.bss区域数据清零。

跳转启动函数

跳转到C语言入口处函数_start

启动函数_start

首先初始化串口,再进入主函数

1
2
3
4
5
6
7
8
// FreeRTOSv202112.00/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC/init/startup.c

void _start(void)
{
uart_init();
main(0, 0);
exit(0);
}

串口初始化uart_init

配置波特率并开启发送

1
2
3
4
5
6
7
// FreeRTOSv202112.00/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC/syscall.c

void uart_init()
{
UART0_ADDR->BAUDDIV = 16;
UART0_ADDR->CTRL = UART_CTRL_TX_EN;
}

主函数main

这里也只负责调用不同实现

1
2
3
4
5
6
7
// FreeRTOSv202112.00/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC/main_blinky.c

int main()
{
main_blinky();
return 0;
}

示例函数main_blinky

首先创建队列用于两任务进行通信。再创建收发队列任务,启动任务调度器。

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
void main_blinky( void )
{
/* Create the queue. */
xQueue = xQueueCreate( mainQUEUE_LENGTH, sizeof( uint32_t ) );

if( xQueue != NULL )
{
/* Start the two tasks as described in the comments at the top of this
* file. */
xTaskCreate( prvQueueReceiveTask, /* The function that implements the task. */
"Rx", /* The text name assigned to the task - for debug only as it is not used by the kernel. */
configMINIMAL_STACK_SIZE, /* The size of the stack to allocate to the task. */
NULL, /* The parameter passed to the task - not used in this case. */
mainQUEUE_RECEIVE_TASK_PRIORITY, /* The priority assigned to the task. */
NULL ); /* The task handle is not required, so NULL is passed. */

xTaskCreate( prvQueueSendTask,
"TX",
configMINIMAL_STACK_SIZE,
NULL,
mainQUEUE_SEND_TASK_PRIORITY,
NULL );

/* Start the tasks and timer running. */
vTaskStartScheduler();
}

/* If all is well, the scheduler will now be running, and the following
* line will never be reached. If the following line does execute, then
* there was insufficient FreeRTOS heap memory available for the Idle and/or
* timer tasks to be created. See the memory management section on the
* FreeRTOS web site for more details on the FreeRTOS heap
* http://www.freertos.org/a00111.html. */
for( ; ; )
{
}
}

系统函数

队列-创建

1
2
3
4
5
6
7
8
9
10
11
12
// FreeRTOSv202112.00/FreeRTOS/Source/queue.c
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength,
const UBaseType_t uxItemSize,
const uint8_t ucQueueType )

// FreeRTOSv202112.00/FreeRTOS-Plus/Source/AWS/ota/test/cbmc/FreeRTOS-Kernel/include/queue.h
#define queueQUEUE_TYPE_BASE ( ( uint8_t ) 0U )
#define queueQUEUE_TYPE_SET ( ( uint8_t ) 0U )
#define queueQUEUE_TYPE_MUTEX ( ( uint8_t ) 1U )
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE ( ( uint8_t ) 2U )
#define queueQUEUE_TYPE_BINARY_SEMAPHORE ( ( uint8_t ) 3U )
#define queueQUEUE_TYPE_RECURSIVE_MUTEX ( ( uint8_t ) 4U )

输入参数

  • uxQueueLength队列长度
  • uxItemSize队列元素大小
  • ucQueueType队列类型,参考头文件可以填写queueQUEUE_TYPE_xxx

输出参数

  • QueueHandle_t队列指针

内部调用逻辑

1
2
3
4
5
6
7
8
9
xQueueGenericCreate
|-pvPortMalloc // 申请足够的内存
|-vTaskSuspendAll // 进入关键区域,将调度器挂起uxSchedulerSuspended加1,
|-portSOFTWARE_BARRIER // 等待内部中断xInsideInterrupt处理完
portMEMORY_BARRIER // 内存屏障,M3不需要??
xTaskResumeAll
malloc // C库函数,申请内存
prvInitialiseNewQueue // 初始化队列结构
|-xQueueGenericReset // 队列通用初始化,为结构体赋值

其他

队列内存结构为:Queue_t头部信息数据+元素数据列表

队列-发送数据

队列-接收数据

任务-全部恢复

1
BaseType_t xTaskResumeAll( void )

注意

函数xTaskResumeAll与函数vTaskSuspendAll需要成对出现

内部调用逻辑

1
2
xTaskResumeAll
|-vPortEnterCritical # 宏包装taskENTER_CRITICAL

列表

1
2
// FreeRTOSv202112.00/FreeRTOS/Source/list.c
void vListInitialise( List_t * const pxList )

关键结构

时钟滴答

TickType_t

configUSE_16_BIT_TICKS时钟滴答计数值类型。1–16位uint16_t;0–32位uint32_t

基本数据

BaseType_t

定义为架构中执行效率最高的数据类型。它的典型的是,32位架构上为32位,16位架构上为16位,8位架构上为8位。

布尔型pdTRUE/pdFALSE也被定义为BaseType_t

队列

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
typedef xQUEUE Queue_t;

typedef struct QueueDefinition
{
int8_t *pcHead; /*< 指向存储数据区开始处,即跳过头部区域 */
int8_t *pcWriteTo; /*< 指向存储数据区可使用处,跳过已经入队的元素 */

union
{
QueuePointers_t xQueue; /*< 队列数据,pcTail指向存储数据区结束处;pcReadFrom指向存储数据区最后一元素区域 */
SemaphoreData_t xSemaphore; /*< 信号量使用 */
} u;

List_t xTasksWaitingToSend; /*< 阻塞等待写入数据的任务列表,按优先级排序 */
List_t xTasksWaitingToReceive; /*< 阻塞等待读取数据的任务列表,按优先级排序. */

volatile UBaseType_t uxMessagesWaiting; /*< 当前队列中元素个数 */
UBaseType_t uxLength; /*< 队列元素个数. */
UBaseType_t uxItemSize; /*< 队列元素大小 */

volatile int8_t cRxLock; /*< 上锁时,存储从队列移除元素数;无锁时,值为queueUNLOCKED */
volatile int8_t cTxLock; /*< 上锁时,存储向队列添加元素数;无锁时,值为queueUNLOCKED */

#if ((configSUPPORT_STATIC_ALLOCATION == 1) && (configSUPPORT_DYNAMIC_ALLOCATION == 1))
uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the memory used by the queue was statically allocated to ensure no attempt is made to free the memory. */
#endif

#if (configUSE_QUEUE_SETS == 1)
struct QueueDefinition *pxQueueSetContainer;
#endif

#if (configUSE_TRACE_FACILITY == 1)
UBaseType_t uxQueueNumber;
uint8_t ucQueueType; /*< 队列类型 */
#endif
} xQUEUE;

任务控制块

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
typedef tskTCB TCB_t;

typedef struct tskTaskControlBlock /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
volatile StackType_t * pxTopOfStack; /*< Points to the location of the last item placed on the tasks stack. THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */

#if ( portUSING_MPU_WRAPPERS == 1 )
xMPU_SETTINGS xMPUSettings; /*< The MPU settings are defined as part of the port layer. THIS MUST BE THE SECOND MEMBER OF THE TCB STRUCT. */
#endif

ListItem_t xStateListItem; /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */
ListItem_t xEventListItem; /*< Used to reference a task from an event list. */
UBaseType_t uxPriority; /*< The priority of the task. 0 is the lowest priority. */
StackType_t * pxStack; /*< Points to the start of the stack. */
char pcTaskName[ configMAX_TASK_NAME_LEN ]; /*< Descriptive name given to the task when created. Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */

#if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )
StackType_t * pxEndOfStack; /*< Points to the highest valid address for the stack. */
#endif

#if ( portCRITICAL_NESTING_IN_TCB == 1 )
UBaseType_t uxCriticalNesting; /*< Holds the critical section nesting depth for ports that do not maintain their own count in the port layer. */
#endif

#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxTCBNumber; /*< Stores a number that increments each time a TCB is created. It allows debuggers to determine when a task has been deleted and then recreated. */
UBaseType_t uxTaskNumber; /*< Stores a number specifically for use by third party trace code. */
#endif

#if ( configUSE_MUTEXES == 1 )
UBaseType_t uxBasePriority; /*< The priority last assigned to the task - used by the priority inheritance mechanism. */
UBaseType_t uxMutexesHeld;
#endif

#if ( configUSE_APPLICATION_TASK_TAG == 1 )
TaskHookFunction_t pxTaskTag;
#endif

#if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
void * pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
#endif

#if ( configGENERATE_RUN_TIME_STATS == 1 )
configRUN_TIME_COUNTER_TYPE ulRunTimeCounter; /*< Stores the amount of time the task has spent in the Running state. */
#endif

#if ( configUSE_NEWLIB_REENTRANT == 1 )

/* Allocate a Newlib reent structure that is specific to this task.
* Note Newlib support has been included by popular demand, but is not
* used by the FreeRTOS maintainers themselves. FreeRTOS is not
* responsible for resulting newlib operation. User must be familiar with
* newlib and must provide system-wide implementations of the necessary
* stubs. Be warned that (at the time of writing) the current newlib design
* implements a system-wide malloc() that must be provided with locks.
*
* See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html
* for additional information. */
struct _reent xNewLib_reent;
#endif

#if ( configUSE_TASK_NOTIFICATIONS == 1 )
volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
#endif

/* See the comments in FreeRTOS.h with the definition of
* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */
#if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e731 !e9029 Macro has been consolidated for readability reasons. */
uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the task is a statically allocated to ensure no attempt is made to free the memory. */
#endif

#if ( INCLUDE_xTaskAbortDelay == 1 )
uint8_t ucDelayAborted;
#endif

#if ( configUSE_POSIX_ERRNO == 1 )
int iTaskErrno;
#endif
} tskTCB;

目标

移植CORTEX_LM3S811_GCC为QEMU中运行

问题

内联汇编指令寄存器重排

__asm volatile("push {r1,r0,lr}");实际产生的汇编是push {r0,r1,lr},容易造成pop {r3,r4,lr}理解偏差。

官方手册简介

所有变量类型都显示声明为signedunsigned

变量命名规则:c代表chars代表int16_tl代表int32_t(long)x代表BaseType_t;前缀u代表unsignedp代表指针;

函数命名规则:使用前缀标识返回值类型;在变量命名规则上,增加v代表void

宏名称命名规则:使用小写单词开头标明宏定义位置;

堆管理

基本情况

C标准库的内存管理不适合嵌入式的原因:

  • 在小型嵌入式系统中并不是都是可用的;
  • 实现体积比较大,会消耗可贵的代码空间;
  • 很少是线程安全的;
  • 运行不确定性;执行函数所花费的时间在不同调用会有差异;
  • 会遭受内存碎片侵蚀;
  • 会使链接器配置变复杂;
  • 若允许栈空间增长到正在使用的内存中,它们可能称为难以调试错误的来源;

pvPortMalloc

vPortFree

FreeRTOS/Source/portable/MemMang中定义了5中栈管理方式:heap_1~heap_5

configTOTAL_HEAP_SIZE配置栈空间大小

创建每个任务都需要从内存分配任务控制块(Task Control Block, TCB)和栈空间。

申请内存

Heap_1

适合仅仅创建任务和其他内核对象的场景。只实现内存申请,而没有实现内存释放接口。

image-20230219214120657

Heap_2

提前分配内存块,大小由参数决定

configTOTAL_HEAP_SIZE

适合重复创建任务,但任务大小一致的场景

image-20230222092456582

非确定性,当运行速度比标准库块

推荐使用Heap_4代替Heap_2

Heap_3

使用标准库函数管理内存,通过暂停调度来保障线程安全

Heap_4

与Heap_1和Heap_4一样,将内存划分为小块。

拥有合并相邻空闲内存的功能,减少内存碎片的风险。

image-20230222093624554

有时需要确保使用的是内部内存,可以配置configAPPLICATION_ALLOCATED_HEAP为1,内存数据将由应用程序声明的数据替换。数据类型为uint8_tucHeap数组,大小为configTOTAL_HEAP_SIZE。

不同编译器的语法不一样。

使用GCC语法声明heap_4使用的数组,数组将会在.my_heap区域:

1
uint8_t ucHeap[configTOTAL_HEAP_SIZE] __attribute__((section(".my_heap")));

使用IAR语法声明heap_4使用的数组,数组将会在绝对地址0x20000000处:

1
uint8_t ucHeap[configTOTAL_HEAP_SIZE] @ 0x20000000;

Heap_5

分配算法与Heap_4一样,不同的是,Heap_5可以使用非连续的内存区域。

在使用前,必须显示调用vPortDefineHeapRegions()完成内存初始化。函数参数是区域结构体数组,每块区域结构体类型为HeapRegion_t

1
2
3
4
5
typedef struct HeapRegion
{
uint8_t *pucStartAddress;
size_t xSizeInBytes;
} HeapRegion_t;

显示声明时,内存地址pucStartAddress必须从低到高的顺序声明,并且有结束的区域pucStartAddress=NULL

例如有三块内存区域:

image-20230222102857053

使用Heap_5需要显示初始化这几块内存

1
2
3
4
5
6
7
8
9
10
11
12
const HeapRegion_t xHeapRegion[] = 
{
{(uint8_t *)0x00010000, 65*1024},
{(uint8_t *)0x00020000, 32*1024},
{(uint8_t *)0x00030000, 32*1024},
{NULL, 0},
};

int main(void)
{
vPortDefineHeapRegions(xHeapRegion);
}

若真按上面代码初始化,就没有内存给其他变量使用。在项目中,链接阶段将会分配每个变量内存地址。可以将RAM1划分为两个区域,0x0001nnn以上区域由系统内存管理。优化后的GCC版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#define RAM1_HEAP_SIZE (30*1024)
static uint8_t ucHeap[RAM1_HEAP_SIZE];

const HeapRegion_t xHeapRegion[] =
{
{ucHeap, RAM1_HEAP_SIZE},
{(uint8_t *)0x00020000, 32*1024},
{(uint8_t *)0x00030000, 32*1024},
{NULL, 0},
};

int main(void)
{
vPortDefineHeapRegions(xHeapRegion);
}

堆相关函数

xPortGetFreeHeapSize

函数原型:

1
size_t xPortGetFreeHeapSize( void );

返回堆中可用字节数。

使用heap_3时,函数不可用。

xPortGetMinimumEverFreeHeapSize

函数原型:

1
size_t xPortGetMinimumEverFreeHeapSize( void );

返回应用开始执行后,历史最小可用字节数。

只有heap_4和heap_5可使用此函数。

内存申请失败钩子函数

当调用pvPortMalloc没有足够内存时,将会返回NULL,即内存申请失败。若应用需要处理这种场景,需要配置configUSE_MALLOC_FAILED_HOOK,然后应用需要实现内存申请失败钩子函数,原型如下:

1
void vApplicationMallocFailedHook( void );

任务管理

3.1 章节介绍和范围

范围

本章旨在让读者很好地理解:

  • FreeRTOS如何为应用程序中的每个任务分配处理时间。

  • FreeRTOS如何在任何给定时间内选择任务执行。

  • 每个任务的相对优先级如何影响系统行为。

  • 任务可以存在的状态。

读者还可以容易理解:

  • 如何实现任务。
  • 如何创建一个或多个任务的实例。
  • 如何使用任务参数。
  • 如何更改已经创建的任务的优先级。
  • 如何删除一个任务。
  • 如何使用任务实现周期性处理(软件计时器将在后面的一章中讨论)。
  • 空闲任务的执行时间以及如何使用它。

本章中介绍的概念是理解FreeRTOS的基础:如何使用系统和系统应用程序的行为。所以,这也是本书中最详细章节。

3.2 任务函数

任务都是由C语言函数实现。它们唯一特殊之处在于函数原型,它必须返回void类型并接受一个void类型指针。Listing 11展示了函数原型:

1
2
// Listing 11
void ATaskFunction( void *pvParameters );

每个任务本身都是一个小程序。它有一个入口,通常会在无限循环中永久运行,并且不会退出。Listing 12展示了典型的任务结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
// Listing 12
void ATaskFunction( void *pvParameters )
{
/* 变量可以像普通函数一样进行声明。使用此示例函数创建的每个任务实例,都有其lVariableExample变量的副本。如果变量声明为静态,这就是错误的。在这种情况下,变量只有一个副本,并且副本将被创建的任务实例共享。(变量名称中的前缀参考章节1.5 数据类型和编码风格指南。) */
int32_t lVariableExample = 0;
/* 任务通常被实现为无限循环。 */
for( ;; )
{
/* 实现任务功能的代码写到这里。 */
}
/* 即使任务跳出循环,那么必须在其实现函数结束之前将任务删除。传递给vTaskDelete() API函数的NULL参数,它表示要删除的是调用(此)任务。API函数命名规范在0章节中已描述。 */
vTaskDelete( NULL );
}

任务不需要,应该调用删除任务

1
2
3
4
5
6
7
8
9
10
11
void ATaskFunction( void *pvParameters )
{
int32_t lVariableExample = 0;
/* 一般就是无限循环 */
for( ;; )
{
/* 任务功能代码逻辑. */
}
/* 一定要返回,需要显示调用删除 */
vTaskDelete( NULL );
}

顶层任务状态

简化模型,假定系统中只有一个核,有许多任务需要运行。当任务为运行状态时,处理器会执行任务代码。当任务为非运行状态时,任务被休眠,运行上下文将会被保存。当任务恢复运行状态时,运行上下文也需要恢复。

由非运行态转为运行态的任务称为被切入或换入,反之,称为被切出或换出。系统中调度器是唯一能切换任务的实体。

image-20230222111533707

创建任务

xTaskCreate

函数原型:

1
2
3
4
5
6
BaseType_t xTaskCreate( TaskFunction_t pvTaskCode, 
const char * const pcName,
uint16_t usStackDepth,
void *pvParameters,
UBaseType_t uxPriority,
TaskHandle_t *pxCreatedTask );

这可能是所有API中最复杂的,但任务也是多任务系统的最基本组件。

参数表

参数名描述
pvTaskCode任务函数入口
pcName任务的描述名称。configMAX_TASK_NAME_LEN配置名称最大长度。超过会被截断
usStackDepth任务使用的栈大小,单位是字(words)。configMINIMAL_STACK_SIZE配置空闲任务和任务最小栈大小。
pvParameters任务使用的void*类型参数指针。
uxPriority指定任务运行优先级,范围0-(configMAX_PRIORITIES – 1),最低优先级为0。
pxCreatedTask创建的任务句柄,可以会被其他接口引用。没有需要可以设置为NULL。

返回值

pdPASS创建成功;pdFAIL创建失败;

Example 1 创建任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void vTask1( void *pvParameters )
{
const char *pcTaskName = "Task 1 is running\r\n";
volatile uint32_t ul; /* volatile to ensure ul is not optimized away. */
/* As per most tasks, this task is implemented in an infinite loop. */
for( ;; )
{
/* Print out the name of this task. */
vPrintString( pcTaskName );
/* Delay for a period. */
for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
{
/* This loop is just a very crude delay implementation. There is
nothing to do in here. Later examples will replace this crude
loop with a proper delay/sleep function. */
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void vTask2( void *pvParameters )
{
const char *pcTaskName = "Task 2 is running\r\n";
volatile uint32_t ul; /* volatile to ensure ul is not optimized away. */
/* As per most tasks, this task is implemented in an infinite loop. */
for( ;; )
{
/* Print out the name of this task. */
vPrintString( pcTaskName );
/* Delay for a period. */
for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
{
/* This loop is just a very crude delay implementation. There is
nothing to do in here. Later examples will replace this crude
loop with a proper delay/sleep function. */
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int main( void )
{
/* Create one of the two tasks. Note that a real application should check
the return value of the xTaskCreate() call to ensure the task was created
successfully. */
xTaskCreate( vTask1, /* Pointer to the function that implements the task. */
"Task 1",/* Text name for the task. This is to facilitate
debugging only. */
1000, /* Stack depth - small microcontrollers will use much
less stack than this. */
NULL, /* This example does not use the task parameter. */
1, /* This task will run at priority 1. */
NULL ); /* This example does not use the task handle. */
/* Create the other task in exactly the same way and at the same priority. */
xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, NULL );
/* Start the scheduler so the tasks start executing. */
vTaskStartScheduler();

/* If all is well then main() will never reach here as the scheduler will
now be running the tasks. If main() does reach here then it is likely that
there was insufficient heap memory available for the idle task to be created.
Chapter 2 provides more information on heap memory management. */
for( ;; );
}
image-20230222175228346

Task2可以被Task1创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void vTask1( void *pvParameters )
{
const char *pcTaskName = "Task 1 is running\r\n";
volatile uint32_t ul; /* volatile to ensure ul is not optimized away. */
/* If this task code is executing then the scheduler must already have
been started. Create the other task before entering the infinite loop. */
xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, NULL );
for( ;; )
{
/* Print out the name of this task. */
vPrintString( pcTaskName );
/* Delay for a period. */
for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
{
/* This loop is just a very crude delay implementation. There is
nothing to do in here. Later examples will replace this crude
loop with a proper delay/sleep function. */
}
}
}

Example 2 使用任务参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void vTaskFunction( void *pvParameters )
{
char *pcTaskName;
volatile uint32_t ul; /* volatile to ensure ul is not optimized away. */
/* The string to print out is passed in via the parameter. Cast this to a
character pointer. */
pcTaskName = ( char * ) pvParameters;
/* As per most tasks, this task is implemented in an infinite loop. */
for( ;; )
{
/* Print out the name of this task. */
vPrintString( pcTaskName );
/* Delay for a period. */
for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
{
/* This loop is just a very crude delay implementation. There is
nothing to do in here. Later exercises will replace this crude
loop with a proper delay/sleep function. */
}
}
}
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
/* Define the strings that will be passed in as the task parameters. These are
defined const and not on the stack to ensure they remain valid when the tasks are
executing. */
static const char *pcTextForTask1 = "Task 1 is running\r\n";
static const char *pcTextForTask2 = "Task 2 is running\r\n";
int main( void )
{
/* Create one of the two tasks. */
xTaskCreate( vTaskFunction, /* Pointer to the function that
implements the task. */
"Task 1", /* Text name for the task. This is to
facilitate debugging only. */
1000, /* Stack depth - small microcontrollers
will use much less stack than this. */
(void*)pcTextForTask1, /* Pass the text to be printed into the
task using the task parameter. */
1, /* This task will run at priority 1. */
NULL ); /* The task handle is not used in this
example. */
/* Create the other task in exactly the same way. Note this time that multiple
tasks are being created from the SAME task implementation (vTaskFunction). Only
the value passed in the parameter is different. Two instances of the same
task are being created. */
xTaskCreate( vTaskFunction, "Task 2", 1000, (void*)pcTextForTask2, 1, NULL );
/* Start the scheduler so the tasks start executing. */
vTaskStartScheduler();

/* If all is well then main() will never reach here as the scheduler will
now be running the tasks. If main() does reach here then it is likely that
there was insufficient heap memory available for the idle task to be created.
Chapter 2 provides more information on heap memory management. */
for( ;; );
}

任务优先级

configMAX_PRIORITIES

configUSE_PORT_OPTIMISED_TASK_SELECTION

时间尺度和滴答中断

configTICK_RATE_HZ

100

image-20230223094018410

pdMS_TO_TICKS()

Example 3. Experimenting with priorities

image-20230223094342546

扩展非运行状态

阻塞状态(Blocked)

暂停状态(Suspended)

就绪状态(Ready)

完整的状态切换图

image-20230223094909681

Example 4. Using the Blocked state to create a delay

INCLUDE_vTaskDelay

1
void vTaskDelay( TickType_t xTicksToDelay );
image-20230223095233646

vTaskDelayUntil()函数

1
void vTaskDelayUntil( TickType_t * pxPreviousWakeTime, TickType_t xTimeIncrement );

Example 5. Converting the example tasks to use vTaskDelayUntil()

Example 6. Combining blocking and non-blocking tasks

image-20230223100440966

空闲任务和钩子函数

configIDLE_SHOULD_YIELD

清理内核资源

空闲任务钩子函数

使用场景

空闲任务钩子函数的限制

使用事项

1
void vApplicationIdleHook( void );

configUSE_IDLE_HOOK

改变任务优先级

vTaskPrioritySet()

1
void vTaskPrioritySet( TaskHandle_t pxTask, UBaseType_t uxNewPriority );

uxTaskPriorityGet()

1
UBaseType_t uxTaskPriorityGet( TaskHandle_t pxTask );

Example 8. Changing task priorities

删除任务

vTaskDelete()

INCLUDE_vTaskDelete

1
void vTaskDelete( TaskHandle_t pxTaskToDelete );

Example 9. Deleting tasks

线程本地存储

调度算法

任务状态和事件回顾

Running

Ready

Blocked

Suspended

配置调度算法

configUSE_PREEMPTION

configUSE_TIME_SLICING

configUSE_TICKLESS_IDLE

Round Robin Scheduling

Fixed Priority Pre-emptive Scheduling with Time Slicing

基于时间片优先级抢占调度

image-20230224092259473
image-20230224092550083

configIDLE_SHOULD_YIELD=1

image-20230224093117397

优先级抢占调度(无时间片)

image-20230224094055809

合作调度

image-20230224095111125

队列管理

队列特性

数据存储

First In First Out FIFO

image-20230224095944873

多任务访问

队列读取阻塞

队列写入阻塞

多队列阻塞

使用队列

xQueueCreate

QueueHandle_t

1
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );

xQueueSendToBack/xQueueSendToFront

xQueueSend

1
2
3
4
5
6
BaseType_t xQueueSendToFront( QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait );
BaseType_t xQueueSendToBack( QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait );

portMAX_DELAY

INCLUDE_vTaskSuspend

pdPASS

errQUEUE_FULL

xQueueReceive

1
2
3
BaseType_t xQueueReceive( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait );

pdPASS

errQUEUE_EMPTY

uxQueueMessagesWaiting

1
UBaseType_t uxQueueMessagesWaiting( QueueHandle_t xQueue );

Example 10. Blocking when receiving from a queue

image-20230224140047388

从多个源接收数据

image-20230224140141858

Example 11. Blocking when sending to a queue, and sending structures on a queue

image-20230224140630606

传输大量或可变大小的数据

队列指针

使用队列传输不同类型和长度数据

IPStackEvent_t

从多个队列接收

队列集

xQueueCreateSet

1
QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength );

uxEventQueueLength 每个队列最大长度和

xQueueAddToSet

1
2
BaseType_t xQueueAddToSet( QueueSetMemberHandle_t xQueueOrSemaphore, 
QueueSetHandle_t xQueueSet );

xQueueSelectFromSet

1
2
QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet,
const TickType_t xTicksToWait );

Example 12. Using a Queue Set

More Realistic Queue Set Use Cases

使用队列构建邮箱

xQueueOverwrite

1
BaseType_t xQueueOverwrite( QueueHandle_t xQueue, const void * pvItemToQueue );

xQueuePeek

1
2
3
BaseType_t xQueuePeek( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait );

软件计时器管理

FreeRTOS/Source/timers.c

configUSE_TIMERS

软件计时器回调函数

1
void ATimerCallback( TimerHandle_t xTimer );

不能调用可能进入阻塞状态的API

软件计时器的属性和状态

软件计时器的周期

单次和自动重载计时器

image-20230227140145505

软件计时器状态

休眠

运行

image-20230227140355152
image-20230227140419033

软件计时器上下文

RTOS守护任务

configTIMER_TASK_PRIORITY

configTIMER_TASK_STACK_DEPTH

不能调用可能进入阻塞状态的API

计时器命令队列

configTIMER_QUEUE_LENGTH

image-20230227140850116

守护任务调度

image-20230227141013158
image-20230227141229467

创建并启动一个软件计时器

xTimerCreate

1
2
3
4
5
TimerHandle_t xTimerCreate( const char * const pcTimerName,
TickType_t xTimerPeriodInTicks,
UBaseType_t uxAutoReload,
void * pvTimerID,
TimerCallbackFunction_t pxCallbackFunction );

xTimerStart

1
BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait );

INCLUDE_vTaskSuspend

portMAX_DELAY

Example 13. Creating one-shot and auto-reload timers

计时器编号

vTimerSetTimerID

1
void vTimerSetTimerID( const TimerHandle_t xTimer, void *pvNewID );

pvTimerGetTimerID

1
void *pvTimerGetTimerID( TimerHandle_t xTimer );

Example 14. Using the callback function parameter and the software timer ID

改变计时器周期

xTimerChangePeriod

1
2
3
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer, 
TickType_t xNewTimerPeriodInTicks,
TickType_t xTicksToWait );

重置软件计时器

image-20230228092540839

xTimerReset

1
BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait );

Example 15. Resetting a software timer

中断管理

ISR中使用系统API

中断安全API

FromISR

使用独立的中断安全API的优势

使用独立的中断安全API的劣势

xHigherPriorityTaskWoken参数

一般用pbFALSE

portYIELD_FROM_ISR和portEND_SWITCHING_ISR宏

taskYIELD

1
2
portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );

延时中断处理

image-20230228134407138

用于同步的二进制信号量

image-20230228135032388
image-20230228135534394

xSemaphoreCreateBinary

1
SemaphoreHandle_t xSemaphoreCreateBinary( void );

xSemaphoreTake

1
BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );

xSemaphoreGiveFromISR

1
2
BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore, 
BaseType_t *pxHigherPriorityTaskWoken );

Example 16. Using a binary semaphore to synchronize a task with an interrupt

vPortGenerateSimulatedInterrupt

vPortSetInterruptHandler

image-20230228140557184

Improving the Implementation of the Task Used in Example 16

image-20230228140947975
image-20230228141049239

计数信号量

configUSE_COUNTING_SEMAPHORES

image-20230301092559990

xSemaphoreCreateCounting

1
2
SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount );

Example 17. Using a counting semaphore to synchronize a task with an interrupt

延时工作到系统守护任务

xTimerPendFunctionCallFromISR

1
2
3
4
5
BaseType_t xTimerPendFunctionCallFromISR( PendedFunction_t xFunctionToPend,
void *pvParameter1,
uint32_t ulParameter2,
BaseType_t *pxHigherPriorityTaskWoken );
void vPendableFunction( void *pvParameter1, uint32_t ulParameter2 );

Example 18. Centralized deferred interrupt processing

image-20230301093446475

在中断服务函数(ISR)中使用队列

xQueueSendToFrontFromISR和xQueueSendToBackFromISR

1
2
3
4
5
6
7
8
BaseType_t xQueueSendToFrontFromISR( QueueHandle_t xQueue, 
void *pvItemToQueue
BaseType_t *pxHigherPriorityTaskWoken
);
BaseType_t xQueueSendToBackFromISR( QueueHandle_t xQueue,
void *pvItemToQueue
BaseType_t *pxHigherPriorityTaskWoken
);

考虑合适使用ISR队列

DMA

Example 19. Sending and receiving on a queue from within an interrupt

image-20230301094553407

中断嵌套

configMAX_SYSCALL_INTERRUPT_PRIORITY

configMAX_API_CALL_INTERRUPT_PRIORITY

configKERNEL_INTERRUPT_PRIORITY

数字优先级

逻辑优先级

image-20230301100213478

ARM Cortex-M1和GIC的用户说明

configASSERT

image-20230301101125569

资源管理

互斥现象

临界段和暂停调度器

临界段基础

taskENTER_CRITICAL

taskEXIT_CRITICAL

configMAX_SYSCALL_INTERRUPT_PRIORITY

taskENTER_CRITICAL_FROM_ISR

taskEXIT_CRITICAL_FROM_ISR

暂停调度器

vTaskSuspendAll

1
void vTaskSuspendAll( void );

xTaskResumeAll

1
BaseType_t xTaskResumeAll( void );

互斥体(二进制信号量)

image-20230302134248107

xSemaphoreCreateMutex

1
SemaphoreHandle_t xSemaphoreCreateMutex( void );

Example 20. Rewriting vPrintString() to use a semaphore

image-20230302134426159

优先级反转

image-20230302134556524

优先级继承

image-20230302134936848

死锁

递归互斥锁

xSemaphoreCreateRecursiveMutex

xSemaphoreTakeRecursive

xSemaphoreGiveRecursive

互斥锁和任务调度

image-20230303091409614
image-20230303092336449

守护任务

只有守护任务才能直接访问资源

Example 21. Re-writing vPrintString() to use a gatekeeper task

configUSE_TICK_HOOK

1
void vApplicationTickHook( void );

事件组

事件组特性

EventBits_t

image-20230306093213414
image-20230306093348768

EventBits_t数据类型细节

configUSE_16_BIT_TICKS

多任务访问

使用事件组的一个实际实例

FreeRTOS+TCP TCP/IP

FreeRTOS_Socket_t

使用事件组的事件管理

xEventGroupCreate

1
EventGroupHandle_t xEventGroupCreate( void );

xEventGroupSetBits

1
2
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet );

xEventGroupSetBitsFromISR

1
2
3
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
BaseType_t *pxHigherPriorityTaskWoken );

xEventGroupWaitBits

1
2
3
4
5
EventBits_t xEventGroupWaitBits( const EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait );

Example 22. Experimenting with event groups

使用事件组的任务同步

xEventGroupSync

1
2
3
4
EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
const EventBits_t uxBitsToWaitFor,
TickType_t xTicksToWait );

Example 23. Synchronizing tasks

任务通知

通过中间对象通信

image-20230307094905368

任务通知——直接通信方式

image-20230307101348461

configUSE_TASK_NOTIFICATIONS

任务通知:优势和限制

性能优势

内存消耗优势

限制

使用任务通知

任务通知API选项

xTaskNotifyGive

1
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );

vTaskNotifyGiveFromISR

1
2
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify, 
BaseType_t *pxHigherPriorityTaskWoken );

ulTaskNotifyTake

1
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait );

Example 24. Using a task notification in place of a semaphore, method 1

image-20230307134340180

Example 25. Using a task notification in place of a semaphore, method 2

xTaskNotify和xTaskNotifyFromISR

1
2
3
4
5
6
7
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, 
uint32_t ulValue,
eNotifyAction eAction );
BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
BaseType_t *pxHigherPriorityTaskWoken );

xTaskNotifyWait

1
2
3
4
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait );

Task Notifications Used in Peripheral Device Drivers: UART Example

Task Notifications Used in Peripheral Device Drivers: ADC Example

Task Notifications Used Directly Within an Application

image-20230307135357762

低功耗支持

开发者支持

configASSERT

默认无效:

1
assert( pxMyPointer != NULL );

需要改为

configASSERT()

configASSERT定义示例

FreeRTOS+跟踪

image-20230308092623605

调试相关钩子(回调)函数

内存申请失败

栈溢出

查看运行时和任务状态信息

任务运行时统计

运行时统计时钟

10~100倍频率

配置应用以收集运行时统计

configGENERATE_RUN_TIME_STATS

portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()

portGET_RUN_TIME_COUNTER_VALUE()

portALT_GET_RUN_TIME_COUNTER_VALUE(Time)

uxTaskGetSystemState

1
2
3
UBaseType_t uxTaskGetSystemState( TaskStatus_t * const pxTaskStatusArray,
const UBaseType_t uxArraySize,
uint32_t * const pulTotalRunTime );
1
2
3
4
5
6
7
8
9
10
11
typedef struct xTASK_STATUS
{
TaskHandle_t xHandle;
const char *pcTaskName;
UBaseType_t xTaskNumber;
eTaskState eCurrentState;
UBaseType_t uxCurrentPriority;
UBaseType_t uxBasePriority;
uint32_t ulRunTimeCounter;
uint16_t usStackHighWaterMark;
} TaskStatus_t;

vTaskList辅助函数

configUSE_TRACE_FACILITY

configUSE_STATS_FORMATTING_FUNCTIONS

1
void vTaskList( signed char *pcWriteBuffer );
image-20230308094211231

vTaskGetRunTimeStats辅助函数

configGENERATE_RUN_TIME_STATS

configUSE_STATS_FORMATTING_FUNCTIONS

1
void vTaskGetRunTimeStats( signed char *pcWriteBuffer );
image-20230308095815972

产生和显示运行时统计示例

跟踪钩子宏

traceTASK_INCREMENT_TICK(xTickCount)

traceTASK_SWITCHED_OUT()

traceTASK_SWITCHED_IN()

traceBLOCKING_ON_QUEUE_RECEIVE(pxQueue)

traceBLOCKING_ON_QUEUE_SEND(pxQueue)

traceQUEUE_SEND(pxQueue)

traceQUEUE_SEND_FAILED(pxQueue)

traceQUEUE_RECEIVE(pxQueue)

traceQUEUE_RECEIVE_FAILED(pxQueue)

traceQUEUE_SEND_FROM_ISR(pxQueue)

traceQUEUE_SEND_FROM_ISR_FAILED(pxQueue)

traceQUEUE_RECEIVE_FROM_ISR(pxQueue)

traceQUEUE_RECEIVE_FROM_ISR_FAILED(pxQueue)

traceTASK_DELAY_UNTIL()

traceTASK_DELAY()

定义跟踪钩子宏

FreeRTOSConfig.h

FreeRTOS感知调试插件

Eclipse (StateViewer)

Eclipse (ThreadSpy)

IAR

ARM DS-5

Atollic TrueStudio

Microchip MPLAB

iSYSTEM WinIDEA

问题解答

中断优先级

configMAX_SYSCALL_INTERRUPT_PRIORITY

栈溢出

uxTaskGetStackHighWaterMark

1
UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask );

运行时栈检查——概述

configCHECK_FOR_STACK_OVERFLOW

1
void vApplicationStackOverflowHook( TaskHandle_t *pxTask, signed char *pcTaskName );

运行时栈检查——方法1

configCHECK_FOR_STACK_OVERFLOW 1

运行时栈检查——方法2

configCHECK_FOR_STACK_OVERFLOW 2

不适当地使用printf()和sprintf()

Printf-stdarg.c

其他常见的错误来源

症状:向演示中添加一个简单的任务会导致演示崩溃

症状:在中断中使用API函数会导致应用程序崩溃

症状:有时应用程序会在中断服务例程中崩溃

症状:尝试启动第一个任务时崩溃

症状:意外禁用中断,或者临界段未正确嵌套

症状:应用程序甚至在调度程序启动之前就崩溃了

症状:当调度程序挂起时,或从关键部分内部调用API函数,会导致应用程序崩溃

系统概览

平台是由一个或多个元件组成的单个集成电路。有些组件可能是RISC-V内核,而其他组件可能具有不同的功能。通常,它们都将连接到单个系统总线。单个RISC-V核心包含一个或多个硬件线程,称为hart。

用户与运行调试器(如gdb)的调试主机(如笔记本电脑)交互。调试器与调试转换器(例如OpenOCD,它可能包括一个硬件驱动程序)通信,以与调试传输硬件(例如Olimex USB-JTAG适配器)通信。调试传输硬件将调试主机连接到平台的调试传输模块(Debug Transport Module, DTM)。DTM使用调试模块接口(DMI)提供对一个或多个调试模块(dm)的访问。

平台中每个hart都由一个DM控制。对hart-DM映射没有进一步的限制,但通常单个核心中的所有hart都由同一个DM控制。在大多数平台中,只有一个DM控制平台中的所有hart。

2024-09-14T09:34:55.png

调试模块DM

调试模块接口DMI

调试模块从属于称为调试模块接口(DMI)的总线。总线的主人是调试传输模块(Debug Transport Module)。调试模块接口可以是一个普通的总线,有一个主总线和一个从总线,也可以使用功能更全面的总线,如TileLink或AMBA高级外围总线。

DMI使用7到32位地址位。如果在这个DMI上有额外的DM,DMI地址空间中下一个DM的基址在nextdm中给出。调试模块自己的状态和寄存器应该只在上电时重置,而dmcontrol中的dmactive为0。如果dmactive为1,即使会引起CSRs被清零,所有hart在系统重置期间也要保持halt状态。

重置控制

调试模块控制一个全局复位信号ndmreset(非调试模块复位),它可以复位或保持复位平台中的每个组件,除了调试模块和调试传输模块。

选择hart

一个单一的DM可以管理高达2^20个hart。

要枚举所有hart,调试器必须首先通过将所有hart写入hartsel(假设最大大小)并回读值来确定HARTSELLEN,以查看实际设置了哪些位。然后,它从0开始选择每个hart,直到在dmstatus中anynonexistent为1,或者达到最高索引(取决于HARTSELLEN)。

抽象命令

DM支持一组抽象命令,其中大部分是可选的。根据实现的不同,调试器可能能够执行一些抽象命令,即使所选的hart没有停止。调试器只能通过尝试特定状态下的特定hart支持哪些抽象命令,然后查看abstractcs中cmderr以确定它们是否成功。

如果命令接受参数,调试器必须在写入命令之前将它们写入数据寄存器。

每个抽象命令都是32位值。前8位包含cmdtype,它决定了命令的类型。

cmdtypeCommand
0Access Register
1Quick Access
2Access Memory

Access Register

3.6.1.1 Access Register

遵循以下操作顺序:

  1. 若write为0且transfer为1,则将regno指定的寄存器的数据拷贝到数据区arg0中,并执行从m模式读取该寄存器时发生的任何副作用。
  2. 若write为1且transfer为1,则将数据区arg0的数据拷贝到regno指定的寄存器中,并执行从m模式写入该寄存器时发生的任何副作用。
  3. 若aarpostincrement为1,则将增加regno。
  4. 若postexec为1,执行Program Buffer.

抽象寄存器号表

地址范围说明
0x0000 - 0x0fffCSRs,可以通过dpc访问PC
0x1000 - 0x101fGPRs
0x1020 - 0x103f浮点寄存器
0xc000 - 0xffff保留给非标扩展和内部使用的

Quick Access

3.6.1.2 Quick Access

遵循以下操作顺序:

  1. 若hart已经暂停,命令将会设置cmderr为”halt/resume”并不会继续执行。
  2. 暂停hart。若hart因某些其他原因(如breakpoint)暂停,命令将会设置cmderr为”halt/resume”并不会继续执行。
  3. 执行Program Buffer。若发生异常,cmderr会被设置为”exception”并且program buffer结束执行,但快速访问命令会继续。
  4. 恢复hart。

Access Memory

3.6.1.3 Access Memory

遵循以下操作顺序:

  1. 若write为0,从arg1指定的内存位置拷贝数据到数据区arg0中。
  2. 若write为1,从数据区arg0拷贝数据到arg1指定的内存位置中。
  3. 若aampostincrement为1,则将增加arg1.

若任何操作失败,设置cmderr并不会执行任何剩余步骤。

程序缓冲区

3.7 Program Buffer

为支持在已停止的hart上执行任意指令,调试模块可以包括程序缓冲区,调试器可以将小程序写入其中。

调试器可以将一个小程序写入程序缓冲区,然后使用Access Register抽象命令执行它一次,命令中postexec位为1。调试器可以编写任何它喜欢的程序(包括跳出程序缓冲区),但是程序必须以ebreak或c.ebreak结束。

若progbufsize为1,则impebreak必须为1. 程序缓冲区可容纳一条32位或16位指令,所以在这种情况下,调试器必须只写一条指令,而不管它的大小。

执行程序缓冲区可能会破坏dpc。如果是这种情况,必须可以使用抽象命令读取/写入dpc,而不设置postexec。调试器必须尝试在停止和执行程序缓冲区之间保存dpc,然后在离开调试模式之前恢复dpc。

状态概览

下图显示了运行/停止调试期间hart经过的状态的概念视图,这些状态受到dmcontrol、abstractcs、abstractauto和command的不同字段的影响。

image-20240914114316238.png

系统总线访问

3.9 System Bus Access

调试器可以使用Program Buffer或Access Memory抽象命令从hart的角度访问内存。一个调试模块也可以包括一个System Bus Access块来提供内存访问而不涉及hart,而不管是否实现了Program Buffer。

System Bus Access块支持8, 16, 32, 64和128位访问。下表显示在sbdata中每个访问大小可以访问的位。

访问大小数据位
8sbdata0位7:0
16sbdata0位15:0
32sbdata0
64sbdata1, sbdata0
128sbdata3, sbdata2, sbdata1, sbdata0

DM寄存器

3.12 Debug Module Registers

本节中描述的寄存器是通过DMI总线访问的。每个DM都有一个基址(第一个DM为0)。下面的寄存器地址是这个基址的偏移量。

调试模块调试总线寄存器表

地址名称
0x04Abstract Data 0 (data0)
0x0fAbstract Data 11 (data11)
0x10Debug Module Control (dmcontrol)
0x11Debug Module Status (dmstatus)
0x12Hart Info (hartinfo)
0x13Halt Summary 1 (haltsum1)
0x14Hart Array Window Select (hawindowsel)
0x15Hart Array Window (hawindow)
0x16Abstract Control and Status (abstractcs)
0x17Abstract Command (command)
0x18Abstract Command Autoexec (abstractauto)
0x19Configuration String Pointer 0 (confstrptr0)
0x1aConfiguration String Pointer 1 (confstrptr1)
0x1bConfiguration String Pointer 2 (confstrptr2)
0x1cConfiguration String Pointer 3 (confstrptr3)
0x1dNext Debug Module (nextdm)
0x20Program Buffer 0 (progbuf0)
0x2fProgram Buffer 15 (progbuf15)
0x30Authentication Data (authdata)
0x34Halt Summary 2 (haltsum2)
0x35Halt Summary 3 (haltsum3)
0x37System Bus Address 127:96 (sbaddress3)
0x38System Bus Access Control and Status (sbcs)
0x39System Bus Address 31:0 (sbaddress0)
0x3aSystem Bus Address 63:32 (sbaddress1)
0x3bSystem Bus Address 95:64 (sbaddress2)
0x3cSystem Bus Data 31:0 (sbdata0)
0x3dSystem Bus Data 63:32 (sbdata1)
0x3eSystem Bus Data 95:64 (sbdata2)
0x3fSystem Bus Data 127:96 (sbdata3)
0x40Halt Summary 0 (haltsum0)

dmstatus, 0x11

3.12.1 Debug Module Status (dmstatus, at 0x11)

这个寄存器报告整个调试模块以及由hasel所选hart的状态。它的地址在将来不会改变,因为它包含了version。

整个寄存器是只读的。

dmcontrol, 0x10

3.12.2 Debug Module Control (dmcontrol, at 0x10)

这个寄存器控制整个调试模块以及由hasel所选的hart。

在本文档中,我们提到hartsel,它是hartselhi和hartsello的组合。虽然规范允许20个hartsel位,但实现可能会选择实现少于这个数。hartsel的实际宽度被称为HARTSELLEN。它最少为0,最多为20。调试器应该通过将所有的1写入HARTSELLEN(假设最大大小)并回读值以查看实际设置了哪些位来发现HARTSELLEN。

在任何给定的写操作中,调试器最多只能将1写入以下位中的一个:resumereq、hartreset、ackhavereset、setresethaltreq和clrresethaltreq。其他的必须写成0。

resethaltreq是每个hart可选内部状态位,不能读取,但可以使用setresethaltreq和clrresethaltreq写入。

hartinfo, 0x12

3.12.3 Hart Info (hartinfo, at 0x12)

这个寄存器能获取hartsel选择的hart的信息。

这个寄存器是可选的。若它不可用它会读到全0。

如果包含这个寄存器,调试器可以通过编写显式访问data或dscratch寄存器的程序来对程序缓冲区做更多的工作。

hawindowsel, 0x14

3.12.4 Hart Array Window Select (hawindowsel, at 0x14)

hawindowsel有效可写位取决于具体实现,最多为15位。比如单个DM支持48个hart,则此寄存器只有bit0可以写入,其余位均为0.

hawindow, 0x15

3.12.5 Hart Array Window (hawindow, at 0x15)

hart数组掩码寄存器,与hawindowsel一起选择hart数组。比如bit0代表hawindowsel * 32索引的hart,bit31代表hawindowsel * 32 + 31索引的hart。

abstractcs, 0x16

3.12.6 Abstract Control and Status (abstractcs, at 0x16)

当一个抽象命令正在执行时,写这个寄存器会造成cderr被设置为1(忙),如果它是0.

支持RV32,datacount至少为1;支持RV64,至少为2;支持RV128,至少为4.

command, 0x17

3.12.7 Abstract Command (command, at 0x17)

写这个寄存器会造成相关抽象命令被执行。

当一个抽象命令正在执行时,写这个寄存器会造成cderr被设置为1(忙),如果它是0.

如果cmderr为非0,写这个寄存器会被忽略。

abstractauto, 0x18

3.12.8 Abstract Command Autoexec (abstractauto, at 0x18)

这个寄存器是可选的。包含它允许更有效的突发访问。调试器可以通过设置位并读取它们来检测它是否被支持。

当一个抽象命令正在执行时,写这个寄存器会造成cderr被设置为1(忙),如果它是0.

confstrptr0, 0x19

3.12.9 Configuration String Pointer 0 (confstrptr0, at 0x19)

当设置confstrptrvalid时,读取该寄存器将返回配置字符串指针的第31:0位。读取其他的confstrptr寄存器返回地址的上位。

当实现系统总线主控时,这必须是一个可以与系统总线访问模块一起使用的地址。否则,这必须是一个可以用来访问ID0的hart的配置字符串的地址。

如果confstrptrvalid为0,则confstrptr寄存器保存标识符信息,该信息在本文档中没有进一步指定。

nextdm, 0x1d

3.12.10 Next Debug Module (nextdm, at 0x1d)

如果在DMI上有超过一个DM,这个寄存器包含链中下一个DM的基地址,如果是链中最后一个则为0.

data0, 0x04

3.12.11 Abstract Data 0 (data0, at 0x04)

data0到data11是基本的读/写寄存器,可以通过抽象命令读取或更改。dataccount表示实现了多少个,从data0开始,依次递增。

progbuf0, 0x20

3.12.12 Program Buffer 0 (progbuf0, at 0x20)

progbuf0到progbuf15提供对可选程序缓冲区的读/写访问。progbufsize表示从progbuf0开始实现的数目,依次递增。

authdata, 0x30

3.12.13 Authentication Data (authdata, at 0x30)

这个寄存器作为一个32位的串行端口进出认证模块。

当authbusy为0时,调试器可以通过读取或写入该寄存器与身份验证模块通信。没有单独的机制来指示溢出/下溢。

haltsum0, 0x40

3.12.14 Halt Summary 0 (haltsum0, at 0x40)

这个只读寄存器中的每个位表示一个特定的hart是否停止。不可用/不存在的hart不被认为是停止的。

LSB代表hart hartsel[19:5],5'h0的暂停状态,MSB代表harthartsel[19:5],5'h1f的暂停状态。

haltsum1, 0x13

3.12.15 Halt Summary 1 (haltsum1, at 0x13)

这个只读寄存器中的每个位表示一组(32个)hart是否停止。不可用/不存在的hart不被认为是停止的。

这个寄存器不能出现在少于33个hart的系统中。

LSB代表一组harthartsel[19:10],10'h0到harthartsel[19:10],10'h1f的暂停状态。MSB代表一组harthartsel[19:10],10'h3e0到harthartsel[19:10],10'h3ff的暂停状态。

haltsum2, 0x34

3.12.16 Halt Summary 2 (haltsum2, at 0x34)

这个只读寄存器中的每个位表示一组(1024个)hart是否停止。不可用/不存在的hart不被认为是停止的。

这个寄存器不能出现在少于1025个hart的系统中。

LSB代表一组harthartsel[19:15],15'h0到harthartsel[19:15],15'h3ff的暂停状态。MSB代表一组harthartsel[19:15],15'h7c00到harthartsel[19:15],15'h7fff的暂停状态。

haltsum3, 0x35

3.12.17 Halt Summary 3 (haltsum3, at 0x35)

这个只读寄存器中的每个位表示一组(32768个)hart是否停止。不可用/不存在的hart不被认为是停止的。

这个寄存器不能出现在少于32769个hart的系统中。

LSB代表一组hart20'h0到hart20'h7fff的暂停状态。MSB代表一组hart20'hf8000到hart20'hfffff的暂停状态。

sbcs, 0x38

3.12.18 System Bus Access Control and Status (sbcs, at 0x38)

sbaddress0, 0x39

3.12.19 System Bus Address 31:0 (sbaddress0, at 0x39)

如果sbasize为0,则这个寄存器不存在。

如果sberror为0,sbbusyerror为0,sbreadonaddr为1,然后开始向该寄存器写入以下内容:

  1. sbbusy置位。
  2. 从sbaddress的新值执行一次总线读取。
  3. 如果读成功且sbautoincrement为1,增加sbaddress。
  4. sbbusy清零。

sbaddress1, 0x3a

3.12.20 System Bus Address 63:32 (sbaddress1, at 0x3a)

如果sbasize少于33,则这个寄存器不存在。

sbaddress2, 0x3b

3.12.21 System Bus Address 95:64 (sbaddress2, at 0x3b)

如果sbasize少于65,则这个寄存器不存在。

sbaddress3, 0x37

3.12.22 System Bus Address 127:96 (sbaddress3, at 0x37)

如果sbasize少于97,则这个寄存器不存在。

sbdata0, 0x3c

3.12.23 System Bus Data 31:0 (sbdata0, at 0x3c)

如果sbc中的所有saccess位都是0,那么这个寄存器不存在。

对这个寄存器写入开始如下:

  1. sbbusy置位。
  2. 对sbdata的新值执行总线写入到sbaddress。
  3. 如果写成功且设置了sbautoincrement,增加sbaddress。
  4. sbbusy清零。

从这个寄存器读取开始如下:

  1. 返回数据。
  2. sbbusy置位。
  3. 如果设置了sbreadondata,则从saddress中包含的地址执行一次系统总线读取,将结果放入sbdata中。
  4. 如果设置了sbautoincrement,增加sbaddress。
  5. sbbusy清零。

sbdata1, 0x3d

3.12.24 System Bus Data 63:32 (sbdata1, at 0x3d)

如果sbaccess64和sbaccess128为0,那么这个寄存器不存在。

sbdata2, 0x3e

3.12.25 System Bus Data 95:64 (sbdata2, at 0x3e)

只有当sbaccess128为1时,这个寄存器才存在。

sbdata3, 0x3f

3.12.26 System Bus Data 127:96 (sbdata3, at 0x3f)

只有当sbaccess128为1时,这个寄存器才存在。

RISC-V调试

调试模式

调试模式是一种特殊的处理器模式,仅在hart暂停用于外部调试时使用。

当从可选的程序缓冲区执行代码时,hart保持在调试模式,并适用以下内容:

  1. 所有操作都在机器模式特权级别执行,除了根据mprven可以忽略处于状态的MPRV。
  2. 所有中断(包括NMI)被屏蔽。
  3. 异常不更新任何寄存器。包括cause, epc, tval, dpc和mstatus。它们终止程序缓冲区的执行。
  4. 如果一个触发器匹配上,则不采取任何操作。
  5. 计数器可能会停止,这取决于dcsr中的stopcount。
  6. 计时器可能会停止,这取决于dcsr中的stoptime。
  7. wfi指令起着nop的作用。
  8. 几乎所有改变特权级别的指令都有未定义的行为。这包括ecall、mret、sret和uret。(要更改特权级别,调试器可以在dcsr中写入prv)。唯一的例外是ebreak。当在调试模式下执行该命令时,它会再次停止hart,但不会更新dpc或dcsr。
  9. 完成Program Buffer的执行被认为是fence指令的输出。
  10. 如果所有的控制转移指令的目的地在程序缓冲区中,它们都可能被视为非法指令。如果其中一条指令是非法指令,那么所有这些指令都必须是非法指令。
  11. 如果控制转移指令的目的地在程序缓冲区之外,则所有控制转移指令都可能被视为非法指令。如果其中一条指令是非法指令,那么所有这些指令都必须是非法指令。
  12. 依赖于PC值的指令(例如auipc)可能作为非法指令。
  13. 有效的XLEN是DXLEN。

等待中断指令

如果在wfi执行时请求暂停,则hart必须离开已停止状态,完成该指令的执行,然后进入调试模式。

单步调试

调试器可以使暂停的hart执行一条指令,然后在设置resumereq之前通过设置step重新进入调试模式。

如果执行或获取该指令导致异常,则在PC更改为异常处理程序并更新相应的tval和cause寄存器后立即重新进入调试模式。

如果执行或获取指令导致触发器触发,则在触发器触发后立即重新进入调试模式。在这种情况下,cause被设置为2(trigger)而不是4(single step)。指令是否执行取决于触发器的具体配置。

如果被执行的指令导致PC改变到一个地址,在这个地址中指令获取会导致异常,那么这个异常直到下一次hart被恢复时才会发生。

类似地,新地址的触发器在hart实际尝试执行该指令之前不会触发。

如果正在跳过的指令是wfi并且通常会使hart停止,那么该指令将被视为nop。

重置

如果在hart从复位状态出来时断言了暂停信号(由hart的调试模块中的暂停请求位驱动)或resethaltreq, hart必须在执行任何指令之前进入调试模式,但在执行任何初始化之后,通常会在执行第一个指令之前发生

dret指令

为了从调试模式返回,定义了一条新指令:dret。编码为0x7b200073。在支持此指令的硬件上,在调试模式下执行dret会将pc更改为存储在dpc中的值。将当前权限级别更改为dcsr中prv指定的权限级别。hart不再处于调试模式。

核调试寄存器

地址名称
0x7b0Debug Control and Status (dcsr)
0x7b1Debug PC (dpc)
0x7b2Debug Scratch Register 0 (dscratch0)
0x7b3Debug Scratch Register 1 (dscratch1)

dcsr, 0x7b0

4.8.1 Debug Control and Status (dcsr, at 0x7b0)

dpc, 0x7b1

4.8.2 Debug PC (dpc, at 0x7b1)

dscratch0, 0x7b2

4.8.3 Debug Scratch Register 0 (dscratch0, at 0x7b2)

dscratch1, 0x7b3

4.8.4 Debug Scratch Register 1 (dscratch1, at 0x7b3)

虚拟调试寄存器

虚拟寄存器是一种不直接存在于硬件中的寄存器,但调试器将其显示为存在的寄存器。调试软件应该实现它们,但是硬件可以跳过这一节。虚拟寄存器的存在是为了让用户访问不属于标准调试器的功能,而不需要他们在调试器访问这些寄存器时仔细修改调试寄存器。

priv

4.9.1 Privilege Level (priv, at virtual)

用户可以读取此寄存器以检查hart停止时运行的特权级别。用户可以写这个寄存器来更改hart恢复时运行的特权级别。

编码特权等级
0User/Application
1Supervisor
3Machine

触发器模块

触发器可以导致断点异常、进入调试模式或跟踪操作,而无需执行特殊指令。这使得它们在从ROM中调试代码时非常宝贵。

触发器在调试模式下不会触发。

调试器可以构建所有触发器及其特性的列表,如下所示:

  1. 将0写入tselect。
  2. 回读tselect并检查它是否包含写入的值。如果不是,则退出循环。
  3. 读tinfo。
  4. 如果导致异常,调试器必须读取tdata1以发现类型。(如果type为0,则此触发器不存在。退出循环。)
  5. 如果info为1,则此触发器不存在。退出循环。
  6. 否则,所选触发器支持info中发现的类型。
  7. 重复,增加tselect中的值。

本地M-Mode触发器

触发器可用于本机调试。在一个功能齐全的系统上,触发器将使用u或s来设置,当触发它们时,可能会导致一个断点异常被捕获到一个更特权的模式。也可以将触发器本地设置为在M模式下触发。在这种情况下,没有更高的特权模式可以捕获。当这样的触发器在陷阱处理程序中导致断点异常时,这将使系统无法恢复正常执行。

简单的解决方案是在“M模式”和MIE在mstatus为0的状态下,让硬件防止触发器触发action=0。它的限制是,当用户想要触发触发器时,可能会在其他时间禁用中断。

触发器寄存器

这些寄存器是CSR,可以使用RISC-V csr操作码访问,也可以选择使用抽象调试命令。

action编码表

描述
0引发断点异常。(当软件想要在没有外部调试器的情况下使用触发模块时使用。)
1进入调试模式。(仅当触发器的dmode为1时支持。)
2 - 5保留供trace规范使用。
other保留以备将来使用。

触发器寄存器表

地址名称
0x7a0Trigger Select (tselect)
0x7a1Trigger Data 1 (tdata1)
0x7a1Match Control (mcontrol)
0x7a1Instruction Count (icount)
0x7a1Interrupt Trigger (itrigger)
0x7a1Exception Trigger (etrigger)
0x7a2Trigger Data 2 (tdata2)
0x7a3Trigger Data 3 (tdata3)
0x7a3Trigger Extra (RV32) (textra32)
0x7a3Trigger Extra (RV64) (textra64)
0x7a4Trigger Info (tinfo)
0x7a5Trigger Control (tcontrol)
0x7a8Machine Context (mcontext)
0x7aaSupervisor Context (scontext)

tselect, 0x7a0

5.2.1 Trigger Select (tselect, at 0x7a0)

这个寄存器确定哪个触发器可以通过其他触发器寄存器访问。可访问触发器集必须从0开始,并且是连续的。

tdata1, 0x7a1

5.2.2 Trigger Data 1 (tdata1, at 0x7a1)

tdata2, 0x7a2

5.2.3 Trigger Data 2 (tdata2, at 0x7a2)

tdata3, 0x7a3

5.2.4 Trigger Data 3 (tdata3, at 0x7a3)

tinfo, 0x7a4

5.2.5 Trigger Info (tinfo, at 0x7a4)

tcontrol, 0x7a5

5.2.6 Trigger Control (tcontrol, at 0x7a5)

mcontext, 0x7a8

5.2.7 Machine Context (mcontext, at 0x7a8)

scontext, 0x7aa

5.2.8 Supervisor Context (scontext, at 0x7aa)

mcontrol, 0x7a1

5.2.9 Match Control (mcontrol, at 0x7a1)

当type为2时,这个寄存器可以作为tdata1访问。

icount, 0x7a1

5.2.10 Instruction Count (icount, at 0x7a1)

当type为3时,这个寄存器可以作为tdata1访问。

itrigger, 0x7a1

5.2.11 Interrupt Trigger (itrigger, at 0x7a1)

当type为4时,这个寄存器可以作为tdata1访问。

etrigger, 0x7a1

5.2.12 Exception Trigger (etrigger, at 0x7a1)

当type为5时,这个寄存器可以作为tdata1访问。

textra32, 0x7a3

5.2.13 Trigger Extra (RV32) (textra32, at 0x7a3)

textra64, 0x7a3

5.2.14 Trigger Extra (RV64) (textra64, at 0x7a3)

调试传输模块DTM

调试传输模块通过一个或多个传输(例如JTAG或USB)提供对DM的访问。

单个平台中可能有多个dtm。理想情况下,与外部世界通信的每个组件都包含DTM,允许通过平台支持的每种传输对平台进行调试。例如,USB组件可以包含DTM。这样就可以轻松地通过USB调试任何平台。所需要的就是已经在使用的USB模块也可以访问调试模块接口。

JTAG调试传输模块

这个调试传输模块是基于一个正常的JTAG测试访问端口(TAP)。通过首先使用JTAG指令寄存器(IR)选择一个JTAG寄存器,然后通过JTAG数据寄存器(DR)访问它,JTAG TAP允许访问任意JTAG寄存器。

JTAG DTM寄存器

用作DTM的JTAG抽头必须具有至少5位的IR。当TAP复位时,IR必须默认为00001,选择IDCODE指令。调试器可能使用的唯一常规JTAG寄存器是BYPASS和IDCODE,但是该规范为许多其他标准JTAG指令留下了IR空间。未实现指令必须选择BYPASS寄存器。

JTAG DTM TAP寄存器表

地址名称描述
0x00BYPASSJTAG recommends this encoding
0x01IDCODEJTAG recommends this encoding
0x10DTM Control and Status (dtmcs)For Debugging
0x11Debug Module Interface Access (dmi)For Debugging
0x12Reserved (BYPASS)Reserved for future RISC-V debugging
0x13Reserved (BYPASS)Reserved for future RISC-V debugging
0x14Reserved (BYPASS)Reserved for future RISC-V debugging
0x15Reserved (BYPASS)Reserved for future RISC-V standards
0x16Reserved (BYPASS)Reserved for future RISC-V standards
0x17Reserved (BYPASS)Reserved for future RISC-V standards
0x1fBYPASSJTAG requires this encoding

IDCODE, 0x01

6.1.3 IDCODE (at 0x01)

当TAP状态机复位时,选择这个寄存器(在IR中)。其定义与IEEE标准1149.1-2013中的定义完全一致。

dtmcs, 0x10

6.1.4 DTM Control and Status (dtmcs, at 0x10)

在以后的版本中,这个寄存器的大小将保持不变,以便调试器始终可以确定DTM的版本。

dmi, 0x11

6.1.5 Debug Module Interface Access (dmi, at 0x11)

这个寄存器允许访问调试模块接口(DMI)。

在Update-DR中,DTM将启动op中指定的操作,除非op中报告的当前状态为sticky。 在Capture-DR中,DTM使用该操作的结果更新数据,如果当前操作不是sticky,则更新op。

BYPASS 0x1f

6.1.6 BYPASS (at 0x1f)

无效的1位寄存器。当调试器不想与此TAP通信时使用它。

FreeRTOS源码解析

基本配置

1
#define portSTACK_GROWTH			( -1 )

任务管理

任务控制块TCB

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
/*
* Task control block. A task control block (TCB) is allocated for each task,
* and stores task state information, including a pointer to the task's context
* (the task's run time environment, including register values)
*/
typedef struct tskTaskControlBlock /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
volatile StackType_t * pxTopOfStack; /**< Points to the location of the last item placed on the tasks stack. THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */

ListItem_t xStateListItem; /**< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */
ListItem_t xEventListItem; /**< Used to reference a task from an event list. */
UBaseType_t uxPriority; /**< The priority of the task. 0 is the lowest priority. */
StackType_t * pxStack; /**< Points to the start of the stack. */
char pcTaskName[ configMAX_TASK_NAME_LEN ]; /**< Descriptive name given to the task when created. Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */

UBaseType_t uxBasePriority; /**< The priority last assigned to the task - used by the priority inheritance mechanism. */
UBaseType_t uxMutexesHeld;

volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];

uint8_t ucDelayAborted;
} tskTCB;

/* The old tskTCB name is maintained above then typedefed to the new TCB_t name
* below to enable the use of older kernel aware debuggers. */
typedef tskTCB TCB_t;

动态创建任务

1
2
3
4
5
6
7
// tasks.c
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
const configSTACK_DEPTH_TYPE usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )

portSTACK_GROWTH < 0,栈向下增长;为避免栈增长到TCB中,需要先申请栈再申请TCB结构。

pxNewTCB->pxStack = pxStack;将栈赋值到TCB中。

prvInitialiseNewTask初始化TCB结构。

prvAddNewTaskToReadyList将任务添加到就绪列表中。

1
static void prvAddNewTaskToReadyList( TCB_t * pxNewTCB )

taskENTER_CRITICAL进入临界区,关闭中断。

prvAddTaskToReadyList将任务按优先级加入就绪队列中。

taskEXIT_CRITICAL退出临界区,开中断。

开始任务调度

1
2
// tasks.c
void vTaskStartScheduler( void )

xTaskCreate创建idle任务。

xTimerCreateTimerTask创建计时器任务。

portDISABLE_INTERRUPTS禁用中断。

xPortStartScheduler调用开启硬件调度接口,一般是启动tick计时器。

1
2
// port.c
BaseType_t xPortStartScheduler( void )

portDISABLE_INTERRUPTS禁用中断。

configSETUP_TICK_INTERRUPT配置tick计时器。

vPortRestoreTaskContext开始执行第一个任务。

汇编程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// portASM.S
/******************************************************************************
* vPortRestoreTaskContext is used to start the scheduler.
*****************************************************************************/
.align 8
.type vPortRestoreTaskContext, %function
vPortRestoreTaskContext:
.set freertos_vector_base, _freertos_vector_table

/* Install the FreeRTOS interrupt handlers. */
LDR X1, =freertos_vector_base
#if defined( GUEST )
MSR VBAR_EL1, X1
#else
MSR VBAR_EL3, X1
#endif
DSB SY
ISB SY

/* Start the first task. */
portRESTORE_CONTEXT

任务切换

MPS2_QEMU会调用汇编处理程序xPortPendSVHandler

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
void xPortPendSVHandler( void )
{
/* This is a naked function. */

__asm volatile
(
" mrs r0, psp \n"
" isb \n"
" \n"
" ldr r3, pxCurrentTCBConst \n"/* Get the location of the current TCB. */
" ldr r2, [r3] \n"
" \n"
" stmdb r0!, {r4-r11} \n"/* Save the remaining registers. */
" str r0, [r2] \n"/* Save the new top of stack into the first member of the TCB. */
" \n"
" stmdb sp!, {r3, r14} \n"
" mov r0, %0 \n"
" msr basepri, r0 \n"
" bl vTaskSwitchContext \n"
" mov r0, #0 \n"
" msr basepri, r0 \n"
" ldmia sp!, {r3, r14} \n"
" \n"/* Restore the context, including the critical nesting count. */
" ldr r1, [r3] \n"
" ldr r0, [r1] \n"/* The first item in pxCurrentTCB is the task top of stack. */
" ldmia r0!, {r4-r11} \n"/* Pop the registers. */
" msr psp, r0 \n"
" isb \n"
" bx r14 \n"
" \n"
" .align 4 \n"
"pxCurrentTCBConst: .word pxCurrentTCB \n"
::"i" ( configMAX_SYSCALL_INTERRUPT_PRIORITY )
);
}

OpenAI辅助理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void PendSV_Handler(void) {
/* 保存当前任务的上下文 */
__asm volatile (
"MRS R0, PSP \n" /* 获取进程堆栈指针 */
"STMDB R0!, {R4-R11} \n" /* 保存R4-R11寄存器到堆栈 */
"LDR R1, =pxCurrentTCB \n" /* 加载当前TCB指针地址 */
"LDR R2, [R1] \n" /* 加载当前TCB指针 */
"STR R0, [R2] \n" /* 保存堆栈指针到当前TCB */
);

/* 切换到下一个任务 */
vTaskSwitchContext();

/* 恢复下一个任务的上下文 */
__asm volatile (
"LDR R1, =pxCurrentTCB \n" /* 加载当前TCB指针地址 */
"LDR R2, [R1] \n" /* 加载当前TCB指针 */
"LDR R0, [R2] \n" /* 加载堆栈指针 */
"LDMIA R0!, {R4-R11} \n" /* 恢复R4-R11寄存器 */
"MSR PSP, R0 \n" /* 恢复进程堆栈指针 */
"BX LR \n" /* 返回任务 */
);
}

然后调用切换上下文函数

1
2
// task.c
void vTaskSwitchContext( void )

taskSELECT_HIGHEST_PRIORITY_TASK选择最高优先级任务

时钟管理

时钟节拍

MPS2_QEMU会调用处理程序xPortSysTickHandler

1
2
// port.c
void xPortSysTickHandler( void )

portDISABLE_INTERRUPTS关闭中断。

xTaskIncrementTick尝试增加tick值,成功后触发PendSV中断,强行进行一次调度。

portENABLE_INTERRUPTS打开中断。

1
2
// tasks.c
BaseType_t xTaskIncrementTick( void )

taskSWITCH_DELAYED_LISTS系统tick值反转时,将延时任务队列与反转反转队列互换。

判断是否有超时的任务,并将其移入就绪队列中。

任务延时

1
void vTaskDelay( const TickType_t xTicksToDelay )

vTaskSuspendAll关闭调度器。

prvAddCurrentTaskToDelayedList将当前任务加到延时列表中。

xTaskResumeAll恢复调度器。

portYIELD_WITHIN_API当恢复调度时未进行再次调度,则强制进行调度并让任务进入睡眠状态。

1
2
3
static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait,
const BaseType_t xCanBlockIndefinitely )

uxListRemove将当前任务移除队列中。

xTimeToWake = xConstTickCount + xTicksToWait计算下次唤醒的时刻。需要考虑时间反转的场景。

基本信息

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内核驱动开发

0%