进程间通信--信号量

1.信号量
信号量是一个特殊变量,只允许对它进行等待和发送信号这两种操作。在linux编程中,“等待”和“发送信号”分别对应P和V操作。
那么如何理解P和V操作呢?做一个简单的例子,假设有两个进程proc1和proc2,这两个进程都需要在执行过程中的某个时刻对一个内存区进行独占式的访问。我们定义一个二进制信号量sv,该变量的初始值为1,两个进程都可以访问它。一旦其中一个进程执行了P操作,他将得到信号量,并且可以进入临界区域,此时sv值减1。而第二个进程将被阻止进入临界区。当它试图执行P操作时,它将被挂起直到第一个进程离开临界区域并执行V操作释放信号量,简要过程如下图:
在这里插入图片描述
用C语言实现这个过程,具体需要用到以下几个函数的配合。

(1)linux的信号机制
#include <sys/sem.h>

int semget(key_t key, int num_sems, int sem_flags);
作用是创建一个新信号量或取得一个已有信号量的键
第一个参数key是整数值,他是一个信号量键,通过这个键,此函数生成一个相应的信号量标识符。
第二个参数num_sems指定需要的信号量数目。它几乎总是取值为1。
第三个参数是sem_flag,开权限。
返回值:成功,返回其他信号量函数将用到的信号量标识符。

int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);
作用是用于改变信号量的值。
第一个参数是semget返回的信号量标识符
第二个参数指向一个结构数组的指针,包含信号量编号,信号量在一次操作中需要改变的值,SEM_UNDO。

int semctl(int sem_id, int sem_num, int command, …);
这个函数用来控制信号量信息。
第一个参数sem_id是由semget返回的信号量标识符。
第二个参数sem_num参数是信号量编号,当需要用到成组的信号量时,需要用到这个参数,它一般取值为0,表示这是第一个也是唯一一个信号量。
第三个参数command参数是将要采取的动作。
如果还有第四个参数,它将会是一个union semun结构。

下面用实例来理解一下,这些函数到底怎么用。用两个不同字符的输出来表示进入和离开临界区域。如果程序启动时带有一个参数,它将在进入和退出临界区域时打印字符X;而程序的其他运行实例将在进入和退出临界区域时打印字符O。因为在任一给定时刻,只有一个进程可以进入临界区域,所以字符X和O时成对出现的。

semun.h //有些Linux系统不包含这个头,会报错,手动添加即可。

#if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)
    /* union semun is defined by including <sys/sem.h> */
#else
    /* according to X/OPEN we have to define it ourselves */
    union semun {
        int val;                    /* value for SETVAL */
        struct semid_ds *buf;       /* buffer for IPC_STAT, IPC_SET */
        unsigned short int *array;  /* array for GETALL, SETALL */
        struct seminfo *__buf;      /* buffer for IPC_INFO */
    };
#endif

sem1.c

/* After the #includes, the function prototypes and the global variable, we come to the
 main function. There the semaphore is created with a call to semget, which returns the
 semaphore ID. If the program is the first to be called (i.e. it's called with a parameter
 and argc > 1), a call is made to set_semvalue to initialize the semaphore and op_char is
 set to X. */

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

#include <sys/sem.h>

#include "semun.h"

static int set_semvalue(void);
static void del_semvalue(void);
static int semaphore_p(void);
static int semaphore_v(void);

static int sem_id;


int main(int argc, char *argv[])
{
    int i;
    int pause_time;
    char op_char = 'O';

    srand((unsigned int)getpid());
   
    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);
        }
        op_char = 'X';
        sleep(2);
    }

/* Then we have a loop which enters and leaves the critical section ten times.
 There, we first make a call to semaphore_p which sets the semaphore to wait, as
 this program is about to enter the critical section. */

    for(i = 0; i < 10; i++) {        

        if (!semaphore_p()) exit(EXIT_FAILURE);
        printf("%c", op_char);fflush(stdout);
        pause_time = rand() % 3;
        sleep(pause_time);
        printf("%c", op_char);fflush(stdout);

/* After the critical section, we call semaphore_v, setting the semaphore available,
 before going through the for loop again after a random wait. After the loop, the call
 to del_semvalue is made to clean up the code. */

        if (!semaphore_v()) exit(EXIT_FAILURE);
       
        pause_time = rand() % 2;
        sleep(pause_time);
    }    

    printf("\n%d - finished\n", getpid());

    if (argc > 1) {    
        sleep(10);
        del_semvalue();
    }
       
    exit(EXIT_SUCCESS);
}

/* The function set_semvalue initializes the semaphore using the SETVAL command in a
 semctl call. We need to do this before we can use the semaphore. */

static int set_semvalue(void)
{
    union semun sem_union;

    sem_union.val = 1;
    if (semctl(sem_id, 0, SETVAL, sem_union) == -1) return(0);
    return(1);
}

/* The del_semvalue function has almost the same form, except the call to semctl uses
 the command IPC_RMID to remove the semaphore's ID. */

static void del_semvalue(void)
{
    union semun sem_union;
   
    if (semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
        fprintf(stderr, "Failed to delete semaphore\n");
}

/* semaphore_p changes the semaphore by -1 (waiting). */

static int semaphore_p(void)
{
    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);
}

/* semaphore_v is similar except for setting the sem_op part of the sembuf structure to 1,
 so that the semaphore becomes available. */

static int semaphore_v(void)
{
    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);
}

当然信号量也分为其他很多种,这里只讲到了简单的一种实现,二进制信号量和计数信号量在公司级的代码中有着广泛的使用,用于线程和进程之间的互斥访问临界资源,作为一种"锁"机制,避免由于多方操作引起的异常。后面可以对SemB,SemC,Mutex等相关知识做进一步的了解。
进一步学习可以参考:http://blog.chinaunix.net/uid-20641464-id-1595741.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值