Zephyr创建线程触发MPU故障(数据访问违例)问题排查
Zephyr+nRF52840 DK BLE Central RSSI记录线程触发MPU故障排查
问题背景
基于Zephyr和nRF52840 DK开发BLE Central设备,需求为每秒记录当前连接的RSSI值。最初在connected回调末尾直接调用RSSI获取函数,虽能正常运行,但30秒后会被外设断开连接,推测是该函数阻塞主线程导致配对及服务发现受阻。
计划在connected回调末尾创建独立线程处理RSSI记录逻辑,并在disconnected回调中终止线程,但创建线程时触发MPU故障(数据访问违例),报错信息如下:
[00:00:04.303,436] <err> os: ***** MPU FAULT ***** [00:00:04.303,466] <err> os: Data Access Violation [00:00:04.303,466] <err> os: MMFAR Address: 0x58 [00:00:04.303,466] <err> os: r0/a1: 0x00000000 r1/a2: 0x20005000 r2/a3: 0x00000800 [00:00:04.303,497] <err> os: r3/a4: 0x00000058 r12/ip: 0x00000000 r14/lr: 0x000260f3 [00:00:04.303,497] <err> os: xpsr: 0x410d0000 [00:00:04.303,497] <err> os: Faulting instruction address (r15/pc): 0x00026074 [00:00:04.303,527] <err> os: >>> ZEPHYR FATAL ERROR 19: Unknown error on CPU 0 [00:00:04.303,588] <err> os: Current thread: 0x20001088 (unknown) [00:00:04.378,540] <err> os: Halting system
通过addr2line定位到故障指令位于Zephyr内核的dlist.h文件第204行,而非业务代码。以下是线程创建及相关函数实现(已简化):
#define THREAD_STACK_SIZE_RSSI 2048 #define THREAD_PRIORITY_RSSI 1 static void thread_entrypoint_rssi(void *p1, void *p2, void *p3); uint16_t conn_handle; struct k_thread *thread_data_rssi; k_tid_t thread_id_rssi; K_THREAD_STACK_DEFINE(thread_stack_rssi, THREAD_STACK_SIZE_RSSI); static void connected_cb(struct bt_conn *conn, uint8_t conn_err) { if (conn_err) { /* Some error handling */ } if (conn == default_conn) { int err = bt_conn_set_security(conn, BT_SECURITY_L3); if (err) { /* Some error handling */ } // Setting the discovering parameters and start to discover ... err = bt_gatt_discover(conn, &discover_params); if (err) { /* Some error handling */ } // HERE IS MY PROBLEM thread_id_rssi = k_thread_create(thread_data_rssi, thread_stack_rssi, THREAD_STACK_SIZE_RSSI, thread_entrypoint_rssi, conn, NULL, NULL, K_PRIO_PREEMPT(1), 0, K_NO_WAIT); // This works for 30 seconds (after what the peripheral close the connection) // thread_entrypoint_rssi(conn); } } static bool read_conn_rssi(int8_t *rssi) { struct net_buf *buf, *rsp = NULL; struct bt_hci_cp_read_rssi *cp; struct bt_hci_rp_read_rssi *rp; int err; buf = bt_hci_cmd_create(BT_HCI_OP_READ_RSSI, sizeof(*cp)); if (!buf) { /* Some error handling returning false */ } cp = net_buf_add(buf, sizeof(*cp)); cp->handle = sys_cpu_to_le16(conn_handle); err = bt_hci_cmd_send_sync(BT_HCI_OP_READ_RSSI, buf, &rsp); if (err) { /* Some error handling returning false */ } rp = (void *)rsp->data; *rssi = rp->rssi; net_buf_unref(rsp); return true; } static bool set_conn_handle(struct bt_conn *conn) { int ret = bt_hci_get_conn_handle(conn, &conn_handle); if (ret) return false; return true; } static void thread_entrypoint_rssi(void *p1, void *p2, void *p3) { struct bt_conn *conn = (struct bt_conn *)p1; int8_t rssi = 0xFF; if (!set_conn_handle(conn)) { /* Some error handling */ } while(1) { if (!read_conn_rssi(&rssi)) break; LOG_WRN("RSSI = %d", rssi); k_sleep(K_SECONDS(1)); } } static void disconnected_cb(strcut bt_conn *conn, uint8_t reason) { k_thread_abort(thread_id_rssi); ... }
已尝试的排查手段:
- 将线程优先级改为负值,无效果
- 增大栈空间(包括
prj.conf中的配置及线程栈定义),未解决问题 - 使用
K_THREAD_DEFINE宏定义线程编译失败,提示"initializer element is not constant"
相关prj.conf配置如下:
CONFIG_BT=y CONFIG_BT_DEVICE_NAME="BLE CENTRAL TEST" CONFIG_BT_CENTRAL=y CONFIG_BT_GATT_CLIENT=y CONFIG_CONSOLE=y CONFIG_GPIO=y CONFIG_SERIAL=y CONFIG_EVENTS=y CONFIG_HW_STACK_PROTECTION=y # Need to fine tune these values, for now they are random CONFIG_MAIN_STACK_SIZE=100500 CONFIG_HEAP_MEM_POOL_SIZE=10000 CONFIG_BT_ATT_TX_COUNT=255 CONFIG_BT_BUF_ACL_TX_COUNT=255 CONFIG_BT_CTLR=y CONFIG_BT_CTLR_CONN_RSSI=y CONFIG_BT_SMP=y CONFIG_BT_BONDABLE=y ...
故障原因及解决方法
核心故障原因
代码中struct k_thread *thread_data_rssi;仅声明了指针但未分配内存,k_thread_create需要传入一个已初始化的struct k_thread对象来存储线程的元数据(如链表节点、状态等),传入空指针会导致内核访问非法地址,触发MPU数据访问违例。
解决方法
方法一:静态分配线程控制块(推荐,嵌入式场景更可靠)
将线程控制块从指针改为静态分配的结构体,确保内存合法:
// 替换原来的 struct k_thread *thread_data_rssi; static struct k_thread thread_data_rssi;
修改后k_thread_create有合法的内存存储线程数据,不会触发MPU故障。
方法二:动态分配线程控制块(不推荐)
若必须动态分配,使用Zephyr提供的内存分配函数:
thread_data_rssi = k_malloc(sizeof(struct k_thread)); if (!thread_data_rssi) { // 内存分配失败的错误处理逻辑 }
线程终止后需调用k_free(thread_data_rssi)释放内存,但嵌入式系统中静态分配更稳定,避免内存碎片问题。
额外优化建议
- 使用官方API简化RSSI获取:替代手动构造HCI命令,直接使用Zephyr提供的
bt_conn_get_rssiAPI,减少代码复杂度并避免全局变量conn_handle的风险:static void thread_entrypoint_rssi(void *p1, void *p2, void *p3) { struct bt_conn *conn = (struct bt_conn *)p1; int8_t rssi; int err; while (1) { err = bt_conn_get_rssi(conn, &rssi); if (!err) { LOG_WRN("RSSI = %d", rssi); } k_sleep(K_SECONDS(1)); } } - 安全终止线程:在
disconnected_cb中先检查线程ID是否有效,避免空指针操作:static void disconnected_cb(struct bt_conn *conn, uint8_t reason) { if (thread_id_rssi) { k_thread_abort(thread_id_rssi); thread_id_rssi = NULL; // 重置标记,防止重复操作 } // 其他断开连接的处理逻辑 } - 修复拼写错误:
disconnected_cb的参数声明中strcut应为struct,避免编译警告。 - 栈空间优化:启用
CONFIG_THREAD_STACK_INFO配置,统计线程实际栈使用情况,按需调整栈大小,避免内存浪费。
内容的提问来源于stack exchange,提问作者Groux
相关产品推荐
相关产品推荐

