Linux进程间通信(IPC)编程实践(九)System V信号量---封装一个信号量操作的工具集

System信号量集主要API

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #include <sys/types.h>  
  2. #include <sys/ipc.h>  
  3. #include <sys/sem.h>  
  4. int semget(key_t key, int nsems, int semflg);  
  5. int semctl(int semid, int semnum, int cmd, ...);  
  6. int semop(int semid, struct sembuf *sops, unsigned nsops);  

semget

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. int semget(key_t key, int nsems, int semflg);  

创建/访问一个信号量集

参数:

   key: 信号集键(key)

   nsems:信号集中信号量的个数

   semflg: 由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志一致

返回值:成功返回一个非负整数,即该信号集的标识码;失败返回-1;

   此时创建的信号量集中的每一个信号量都会有一个默认值: 0, 如果需要更改该值, 则需要调用semctl函数->更改初始值;

   我们将创建和打开分开封装,对于不关心的参数直接添0:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /** 示例1: 封装一个创建一个信号量集函数  
  2. 该信号量集包含1个信号量; 
  3. 权限为0666 
  4. **/  
  5. int sem_create(key_t key)  
  6. {  
  7.     int semid = semget(key, 1, IPC_CREAT|IPC_EXCL|0666);  
  8.     if (semid == -1)  
  9.         err_exit("sem_create error");  
  10.     return semid;  
  11. }  
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /** 示例2: 打开一个信号量集 
  2. nsems(信号量数量)可以填0, 
  3. semflg(信号量权限)也可以填0, 表示使用默认的权限打开 
  4. **/  
  5. int sem_open(key_t key)  
  6. {  
  7.     int semid = semget(key, 0, 0);  
  8.     if (semid == -1)  
  9.         err_exit("sem_open error");  
  10.     return semid;  
  11. }  

shmctl

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. int semctl(int semid, int semnum, int cmd, ...);  

控制信号量集

参数

   semid:由semget返回的信号集标识码

   semnum:信号集中信号量的序号(注意: 从0开始The semaphores in a set are numbered starting at 0.)

   cmd:将要采取的动作(常用取值如下)


   如果该函数需要第四个参数(有时是不需要第四个参数的, 取决于cmd的取值), 则程序中必须定义如下的联合体:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. union semun  
  2. {  
  3.     int              val;    /* Value for SETVAL */  
  4.     struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */  
  5.     unsigned short  *array;  /* Array for GETALL, SETALL */  
  6.     struct seminfo  *__buf;  /* Buffer for IPC_INFO (Linux-specific)*/  
  7. };  
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //struct semid_ds : Linux内核为System V信号量维护的数据结构  
  2. struct semid_ds  
  3. {  
  4.     struct ipc_perm sem_perm;  /* Ownership and permissions */  
  5.     time_t          sem_otime; /* Last semop time */  
  6.     time_t          sem_ctime; /* Last change time */  
  7.     unsigned long   sem_nsems; /* No. of semaphores in set */  
  8. };  
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /** 示例1: 将信号量集semid中的第一个信号量的值设置成为value(SETVAL) 
  2. 注意: semun联合体需要自己给出(从man-page中拷贝出来即可) 
  3. **/  
  4. union semun  
  5. {  
  6.     int              val;    /* Value for SETVAL */  
  7.     struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */  
  8.     unsigned short  *array;  /* Array for GETALL, SETALL */  
  9.     struct seminfo  *__buf;  /* Buffer for IPC_INFO 
  10.                                            (Linux-specific) */  
  11. };  
  12. int sem_setval(int semid, int value)  
  13. {  
  14.     union semun su;  
  15.     su.val = value;  
  16.     if (semctl(semid, 0, SETVAL, su) == -1)  
  17.         err_exit("sem_setval error");  
  18.     return 0;  
  19. }  
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /** 示例2: 获取信号量集中第一个信号所关联的值(GETVAL) 
  2. 注意: 此时第四个参数可以不填, 而信号量所关联的值可以通过semctl的返回值返回(the value of semval.) 
  3. **/  
  4. int sem_getval(int semid)  
  5. {  
  6.     int value = semctl(semid, 0, GETVAL);  
  7.     if (value == -1)  
  8.         err_exit("sem_getval error");  
  9.     return value;  
  10.     return 0;  
  11. }  
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /** 示例3: 删除一个信号量集(注意是删除整个集合) 
  2.     IPC_RMID  Immediately  remove(立刻删除)  the  semaphore  set,  awakening  all  processes blocked in semop(2) calls on the set (with an error return and errno set to  EIDRM)[然后唤醒所有阻塞在该信号量上的进程]. The  argument  semnum  is ignored[忽略第二个参数]. 
  3. **/  
  4. int sem_delete(int semid)  
  5. {  
  6.     if (semctl(semid, 0, IPC_RMID) == -1)  
  7.         err_exit("sem_delete error");  
  8.     return 0;  
  9. }  
  10.   
  11. //测试代码  
  12. int main(int argc,char *argv[])  
  13. {  
  14.     int semid = sem_create(0x1234); //创建一个信号量集  
  15.     sem_setval(semid, 500);         //设置值  
  16.     cout << sem_getval(semid) << endl;  //获取值  
  17.     sleep(10);  
  18.     sem_delete(semid);      //删除该集合  
  19. }  
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /**示例4: 获取/设置信号量的权限 
  2. 注意:一定要设定struct semid_ds结构体, 以指定使用semun的哪个字段 
  3. **/  
  4. int sem_getmode(int semid)  
  5. {  
  6.     union semun su;  
  7.   
  8.     // 注意: 下面这两行语句一定要设定.  
  9.     // (告诉内核使用的semun的哪个字段)  
  10.     struct semid_ds sd;  
  11.     su.buf = &sd;  
  12.     //  
  13.     if (semctl(semid, 0, IPC_STAT, su) == -1)  
  14.         err_exit("sem_getmode error");  
  15.     printf("current permissions is: %o\n", su.buf->sem_perm.mode);  
  16.     return 0;  
  17. }  
  18. int sem_setmode(int semid, char *mode)  
  19. {  
  20.     union semun su;  
  21.     // 注意: 下面这两行语句一定要设定.  
  22.     // (告诉内核使用的semun的哪个字段)  
  23.     struct semid_ds sd;  
  24.     su.buf = &sd;  
  25.     //  
  26.     sscanf(mode, "%o", (unsigned int *)&su.buf->sem_perm.mode);  
  27.   
  28.     if (semctl(semid, 0, IPC_SET, su) == -1)  
  29.         err_exit("sem_setmode error");  
  30.     return 0;  
  31. }  

semop

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. int semop(int semid, struct sembuf *sops, unsigned nsops);  

   用来操纵一个信号量集, 以实现P,V操作

参数:

   semid:是该信号量的标识码,也就是semget函数的返回值

   sops:是个指向一个结构数组(如果信号量集中只有一个信号量的话, 只有一个结构体也可)的指针

   nsops:所设置的信号量个数(如果nsops>1话, 需要将sops[第二个参数]设置成为一个结构数组, 具体参考Man-Page给出的示例代码), 第三个参数其实也指出第二个参数所表示对象的个数;

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //sembuf结构体  
  2. struct sembuf  
  3. {  
  4.     unsigned short sem_num;  /*semaphore number:信号量的编号(从0开始)*/  
  5.     short          sem_op;   /* semaphore operation(+1, 0, -1) */  
  6.     short          sem_flg;  /* operation flags: 常用取值为SEM_UNDO(解释见下) */  
  7. };  

   sem_op是信号量一次PV操作时加减的数值,一般只会用到两个值,一个是“-1”,也就是P操作,等待信号量变得可用;另一个是“+1”,也就是V操作,发出信号量已经变得可用, 该参数还可以等于0, 表示进程将阻塞直到信号量的值等于0;

   sem_flg有三个取值: SEM_UNDO(在进程结束时, 将该进程对信号量的操作复原[即:取消该进程对信号量所有的操作], 推荐使用), IPC_NOWAIT(非阻塞)或0(默认操作, 并不撤销操作);

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /** 示例: P,V操作封装  
  2. **可以将sembuf的第三个参数设置为IPC_NOWAIT/0, 以查看程序的状态的变化 
  3. **/  
  4. int sem_P(int semid)  
  5. {  
  6.     struct sembuf sops = {0, -1, SEM_UNDO};  
  7.     if (semop(semid, &sops, 1) == -1)  
  8.         err_exit("sem_P error");  
  9.     return 0;  
  10. }  
  11. int sem_V(int semid)  
  12. {  
  13.     struct sembuf sops = {0, +1, SEM_UNDO};  
  14.     if (semop(semid, &sops, 1) == -1)  
  15.         err_exit("sem_V error");  
  16.     return 0;  
  17. }  
下面我们封装一个信号量操作函数工具,将主要的操作封装起来,可以像命令一样使用。
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /** 信号量综合运用示例: 
  2. 编译完成之后, 直接运行./semtool, 程序将打印该工具的用法; 
  3. 下面的这些函数调用, 只不过是对上面所封装函数的稍稍改动, 理解起来并不困难; 
  4. **/  
  5. //semtool.cpp  
  6. #include "Usage.h"  
  7.   
  8. int main(int argc,char *argv[])  
  9. {  
  10.     int opt = getopt(argc, argv, "cdpvs:gfm:");  
  11.     if (opt == '?')  
  12.         exit(EXIT_FAILURE);  
  13.     else if (opt == -1)  
  14.     {  
  15.         usage();  
  16.         exit(EXIT_FAILURE);  
  17.     }  
  18.   
  19.     key_t key = ftok("."'s');  
  20.     int semid;  
  21.     switch (opt)  
  22.     {  
  23.     case 'c':  
  24.         sem_create(key);  
  25.         break;  
  26.     case 'd':  
  27.         semid = sem_open(key);  
  28.         sem_delete(semid);  
  29.         break;  
  30.     case 'p':  
  31.         semid = sem_open(key);  
  32.         sem_P(semid);  
  33.         sem_getval(semid);  
  34.         break;  
  35.     case 'v':  
  36.         semid = sem_open(key);  
  37.         sem_V(semid);  
  38.         sem_getval(semid);  
  39.         break;  
  40.     case 's':  
  41.         semid = sem_open(key);  
  42.         sem_setval(semid, atoi(optarg));  
  43.         sem_getval(semid);  
  44.         break;  
  45.     case 'g':  
  46.         semid = sem_open(key);  
  47.         sem_getval(semid);  
  48.         break;  
  49.     case 'f':  
  50.         semid = sem_open(key);  
  51.         sem_getmode(semid);  
  52.         break;  
  53.     case 'm':  
  54.         semid = sem_open(key);  
  55.         sem_setmode(semid, argv[2]);  
  56.         sem_getmode(semid);  
  57.         break;  
  58.     default:  
  59.         break;  
  60.     }  
  61.   
  62.     return 0;  

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //Usage.h  
  2. #ifndef USAGE_H_INCLUDED  
  3. #define USAGE_H_INCLUDED  
  4.   
  5. #include <iostream>  
  6. #include <string>  
  7.   
  8. #include <stdio.h>  
  9. #include <stdlib.h>  
  10. #include <string.h>  
  11. #include <string.h>  
  12.   
  13. #include <sys/types.h>  
  14. #include <sys/stat.h>  
  15. #include <sys/ipc.h>  
  16. #include <sys/wait.h>  
  17. #include <sys/time.h>  
  18. #include <sys/msg.h>  
  19. #include <sys/shm.h>  
  20. #include <sys/mman.h>  
  21. #include <sys/sem.h>  
  22. #include <fcntl.h>  
  23. #include <signal.h>  
  24. #include <unistd.h>  
  25. #include <grp.h>  
  26. #include <pwd.h>  
  27. #include <time.h>  
  28. #include <errno.h>  
  29. #include <mqueue.h>  
  30. using namespace std;  
  31. inline void err_quit(std::string message);  
  32. inline void err_exit(std::string message);  
  33.   
  34. void usage()  
  35. {  
  36.     cerr << "Usage:" << endl;  
  37.     cerr << "./semtool -c        #create" << endl;  
  38.     cerr << "./semtool -d        #delte" << endl;  
  39.     cerr << "./semtool -p        #signal" << endl;  
  40.     cerr << "./semtool -v        #wait" << endl;  
  41.     cerr << "./semtool -s <val>  #set-value" << endl;  
  42.     cerr << "./semtool -g        #get-value" << endl;  
  43.     cerr << "./semtool -f        #print-mode" << endl;  
  44.     cerr << "./semtool -m <mode> #set-mode" << endl;  
  45. }  
  46.   
  47. int sem_create(key_t key)  
  48. {  
  49.     int semid = semget(key, 1, IPC_CREAT|IPC_EXCL|0666);  
  50.     if (semid == -1)  
  51.         err_exit("sem_create error");  
  52.     return semid;  
  53. }  
  54. int sem_open(key_t key)  
  55. {  
  56.     int semid = semget(key, 0, 0);  
  57.     if (semid == -1)  
  58.         err_exit("sem_open error");  
  59.     return semid;  
  60. }  
  61.   
  62. union semun  
  63. {  
  64.     int              val;    /* Value for SETVAL */  
  65.     struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */  
  66.     unsigned short  *array;  /* Array for GETALL, SETALL */  
  67.     struct seminfo  *__buf;  /* Buffer for IPC_INFO (Linux-specific) */  
  68. };  
  69.   
  70. int sem_getmode(int semid)  
  71. {  
  72.     union semun su;  
  73.   
  74.     // 注意: 下面这两行语句一定要设定.  
  75.     // (告诉内核使用的semun的哪个字段)  
  76.     struct semid_ds sd;  
  77.     su.buf = &sd;  
  78.     //  
  79.     if (semctl(semid, 0, IPC_STAT, su) == -1)  
  80.         err_exit("sem_getmode error");  
  81.     printf("current permissions is: %o\n", su.buf->sem_perm.mode);  
  82.     return 0;  
  83. }  
  84. int sem_setmode(int semid, char *mode)  
  85. {  
  86.     union semun su;  
  87.     // 注意: 下面这两行语句一定要设定.  
  88.     // (告诉内核使用的semun的哪个字段)  
  89.     struct semid_ds sd;  
  90.     su.buf = &sd;  
  91.     //  
  92.     sscanf(mode, "%o", (unsigned int *)&su.buf->sem_perm.mode);  
  93.   
  94.     if (semctl(semid, 0, IPC_SET, su) == -1)  
  95.         err_exit("sem_setmode error");  
  96.     return 0;  
  97. }  
  98. int sem_getval(int semid)  
  99. {  
  100.     int value = semctl(semid, 0, GETVAL);  
  101.     if (value == -1)  
  102.         err_exit("sem_getval error");  
  103.     cout << "current value: " << value << endl;  
  104.     return value;  
  105. }  
  106. int sem_setval(int semid, int value)  
  107. {  
  108.     union semun su;  
  109.     su.val = value;  
  110.     if (semctl(semid, 0, SETVAL, su) == -1)  
  111.         err_exit("sem_setval error");  
  112.     return 0;  
  113. }  
  114.   
  115. int sem_delete(int semid)  
  116. {  
  117.     if (semctl(semid, 0, IPC_RMID) == -1)  
  118.         err_exit("sem_delete error");  
  119.     return 0;  
  120. }  
  121.   
  122. // 为了能够打印信号量的持续变化, 因此sem_flg我们并没用SEM_UNDO  
  123. // 但是我们推荐使用SEM_UNDO  
  124. int sem_P(int semid)  
  125. {  
  126.     struct sembuf sops = {0, -1, 0};  
  127.     if (semop(semid, &sops, 1) == -1)  
  128.         err_exit("sem_P error");  
  129.     return 0;  
  130. }  
  131. int sem_V(int semid)  
  132. {  
  133.     struct sembuf sops = {0, +1, 0};  
  134.     if (semop(semid, &sops, 1) == -1)  
  135.         err_exit("sem_V error");  
  136.     return 0;  
  137. }  
  138.   
  139. inline void err_quit(std::string message)  
  140. {  
  141.     std::cerr << message << std::endl;  
  142.     exit(EXIT_FAILURE);  
  143. }  
  144. inline void err_exit(std::string message)  
  145. {  
  146.     perror(message.c_str());  
  147.     exit(EXIT_FAILURE);  
  148. }  
  149.   
  150. #endif // USAGE_H_INCLUDED  

附:ftok函数

系统建立IPC通讯(如消息队列、共享内存时)必须指定一个ID值。通常情况下,该id值通过ftok函数得到。
ftok原型如下:

[cpp]  view plain  copy
  1. key_t ftok( char * fname, int id )  
fname就时你指定的文件名(该文件必须是存在而且可以访问的),id是子序号,虽然为int,但是只有8个比特被使用(0-255)。
返回值:

当成功执行的时候,一个key_t值将会被返回,否则 -1 被返回。

在我们获取到key之后,就可以使用该key作为某种方法的进程间通信的key值。

猜你喜欢

转载自blog.csdn.net/zjy900507/article/details/80204543