C程序-单处理器系统上的输出?
在嵌入式系统中,由于各种合理的原因,这种趋势还没有完全转向多核处理器 因此,对于单处理器系统,了解使用各种机制和多线程特性的同步行为仍然很重要。而且,每当我面对采访时,他们都会问我关于单处理器系统上某个特定C程序的行为的问题 因此,如果我想分析单处理器系统上的示例C程序,以检查它们在家中的行为,我将如何做到这一点?我家的CPU有一个核心的i3处理器。有没有一种方法可以让我的操作系统或编译器通过只考虑一个CPU来强制检查行为 示例:C程序-单处理器系统上的输出?,c,multithreading,synchronization,C,Multithreading,Synchronization,在嵌入式系统中,由于各种合理的原因,这种趋势还没有完全转向多核处理器 因此,对于单处理器系统,了解使用各种机制和多线程特性的同步行为仍然很重要。而且,每当我面对采访时,他们都会问我关于单处理器系统上某个特定C程序的行为的问题 因此,如果我想分析单处理器系统上的示例C程序,以检查它们在家中的行为,我将如何做到这一点?我家的CPU有一个核心的i3处理器。有没有一种方法可以让我的操作系统或编译器通过只考虑一个CPU来强制检查行为 示例: int x=0; 片段-1 while(x);
int x=0;
片段-1
while(x);
x++;
代码片段-2
while(!x);
x--;
考虑单处理器系统,我想检查一个C程序的行为,其中
- 代码段1和代码段2位于多个线程中
- 代码段1位于主程序中,代码段2位于ISR中
- Snippet1和Snippet2都位于两个不同的ISR中(考虑中断是按优先级捕获的,并且在ISR内部时,如果有一个优先级更高的新中断,则立即执行优先级最高的中断-例如:重置)
在上述问题中,我的主要目标是确定是否存在死锁,如果存在,则需要确定解决方案。请把你的想法写进去。谢谢。我建议您通过编译新内核创建一个单处理器系统,在i3中只启用一个内核(显然性能会降低) 按照以下链接中的步骤操作 配置时,
转到处理器类型和功能 取消选中对称多处理支持 并研究了创建单处理器系统的指令
您只需根据需要设置“maxcpus”内核参数即可启动linux。 它指定SMP Linux内核应使用的最大处理器数量。
例如,maxcpus=1。单处理器和多处理器系统仅在程序已经无效的区域不同(“根据标准导致未定义的行为”) 您的示例程序修改ISR中的共享变量,而不使用
volatile
修饰符,也不防止并发执行其他ISR
前者的效果是,编译器可以在假定x
不能更改的情况下优化代码:
while(x);
x++;
将编译为执行以下步骤的汇编程序指令:
loop:
read x into register0
test register0 != 0
if true => goto loop
increment register0
write register0 to x
在优化过程中,编译器发现x
不是易失性的
,并将内存访问移出循环:
read x into register0
loop:
test register0 != 0
if true => goto loop
increment register0
write register0 to x
随后,它发现在循环执行期间,register0
从未被修改,因此测试也可以移到循环之外:
read x into register0
test register0 != 0
loop:
if true => goto loop
increment register0
write register0 to x
然后,一些编译器执行额外的步骤并反转测试,以便能够在循环中使用更便宜的指令
read x into register0
test register0 != 0
if false => goto skip
loop:
goto loop
skip:
increment register0
write register0 to x
显然,这不是你想要的
另一个问题是,由于IRQ优先级,ISR可能会或可能不会相互中断,并且在多处理器系统中,多个ISR可能同时在不同的处理器上运行
假设代码正确地使用了volatile
,您可以通过假设在任意两条指令之间可以发生更高优先级的中断和任务调度,从理论上验证该行为;代码段的汇编程序伪代码是
push register0
loop:
load x into register0
test register0 != 0
if true => goto loop
write 1 to x // can you see what I did there?
pop register0
及
一个可能的星座是
CPU1 push register0
CPU2 push register0
CPU1 load x into register0 [value = 0]
CPU2 load x into register0 [value = 0]
CPU1 test register0 != 0 [false]
CPU2 test register0 == 0 [true]
CPU1 if true => goto loop [not taken]
CPU2 if true => goto loop [taken]
CPU1 increment register0 [value = 1]
CPU2 read x into register0 [value = 0]
CPU1 write register0 to x [value = 1]
CPU2 test register0 == 0 [true]
CPU1 pop register0
CPU2 if true => goto loop [taken]
CPU1 ...
CPU2 read x into register0 [value = 1]
CPU1 ...
CPU2 test register0 == 0 [false]
CPU1 ...
CPU2 if true => goto loop [not taken]
CPU1 ...
CPU2 decrement register0 [value = 0]
CPU1 ...
CPU2 write register0 to x [value = 0]
CPU1 ...
CPU2 pop register0
从理论上解决这一问题的通常方法是确定持有某些假设的指令范围,然后寻找在并发执行时这些假设如何可能是错误的:
// precondition: address at stack pointer is unused
// precondition: decrementing the stack pointer will not bring us to a used address
push register0
// postcondition: address at stack pointer is unused
// postcondition: register0 is unused
为了满足这些条件,系统范围的约定是当前堆栈指针下的所有内存都是未使用的。这样,ISR可以始终假定允许将数据推送到堆栈中。请注意,写入数据和递减堆栈指针是一个原子操作。如果另一个中断到达此处,其数据也将被推送到堆栈上,但使用不同的地址
loop:
// precondition: register0 is unused
read x into register0
// begin assumption: register0 contains a copy of x
我想你能看到这是怎么回事。如果我们从这里开始被打断,x
的值发生变化,那么这个假设将是错误的
test register0 != 0
// postcondition: processor status contains result of (register0 != 0)
if true => goto loop
// postcondition[true]: register0 != 0
// postcondition[false]: register0 == 0
这就是我们证明退出循环的唯一方法是当register0==0
时。因此:
increment register0
write register0 to x
// end assumption: register0 contains a copy of x
可以扩充到
// precondition: register0 is 0
increment register0
// postcondition: register0 is 1
// precondition: register0 is 1
write register0 to x
// end assumption: register0 contains a copy of x
然后可以简化为
// precondition: register0 is 0
// modified assumption: register0 contains a copy of x, minus one
// due to precondition, x needs to be written as 1
write 1 to x
// end assumption: register0 contains a copy of x, minus one
最后一条指令不使用寄存器0,因此在现在消除的increment
操作之前,可以向上移动“结束假设”语句:
// end assumption: register0 contains a copy of x
// precondition: register0 is 0
write 1 to x
先决条件很容易从循环中证明
// precondition: stack pointer points at address below where we placed the saved copy
// precondition: memory below the stack pointer is unused
pop register0
// postcondition: stack pointer points at unused memory
// postcondition: stack pointer points at the same address as before the push
// postcondition: register0 is restored
因此,您需要处理违反假设的情况,即从我们读取x
值到写回新值的时间之间修改x值的任何情况,以及由于无法调用可能生成该值的代码而无法满足条件的情况
这两种情况都可能发生在单处理器和多处理器设计上;不同之处在于多处理机有一个额外的故障模式,它隐藏了一些错误
单处理器的故障模式包括
- ISR1读取
- ISR2读取(ISR2具有更高的优先级)
- ISR2写入
- ISR1写入
及
- ISR2进入忙循环,等待条件改变
- ISR1被阻止,因为ISR2(更高优先级)处于活动状态
案例1相当于
- 主循环读取
- ISR阅读
- ISR写入
- 主循环写入
及
- 线程1读取
- 线程2读取
- 线程2写入
- 线程1写入
案例2相当于
// precondition: stack pointer points at address below where we placed the saved copy
// precondition: memory below the stack pointer is unused
pop register0
// postcondition: stack pointer points at unused memory
// postcondition: stack pointer points at the same address as before the push
// postcondition: register0 is restored