信号量在C语言中的实现

信号量在C语言中的实现,c,ipc,semaphore,C,Ipc,Semaphore,我正在用C编写一个信号量的简单实现,当我的实现(作为二进制信号量)工作时,我对它的有效性有一个疑问 我的担忧源于我对等待函数的定义: void Wait(int semid) { char *shmPtr; shmPtr = shmat(semid, NULL, 0); if(shmPtr == (void *) -1){ printf("Could not attach to semaphore...\n"); exit(1);

我正在用C编写一个信号量的简单实现,当我的实现(作为二进制信号量)工作时,我对它的有效性有一个疑问

我的担忧源于我对等待函数的定义:

void Wait(int semid) {
    char *shmPtr;

    shmPtr = shmat(semid, NULL, 0);

    if(shmPtr == (void *) -1){
        printf("Could not attach to semaphore...\n");
        exit(1);
    } 

    //Wait for the value in shared memory to 
    //equal 0, then set it equal to 1,
    //detach and return
    while( (*shmPtr) != 0);

    (*shmPtr) = 1;

    if(shmdt(shmPtr) < 0) {
        printf("Cannot detach from semaphore...");
    }

    return;

}

正如Tom所评论的,为了使信号量正确,您需要一个原子测试和设置,或者一个比较和交换(比较交换)

但这还不是全部。因为您使用的是共享内存(即通过多个进程),所以C11()提供的原子操作是不够的

因为您无论如何都在调用Posix函数,所以我认为您可以访问Posix信号量


“POSIX信号量允许进程和线程同步它们的操作。”()

我不知道如何在PC上实现这一点(如果您发现了,请回来发布您自己的答案),但您需要的是我所说的“原子访问保护”,您需要一种机制来强制在设定的时间内对给定变量进行原子访问。这意味着您实际上强制所有线程/进程暂停片刻,而只有一个线程可以访问变量。然后它对变量进行处理(例如:读取、修改、写入),然后在完成后重新启用其他线程。通过这种方式,您可以保证在这些操作期间该线程对该变量的原子访问。现在,所有的比赛条件都解决了

在C语言中,我认为这是高度依赖于体系结构的,并且依赖于通过
\uu asm
关键字等内联汇编代码编写的C函数,和/或依赖于将特定硬件寄存器中的位设置为特定值以强制执行特定行为。使用
\u asm
关键字的示例:

封装在C函数中的内联汇编代码示例:

int add(int i, int j)
{
  int res = 0;
  __asm ("ADD %[result], %[input_i], %[input_j]"
    : [result] "=r" (res)
    : [input_i] "r" (i), [input_j] "r" (j)
  );
  return res;
}
一旦您有了“原子访问保护”“功能来提供原子访问,您可以执行以下操作:

// atomic access guard ON

// Do whatever you want here: it's all atomic now!
// Read, modify, write, etc.
// - CAUTION: NO OTHER THREADS CAN RUN DURING THIS TIME, SO GET OUT OF THIS QUICKLY

// atomic access guard OFF
taskENTER_CRITICAL() // This supports nested calls, and ends up calling `portDISABLE_INTERRUPTS()` anyway.

// do your atomic access here

taskEXIT_CRITICAL() 
uint8_t SREG_bak = SREG; //save global interrupt state
cli(); //clear (disable) interrupts

//atomic variable access guaranteed here

SREG = SREG_bak; //restore interrupt state
在单核系统上,如我熟悉的微控制器(STM32和AVR/Arduino),只需关闭所有中断即可确保原子访问。Ex:在ARM core STM32微控制器上,通过使用必要的CMSIS(ARM提供的)功能按如下方式执行:

// Read PRIMASK register, check interrupt status before you disable them 
// Returns 0 if they are enabled, or non-zero if disabled 
uint32_t prim = __get_PRIMASK();

// Disable interrupts 
__disable_irq();

// Do some stuff here which can not be interrupted 

// Enable interrupts back, but only if they were previously enabled (prevents nesting problems)
if (!prim) 
{
    __enable_irq();
}
资料来源:

如果使用,请执行以下操作:

// atomic access guard ON

// Do whatever you want here: it's all atomic now!
// Read, modify, write, etc.
// - CAUTION: NO OTHER THREADS CAN RUN DURING THIS TIME, SO GET OUT OF THIS QUICKLY

// atomic access guard OFF
taskENTER_CRITICAL() // This supports nested calls, and ends up calling `portDISABLE_INTERRUPTS()` anyway.

// do your atomic access here

taskEXIT_CRITICAL() 
uint8_t SREG_bak = SREG; //save global interrupt state
cli(); //clear (disable) interrupts

//atomic variable access guaranteed here

SREG = SREG_bak; //restore interrupt state
见:

如果使用AVR核心微控制器,如ATmega328(基本Arduino Uno处理器),请执行以下操作:

// atomic access guard ON

// Do whatever you want here: it's all atomic now!
// Read, modify, write, etc.
// - CAUTION: NO OTHER THREADS CAN RUN DURING THIS TIME, SO GET OUT OF THIS QUICKLY

// atomic access guard OFF
taskENTER_CRITICAL() // This supports nested calls, and ends up calling `portDISABLE_INTERRUPTS()` anyway.

// do your atomic access here

taskEXIT_CRITICAL() 
uint8_t SREG_bak = SREG; //save global interrupt state
cli(); //clear (disable) interrupts

//atomic variable access guaranteed here

SREG = SREG_bak; //restore interrupt state
请看我的回答:

因此,现在您需要做一些研究(请回帖),研究如何在给定的操作系统和/或体系结构上用C实现这一原则,和/或通过一些对内核的特殊调用或其他方式。这甚至可能需要您编写自己的内联程序集来完成这项工作,然后将其封装在C函数中进行调用

我期待着看到你如何完成它

更新:我只是在FreeRTOS源代码中做了一些挖掘,看看它们是如何禁用中断的,下面是我在使用GCC编译器时为ARM Cortex M3处理器(如STM32微控制器)找到的:

来自“FreeRTOSv9.0.0/FreeRTOS/Source/portable/GCC/ARM_CM3/portmacro.h”:


这叫做比赛条件。你需要一些你没有的原子测试和设置操作。否则两个进程都会将其视为零,然后它们都会尝试将其更改为1,每个进程都认为自己是第一个这样做的进程。基本上,你无法知道它在将其视为零和将其设置为一之间没有变化。这正是我所担心的。这个实现作为一个二进制信号量似乎工作得很好,因为两个进程不会同时等待。我不知道在同一个语句中检查值和增加值的方法,你有什么想法吗?你没有实现信号量。您在某个时候使用了一个(在某些外部库的帮助下)有人实现了它。了解你的代码在最低层次上的作用,使你成为一名更好的程序员,而不仅仅是接受某些东西可以工作并继续前进。
while((*shmPtr)!=0)编译器将在其权限范围内完全优化此循环。这只是一个练习,我通常在需要同步时使用sys/sem.h。然而,我在我的帖子中添加了wikipedia的testAndSet实现,对此我有一个问题。如果我将while循环中的条件替换为对testAndSet的调用,是否会出现相同的问题?如果两个线程正在等待,且信号量更改为0,则假设读取oldValue后调用testAndSet switches上下文的第一个进程。这难道不会引起与我开始时完全相同的问题吗?是的,同样的问题也会发生。维基百科上的“实现”仅用于说明,并且具有相同的竞争条件。