1、NucluesPlus信号Signal处理流程介绍
NucleusPlus内核信号处理流程是,如果信号是发给自己(发送/处理信号的都为当前线程),那么简单调用信号处理函数即可;如果信号是发送给其他线程的,如果接收信号的线程可以被信号中断,那么构建一个信号栈(单核情况下,发送信号的线程为当前正在执行的线程,那么接收信号的线程肯定是等待执行或者挂起状态,不管是等待执行还是挂起状态,接收信号的线程上下文都保存在线程的栈里面)
2、NucluesPlus线程上下文及信号处理过程
2.1、线程上下文
已中断为例,接收信号的线程执行时因为时间片用完或者中断唤醒了更高优先级线程,接收信号的线程被切换出cpu,那么接收信号线程的中断上下文栈内容如下:
NucluesPlus内核栈为满递减栈(栈底在高地址,栈顶在低地址,栈指针指向栈顶元素),上图中左边为内存低地址,右边为内存高地址,最左边为栈顶;中断处理函数保存中断上下文时,会设置thread -> tc_stack_pointer指向中断上下文栈顶,线程恢复时通过thread -> tc_stack_pointer指针即可获取中断上下文的内容;栈顶元素为上下文类型,1表示为中断上下文(中断需要保存所有寄存器,包括r0-r3;0表示非中断上下文,线程调用调度函数切换成出cpu,r0-r3是不需要保存的;中断上下文保存参考https://blog.csdn.net/arm7star/article/details/105565769);栈里面的pc为线程恢复执行时的指令地址(被中断点)。
2.2、信号处理过程
调度程序调度线程时都是从thread -> tc_stack_pointer指向的上下文恢复线程的寄存器,与中断类似,Signal也是中断接收信号的线程(在接收信号的线程正常流程中插入了一段代码执行),信号处理过程如下:
3、信号上下文Frame创建
NucleusPlus内核调用TCT_Build_Signal_Frame在线程栈里面创建信号的Frame,与中断上下文类似,中断上下文用于恢复到被中断的点继续执行线程,信号的Frame,让线程恢复时恢复到信号处理函数执行,信号处理时恢复线程的栈指针tc_stack_pointer,再由调度程序恢复线程的上下文。
3.1、获取接收信号线程的栈指针task -> tc_stack_pointer
// REG_Stack_Ptr = (BYTE_PTR) task -> tc_stack_pointer;
LDR r3,[r0,#TC_STACK_POINTER] // Pickup the current stack pointer
3.2、信号处理函数TCC_Signal_Shell地址入栈
信号处理函数为TCC_Signal_Shell,获取TCC_Signal_Shell函数的地址Signal_Shell,保存到接收信号的线程的栈里面;(此时接收信号的线程栈里面的内容为,{TCC_Signal_Shell,线程上下文})
LDR r2,Signal_Shell // Pickup address of shell entry
SUB r3,r3,#4 // Reserve a word
STR r2,[r3], #-4 // Store entry address on stack
3.3、sp入栈(信号处理函数上下文sp)
sp指针入栈(入栈TCC_Signal_Shell时,sp(r3)减了4,此处需要加4才是实际需要恢复的栈;信号处理函数执行时,栈指针指向线程上下文)
ADD r2,r3,#0x4 // Compute initial sp
STR r2,[r3], #-4 // Store initial ip
3.4、其他通用寄存器入栈
其他寄存器入栈(信号处理函数没有上下文,r0-r3通常用于传递参数,TCC_Signal_Shell没有参数,r0-r3不需要保存,TCC_Signal_Shell不会返回,lr寄存器也不需要保存(线程主动让出cpu时的上下文也没有保存lr),r0-r12没有实际用处,仅供调度程序恢复使用,因此都以0入栈)
STR r2,[r3], #-4 // Store initial fp
LDR r2,[r0,#TC_STACK_START] // Pickup the stack starting address
STR r2,[r3], #-4 // Store initial r10
MOV r2,#0 // Clear value for initial registers
STR r9,[r3], #-4 // Store initial r9
STR r2,[r3], #-4 // Store initial r8
STR r2,[r3], #-4 // Store initial r7
STR r2,[r3], #-4 // Store initial r6
STR r2,[r3], #-4 // Store initial r5
STR r2,[r3], #-4 // Store initial r4
3.5、栈类型入栈
栈类型入栈(r0为0,非中断上下文,r0-r4)
STR r2,[r3, #0] // Store solicited stack type on the
// top of the stack
3.6、更新线程栈指针(task -> tc_stack_pointer)
设置线程栈指针(task -> tc_stack_pointer指向信号栈Frame)
/* Save the new stack pointer into the task's control block. */
// task -> tc_stack_pointer = (VOID *) (REG_Stack_Ptr - REG_Stack_Size);
STR r3,[r0, #TC_STACK_POINTER] // Save stack pointer
信号栈创建完成后,返回发送信号的函数即可,之前线程的栈指针保存到task -> tc_saved_stack_ptr;如果接收信号的线程处于未就绪状态,需要保存接收线程的状态,唤醒接收信号的线程(信号处理比较紧急,接收信号的线程需要尽快处理信号)。
信号栈与线程让出cpu时的栈内容一致,只不过信号栈里面只有pc、sp寄存器有实际用处,其他寄存器都填充0。
4、信号处理TCC_Signal_Shell
(TCC_Signal_Shell代码为内核代码,非开源代码,仅提供实现原理介绍)
TCC_Signal_Shell处理比较简单,获取线程挂起的信号tc_signals,调用tc_signal_handler处理所有信号;信号处理完后根据tc_saved_stack_ptr判断信号是自己发送给自己还是别的线程发送的,如果是自己发送给自己,那么信号处理是直接调用TCC_Signal_Shell函数来处理信号的,直接返回上一级函数(发送信号的函数)即可,如果是别的线程发送的,那么是通过恢复信号上下文来调用TCC_Signal_Shell处理信号的,此时信号处理完后返回需要单独考虑。
通过恢复信号上下文来调用TCC_Signal_Shell处理信号;如果发送信号时,接收信号的线程处于就绪状态,直接调用TCT_Signal_Exit恢复当前线程的上下文即可,当前线程继续执行;如果发送信号时,线程处于未就绪状态,恢复之前的未就绪状态,设置需要调度的线程;
TCT_Signal_Exit保存线程的时间片(信号处理在线程上下文执行,信号处理函数消耗的时间片片计入线程的时间片里面),使用tc_saved_stack_ptr恢复tc_stack_pointer(此时线程栈已经恢复发送信号前的栈了),调用TCT_Schedule调度线程。