用户态线程2未运行问题排查及ucontext调试等技术咨询
用户态线程实现代码
#define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <ucontext.h> #include <signal.h> #include <sys/mman.h> #include <unistd.h> #include <sched.h> #define MAX_THREADS 4 #define STACK_SIZE 8192 typedef struct { ucontext_t context; int active; void (*func)(void); } Thread; Thread threads[MAX_THREADS]; int current_thread = 0; void schedule() { int next_thread = (current_thread + 1) % MAX_THREADS; int original_thread = current_thread; do { if (threads[next_thread].active) { // Save the current context to the current thread's context getcontext(&threads[current_thread].context); // Set the current_thread to the next thread current_thread = next_thread; // Swap the context to the next thread's context swapcontext(&threads[original_thread].context, &threads[current_thread].context); // The code below will only execute if we return to this thread break; } next_thread = (next_thread + 1) % MAX_THREADS; } while (next_thread != original_thread); } void thread_func1() { printf("Thread 1 is running...\n"); threads[current_thread].active = 0; } void thread_func2() { printf("Thread 2 is running...\n"); threads[current_thread].active = 0; } void monitor_thread(void* arg) { while (1) { if (!threads[current_thread].active) { schedule(); threads[current_thread].func(); } } } int create_thread(void (*func)(void)) { int thread_id = -1; for (int i = 0; i < MAX_THREADS; i++) { if (!threads[i].active) { thread_id = i; break; } } if (thread_id == -1) { return -1; } getcontext(&threads[thread_id].context); threads[thread_id].context.uc_stack.ss_sp = malloc(STACK_SIZE); threads[thread_id].context.uc_stack.ss_size = STACK_SIZE; makecontext(&threads[thread_id].context, (void (*)())func, 0); threads[thread_id].func = func; threads[thread_id].active = 1; return thread_id; } int main() { create_thread(thread_func1); create_thread(thread_func2); int pid = clone((int (*)(void *))monitor_thread, malloc(STACK_SIZE) + STACK_SIZE, CLONE_VM | CLONE_THREAD, NULL); setcontext(&threads[current_thread].context); return 0; }
问题
- 如何调试ucontext?EDB是否可以提供帮助?
- 如何避免在线程例程函数中手动设置完成状态?
- 应由谁来清理线程管理器的资源?
回答
1. ucontext调试方法及EDB的作用
- 用GDB调试时,必须编译代码时加上
-g选项保留调试符号。ucontext切换是用户态操作,GDB默认的栈跟踪会混乱,可执行set context-sections all让GDB显示完整的上下文寄存器和内存信息,也可以直接打印threads[current_thread].context结构体查看上下文状态。 - EDB作为可视化用户态调试器,和GDB核心能力一致,能跟踪
getcontext/swapcontext/makecontext的调用,其图形界面更直观查看寄存器、栈的实时变化,适合观察上下文切换时的状态流转,但调试逻辑和GDB没有本质区别,重点还是要关注上下文切换的关键函数调用点。
2. 避免手动设置完成状态的方案
可以给线程添加一个统一的包装函数,让线程例程执行完毕后自动处理状态:
- 定义包装函数,负责执行用户逻辑并自动更新线程状态:
void thread_wrapper(void (*func)(void)) { func(); // 线程执行完毕后自动标记为非活跃 threads[current_thread].active = 0; // 主动触发调度,切换到其他线程 schedule(); }
- 修改
create_thread中的makecontext调用,将线程入口设为包装函数,而非直接用户函数:
// 注意makecontext的参数个数要匹配,这里传1个参数(用户函数) makecontext(&threads[thread_id].context, (void (*)())thread_wrapper, 1, func);
这样用户编写的线程函数无需关心状态设置,所有收尾工作由包装函数统一处理。
3. 线程管理器的资源清理职责
- 用户态线程的资源:由线程管理器(monitor_thread)负责清理。在调度间隙(比如
schedule函数中),检查所有线程的active状态,对已终止(active=0)的线程,调用free(threads[i].context.uc_stack.ss_sp)释放栈内存,并重置线程结构体状态。 - 线程管理器自身的资源:
- 管理器的栈是通过
malloc分配后传给clone的,需要在管理器退出后由主线程释放。可以在管理器的循环中添加检测逻辑:当所有用户态线程都处于非活跃状态时,退出循环并返回,主线程通过waitpid等待管理器线程退出后,再释放对应的栈内存。 - 主线程自身的资源(比如全局的
threads数组),如果是静态分配的无需手动释放,若为动态分配则由主线程在程序退出前清理。
- 管理器的栈是通过
内容的提问来源于stack exchange,提问作者user9846777
相关产品推荐
相关产品推荐

