Linux下的IPC-信号量的使用

        几个进程映射同一内存区是一种最快的IPC方法,但单纯使用mmap,各进程之间,会有数据“不一致”的风险,需要一种机制保护共享区在某一时刻只允许一个进程操作,这时就要使用信号量了。因此本文可认为是上一篇文章《Linux下的IPC-共享内存的使用》的继续。

        本文以一个完整的程序为例子,来说明信号量的使用。以下是整个程序的代码。

#include <stdio.h>
#include <unistd.h>
#include <sys/sem.h>
#include <sys/types.h>
#include <sys/ipc.h> 
#include <stdlib.h> 
#include <errno.h>
#include <string.h>
//==========================对信号量的封装函数==============================
#define IPC_ID 0x26
 /* 我们必须自己定义 semun 联合类型。 */
union semun 
{ 
    int val; 
    struct semid_ds *buf; 
    unsigned short int *array; 
    struct seminfo *__buf; 
};
int WhenError(const char * msg, int eno)
{
    printf("semaphore error[%d]:%s, %s\n", eno, msg, strerror(eno));
    exit(0);
    return -1;
}
/* 获取一个count元信号量的标识符。如果需要则创建这个信号量 */
int CreateSemaphore(int count)
{
   key_t key;
   key = ftok(".",IPC_ID);
   int semid;
   semid = semget (key, count, 0666|IPC_CREAT);
   return (semid == -1)? WhenError("create sem(semget)", errno):semid;
}
/* 释放信号量。所有用户必须已经结束使用这个信号量。如果失败,返回 -1 */
int FreeSemaphore(int semid)
{  
   union semun ignored_argument; 
   return semctl (semid, 0, IPC_RMID, ignored_argument); //the second and forth args will be ignored in this case.
}

/* Set a semaphore to a value。*/
int SetSemaphoreValue(int semid, int index, int value)
{
  union semun argument;
  argument.val = value;
  int ret;
  ret= semctl(semid,index,SETVAL,argument);
  return (ret == -1)? WhenError("Set Value(semctl)", errno):ret;
}
int SetSemaphoreValues(int semid, unsigned short * values)
{
  union semun argument;
  argument.array=values;
  int ret;
  ret= semctl(semid,0,SETALL,argument);
  return (ret == -1)? WhenError("Set Values(semctl)", errno):ret;
}
int GetSemaphoreValue(int semid, int index)
{
  int ret = semctl(semid,index,GETVAL,0);
  return (ret == -1)? WhenError("Get Value(semctl)", errno):ret;
} 
/* 如sem[index]为负且不超时,则阻塞,超时则返回EAGAIN,否则将信号量减1,返回0*/ 
int TryLockSemaphore(int semid, int index, int milliseconds)
{
   	int ret;
	struct sembuf operation; 
	operation.sem_num = index; 
	operation.sem_op = -1; 
if(milliseconds<1) 
{
	  operation.sem_flg = IPC_NOWAIT; 
ret= semop (semid, &operation, 1); 
}
Else
{
	  operation.sem_flg = SEM_UNDO; 
      struct timespec timeout;
      timeout.tv_sec = (milliseconds / 1000);
      timeout.tv_nsec = (milliseconds - timeout.tv_sec*1000L)*1000000L;
	  ret= semtimedop(semid, &operation, 1, &timeout); 
}
    if(ret == -1)
    {
      if(errno == EAGAIN) return EAGAIN;
    }
	return (ret == -1)? WhenError("Wait(semop)", errno):ret;
}
/* 如果sem[index]为负,则阻塞,直到信号量值为正,然后将其减1*/ 
int LockSemaphore(int semid, int index) 
{
	struct sembuf operation; 
	operation.sem_num = index; 
	operation.sem_op = -1; /* 减一。 */  
	operation.sem_flg = SEM_UNDO; /* 允许撤销操作 */
	int ret;
	ret= semop (semid, &operation, 1); 
	return (ret == -1)? WhenError("Wait(semop)", errno):ret;
} 
/* 将sem[index]值加一。 这个操作会立即返回,应配合LockSemaphore一起使用。*/
int UnlockSemaphore(int semid, int index) 
{
	struct sembuf operation; 
	operation.sem_num = index; 
	operation.sem_op = 1; /* 加一 */
	operation.sem_flg = SEM_UNDO; /* 允许撤销操作 */ 
	int ret;
	ret= semop (semid, &operations, 1); 
	return (ret == -1)? WhenError("Post(semctl)", errno):ret;
} 
//========================================================================
int main()
{
   pid_t pid;
   int semid;
   semid = CreateSemaphore(1);
  SetSemaphoreValue(semid, 0, 1);

   pid = fork();
   if(pid==0)   //child 1
   {
      sleep(1);
      int res;
      res = LockSemaphore(semid, 0); 
/* Try LockSemaphore的用法
  res = EAGAIN;
  while(res== EAGAIN)
{
    res= TryLockSemaphore(semid, 0, 1000);   //设置1000ms超时 
  //do something
}
*/
      printf("child 1 set[%d]....\n",res); 
      // do critical things
      UnlockSemaphore (semid, 0); 
      printf("child 1 post[%d]....\n",res);
      return 0;
   }

  int dd;
  dd = LockSemaphore (semid, 0); 
   printf("parent set 1[%d]....\n",dd); 
   sleep(5);
   dd = UnlockSemaphore (semid, 0); 
   sleep(2);
   printf("parent end: ...[%d]\n",dd); 
   FreeSemaphore(semid);
}

       信号量的操作,涉及3个系统函数semget、semop和semctl,它们分别负责信号量的“创建”、PV操作和信号量的控制(查询值、释放等)。这三个函数的原型及其各参数的意义,请参阅有关文章,这里不列出了。

        对信号量,有一点不理解的是,为什么内核只提供了创建信号量集合的接口?当然,这是对单个信号量的一种扩展,但信号量的使用还是一个一个来使用的,将信号量集合封装成单个信号量使用,并不困难(本文即是提供了一种简单的封装),一般情况下,一个中小型程序需要的信号量个数并不多。

        在这个代码中,前面一大段代码都是对内核提供的信号集合的封装,使之便于应用。在这个简单的封装中,首先定义semun(比较奇怪,不知道为什么)和IPC_ID(用于产生一个唯一的信号量Id),下表是对封装函数的一些说明。

函数

说明

int WhenError(const char * msg, int eno)

错误处理函数,实际应用时,自己写一个吧。

int CreateSemaphore(int count)

“创建”一个信号量集合,count表示本程序需要的信号量个数,返回值为信号量组Id,以下各个调用信号量的函数都是以(Id,index)作为对某一特定信号量的标识的。

int FreeSemaphore(int semid)

释放信号量集合,程序结束时应用调用,否则该信号量一直存在。

int SetSemaphoreValue(int semid, int index, int value)

int SetSemaphoreValues(int semid, unsigned short * values)

用于信号量的初始值的设置,如果是互斥信号量,设置1。

int GetSemaphoreValue(int semid, int index)

查看信号量的值,它不会阻塞,某些情况下,可以用在轮询方式下。

int LockSemaphore(int semid, int index)

进入“单进程操作”区,应调用该函数,如果信号量的值不大于1,则阻塞,表明其他进程正在操作。

int UnlockSemaphore(int semid, int index)

结束“单进程操作”区,解除其他进程中被阻塞LockSemaphore,因此它应配合LockSemaphore一起使用。

int TryLockSemaphore(int semid, int index, int milliseconds)

用法与LockSemaphore类似,只是超时返回,返回值为EAGAIN,此时该进程可做其它事情,不能进入“单进程操作”区。milliseconds小于1时,无论是否能进入“单进程操作”区,都立即返回。

        由于信号量是系统全局资源,所以semget 中第一个参数key的选择应遵循的原则是(不一定非如上面示例中那样用ftok生成):如果希望本程序的信号量不与其他程序中的进程所共用,则需要一个全局的唯一数(别人没有用过的,别人不太可能想到的),如果需要与其他进程共用,则需要一个“众所周知”的量(双方约定)。

        一般情况下,程序结束时需要调用FreeSemaphore,如果程序由于异常非正常中止,则信号量不能释放,程序再次启动,“创建”同样信号量时,系统返回的是上次已经创建的信号量,这点在程序调试时尤其重要。

Semop中的信号操作标志sem_flg,有两种选择

  •  IPC_NOWAIT :对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。
  • SEM_UNDO:程序结束时(不论正常或不正常),保证信号值会被重设为semop()调用前的值。这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。

阻塞情况下,使用SEM_UNDO,不阻塞情况下用IPC_NOWAIT。

        上面示例只用到一个信号量,但可以扩展到多个信号量的使用,创建时count设置为信号量的个数,对各信号量的操作,其Index不同。

        本程序的封装采用的是C语言方式,为的是更加普遍,如果采用C++中的类,封装会更“漂亮”些,同时可以采用有意义的字符串来标识每一个信号量,而不是采用生硬的下标(Index)方式,程序中,只要采用一个函数来映射字符串和下标即可。

      需要说明的是,信号量一般作为进程间读写共享数据或者同步进程之用,但信号量本身也具有一个整型量的信息,在进程间也可以传递类似“状态”的信息,例如UI进程给后台进程通知某事件发生,需要后台进程做出某种响应,这种情况下,采用信号量也是可以的。




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值