发布时间:2022-12-02 13:30
在单核的ThreadX内核中,内核的临界资源互斥通过关中断实现;在多核cpu上,关闭整个cpu的代价比较大,单核上仍然使用关中断实现(其他核的中断仍然开启),但是与其他核之间互斥通过_tx_thread_smp_protection实现:
首先,单核已经关中断,那么正在访问资源的线程不会被切换出去,在退出临界资源之前,当前核上的线程不会被中断,也就是在退出临界资源前当前核不会有其他线程执行,当前核的线程之间是互斥的;
其次,_tx_thread_smp_protection标记了占用_tx_thread_smp_protection的核,其他核的线程如果检查到_tx_thread_smp_protection被其他核占用,那么等待_tx_thread_smp_protection被释放,因此实现了核间互斥。
typedef struct TX_THREAD_SMP_PROTECT_STRUCT
{
ULONG tx_thread_smp_protect_in_force;
ULONG tx_thread_smp_protect_core;
ULONG tx_thread_smp_protect_count;
ULONG tx_thread_smp_protect_pad_0;
ULONG tx_thread_smp_protect_pad_1;
ULONG tx_thread_smp_protect_pad_2;
ULONG tx_thread_smp_protect_pad_3;
} TX_THREAD_SMP_PROTECT;
tx_thread_smp_protect_in_force: 是否被占用(0没有被占用,1被占用)
tx_thread_smp_protect_core: 被哪个cpu核占用(0xFFFFFFFF无效)
tx_thread_smp_protect_count: 被占用次数。
_tx_thread_smp_protect保护临界资源,实现的功能类似linux内核的自旋锁,主要用于核间的互斥,一个核在访问临界资源的时候,避免其他核访问该临界资源。
_tx_thread_smp_protect基本原理就是关闭当前核的中断,检查_tx_thread_smp_protection是否被当前核占用,如果是,那么对_tx_thread_smp_protection的占用次数加1,返回关中断之前的中断标志(调用_tx_thread_smp_protect的时候可能就已经是关闭了中断,那么退出临界资源应该恢复关中断的状态,不能简单开启中断;内核的所有临界资源都用_tx_thread_smp_protection保护,存在临界资源嵌套,也就是在临界资源里面再次调用_tx_thread_smp_protect,所以前面对_tx_thread_smp_protection的占用次数加1)。
_tx_thread_smp_protect主要实现代码如下:
076 .global _tx_thread_smp_protect
077 .type _tx_thread_smp_protect, @function
078 _tx_thread_smp_protect:
079
080 /* Disable interrupts so we don't get preempted. */
081
082 MRS x0, DAIF // Pickup current interrupt posture
083 MSR DAIFSet, 0x3 // Lockout interrupts
084
085 /* Pickup the CPU ID. */
086
087 MRS x2, MPIDR_EL1 // Pickup the core ID
088 #ifdef TX_ARMV8_2
089 #if TX_THREAD_SMP_CLUSTERS > 1
090 UBFX x7, x2, #16, #8 // Isolate cluster ID
091 #endif
092 UBFX x2, x2, #8, #8 // Isolate core ID
093 #else
094 #if TX_THREAD_SMP_CLUSTERS > 1
095 UBFX x7, x2, #8, #8 // Isolate cluster ID
096 #endif
097 UBFX x2, x2, #0, #8 // Isolate core ID
098 #endif
099 #if TX_THREAD_SMP_CLUSTERS > 1
100 ADDS x2, x2, x7, LSL #2 // Calculate CPU ID
101 #endif
102
103 LDR x1, =_tx_thread_smp_protection // Build address to protection structure
104 LDR w3, [x1, #4] // Pickup the owning core
105 CMP w3, w2 // Is it this core?
106 BEQ _owned // Yes, the protection is already owned
107
108 LDAXR w4, [x1, #0] // Pickup the protection flag
109 CBZ w4, _get_protection // Yes, get the protection
110 MSR DAIF, x0 // Restore interrupts
111 ISB //
112 #ifdef TX_ENABLE_WFE
113 WFE // Go into standby
114 #endif
115 B _tx_thread_smp_protect // On waking, restart the protection attempt
116
117 _get_protection:
118 MOV x4, #1 // Build lock value
119 STXR w5, w4, [x1] // Attempt to get the protection
120 CBZ w5, _got_protection // Did it succeed? w5 = 0 means success!
121 MSR DAIF, x0 // Restore interrupts
122 B _tx_thread_smp_protect // Restart the protection attempt
123
124 _got_protection:
125 DMB ISH //
126 STR w2, [x1, #4] // Save owning core
127 _owned:
128 LDR w5, [x1, #8] // Pickup ownership count
129 ADD w5, w5, #1 // Increment ownership count
130 STR w5, [x1, #8] // Store ownership count
131 DMB ISH //
132 RET
保存DAIF到x0寄存器(x0也是函数的返回值),设置DAIF的IF标志位,禁止IRQ、FIQ中断。
082 MRS x0, DAIF // Pickup current interrupt posture // 保存关中断前的DAIF(下一行会修改IF标志位,退出临界资源后需要恢复DAIF;x0在这里除了保存DAIF外,也是_tx_thread_smp_protect的返回值)
083 MSR DAIFSet, 0x3 // Lockout interrupts // IF位设置为1,禁止当前核的IRQ、FIQ中断(当前核实现互斥,当前核在访问临界资源过程中不会被切换出去,当前核没有中断及线程被调度执行,当前核的临界资源不存在被中断的情况)
通过MPIDR_EL1寄存器读取当前核的cpu ID,保存到x2寄存器里面。
085 /* Pickup the CPU ID. */
086
087 MRS x2, MPIDR_EL1 // Pickup the core ID
088 #ifdef TX_ARMV8_2
089 #if TX_THREAD_SMP_CLUSTERS > 1
090 UBFX x7, x2, #16, #8 // Isolate cluster ID
091 #endif
092 UBFX x2, x2, #8, #8 // Isolate core ID
093 #else
094 #if TX_THREAD_SMP_CLUSTERS > 1
095 UBFX x7, x2, #8, #8 // Isolate cluster ID
096 #endif
097 UBFX x2, x2, #0, #8 // Isolate core ID
098 #endif
099 #if TX_THREAD_SMP_CLUSTERS > 1
100 ADDS x2, x2, x7, LSL #2 // Calculate CPU ID
101 #endif
获取占用_tx_thread_smp_protection的cpu ID(_tx_thread_smp_protection没有被占用时,cpu ID是一个无效值,不等于任何cpu ID),如果是当前cpu占用了_tx_thread_smp_protection,那么不需要再次占用,跳转到_owned,只需要简单对占用计算器加1即可。
103 LDR x1, =_tx_thread_smp_protection // Build address to protection structure // x1 = &_tx_thread_smp_protection
104 LDR w3, [x1, #4] // Pickup the owning core // w3 = _tx_thread_smp_protection.tx_thread_smp_protect_core,通过成员变量的偏移读取成员变量的值
105 CMP w3, w2 // Is it this core? // w2: 当前核的cpu ID,w3: 占用_tx_thread_smp_protection核的cpu ID
106 BEQ _owned // Yes, the protection is already owned // 占用_tx_thread_smp_protection的cpu ID等于当前核的cpu ID(在临界资源里面再次调用_tx_thread_smp_protect),跳转到_owned(已经获取到了...)
_tx_thread_smp_protection的cpu ID有两种情况,一种是其他cpu的ID,另外一种是无效cpu ID,如果_tx_thread_smp_protection的cpu ID不是当前核的cpu ID,那么要检查这两种情况,主要就是检查tx_thread_smp_protect_in_force,如果tx_thread_smp_protect_in_force为0,那么_tx_thread_smp_protection就没有被占用;
对tx_thread_smp_protect_in_force的判断改写要实现原子操作,读写过程并没有暂停其他cpu,因此使用LDAXR/STXR实现多核对tx_thread_smp_protect_in_force的互斥;
尝试获取_tx_thread_smp_protection时,先检查tx_thread_smp_protect_in_force是否为0,如果为0,那么就可以获取_tx_thread_smp_protection,跳转到_get_protection去获取_tx_thread_smp_protection,否则恢复DAIF(主要是中断使能位,允许当前核处理中断),然后跳转到_tx_thread_smp_protect,重新去获取_tx_thread_smp_protection,类似ticket自旋锁。
103 LDR x1, =_tx_thread_smp_protection // Build address to protection structure // x1 = &_tx_thread_smp_protection
104 LDR w3, [x1, #4] // Pickup the owning core // w3 = _tx_thread_smp_protection.tx_thread_smp_protect_core,通过成员变量的偏移读取成员变量的值
105 CMP w3, w2 // Is it this core? // w2: 当前核的cpu ID,w3: 占用_tx_thread_smp_protection核的cpu ID
106 BEQ _owned // Yes, the protection is already owned // 占用_tx_thread_smp_protection的cpu ID等于当前核的cpu ID(在临界资源里面再次调用_tx_thread_smp_protect),跳转到_owned(已经获取到了...)
107
108 LDAXR w4, [x1, #0] // Pickup the protection flag // 读_tx_thread_smp_protection.tx_thread_smp_protect_in_force
109 CBZ w4, _get_protection // Yes, get the protection // _tx_thread_smp_protection.tx_thread_smp_protect_in_force为0(LDAXR读的时候,_tx_thread_smp_protection没被其他核占用),跳转到_get_protection去获取_tx_thread_smp_protection
110 MSR DAIF, x0 // Restore interrupts // _tx_thread_smp_protection被其他线程占用,恢复DAIF(对于非嵌套进入临界资源,这里可能恢复中断,如果有中断就绪,那么应该尽快处理中断,所以这里要恢复DAIF)
111 ISB //
112 #ifdef TX_ENABLE_WFE
113 WFE // Go into standby
114 #endif
115 B _tx_thread_smp_protect // On waking, restart the protection attempt // 跳转到_tx_thread_smp_protect,重新尝试获取_tx_thread_smp_protection(上面已经恢复DAIF,_tx_thread_smp_protect开始的地方又会关中断,在恢复关中断几条指令间,如果有中断,cpu会优先处理中断)
前面LDAXR并没有阻止其他核获取_tx_thread_smp_protection,LDAXR读取到_tx_thread_smp_protection没被占用之后,可能有其他核也去获取_tx_thread_smp_protection,所以使用STXR指令去写_tx_thread_smp_protection.tx_thread_smp_protect_in_force,写失败就表明别其他核改写了,如果写成功那么就获取到了_tx_thread_smp_protection。
118 MOV x4, #1 // Build lock value // LDAXR读取到_tx_thread_smp_protection.tx_thread_smp_protect_in_force为0,那么如果在STXR指令之前没有其他核修改_tx_thread_smp_protection.tx_thread_smp_protect_in_force,那么STXR指令就会成功,否则就会失败
119 STXR w5, w4, [x1] // Attempt to get the protection // 尝试写1到_tx_thread_smp_protection.tx_thread_smp_protect_in_force(LDAXR并没有阻止其他核改写_tx_thread_smp_protection.tx_thread_smp_protect_in_force,如果其他核没有改写的话(读完之后,没有其他cpu获取到_tx_thread_smp_protection),那么STXR将会成功,否则,如果其他核先写_tx_thread_smp_protection.tx_thread_smp_protect_in_force(先获取_tx_thread_smp_protection),那么STXR将会失败)
120 CBZ w5, _got_protection // Did it succeed? w5 = 0 means success! // 1成功写入到_tx_thread_smp_protection.tx_thread_smp_protect_core,成功获取到_tx_thread_smp_protection,跳转到_got_protection
121 MSR DAIF, x0 // Restore interrupts // 读取_tx_thread_smp_protection.tx_thread_smp_protect_in_force到写_tx_thread_smp_protection.tx_thread_smp_protect_in_force过程,有其他核先写了_tx_thread_smp_protection.tx_thread_smp_protect_in_force,当前核写失败,需要重新去获取_tx_thread_smp_protection,与前面一样,这里要恢复DAIF,让cpu优先处理中断
122 B _tx_thread_smp_protect // Restart the protection attempt // 跳转到_tx_thread_smp_protect,重新获取_tx_thread_smp_protection
第一次占用_tx_thread_smp_protection那么需要设置tx_thread_smp_protect_core,否则之需要对占用次数tx_thread_smp_protect_count加1即可,然后返回上一级函数(注意临界资源不能开中断,不能被抢占)。
124 _got_protection:
125 DMB ISH //
126 STR w2, [x1, #4] // Save owning core // 当前核的cpu ID写到_tx_thread_smp_protection.tx_thread_smp_protect_core
127 _owned:
128 LDR w5, [x1, #8] // Pickup ownership count
129 ADD w5, w5, #1 // Increment ownership count
130 STR w5, [x1, #8] // Store ownership count // 占用次数_tx_thread_smp_protection.tx_thread_smp_protect_count加1
131 DMB ISH //
132 RET // 返回,这里没有恢复也不能恢复DAIF,需要在退出临界资源时恢复DAIF(假设在临界资源里面恢复了中断,然后中断服务程序里面唤醒了更高优先级的线程或者时间片用完了调度其他线程,那么_tx_thread_smp_protection就得不到释放,_tx_thread_smp_protect就会进入无线循环!!!)
退出临界资源。_tx_thread_smp_unprotect与_tx_thread_smp_protect函数一样,也是先关闭了当前核的中断(正常情况,临界资源退出时中断本来就是关闭的)并且读取了当前核的cpu ID(如果不是占用_tx_thread_smp_protection的核释放_tx_thread_smp_protection,那么就是错误的调用,不能释放其他核占用的_tx_thread_smp_protection),然后对占用次数tx_thread_smp_protect_count减1,如果tx_thread_smp_protect_count不为0,那么还要等待其他临界资源退出。
实现代码如下:
072 .global _tx_thread_smp_unprotect
073 .type _tx_thread_smp_unprotect, @function
074 _tx_thread_smp_unprotect:
075 MSR DAIFSet, 0x3 // Lockout interrupts
076
077 MRS x1, MPIDR_EL1 // Pickup the core ID
078 #ifdef TX_ARMV8_2
079 #if TX_THREAD_SMP_CLUSTERS > 1
080 UBFX x2, x1, #16, #8 // Isolate cluster ID
081 #endif
082 UBFX x1, x1, #8, #8 // Isolate core ID
083 #else
084 #if TX_THREAD_SMP_CLUSTERS > 1
085 UBFX x2, x1, #8, #8 // Isolate cluster ID
086 #endif
087 UBFX x1, x1, #0, #8 // Isolate core ID
088 #endif
089 #if TX_THREAD_SMP_CLUSTERS > 1
090 ADDS x1, x1, x2, LSL #2 // Calculate CPU ID
091 #endif
092
093 LDR x2,=_tx_thread_smp_protection // Build address of protection structure
094 LDR w3, [x2, #4] // Pickup the owning core
095 CMP w1, w3 // Is it this core?
096 BNE _still_protected // If this is not the owning core, protection is in force elsewhere
097
098 LDR w3, [x2, #8] // Pickup the protection count
099 CMP w3, #0 // Check to see if the protection is still active
100 BEQ _still_protected // If the protection count is zero, protection has already been cleared
101
102 SUB w3, w3, #1 // Decrement the protection count
103 STR w3, [x2, #8] // Store the new count back
104 CMP w3, #0 // Check to see if the protection is still active
105 BNE _still_protected // If the protection count is non-zero, protection is still in force
106 LDR x2,=_tx_thread_preempt_disable // Build address of preempt disable flag
107 LDR w3, [x2] // Pickup preempt disable flag
108 CMP w3, #0 // Is the preempt disable flag set?
109 BNE _still_protected // Yes, skip the protection release
110
111 LDR x2,=_tx_thread_smp_protection // Build address of protection structure
112 MOV w3, #0xFFFFFFFF // Build invalid value
113 STR w3, [x2, #4] // Mark the protected core as invalid
114 DMB ISH // Ensure that accesses to shared resource have completed
115 MOV w3, #0 // Build release protection value
116 STR w3, [x2, #0] // Release the protection
117 DSB ISH // To ensure update of the protection occurs before other CPUs awake
118
119 _still_protected:
120 #ifdef TX_ENABLE_WFE
121 SEV // Send event to other CPUs, wakes anyone waiting on the protection (using WFE)
122 #endif
123 MSR DAIF, x0 // Restore interrupt posture
124 RET
通过MPIDR_EL1获取当前核的cpu ID,保存到x1寄存器。(需要检查_tx_thread_smp_protection是否是被当前核占用,不是的话,不能释放_tx_thread_smp_protection)
077 MRS x1, MPIDR_EL1 // Pickup the core ID
078 #ifdef TX_ARMV8_2
079 #if TX_THREAD_SMP_CLUSTERS > 1
080 UBFX x2, x1, #16, #8 // Isolate cluster ID
081 #endif
082 UBFX x1, x1, #8, #8 // Isolate core ID
083 #else
084 #if TX_THREAD_SMP_CLUSTERS > 1
085 UBFX x2, x1, #8, #8 // Isolate cluster ID
086 #endif
087 UBFX x1, x1, #0, #8 // Isolate core ID
088 #endif
089 #if TX_THREAD_SMP_CLUSTERS > 1
090 ADDS x1, x1, x2, LSL #2 // Calculate CPU ID
091 #endif
如果占用_tx_thread_smp_protection的核不是当前核,那么跳转到_still_protected(_still_protected恢复了DAIF,_tx_thread_smp_unprotect需要interrupt_save这个局部变量,而这个变量是_tx_thread_smp_protect返回的,也是在_tx_thread_smp_protect对应的宏TX_DISABLE定义的,那么调用_tx_thread_smp_unprotect能编译通过的前提是必须定义了interrupt_save,必须调用了_tx_thread_smp_protect,_tx_thread_smp_protection不是被当前核占用的条件就是_tx_thread_smp_unprotect多调用了,因此重复恢复DAIF的值,正常情况下是不影响的)
093 LDR x2,=_tx_thread_smp_protection // Build address of protection structure
094 LDR w3, [x2, #4] // Pickup the owning core // 读取_tx_thread_smp_protection.tx_thread_smp_protect_core
095 CMP w1, w3 // Is it this core? // core比较
096 BNE _still_protected // If this is not the owning core, protection is in force elsewhere // _tx_thread_smp_protection不是被当前核占用,跳转到_still_protected,不能释放_tx_thread_smp_protection
097
098 LDR w3, [x2, #8] // Pickup the protection count
099 CMP w3, #0 // Check to see if the protection is still active
100 BEQ _still_protected // If the protection count is zero, protection has already been cleared // 占用计数器已经为0了,实际没有被占用,但是也没有让其他核占用(后面如果禁止抢占的话,是不会修改_tx_thread_smp_protection.tx_thread_smp_protect_core)
_tx_thread_smp_protection占用计数器tx_thread_smp_protect_count减1,如果仍然占用,跳转到_still_protected,还在其他临界资源里面。
102 SUB w3, w3, #1 // Decrement the protection count
103 STR w3, [x2, #8] // Store the new count back // 占用计数器_tx_thread_smp_protection.tx_thread_smp_protect_count减1
104 CMP w3, #0 // Check to see if the protection is still active
105 BNE _still_protected // If the protection count is non-zero, protection is still in force // 占用计数器_tx_thread_smp_protection.tx_thread_smp_protect_count不为0,仍然占用,跳转到_still_protected
从代码上看,退出临界资源后,如果禁止抢占,那么也不真正释放_tx_thread_smp_protection,也就是不让其他核获取到_tx_thread_smp_protection。
猜测这个抢占检查应该是为了让更高优先级的线程尽快执行,简单的说就是禁止抢占的情况下,各个cpu核上等待_tx_thread_smp_protection的线程不一定是最高优先级就绪线程(在禁止抢占过程,有中断或者其他线程唤醒更高优先级就绪线程,但是禁止了抢占,所以等待_tx_thread_smp_protection的线程并不会被切换出去),如果禁止抢占期间立即释放_tx_thread_smp_protection,那么别的核就可以获取到_tx_thread_smp_protection并关闭中断,这就会导致禁止抢占期间唤醒的高优先级线程得不到及时的调度,所以禁止抢占期间不立即释放_tx_thread_smp_protection。
106 LDR x2,=_tx_thread_preempt_disable // Build address of preempt disable flag // 没有占用_tx_thread_smp_protection,已经完全退出了临界资源,检查_tx_thread_preempt_disable
107 LDR w3, [x2] // Pickup preempt disable flag
108 CMP w3, #0 // Is the preempt disable flag set?
109 BNE _still_protected // Yes, skip the protection release // 禁止抢占计数器_tx_thread_preempt_disable不为0,跳过_tx_thread_smp_protection释放(不释放_tx_thread_smp_protection)
主要是恢复_tx_thread_smp_protection的成员变量为未占用状态(tx_thread_smp_protect_in_force、tx_thread_smp_protect_core),然后恢复DAIF寄存器。
111 LDR x2,=_tx_thread_smp_protection // Build address of protection structure
112 MOV w3, #0xFFFFFFFF // Build invalid value
113 STR w3, [x2, #4] // Mark the protected core as invalid
114 DMB ISH // Ensure that accesses to shared resource have completed
115 MOV w3, #0 // Build release protection value
116 STR w3, [x2, #0] // Release the protection
117 DSB ISH // To ensure update of the protection occurs before other CPUs awake
118
119 _still_protected:
120 #ifdef TX_ENABLE_WFE
121 SEV // Send event to other CPUs, wakes anyone waiting on the protection (using WFE)
122 #endif
123 MSR DAIF, x0 // Restore interrupt posture
124 RET