c-linux-IPC-信号量semaphore-学习

###概念###


场景:某个线程(进程)能从信号拿到锁,则运行,否则阻塞等待。

信号量:可以理解为信号集中某个信号当前锁的数值    

             正值:尚可接受的进程数        0:无可用,无等待        负值:阻塞等待在该信号上的进程数

###PV操作###

p:信号量-1   v:信号量+1

P: 进程从信号锁池拿锁,能拿到,则运行;否则,若锁池已空,则阻塞等待。

V: 进程释放锁到信号锁池,若锁池已满(一般不会),则阻塞等待;

    否则,释放锁之后,若有等待的进程,系统会唤醒一个等待在该信号上的进程继续执行。

###信号量实现线程互斥###

     可以认为信号量关联一组线程,保存一个指针,指向线程数组的首地址;

     比如当前信号量为-1,一个线程对其进行P操作,信号量变为-2,说明没有拿到锁,线程等待;

     此时,取值为-2,说明有两个线程等待在该信号上;

     这个时候,其他线程进行V操作,信号量加1,为-1,信号量通知等待的线程中,第一个线程继续执行,第二个线程继续等待。

     也就是说,P操作等待的情况是减1后,信号量小于0;

                   P操作继续执行的情况有两种:a、减1后,信号量大于等于0,不需等待,直接执行;

                                                         b、减1后,信号量小于0,等待中,其他人进行了V操作,通知这个线程,继续执行。

###函数接口###

1.int semget(key_t key, int num_sems, int sem_flags);  //创建一个新信号量(集)或取得一个已有信号量(集)

//key: 关联信号量semid的键值  e.g. (key_t)201808

//num_sems:   信号集中信号的数量 一般为1

//sem_flgs:   IPC_EXCL - 检查是否存在   IPC_CREAT - 创建

//       位或组合             IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一 的信号量,如果信号量已存在,返回一个错误

//返回值:成功返回一个相应信号标识符(非零),失败返回-1


2.int semop(int sem_id, struct sembuf *sops, size_t nsops);  //操作信号量

//sem_id: 信号量标识符

//struct sembuf *sops:  对应一个特定信号的操作

//nsops:  要进行操作的信号的个数  一般为1

  1. struct sembuf{  
  2. unsigned short sem_num; //信号在信号集中的索引,0代表第一个信号,1代表第二个信号  
  3. short sem_op;     //操作类型  
  4. short sem_flg;    //操作标志  
  5. };  

   sem_flg:   IPC_NOWAIT  无阻塞等待,特殊临界情况直接返回EAGAIN

                     SEM_UNDO  

      该参数可设置为 IPC_NOWAIT 或 SEM_UNDO 两种状态。只有将 sem_flg 指定为 SEM_UNDO 标志后,semadj (所指定信号量针对调用进程的调整  值)才会更新。 此外,如果此操作指定SEM_UNDO,系统更新过程中会撤消此信号灯的计数(semadj)。此操作可以随时进行---它永远不会强制等待的过      程。调用进程必须有改变信号量集的权限。
      sem_flg公认的标志是 IPC_NOWAIT 和 SEM_UNDO。如果操作指定SEM_UNDO,当该进程终止时它将会自动撤消

   sem_op > 0 : V操作,信号量加上对应的值,说明进程在释放锁

   sem_op < 0:  P操作,信号量减去对应的绝对值,说明进程在请求锁

   sem_op = 0:  

//返回值:成功返回 0,失败返回 -1

//该函数所做的对于信号量的操作都是原子操作,即整个行为是一个整体,是不可打断的;

//所有操作是否可以立即执行,取决于sem_flg的IPC_NOWAIT标志是否存在。


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

//sem_id: 信号量标识符

//sem_num: 信号集中信号的索引值 (第一个0,第二个1)

//command:命令类型

    IPC_STAT:获取某个信号量集合的semid_ds结构,并将其储存在semun联合体的buf参数所指的地址之中

    IPC_SET:设置某个集合的semid_ds结构的ipc_perm成员的值,该命令所取的值是从semun联合体的buf参数中取到的

    IPC_RMID:从内核删除该信号量集合

    GETALL:用于获取集合中所有信号量的值,整数值存放在无符号短整数的一个数组中,该数组有联合体的array成员所指定

    GETNCNT:返回当前正在等待资源的进程的数目

    GETPID:返回最后一次执行PV操作(semop函数调用)的进程的PID

    GETVAL:返回集合中某个信号量的值

    GETZCNT:返回正在等待资源利用率达到百分之百的进程的数目

    SETALL:把集合中所有信号量的值,设置为联合体的array成员所包含的对应值

    SETVAL:将集合中单个信号量的值设置为联合体的val成员的值

//第四个参数:某些特定操作用到

其中semun联合体的结构如下:

[cpp]  view plain  copy
  1. union semun{    
  2.     int val;    
  3.     struct semid_ds *buf;    
  4.     unsigned short *array;    
  5.     struct seminfo *__buf;  
  6. };   

对于该函数,只有当command取某些特定的值的时候,才会使用到第4个参数,第4个参数它通常是一个union semun结构,定义如下:

[cpp]  view plain  copy
  1. union semun{    
  2.     int val;    
  3.     struct semid_ds *buf;    
  4.     unsigned short *arry;    
  5. };    

当执行SETVAL命令时用到这个成员,他用于指定要把信号量设置成什么值,涉及成员:val

在命令IPC_STAT/IPC_SET中使用,它代表内核中所使用内部信号量数据结构的一个复制 ,涉及成员:buf

在命令GETALL/SETALL命令中使用时,他代表指向整数值一个数组的指针,在设置或获取集合中所有信号量的值的过程中,将会用到该数组,涉及成员:array


###测试例程###

/*ĐĹşĹÁż sem.c*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/sem.h>
#include <iostream>
using namespace std;

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();
static int getsemval();

static void childforkProc();
static void childforkProc(){
    int sn=getsemval();
    cout << "[" << getpid() << ":] " << "semNum:" << sn << " " << endl;
    semaphore_p();
    std::cout << "[" << getpid() << ":] " << "critical region opperator..." << std::endl;     //临界共享区操作  
    sleep(5);
    semaphore_v();
    exit(-1);
}

//带参数第一次运行  不带参数第二次运行
// ./sem 1 & ./sem
int main(int argc,char *argv[]){
    char message='F';
    int i=0;
    int flg=0;

    //创建信号量
    sem_id = semget((key_t)1234,1,0666|IPC_CREAT);
    if(!set_semvalue()){          //初始化信号量
            fprintf(stderr, "Failed to initialize semaphore\n");    
            exit(EXIT_FAILURE);
    }
    
    for(int i=0;i<5;i++){
        flg=fork();
        if(flg > 0){
            //no do
        }else if(flg ==0){  //子进程
            childforkProc();
        }
    }
    
    if(flg > 0){
        sleep(100);
        del_semvalue();
    }

    return 0;  //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");
    }else{
        fprintf(stdout,"has deled semaphore \n");
    }
}

static int semaphore_p(){
    //信号量减1操作   即等待P(s)
    struct sembuf sem_b;
    sem_b.sem_num=0;       //第一个信号
    sem_b.sem_op=-1;       //P()          结果 >= 0 ,执行
    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(s)
    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;
}

//获取当前信号量的的值
static int getsemval(){
    int num=0;              //无可用 无等待
    num=semctl(sem_id,0,GETVAL);
    return num;
}

###参考###

https://blog.csdn.net/qq_30168505/article/details/53041825

https://www.cnblogs.com/nzbbody/p/4219957.html


猜你喜欢

转载自blog.csdn.net/qq_24243483/article/details/79820394