C程序 - Uni处理器系统的输出?

时间:2012-08-06 06:58:50

标签: c multithreading synchronization

在嵌入式系统中,由于各种有效的原因,趋势仍未完全转移到多核处理器。

因此,使用与uni处理器系统相关的各种机制和多线程特征来理解同步行为仍然很重要。此外,每当我面对面试时,他们都会向我询问有关单处理器系统上特定C程序行为的问题。

因此,如果我想在uni处理器系统上分析Sample C程序,检查它们在家中的行为,我该怎么做?我家里的CPU有一个Core i3处理器。有没有办法让我的操作系统或编译器只考虑一个CPU来强行检查行为?

示例:

int x=0;

片段-1

    while(x);
    x++;

Snippet-2

    while(!x);
    x--;

考虑到uni处理器系统,我想检查其中

的C程序的行为
  • 代码段1和代码段2采用多线程
  • 代码段1在主程序中,代码段2在ISR中
  • Snippet1和Snippet2都在两个不同的ISR中(考虑到中断优先级,并且在ISR内部,如果有一个新的中断具有更高的优先级,则立即执行具有最高优先级的中断 - 例如:重置)

在上述问题中,我的主要目标是确定是否存在任何死锁,如果存在,则需要确定解决方案。请加入你的想法。感谢。

4 个答案:

答案 0 :(得分:2)

您可以根据需要使用'maxcpus'内核参数设置启动Linux。 它指定了SMP Linux内核应该使用的最大处理器数。 例如maxcpus = 1。

答案 1 :(得分:0)

我建议你通过在i3中只启用一个核心来编译新内核来创建一个单处理器系统。(显然性能会更低。)

按照以下链接

中的步骤操作

http://www.cyberciti.biz/tips/compiling-linux-kernel-26.html

配置时,

转到处理器类型和功能 - >

取消选中对称多处理支持

并研究创建uni处理器系统的指令。

enter image description here

答案 2 :(得分:0)

单处理器和多处理器系统的区别仅在于程序已经无效的区域("导致未定义的行为"根据标准)。

您的示例程序在不使用volatile修饰符的情况下修改ISR中的共享变量,并且不会阻止其他ISR的并发执行。

前者的作用是编译器可能会优化代码,假设x无法更改:

while(x);
x++;

将被编译为汇编程序指令,执行以下步骤:

loop:
    read x into register0
    test register0 != 0
    if true => goto loop
    increment register0
    write register0 to x

在优化期间,编译器发现x不是volatile,并将内存访问移到循环之外:

    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

    push register0
loop:
    load x into register0
    test register0 == 0
    if true => goto loop
    decrement register0
    write register0 to x
    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

最后一条指令不使用register0,所以"结束假设"在现在消除的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的值的任何情况,以及从未满足您的条件的情况,因为无法调用可能的代码。

这两种情况都可能发生在单处理器和多处理器设计上;区别在于多处理器有一个额外的故障模式,可以隐藏一些错误。

单处理器的故障模式是

  • ISR1读取
  • ISR2读取(ISR2具有更高优先级)
  • ISR2写道
  • ISR1写道

  • ISR2进入忙碌循环,等待条件改变
  • ISR1被阻止,因为ISR2(更高优先级)处于活动状态

案例1相当于

  • mainloop读取
  • ISR读取
  • ISR写道
  • mainloop写道

  • 主题1读取
  • 线程2读取
  • 线程2写
  • 主题1写

案例2相当于

  • ISR进入忙碌循环,等待条件改变
  • 主要循环被阻止,因为ISR处于活动状态

多线程情况下没有死锁,因为线程不会相互阻塞。

对于多处理器(以及多线程情况,而不是死锁),还有一个额外的故障模式:

  • ISR1读取
  • ISR2读取
  • ISR1写道
  • ISR2写道

这不会发生在主循环中(因为IRQ总是具有优先级并阻塞主循环),但确实发生在多个线程中:

  • 主题1读取
  • 线程2读取
  • 主题1写
  • 线程2写

对于所有这些情况,解决方案是确保在关键部分期间锁定其他所有人,其中假设register0包含{{1}的副本需要保持,或者在事后检测到错误,并妥善处理。

这两者实际上都是等价的 - 你需要一个原子指令,既可以给你一个变量的当前状态,也可以一次写入新状态(或者在旧的条件下写新的状态)国家仍然完好无损)。然后,您可以使用单独的变量来表示某人是否在临界区内,或者直接在变量x上使用此特殊指令。

答案 3 :(得分:0)

这在Windows中很容易实现。在Windows任务管理器中,单击“进程”选项卡。在进程列表中,找到您的程序,右键单击,然后在下拉菜单中单击“设置关联...”。这将打开一个对话框,您可以在其中设置可用于运行进程的处理器。取消选中除1之外的所有处理器,您的程序将仅在该处理器上运行。不幸的是,每次启动程序时都必须这样做。