我注意到一种我无法解释的行为:函数的执行时间似乎取决于它在闪存中的位置.我使用的是STM32F746NGH微控制器(基于ARM-CORCEL M7)和STM32CubeIDE(GCC针对ARM的编译器).
Here are my tests:个
我对SysTick计数器进行了初始化,以触发具有固定周期T=1ms的中断.在中断处理程序中,我在两个线程之间切换(就像RTOS一样):让我们将它们命名为Thread1和Thread2.
每个线程只是递增一个变量.
以下是这两个线程的代码:
uint32_t ctr1, ctr2;
void thread1(void)
{
while(1)
{
ctr1++;
}
}
void thread2(void)
{
while(1)
{
ctr2++;
}
}
在监控这些变量时,我注意到ctr2的增量比ctr1快得多.
使用此代码:线程1的S地址为0x08000418,线程2的S的地址为0x0800042C.
Then, I tried to put another function in memory before thread1: let's name it thread0.个
所以我的新代码是:
uint32_t ctr0, ctr1, ctr2;
void thread0(void)
{
while(1)
{
ctr0++;
}
}
void thread1(void)
{
while(1)
{
ctr1++;
}
}
void thread2(void)
{
while(1)
{
ctr2++;
}
}
使用这个新代码:线程0‘S地址是0x08000418(带有先前代码的线程1’S位置),线程1‘S地址是0x0800042C(带有先前代码的线程2’S位置),并且线程2‘S地址是0x08000440.
我可以看到,ctr1和ctr2以相同的速率递增,而ctr0的递增速度比这两个慢得多.
Finally, I've tried with 20 different threads.每个线程递增一个变量(类似于上面共享的代码).我观察到变量以两种不同的速率递增:Speed1和Speed2;Speed1低于Speed2.
Thread | Address | Speed |
---|---|---|
Thread0 | 0x08000418 | speed1 |
Thread1 | 0x0800042C | speed2 |
Thread2 | 0x08000440 | speed2 |
Thread3 | 0x08000454 | speed1 |
Thread4 | 0x08000468 | speed2 |
Thread5 | 0x0800047C | speed2 |
Thread6 | 0x08000490 | speed2 |
Thread7 | 0x080004A4 | speed2 |
Thread8 | 0x080004B8 | speed1 |
Thread9 | 0x080004CC | speed2 |
Thread10 | 0x080004E0 | speed2 |
Thread11 | 0x080004F4 | speed1 |
Thread12 | 0x08000508 | speed2 |
Thread13 | 0x0800051C | speed2 |
Thread14 | 0x08000530 | speed2 |
Thread15 | 0x08000544 | speed2 |
Thread16 | 0x08000558 | speed1 |
Thread17 | 0x0800056C | speed2 |
Thread18 | 0x08000580 | speed2 |
Thread19 | 0x08000594 | speed1 |
我还签入了程序集中,所有线程都有类似的代码(相同的代码大小、相同的指令和相同数量的指令);因此它与代码本身无关. 每个线程有10条指令,因此代码大小为20字节(每条指令的宽度为2字节).它对应于每个线程的内存地址之间的增量(20=0x14).
Here is the code of a thread (as said, other threads have a similar code):个
task0:
08000418: push {r7}
0800041a: add r7, sp, #0
21 task0_ctr += 1;
0800041c: ldr r3, [pc, #8] ; (0x8000428 <task0+16>)
0800041e: ldr r3, [r3, #0]
08000420: adds r3, #1
08000422: ldr r2, [pc, #4] ; (0x8000428 <task0+16>)
08000424: str r3, [r2, #0]
08000426: b.n 0x800041c <task0+4>
08000428: movs r4, r3
0800042a: movs r0, #0
正如您在表中看到的,似乎有一种模式:一个线程的速度为1,两个线程的速度为2,一个线程的速度为1,4个线程的速度为2,然后重新启动模式.
I don't know if it is relevant/related, but in the Cortex M7 reference manual, I've found this section about the flash memory:个
指令预取 每个闪存读取操作提供256位,代表32位至16位的8条指令 16位指令按程序启动.因此,在顺序代码的情况下,在 执行先前的指令行读取需要至少8个CPU周期.预取 允许读取闪存中顺序的下一行指令,而 当前指令行是由CPU请求的.预取可以通过设置 FLASH_ACR寄存器的PRFTEN位.如果至少有一个等待状态是 需要访问闪存.当代码不是顺序的(分支)时,指令可以 既不存在于当前使用的指令行中,也不存在于预取指令中 排队.在这种情况下(未命中),周期数方面的惩罚至少等于 等待状态数. 自适应实时存储器
但我已经查看了表:256位块中完全包含的函数可以具有Speed1或Speed2,对于两个256位块之间共享的函数也可以具有相同的值.
我不明白这一行为的原因可能是什么.
EDIT 1:应请求,以下是线程调度器代码:
__attribute__((naked)) void SysTick_Handler(void)
{
__asm("CPSID I"); // disable global interrupts, equivalent to __disable_irq();
/* save current thread's context: save R4, R5, ..., R11 (xPSR, PC, LR, R12, R3, R2, R1, R0 are automatically pushed on the stack by the processor). */
__asm("PUSH {R4-R11}");
/* OS_Tick += 1 */
__asm("LDR R0, =OS_Tick"); // R0 = &OS_Tick
__asm("LDR R1, [R0]"); // R1 = OS_Tick
__asm("ADD R1, #1"); // R1 += 1
__asm("STR R1, [R0]"); // OS_Tick = 1;
/* Systick_Tick += 1 */
__asm("LDR R0, =Systick_Tick"); // R0 = &Systick_Tick
__asm("LDR R1, [R0]"); // R1 = Systick_Tick
__asm("ADD R1, #1"); // R1 += 1
__asm("STR R1, [R0]"); // Systick_Tick = 1;
/* Scheduler: switch thread */
__asm("LDR R0, =os_kernel_threads_list"); // R0 = &os_kernel_threads_list
__asm("LDR R1, [R0]"); // R1 = current_thread
__asm("STR SP, [R1,#4]"); // stack_ptr = SP
__asm("LDR R2, [R1]"); // R2 = next_tcb
__asm("STR R2, [R0]"); // current_thread = next_tcb (new thread)
__asm("LDR SP, [R2,#4]"); // SP = stack_ptr (new thread)
__asm("POP {R4-R11}"); // restore context (new thread)
__asm("CPSIE I"); // enable global interrupts, equivalent to __enable_irq();
/* return from interrupt */
__asm("BX LR");
}
Os_tick和sytick_tick是两个uint32_t变量. OS_KERNEL_THREADS_LIST是tcb_list变量,如下所示:
/*
* Thread Control Block (TCB) structure
*/
typedef struct tcb_
{
struct tcb_ *next_tcb; // linked-list, pointer to the next thread
int32_t *stack_ptr; // pointer to the top of the thread's stack (next item to pop / last value stacked)
int32_t stack[THREAD_STACK_SIZE]; // thread's stack
} tcb_struct;
/*
* Circular linked-list of threads.
*/
typedef struct
{
tcb_struct *current_thread; // pointer to the current running thread
tcb_struct threads[N_MAX_THREADS]; // array of threads
int n_threads; // number of threads created
} tcb_list;
线程存储在数组中,并以循环链接列表的方式连接.
EDIT2:附加信息:以下是我的时钟设置:
锁相源:晶体振荡器@25 MHz
SYSCLK=PLL_CLK=216 MHz
闪存等待状态=7WS,如STM32数据手册中所建议.