linux 进程学习笔记-信号semaphore

信号灯(信号量)不是进程通信手段,其是用于控制和协调在进程间通信过程中的共享资源访问,就如同互斥锁(两者的区别可以参考这里) 

可以将简单地将信号灯想象成一个计数器,初始时计数器值为n(有n个资源可供使用),当进程占用资源时计数器减1,资源被释放时计数器加1,没有资源可用时计数器为0。 

如果资源只用“可用/不可用”这样的非是即否的状态的话,信号灯称为二值信号灯(binary semaphre),相反地,称为计数信号灯(counting semaphores) 

将信号灯的原理表示成代码的话,则非常简单: 


//假设s为信号灯
//等待信号灯
wait(s) 
while (s<=0)    
{
   //do nothing 
}
//等到了,那么我要使用了哦
s=s-1;
//使用中
P(s)
//我使用完毕了
s=s+1;

  

与共享内存一样,信号量也分为System V和POSIX两种,这里看看System V的。 

  

新建信号量集: 


int semget(key_t key, int nsems, int semflg);
key参数和smflg参数与shmget一样
nsems参数:表示该信号量集中的信号量个数。一个信号量集中可以包含多个信号量,所能包含的最大数量由SEMMSL宏决定。
与之相关联的数据结构的是:
struct semid_ds {
          struct ipc_perm sem_perm;               /* permissions .. see ipc.h */
          __kernel_time_t sem_otime;              /* last semop time */
          __kernel_time_t sem_ctime;              /* last change time */
          struct sem      *sem_base;              /* ptr to first semaphore in array */
          struct sem_queue *sem_pending;        /* pending operations to be processed */
          struct sem_queue **sem_pending_last;    /* last pending operation */
          struct sem_undo *undo;                  /* undo requests on this array */
          unsigned short  sem_nsems;              /* no. of semaphores in array */
  };

其中的sem_nsems字段便是信号集中的信号数量。 

  

用semctl进行一些和设置以及取得信号量的一些属性信息 

int semctl(int semid, int semnum, int cmd, ...); 

semid参数: 

信号量集对应的id 

semnum参数: 

本次操作所针对的信号在信号集中的序号 

cmd参数: 

所进行的操作,这里的操作有很多种,比如IPC_STAT是用于获取信号的属性信息,SETVAL用于设置信号量的值,具体的参考这里 

可变参数(…): 

可变参数取决去前面的cmd参数,但一般是由如下联合构成的: 

union semun 

{ 

 int  val; 

 struct  semid_ds *buf; 

 ushort  *array; 

} arg; 

比如cmd为SETVAL时,semun则被解释为int val,用于设置信号量的值: 

arg.val = 1; 

semctl(semid,0,SETVAL,arg) //将semid对应的信号集中的索引为0的信号的值设置为1 

当cmd为IPC_STAT时,则semun被解释为struct semid_ds* buf,相关信息被复制到buf中 

  

我们用semop函数来操作一个或一组信号量,并保证操作的原子性(当操作几个信号时,要么全部成功,要么全部失败) 

int semop(int semid, struct sembuf *sops, unsigned nsops); 

sops参数: 

为了便于理解,你可以将struct sembuf *sops 参数想象成 struct sembuf sops_array[],其是一个数组,数组中的每一个元素将针对信号量集中的一个信号量进行操作,所以这个操作数组便可以用于表示对多个信号量进行操作。 

struct sembuf结构中包含了要进行的操作的相关信息: 

struct  sembuf { 

   ushort sem_num; /*操作针对信号集中的哪个信号 */ 

   short sem_op; /*操作 */ 

   short sem_flg; /*一些flag: IPC_NOWAIT ,SEM_UNDO*/ 

}; 

关于sem_op操作: 

1)如果其值为正数,该值会加到现有的信号内含值中。通常用于释放所控资源的使用权; 

2)如果sem_op的值为负数,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值。通常用于获取资源的使用权; 

3)如果sem_op的值为0,则操作将暂时阻塞,直到信号的值变为0。 

关于sem_flg标记: 

1)IPC_NOWAIT 对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。 

2)SEM_UNDO 程序结束时(不论正常或不正常),保证信号值会被重设为semop()调用前的值。这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。 

nsops参数: 

sops数组的元素个数。 

  

下面有一个小DEMO: 


#include <stdio.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>
 
void main()
{
key_t unique_key; /* 定义一个IPC 关键字*/
int id;
struct sembuf lock_it;
union semun options;
int i;
 
unique_key = ftok(".", 'a'); /* 生成关键字,字符'a'是一个随机种子*/
 
/* 创建一个新的信号量集合*/
id = semget(unique_key, 1, IPC_CREAT | IPC_EXCL | 0666);
printf("semaphore id=%d
", id);
options.val = 1; /*设置变量值*/
semctl(id, 0, SETVAL, options); /*设置索引0 的信号量*/
 
/*打印出信号量的值*/
i = semctl(id, 0, GETVAL, 0);
printf("value of semaphore at index 0 is %d
", i);
 
/*下面重新设置信号量*/
lock_it.sem_num = 0; /*设置哪个信号量*/
lock_it.sem_op = -1; /*定义操作*/
lock_it.sem_flg = IPC_NOWAIT; /*操作方式*/
 
if (semop(id, &lock_it, 1) == -1)
{
printf("can not lock semaphore.
");
exit(1);
}
 
i = semctl(id, 0, GETVAL, 0);
printf("value of semaphore at index 0 is %d
", i);
 
/*清除信号量*/
semctl(id, 0, IPC_RMID, 0);
}

注,信号量和互斥锁的区别 
原文地址:https://www.cnblogs.com/zendu/p/4988189.html