LINUX进程信号量

 首先了解一下,信号量机概念是由荷兰科学家Dijkstr引入,值得一提的是,它提出的Dijksrtr算法解决了最短路径问题。
      信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况,信号量是一个特殊的变量,并且只有两个操作可以改变其值:等待(wait)与信号(signal)。
因为在Linux与UNIX编程中,"wait"与"signal"已经具有特殊的意义了(暂不知这特殊意义是啥),所以原始概念: 
     用于等待(wait)的P(信号量变量) ; 
     用于信号(signal)的V(信号量变量) ; 
这两字母来自等待(passeren:通过,如同临界区前的检测点)与信号(vrjgeven:指定或释放,如同释放临界区的控制权)的荷兰语。
P操作 负责把当前进程由运行状态转换为阻塞状态,直到另外一个进程唤醒它。

操作为:申请一个空闲资源(把信号量减1),若成功,则退出;若失败,则该进程被阻塞;

V操作 负责把一个被阻塞的进程唤醒,它有一个参数表,存放着等待被唤醒的进程信息。
操作为:释放一个被占用的资源(把信号量加1),如果发现有被阻塞的进程,则选择一个唤醒之。 
补充:查看共享信息的内存的命令是ipcs [-m|-s|-q] (全部的话是ipcs -a) ;查看共享信息的内存的命令是ipcs [-m|-s|-q]。
(一)系统调用函数semget()
函数原型:int semget(key_t key,int nsems,int semflg);
功能描述: 创建一个新的信号量集,或者存取一个已经存在的信号量集。
当调用semget创建一个信号量时,他的相应的semid_ds结构被初始化。ipc_perm中各个量被设置为相应 
值: 
        sem_nsems被设置为nsems所示的值;     
        sem_otime被设置为0;  
        sem_ctime被设置为当前时间
参数介绍: 
         key:所创建或打开信号量集的键值,键值是IPC_PRIVATE,该值通常为0,创建一个仅能被进程进程给我的信号量, 键值不是IPC_PRIVATE,我们可以指定键值,例如1234;也可以一个ftok()函数来取得一个唯一的键值。 
         nsems:创建的信号量集中的信号量的个数,该参数只在创建信号量集时有效。 
         semflg:调用函数的操作类型,也可用于设置信号量集的访问权限,两者通过or表示:
                有IPC_CREAT,IPC_EXCL两种:
IPC_CREAT如果信号量不存在,则创建一个信号量,否则获取。
IPC_EXCL只有信号量不存在的时候,新的信号量才建立,否则就产生错误。
返回值说明: 
如果成功,则返回信号量集的IPC标识符,其作用与信息队列识符一样。 
如果失败,则返回-1,errno被设定成以下的某个值 
EACCES:没有访问该信号量集的权限 
EEXIST:信号量集已经存在,无法创建 
EINVAL:参数nsems的值小于0或者大于该信号量集的限制;或者是该key关联的信号量集已存在,并且nsems 
大于该信号量集的信号量数 
ENOENT:信号量集不存在,同时没有使用IPC_CREAT 
ENOMEM :没有足够的内存创建新的信号量集 
ENOSPC:超出系统限制
每个信号量都有一些相关值:
      semval 信号量的值,一般是一个正整数,它只能通过信号量系统调用semctl函数设置,程序无法直接对它进行修改。
      sempid 最后一个对信号量进行操作的进程的pid.
      semcnt 等待信号量的值大于其当前值的进程数。
      semzcnt 等待信号量的值归零的进程数。
(二)信号量的控制 semctl()
原型:int semctl(int semid,int semnum,int cmd,union semun ctl_arg);  
参数介绍: semid为信号量集引用标志符,即semget 的返回值。  
               semnum第二个参数是信号量数目;
               cmd表示调用该函数执行的操作,其取值和对应操作如下:
标准的IPC函数
(注意在头文件<sys/sem.h>中包含semid_ds结构的定义)
IPC_STAT 把状态信息放入ctl_arg.stat中
IPC_SET 用ctl_arg.stat中的值设置所有权/许可权
IPC_RMID 从系统中删除信号量集合
单信号量操作
(下面这些宏与sem_num指定的信号量合semctl返回值相关)
GETVAL 返回信号量的值(也就是semval)
SETVAL 把信号量的值写入ctl_arg.val中
GETPID 返回sempid值
GETNCNT 返回semncnt(参考上面内容)
GETZCNT 返回semzcnt(参考上面内容)
全信号量操作
GETALL 把所有信号量的semvals值写入ctl_arg.array
SETALL 用ctl_arg.array中的值设置所有信号量的semvals
参数arg代表一个union的semun的实例。semun是在linux/sem.h中定义的:
union semun { 
int val; //执行SETVAL命令时使用 
struct semid_ds *buf; //在IPC_STAT/IPC_SET命令中使用 
unsigned short *array; //使用GETALL/SETALL命令时使用的指针 
}
联合体中每个成员都有各自不同的类型,分别对应三种不同的semctl 功能,如果semval 是SETVAL.则使用的将是ctl_arg.val.
功能:smctl函数依据command参数会返回不同的值。它的一个重要用途是为信号量赋初值,因为进程无法直接对信号量的值进行修改。
(三)信号量操作semop函数
在 Linux 下,PV 操作通过调用semop函数来实现,也只有它能对PV进行操作
调用原型:int semop(int semid,struct sembuf*sops,unsign ednsops); 
返回值:0,如果成功。-1,如果失败:errno=E2BIG(nsops大于最大的ops数目) 
EACCESS(权限不够) 
EAGAIN(使用了IPC_NOWAIT,但操作不能继续进行) 
EFAULT(sops指向的地址无效) 
EIDRM(信号量集已经删除) 
EINTR(当睡眠时接收到其他信号) 
EINVAL(信号量集不存在,或者semid无效) 
ENOMEM(使用了SEM_UNDO,但无足够的内存创建所需的数据结构) 
ERANGE(信号量值超出范围)
参数介绍:
第一个参数semid 是信号量集合标识符,它可能是从前一次的semget调用中获得的。
第二个参数是一个sembuf结构的数组,每个 sembuf 结构体对应一个特定信号的操作,sembuf结构在,<sys/sem.h>中定义
struct sembuf{ 
usign short sem_num;/*信号量索引*/ 
short sem_op;/*要执行的操作*/ 
short sem_flg;/*操作标志*/ 
}
sem_num 存放集合中某一信号量的索引,如果集合中只包含一个元素,则sem_num的值只能为0。
Sem_op取得值为一个有符号整数,该整数实际给定了semop函数将完成的功能。包括三种情况:
      如果sem_op是负数,那么信号量将减去它的值,对应于p()操作。这和信号量控制的资源有关。如果没有使用IPC_NOWAIT,那么调用进程将进入睡眠状态,直到信号量控制的资源可以使用为止。
      如果sem_op是正数,则信号量加上它的值。对应于v()操作。这也就是进程释放信号量控制的资源。
      最后,如果sem_op是0,那么调用进程将调用sleep(),直到信号量的值为0。这在一个进程等待完全空闲的资源时使用。

sem_flag是用来告诉系统当进程退出时自动还原操作,它维护着一个整型变量semadj(信号灯的计数器),可设置为 IPC_NOWAIT 或 SEM_UNDO 两种状态。只有将 sem_flg 指定为 SEM_UNDO 标志后,semadj (所指定信号量针对调用进程的调整值)才会更新,即减去减去sem_num的值。 此外,如果此操作指定SEM_UNDO,系统更新过程中会撤消此信号灯的计数(semadj)。此操作可以随时进行---它永远不会强制等待的过程。调用进程必须有改变信号量集的权限。

实验代码:

pv.h pv.c文件介绍:可以封装成一个块

pv.h

#ifndef __PV_H__
#define __PV_H__
#include <sys/types.h>
int I(int semnums,int value);
int I_2(key_t key,int semnums,int value);
void P(int semid,int semnum,int value);
void V(int semid,int semnum,int value);
void D(int semid);

#endif

pv.c

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "pv.h"
union semun{
   int val;
   struct semid_ds *buf;
   unsigned short *array;
};
int I(int semnums,int value)
{
   int semid;
   if((semid=semget(IPC_PRIVATE,semnums,IPC_CREAT|IPC_EXCL))<0)//创建一个信号量
   {
     perror("semget error");
     return -1;
   }
   union semun un;
   unsigned short *array=(unsigned short*)calloc(semnums,sizeof(unsigned short));
   int i=0;
   for(i=0;i<semnums;i++)
   {
       array[i]=value;
   }
   un.array=array;
   if(semctl(semid,0,SETALL,un)<0)//SETALL 用ctl_arg.array中的值设置所有信号量的semvals
   {
     perror("semctl error");
     return -1;
   }
   free(array);
   return semid;
}
int I_2(key_t key,int semnums,int value)
{
   int semid;
   if((semid=semget(key,semnums,IPC_CREAT|IPC_EXCL))<0)
   {
     perror("semget error");
     return -1;
   }
   union semun un;
   unsigned short *array=(unsigned short*)calloc(semnums,sizeof(unsigned short));
   int i=0;
   for(i=0;i<semnums;i++)
   {
       array[i]=value;
   }
   un.array=array;
   if(semctl(semid,0,SETALL,un)<0)
   {
     perror("semctl error");
     return -1;
   }
   free(array);
   return semid;
}
void P(int semid,int semnum,int value)
{
   assert(value>=0);
   struct sembuf buf[]={{semnum,-value,SEM_UNDO}};
   if(semop(semid,buf,sizeof(buf)/sizeof(struct sembuf))<0)
   {
      perror("semop error");
   }
   else 
   {
     printf("semop P operation success\n");
   }
}
void V(int semid,int semnum,int value)
{
   assert(value>=0);
   struct sembuf buf[]={{semnum,value,SEM_UNDO}};
   if(semop(semid,buf,sizeof(buf)/sizeof(struct sembuf))<0)
   {
      perror("semop error");
   }
   else
   {
     printf("semop P operation success\n");
   }
}
void D(int semid)
{
   if(semctl(semid,0,IPC_RMID,NULL)<0)
   {
     perror("D operation error");
   }
   else 
   {
     printf("D operation success\n");
   }

}

可以调用命令:gcc -o pv.o -c pv.c 链接成一个.o文件

测试文件:test_sem.c

#include "pv.h"
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
void handlesem(key_t skey,int semid);
int main(void)
{
  key_t semkey=0x200;
  int i;
  int semid;
  semid=I_2(semkey,1,1);
  for (i=0;i<3;i++)
  {
    if (fork()==0)           //父进程负责产生3个子进程
      handlesem(semkey,semid);  //子进程中才执行handlesem,做完后就exit。
  }
  D(semid);
  exit(0);
}
void handlesem(key_t skey,int semid)
{
  pid_t pid=getpid(); 
  printf("进程 %d 在临界资源区之前 \n",pid);
  P(semid,0,1);                                      //进程进入临界资源区,信号量减少1
  printf("进程 %d 在使用临界资源时,停止10s \n",pid);
  /*in real life do something interesting */
  sleep(10);
  printf("进程 %d 退出临界区后 \n",pid);
  V(semid,0,1);                                //进程退出临界资源区,信号量加1
  printf("进程 %d 完全退出\n",pid);
  exit(0);

}

执行结果:

进程 18398 在临界资源区之前 
semop error: Invalid argument
进程 18398 在使用临界资源时,停止10s 
进程 18397 在临界资源区之前 
semop error: Invalid argument
进程 18397 在使用临界资源时,停止10s 
进程 18396 在临界资源区之前 
semop error: Invalid argument
进程 18396 在使用临界资源时,停止10s 
进程 18398 退出临界区后 
进程 18397 退出临界区后 
semop error: Invalid argument
进程 18397 完全退出
进程 18396 退出临界区后 
semop error: Invalid argument
进程 18396 完全退出
semop error: Invalid argument

进程 18398 完全退出

线程信号量和进程信号量的区别:

信号量分为有名与无名 
信号量在进程是以有名信号量进行通信的,在线程是以无名信号进行通信的,因为线程LINUX还没有实现进程间的通信,所以在sem_init的第二个参数要为0,而且在多线程间的同步是可以通过有名信号量也可通过无名信号,但是一般情况线程的同步是无名信号量,无名信号量比较简单,有名信号量必须由LINUX 内核管理,由内核结构struct ipc_ids 存储,是随内核持续,系统关闭,信号量则删除,当然也可以显示删除,通过系统调用删除。

猜你喜欢

转载自blog.csdn.net/qq_38211852/article/details/80511119