Linux下的IPC-信号量的使用

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

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

  1. #include <stdio.h>   
  2. #include <unistd.h>   
  3. #include <sys/sem.h>   
  4. #include <sys/types.h>   
  5. #include <sys/ipc.h>    
  6. #include <stdlib.h>    
  7. #include <errno.h>   
  8. #include <string.h>   
  9. //==========================对信号量的封装函数==============================   
  10. #define IPC_ID 0x26   
  11.  /* 我们必须自己定义 semun 联合类型。 */  
  12. union semun   
  13. {   
  14.     int val;   
  15.     struct semid_ds *buf;   
  16.     unsigned short int *array;   
  17.     struct seminfo *__buf;   
  18. };  
  19. int WhenError(const char * msg, int eno)  
  20. {  
  21.     printf("semaphore error[%d]:%s, %s\n", eno, msg, strerror(eno));  
  22.     exit(0);  
  23.     return -1;  
  24. }  
  25. /* 获取一个count元信号量的标识符。如果需要则创建这个信号量 */  
  26. int CreateSemaphore(int count)  
  27. {  
  28.    key_t key;  
  29.    key = ftok(".",IPC_ID);  
  30.    int semid;  
  31.    semid = semget (key, count, 0666|IPC_CREAT);  
  32.    return (semid == -1)? WhenError("create sem(semget)", errno):semid;  
  33. }  
  34. /* 释放信号量。所有用户必须已经结束使用这个信号量。如果失败,返回 -1 */  
  35. int FreeSemaphore(int semid)  
  36. {    
  37.    union semun ignored_argument;   
  38.    return semctl (semid, 0, IPC_RMID, ignored_argument); //the second and forth args will be ignored in this case.   
  39. }  
  40.   
  41. /* Set a semaphore to a value。*/  
  42. int SetSemaphoreValue(int semid, int index, int value)  
  43. {  
  44.   union semun argument;  
  45.   argument.val = value;  
  46.   int ret;  
  47.   ret= semctl(semid,index,SETVAL,argument);  
  48.   return (ret == -1)? WhenError("Set Value(semctl)", errno):ret;  
  49. }  
  50. int SetSemaphoreValues(int semid, unsigned short * values)  
  51. {  
  52.   union semun argument;  
  53.   argument.array=values;  
  54.   int ret;  
  55.   ret= semctl(semid,0,SETALL,argument);  
  56.   return (ret == -1)? WhenError("Set Values(semctl)", errno):ret;  
  57. }  
  58. int GetSemaphoreValue(int semid, int index)  
  59. {  
  60.   int ret = semctl(semid,index,GETVAL,0);  
  61.   return (ret == -1)? WhenError("Get Value(semctl)", errno):ret;  
  62. }   
  63. /* 如sem[index]为负且不超时,则阻塞,超时则返回EAGAIN,否则将信号量减1,返回0*/   
  64. int TryLockSemaphore(int semid, int index, int milliseconds)  
  65. {  
  66.     int ret;  
  67.     struct sembuf operation;   
  68.     operation.sem_num = index;   
  69.     operation.sem_op = -1;   
  70. if(milliseconds<1)   
  71. {  
  72.       operation.sem_flg = IPC_NOWAIT;   
  73. ret= semop (semid, &operation, 1);   
  74. }  
  75. Else  
  76. {  
  77.       operation.sem_flg = SEM_UNDO;   
  78.       struct timespec timeout;  
  79.       timeout.tv_sec = (milliseconds / 1000);  
  80.       timeout.tv_nsec = (milliseconds - timeout.tv_sec*1000L)*1000000L;  
  81.       ret= semtimedop(semid, &operation, 1, &timeout);   
  82. }  
  83.     if(ret == -1)  
  84.     {  
  85.       if(errno == EAGAIN) return EAGAIN;  
  86.     }  
  87.     return (ret == -1)? WhenError("Wait(semop)", errno):ret;  
  88. }  
  89. /* 如果sem[index]为负,则阻塞,直到信号量值为正,然后将其减1*/   
  90. int LockSemaphore(int semid, int index)   
  91. {  
  92.     struct sembuf operation;   
  93.     operation.sem_num = index;   
  94.     operation.sem_op = -1; /* 减一。 */    
  95.     operation.sem_flg = SEM_UNDO; /* 允许撤销操作 */  
  96.     int ret;  
  97.     ret= semop (semid, &operation, 1);   
  98.     return (ret == -1)? WhenError("Wait(semop)", errno):ret;  
  99. }   
  100. /* 将sem[index]值加一。 这个操作会立即返回,应配合LockSemaphore一起使用。*/  
  101. int UnlockSemaphore(int semid, int index)   
  102. {  
  103.     struct sembuf operation;   
  104.     operation.sem_num = index;   
  105.     operation.sem_op = 1; /* 加一 */  
  106.     operation.sem_flg = SEM_UNDO; /* 允许撤销操作 */   
  107.     int ret;  
  108.     ret= semop (semid, &operations, 1);   
  109.     return (ret == -1)? WhenError("Post(semctl)", errno):ret;  
  110. }   
  111. //========================================================================   
  112. int main()  
  113. {  
  114.    pid_t pid;  
  115.    int semid;  
  116.    semid = CreateSemaphore(1);  
  117.   SetSemaphoreValue(semid, 0, 1);  
  118.   
  119.    pid = fork();  
  120.    if(pid==0)   //child 1   
  121.    {  
  122.       sleep(1);  
  123.       int res;  
  124.       res = LockSemaphore(semid, 0);   
  125. /* Try LockSemaphore的用法 
  126.   res = EAGAIN; 
  127.   while(res== EAGAIN) 
  128. { 
  129.     res= TryLockSemaphore(semid, 0, 1000);   //设置1000ms超时  
  130.   //do something 
  131. } 
  132. */  
  133.       printf("child 1 set[%d]....\n",res);   
  134.       // do critical things   
  135.       UnlockSemaphore (semid, 0);   
  136.       printf("child 1 post[%d]....\n",res);  
  137.       return 0;  
  138.    }  
  139.   
  140.   int dd;  
  141.   dd = LockSemaphore (semid, 0);   
  142.    printf("parent set 1[%d]....\n",dd);   
  143.    sleep(5);  
  144.    dd = UnlockSemaphore (semid, 0);   
  145.    sleep(2);  
  146.    printf("parent end: ...[%d]\n",dd);   
  147.    FreeSemaphore(semid);  
  148. }  

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