Semaphore for inter-process communication in Linux

Reprinted from: http://blog.csdn.net/ljianhui/article/details/10243617
1. What is a semaphore
In order to prevent a series of problems caused by multiple programs accessing a shared resource at the same time, we need a way to authorize by generating and using a token, only one thread of execution can access a critical section of code at any one time . A critical section is when the code that performs data updates needs to execute exclusively. The semaphore can provide such an access mechanism, so that only one thread in a critical section is accessing it at the same time, that is to say, the semaphore is used to coordinate the access of the process to the shared resource.

A semaphore is a special variable whose access by the program is an atomic operation, and only waiting (ie, P (signal variable)) and sending (ie, V (signal variable)) information operations are allowed on it. The simplest semaphore is a variable that can only take 0 and 1, which is also the most common form of semaphore, called a binary semaphore. A semaphore that can take multiple positive integers is called a general semaphore. We mainly discuss binary semaphores here.

Second, the working principle of semaphore
Since semaphores can only perform two operations to wait and send signals, namely P(sv) and V(sv), their behavior is as follows:
P(sv): If the value of sv is greater than zero, decrement it by 1; if its value is zero, suspend the execution of the process
V(sv): If another process is suspended waiting for sv, let it resume running, if no process is suspended waiting for sv, add 1 to it.

For example, two processes share the semaphore sv, once one of the processes performs the P(sv) operation, it will get the semaphore and can enter the critical section, decrementing sv by 1. And the second process will be blocked from entering the critical section because when it tries to do P(sv), sv is 0, it will be suspended waiting for the first process to leave the critical section and do V(sv) to release the semaphore , then the second process can resume execution.

Third, the semaphore mechanism of Linux
Linux provides a set of well-designed semaphore interfaces to operate on signals. They are not just for binary semaphores. These functions will be introduced below, but please note that these functions are used to group semaphores. value to operate on. They are declared in the header file sys/sem.h.

1. semget function
Its role is to create a new semaphore or obtain an existing semaphore, the prototype is:
int semget(key_t key, int num_sems, int sem_flags); <span style="font-family: 宋体; background-color: rgb(255, 255, 255);"> </span>
The first parameter key is an integer value (the only non-zero value) through which unrelated processes can access a semaphore, which represents a resource that the program may want to use. The program accesses all semaphores indirectly. First, by calling the semget function and providing a key, the system generates a corresponding signal identifier (the return value of the semget function). Semaphore identifier. If multiple programs use the same key value, the key will coordinate the work.

The second parameter, num_sems, specifies the number of semaphores required, and its value is almost always 1.

The third parameter sem_flags is a set of flags, when you want to create a new semaphore when the semaphore does not exist, you can do a bitwise OR operation with the value IPC_CREAT. With the IPC_CREAT flag set, no error will be generated even if the given key is a key with an existing semaphore. And IPC_CREAT | IPC_EXCL can create a new, unique semaphore and return an error if the semaphore already exists.

The semget function returns a corresponding signal identifier (non-zero) on success and -1 on failure.

2. semop function
Its role is to change the value of the semaphore, the prototype is:
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);  
sem_id is the semaphore identifier returned by semget, and the definition of the sembuf structure is as follows:
struct sembuf{  
    short sem_num;//除非使用一组信号量,否则它为0  
    short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,  
                    //一个是+1,即V(发送信号)操作。  
    short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,  
                    //并在进程没有释放该信号量而终止时,操作系统释放信号量  
};  
struct sembuf{
    short sem_num;//除非使用一组信号量,否则它为0
    short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,
                    //一个是+1,即V(发送信号)操作。
    short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,
                    //并在进程没有释放该信号量而终止时,操作系统释放信号量
};
3. semctl function
This function is used to directly control the semaphore information, its prototype is:
int semctl(int sem_id, int sem_num, int command, ...); <span style="font-family: 宋体; background-color: rgb(255, 255, 255);"> </span>
If there is a fourth parameter, it is usually a union semum structure, defined as follows:
union semun{  
    int val;  
    struct semid_ds *buf;  
    unsigned short *arry;  
};  
union semun{
    int val;
    struct semid_ds *buf;
    unsigned short *arry;
};
The first two parameters are the same as in the previous function, and command is usually one of the following two values
SETVAL: Used to initialize the semaphore to a known value. The value of p is set by the val member in union semun, which is used to set the semaphore before it is used for the first time.
IPC_RMID: Used to delete a semaphore identifier that no longer needs to be used.

Fourth, the process uses semaphore communication
下面使用一个例子来说明进程间如何使用信号量来进行通信,这个例子是两个相同的程序同时向屏幕输出数据,我们可以看到如何使用信号量来使两个进程协调工作,使同一时间只有一个进程可以向屏幕输出数据。注意,如果程序是第一次被调用(为了区分,第一次调用程序时带一个要输出到屏幕中的字符作为一个参数),则需要调用set_semvalue函数初始化信号并将message字符设置为传递给程序的参数的第一个字符,同时第一个启动的进程还负责信号量的删除工作。如果不删除信号量,它将继续在系统中存在,即使程序已经退出,它可能在你下次运行此程序时引发问题,而且信号量是一种有限的资源。

在main函数中调用semget来创建一个信号量,该函数将返回一个信号量标识符,保存于全局变量sem_id中,然后以后的函数就使用这个标识符来访问信号量。

源文件为seml.c,代码如下:
#include <unistd.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#include <stdlib.h>  
#include <stdio.h>  
#include <string.h>  
#include <sys/sem.h>  
  
union semun  
{  
    int val;  
    struct semid_ds *buf;  
    unsigned short *arry;  
};  
  
static int sem_id = 0;  
  
static int set_semvalue();  
static void del_semvalue();  
static int semaphore_p();  
static int semaphore_v();  
  
int main(int argc, char *argv[])  
{  
    char message = 'X';  
    int i = 0;  
  
    //创建信号量  
    sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);  
  
    if(argc > 1)  
    {  
        //程序第一次被调用,初始化信号量  
        if(!set_semvalue())  
        {  
            fprintf(stderr, "Failed to initialize semaphore\n");  
            exit(EXIT_FAILURE);  
        }  
        //设置要输出到屏幕中的信息,即其参数的第一个字符  
        message = argv[1][0];  
        sleep(2);  
    }  
    for(i = 0; i < 10; ++i)  
    {  
        //进入临界区  
        if(!semaphore_p())  
            exit(EXIT_FAILURE);  
        //向屏幕中输出数据  
        printf("%c", message);  
        //清理缓冲区,然后休眠随机时间  
        fflush(stdout);  
        sleep(rand() % 3);  
        //离开临界区前再一次向屏幕输出数据  
        printf("%c", message);  
        fflush(stdout);  
        //离开临界区,休眠随机时间后继续循环  
        if(!semaphore_v())  
            exit(EXIT_FAILURE);  
        sleep(rand() % 2);  
    }  
  
    sleep(10);  
    printf("\n%d - finished\n", getpid());  
  
    if(argc > 1)  
    {  
        //如果程序是第一次被调用,则在退出前删除信号量  
        sleep(3);  
        del_semvalue();  
    }  
    exit(EXIT_SUCCESS);  
}  
  
static int set_semvalue()  
{  
    //用于初始化信号量,在使用信号量前必须这样做  
    union semun sem_union;  
  
    sem_union.val = 1;  
    if(semctl(sem_id, 0, SETVAL, sem_union) == -1)  
        return 0;  
    return 1;  
}  
  
static void del_semvalue()  
{  
    //删除信号量  
    union semun sem_union;  
  
    if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)  
        fprintf(stderr, "Failed to delete semaphore\n");  
}  
  
static int semaphore_p()  
{  
    //对信号量做减1操作,即等待P(sv)  
    struct sembuf sem_b;  
    sem_b.sem_num = 0;  
    sem_b.sem_op = -1;//P()  
    sem_b.sem_flg = SEM_UNDO;  
    if(semop(sem_id, &sem_b, 1) == -1)  
    {  
        fprintf(stderr, "semaphore_p failed\n");  
        return 0;  
    }  
    return 1;  
}  
  
static int semaphore_v()  
{  
    //这是一个释放操作,它使信号量变为可用,即发送信号V(sv)  
    struct sembuf sem_b;  
    sem_b.sem_num = 0;  
    sem_b.sem_op = 1;//V()  
    sem_b.sem_flg = SEM_UNDO;  
    if(semop(sem_id, &sem_b, 1) == -1)  
    {  
        fprintf(stderr, "semaphore_v failed\n");  
        return 0;  
    }  
    return 1;  
}  
运行结果如下:


注:这个程序的临界区为main函数for循环不的semaphore_p和semaphore_v函数中间的代码。

例子分析 :同时运行一个程序的两个实例,注意第一次运行时,要加上一个字符作为参数,例如本例中的字符‘O’,它用于区分是否为第一次调用,同时这个字符输出到屏幕中。因为每个程序都在其进入临界区后和离开临界区前打印一个字符,所以每个字符都应该成对出现,正如你看到的上图的输出那样。在main函数中循环中我们可以看到,每次进程要访问stdout(标准输出),即要输出字符时,每次都要检查信号量是否可用(即stdout有没有正在被其他进程使用)。所以,当一个进程A在调用函数semaphore_p进入了临界区,输出字符后,调用sleep时,另一个进程B可能想访问stdout,但是信号量的P请求操作失败,只能挂起自己的执行,当进程A调用函数semaphore_v离开了临界区,进程B马上被恢复执行。然后进程A和进程B就这样一直循环了10次。

五、对比例子——进程间的资源竞争
看了上面的例子,你可能还不是很明白,不过没关系,下面我就以另一个例子来说明一下,它实现的功能与前面的例子一样,运行方式也一样,都是两个相同的进程,同时向stdout中输出字符,只是没有使用信号量,两个进程在互相竞争stdout。它的代码非常简单,文件名为normalprint.c,代码如下:
#include <stdio.h>  
#include <stdlib.h>  
  
int main(int argc, char *argv[])  
{  
    char message = 'X';  
    int i = 0;    
    if(argc > 1)  
        message = argv[1][0];  
    for(i = 0; i < 10; ++i)  
    {  
        printf("%c", message);  
        fflush(stdout);  
        sleep(rand() % 3);  
        printf("%c", message);  
        fflush(stdout);  
        sleep(rand() % 2);  
    }  
    sleep(10);  
    printf("\n%d - finished\n", getpid());  
    exit(EXIT_SUCCESS);  
}  
运行结果如下:


例子分析
从上面的输出结果,我们可以看到字符‘X’和‘O’并不像前面的例子那样,总是成对出现,因为当第一个进程A输出了字符后,调用sleep休眠时,另一个进程B立即输出并休眠,而进程A醒来时,再继续执行输出,同样的进程B也是如此。所以输出的字符就是不成对的出现。这两个进程在竞争stdout这一共同的资源。通过两个例子的对比,我想信号量的意义和使用应该比较清楚了。

六、信号量的总结
信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。我们通常通过信号来解决多个进程对同一资源的访问竞争的问题,使在任一时刻只能有一个执行线程访问代码的临界区域,也可以说它是协调进程间的对同一资源的访问权,也就是用于同步进程的。

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324452264&siteId=291194637