semctl semget semop 函数系列构成的 信号量

semctl semget semop 函数系列构成的 信号量

信号量原语

简介

信号量是由 Dijkstra 提出的解决多进程沟通和并发编程问题的特殊变量,这种变量只能取自然数,而且只支持两种操作: wait & signal 。它主要处理多个进程访问资源的问题,通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域,而临界区域是指执行数据更新的代码需要独占式地执行。

实现方式

首先创建一个信号量 SV,并且使用一个整数唯一的标识信号量(这个标识量是跨越进程存在的,即在不同的进程中同一个信号量也能唯一的标识该事件)。只有两种 一种是 wait 操作,一种是 signal 操作,不过为了避免和linux的概念重复,又被称为 P 操作 和 V 操作

  • P(SV) :如果 SV 大于 0,那么 SV 的值 减一,这里的值指的是信号量所带的值,而不是标识信号量的整数,就和结构体名和结构体的内的一个变量一样,减的是变量。如果是SV==0,那么就挂起该进程的执行

  • V(SV):如果有其他进程由 因等待SV 挂起,那么 激活给他,如果没有 那么就 SV 加 一

上面很难理解对吧,举个例子吧。
我有两个进程 A & B,他们分想访问资源 C (访问过程的代码为关键代码),利用 信号量 来保证同时只有唯一的一个。比如

  1. 开始时首先设置 SV 的值 为 1

  2. 假设 A 优先 走到 关键代码区域,它会执行 P(SV) ,根据上面的操作,他会减 1 ,同时执行关键的代码。

  3. 在 A 执行关键代码的时候,B运行到了关键代码出,执行 P(SV
    ),它会则会挂起该进程,直到 A 执行完关键代码,并且 执行了 V(SV)

但必须强调的是 P(SV),V(SV ),都必须是原子操作。是由
sys/sem.h 的定义的函数实现的。

有些分类方式业在这提一下啊
信号量按其用途可分为两种:
􀁺 公用信号量:联系一组并发进程,相关的进程均可在此信号量上执行P 和V操作。初值常常为1,用于实现进程互斥。
􀁺 私有信号量:联系一组并发进程,仅允许此信号量拥有的进程执行P 操作,而其他相关进程可在其上施行V 操作。初值常常为0 或正整数,多用于并发进程同步。
信号量按其取值可分为两种:
􀁺 二元信号量:仅允许取值为0 和1,主要用于解决进程互斥问题。
􀁺 一般信号量:允许取值为非负整数,主要用于解决进程同步问题。

函数集

创建or 获得

int semget(key_t key, int num_sems, int sem_flags);  

第一个参数key是整数值(唯一非零),不相关的进程可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget函数并提供一个键,再由系统生成一个相应的信号标识符(semget函数的返回值),只有semget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。如果多个程序使用相同的key值,key将负责协调工作。

第二个参数num_sems指定信号集中信号的个数
具体的值是根据你使用信号量的目的,如果你想要实现进程之间的互斥操作,设置为1 ,而如果想要实现多进程的同步问题,那么至少得大于一。在为零的情况下,是获取该信号量。

第三个参数sem_flags是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。

semget函数成功返回一个相应信号标识符(非零),失败返回-1.

信号量操作

int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);  

它用来实现信号量的 P,V 操作,利用是的结stuct sembuf来指示操作内容

struct sembuf{  
    short sem_num;//除非使用一组信号量,否则它为0  
    short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,  
                    //一个是+1,即V(发送信号)操作。  
    short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,  
                    //并在进程没有释放该信号量而终止时,操作系统释放信号量  
};  

结构体中的变量含义为
sem_num : 指定操作信号量集中的第 sem_num+1 个信号量,一般设置为 0,表示操作第一个
sem_flag:
IPC_NOWAIT :设置信号量操作不等待
SEM_UNDO : 选项会让内核记录一个与调用进程相关的UNDO记录,如果该进程崩溃,则根据这个进程的UNDO记录自动恢复相应信号量的计数值
sem_op : 表示 操作信号量的方式,实现 P | V 的操作

sem_op 意义
大于0 将 sem_val + opt ,但要求有写权限
等于 0 在没有设置 IPC_NOWAIT 的情况下,会挂起 等待 sem_val==0的情况发生,发生则立即返。但设置的话,就会立即返回错误信息EAGAIN
小于 0 在没有设置 IPC_NOWAIT的情况下,会等待 sem_op的绝对值小于 等于sem_val的值,小于的话,就减去它,并且放回,设置的话,与 等于0 的情况基本一致

等于 0 一般被称为 等待 0 ,而小于 0 则可以称为 等待可减

信号量设置

int semctl(int sem_id, int sem_num, int command, ...);  

该函数用于对信号量直接设置
sem_id 为信号量级标识
sem_um 指定被操作信号量在信号量集中的编号
command 为操作命令
enter description here
而... 代表可扩展参数,推荐使用一下啊的结构体填充

union semun {
    int              val;    /* Value for SETVAL */
    struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
    unsigned short  *array;  /* Array for GETALL, SETALL */
    struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                (Linux-specific) */
};

而该结构体中的 semid_ds 和 seminfo 的结构体,seminfo 表示信号量系统资源配置信息,semid_ds则是一个内核结构体,在 semget 函数创建信号量时,被初始化和关联
的具体信息可以去man 一下 该函数,

针对 特殊键值的 IPC_PRIVATE

IPC_PRIVATE 是一个可以放在 semget 函数 中 key 值参数 的特殊键值,其值为0,它会忽略 key 参数的本身作用(标识一个信号量集),创建一个新的信号量,标识由返回值指示。但代表与该进程并无关系的进程无法访问该信号量,必须是有血缘关系的进程才能访问该信号量,
实例:


#include<sys/sem.h>
#include<stdio.h>
#include<iostream>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
union semun{
        int val;
        struct semid_ds * buf;
        unsigned short int * array;
        struct seminfo *__buf;
};
void pv(int semd_id,int op )
{
        struct sembuf sem_b;
        sem_b.sem_num = 0;
        sem_b.sem_op = op;
        sem_b.sem_flg = SEM_UNDO;
        semop(semd_id,&sem_b,1);
}

int main()
{
        int sem_id = semget(IPC_PRIVATE,1,0666);
        std::cout<<"sem_id="<<sem_id<<std::endl;
        union semun  sem_um;
        sem_um.val =1;
        semctl(sem_id,0,SETVAL,sem_um );

        pid_t id = fork();

        if(id<0)
        {
                return 1;
        }
        else if(id==0)
        {
                printf("child try to get binary sem\n");
                pv (sem_id,-1);
                printf("child get the sem de would release it after 5 second\n");
                sleep(10);
                pv(sem_id,1);
                exit(0);
        }
        else {
                printf("parent try to get binary sem \n");
                pv(sem_id,-1);
                printf("parent get the sem and would release it after 5 second\n");
                sleep(5);
                pv(sem_id,1);
        }
        waitpid(id,NULL,0);
        semctl(sem_id,0,IPC_RMID,sem_um);
        return 0;
}

实例二:
无血缘之间进程的信号量通信的操作
转自:这里

#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;  
}  

运行结果:
enter description here
分析:

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

posted on 2015-06-10 01:43  独独  阅读(1074)  评论(0编辑  收藏  举报

导航