4401|2

241

帖子

4

TA的资源

纯净的硅(初级)

楼主
 

Helper2416-39——Linux_Programing——System V IPC –信号量 [复制链接]

本帖最后由 yuanlai2010 于 2014-9-1 22:42 编辑

System V IPC信号量
参与Helper2416开发板助学计划心得

下来的三张帖子是关于一组进程间通信的机制,他们最初由AT&T System V2.0版本的UNIX引入,由于这些机制都出现在同一个版本中并且具有着相似的编程接口,通常把这特定的机制称为System V IPC。
IPC主要包含三个部分:
信号量
共享内存
消息队列

这篇帖子主要是关于信号量的学习心得。

信号量的定义:
信号量是一个特殊的变量,只允许对它进行等待和发送信号这两种操作。
一个用的比较多的,且最简单的信号量就是二值信号量,它的取值就是0和1;

通常当多个程序试图在同一时间访问某一个数据的时候,为了保证数据不被破坏,只允许一个进程对它进行独占式的访问,而这段独占访问的代码就称为临界代码段,通常临界代码段只占一个程序的一小部分。

而信号量的作用就是,当不同程序的临界代码段都需访问同一个数据的时候,让这几个程序在进入临界段之前先去请求事先定义好的一个信号量sv,如果信号量sv的值为1(以二值信号量为例),则当前求情信号量的程序可以进入临界代码段访问数据,同时将sv的值减一。如果这个时候有其他程序也试图进入临界区访问同一数据,它同样要先去申请信号量sv,而此时信号量的值为0,申请失败,则系统会将此任务挂起,等待正在访问此数据的任务完成访问。如果结束访问时没有其他进程正在等待此信号量时,sv就加一,这样其他进程就可以来申请这个信号量了。
对信号量的值进行的增减操作均为原子操作,这是由于信号量主要的作用是维护资源的互斥或多进程的同步访问。而在信号量的创建及初始化上,不能保证操作均为原子性

这样来说:
信号量的本质是一种数据操作锁,它本身不具有数据交换的功能,而是通过控制其他的通信资源(文件,外部设备)来实现进程间通信,它本身只是一种外部资源的标识。信号量在此过程中负责数据操作的互斥、同步等功能。


使用信号量的工具

假定我们有一个信号量变量sv,两个操作定义如下:
P(sv)    如果sv大于0,减小sv。如果sv为0,挂起这个进程的执行。
V(sv)    如果有进程被挂起等待sv,使其恢复执行。如果没有进行被挂起等待sv,增加sv。

一个常用的信号量使用格式(伪代码):
semaphoresv = 1;
loopforever {
    P(sv);
    critical code section;
    V(sv);
    noncritical code section;
}

信号量函数定义如下:
#include
intsemctl(int sem_id, int sem_num, int command, ...);
intsemget(key_t key, int num_sems, int sem_flags);
intsemop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);
通常情况下sys/sem.h文件会自动包含sys/types.h与sys/ipc.h文件
参数key的作用类似于一个文件名,它代表程序可能要使用的某个资源。

semget
函数原型: int semget(key_t key, int num_sems,int sem_flags);
函数功能: 创建一个新的信号量或者取得一个一有信号量的键
函数返回: 如果成功,semget函数会返回一个正数;这是用于其他信号量函数的标识符。如果失败,则会返回-1。
参数说明: 第一个参数key是一个用来允许不相关的进程访问相同信号量的整数值。所有的信号量是为不同的程序通过提供一个key来间接访问的,对于每一个信号量系统 生成一个信号量标识符。信号量键值只可以由semget获得,所有其他的信号量函数所用的信号量标识符都是由semget所返回的。
还有一个特殊的信号量key值,IPC_PRIVATE(通常为0),其作用是创建一个只有创建进程可以访问的信号量。这通常并没有有用的目的,而幸运的是,因为在某些Linux系统上,手册页将IPC_PRIVATE并没有阻止其他的进程访问信号量作为一个bug列出。
num_sems参数是所需要的信号量数目。这个值通常总是1。
sem_flags参数是一个标记集合,与open函数的标记十分类似。低九位是信号的权限,其作用与文件权限类似。另外,这些标记可以与 IPC_CREAT进行或操作来创建新的信号量。设置IPC_CREAT标记并且指定一个已经存在的信号量键值并不是一个错误。如果不需要,IPC_CREAT标记只是被简单的忽略。我们可以使用IPC_CREAT与IPC_EXCL的组合来保证我们可以获得一个新的,唯一的信号量。如果 这个信号量已经存在,则会返回一个错误。

Semop
函数原型: int semop(int sem_id, struct sembuf*sem_ops, size_t num_sem_ops);
函数功能: 用于改变信号量的值
参数说明: 第一个成员,sem_num,是信号量数目,通常为0,除非我们正在使用一个信号量数组。sem_op成员是信号量的变化量值。(我们可以以任何量改变信号量值,而不只是1)通常情况下中使用两个值,-1是我们的P操作,用来等待一个信号量变得可用,而+1是我们的V操作,用来通知一个信号量可用。
最后一个成员,sem_flg,通常设置为SEM_UNDO。这会使得操作系统跟踪当前进程对信号量所做的改变,而且如果进程终止而没有释放这个信号量,如果信号量为这个进程所占有,这个标记可以使得操作系统自动释放这个信号量。将sem_flg设置为SEM_UNDO是一个好习惯,除非我们需要不同的行 为。如果我们确实变我们需要一个不同的值而不是SEM_UNDO,一致性是十分重要的,否则我们就会变得十分迷惑,当我们的进程退出时,内核是否会尝试清 理我们的信号量。
semop的所用动作会同时作用,从而避免多个信号量的使用所引起的竞争条件。

Semctl
函数原型: int semctl(int sem_id, int sem_num,int command, ...);
函数功能: 用来直接控制信号量信息
函数返回: semctl函数依据command参数会返回不同的值。对于SETVAL与IPC_RMID,如果成功则会返回0,否则会返回-1。
参数说明: 第一个参数,sem_id,是由semget所获得的信号量标识符。sem_num参数是信号量数目。当我们使用信号量数组时会用到这个参数。通常,如果 这是第一个且是唯一的一个信号量,这个值为0。command参数是要执行的动作,而如果提供了额外的参数,则是union semun,根据X/OPEN规范,这个参数至少包括下列参数:
unionsemun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
}
许多版本的Linux在头文件(通常为sem.h)中定义了semun联合,尽管X/Open确认说我们必须定义我们自己的联合。如果我们发现我们确实需要定义我们自己的联合,我们可以查看semctl手册页了解定义。
有多个不同的command值可以用于semctl。在这里我们描述两个会经常用到的值。要了解semctl功能的详细信息,我们应该查看手册页。
这两个通常的command值为:
SETVAL:用于初始化信号量为一个已知的值。所需要的值作为联合semun的val成员来传递。在信号量第一次使用之前需要设置信号量。
IPC_RMID:当信号量不再需要时用于删除一个信号量标识。

信号量实践代码
这里还是贴书上的代码好了(稍微改动),能比较详尽的说明信号量的机制
  1. //1 在#include语句之后,我们定义函数原型与全局变量,然后我们进入main函数。在这里使用semget函数调用创建信号量,这会返回一个信号量 ID。如果程序是第一次调用(例如,使用一个参数并且argc > 1来调用),程序就会调用set_semvalue来初始化信号量并且将op_char设置为X。
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>

  5. #include <sys/types.h>
  6. #include <sys/ipc.h>
  7. #include <sys/sem.h>

  8. union semun{
  9.         int val;
  10.         struct semid_ds *buf;
  11.         unsigned short *array;
  12. };

  13. static int set_semvalue(void);
  14. static void del_semvalue(void);
  15. static int semaphore_p(void);
  16. static int semaphore_v(void);

  17. static int sem_id;

  18. int main(int argc, char **argv)
  19. {
  20.     int i;
  21.     int pause_time;
  22.     char op_char = 'Y';

  23.     srand((unsigned int)getpid());

  24.     sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);

  25.     if(argc > 1)
  26.     {
  27.         if(!set_semvalue())
  28.         {
  29.             fprintf(stderr, "Failed to initialize semaphore\n");
  30.             exit(EXIT_FAILURE);
  31.         }
  32.         op_char = 'L';
  33.         sleep(2);
  34.     }
  35. //2 然后我们使用一个循环代码进入并且离开临界区10次。此时会调用semaphore_p函数,这个函数会设置信号量并且等待程序进入临界区。
  36.     for(i=0;i<10;i++)
  37.     {
  38.         if(!semaphore_p()) exit(EXIT_FAILURE);
  39.         printf("%c", op_char); fflush(stdout);
  40.         pause_time = rand() % 3;
  41.         sleep(pause_time);
  42.         printf("%c", op_char); fflush(stdout);
  43. //3 在临界区之后,我们调用semaphore_v函数,在随机的等待之后再次进入for循环之后,将信号量设置为可用。在循环之后,调用del_semvalue来清理代码。
  44.         if(!semaphore_v()) exit(EXIT_FAILURE);

  45.         pause_time = rand() % 2;
  46.         sleep(pause_time);
  47.     }

  48.     printf("\n%d - finished\n", getpid());

  49.     if(argc > 1)
  50.     {
  51.         sleep(10);
  52.         del_semvalue();
  53.     }

  54.     exit(EXIT_SUCCESS);
  55. }

  56. //4 函数set_semvalue在一个semctl调用中使用SETVAL命令来初始化信号量。在我们使用信号量之前,我们需要这样做。

  57. static int set_semvalue(void)
  58. {
  59.     union semun sem_union;

  60.     sem_union.val = 1;
  61.     if(semctl(sem_id, 0, SETVAL, sem_union) == -1) return 0;
  62.     return 1;
  63. }
  64. //5 del_semvalue函数几乎具有相同的格式,所不同的是semctl调用使用IPC_RMID命令来移除信号量ID:

  65. static void del_semvalue(void)
  66. {
  67.     union semun sem_union;

  68.     if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
  69.         fprintf(stderr, "Failed to delete semaphore\n");
  70. }

  71. //6 semaphore_p函数将信号量减1(等待):

  72. static int semaphore_p(void)
  73. {
  74.     struct sembuf sem_b;

  75.     sem_b.sem_num = 0;
  76.     sem_b.sem_op = -1;
  77.     sem_b.sem_flg = SEM_UNDO;
  78.     if(semop(sem_id, &sem_b, 1) == -1)
  79.     {
  80.         fprintf(stderr, "semaphore_p failed\n");
  81.         return 0;
  82.     }
  83.     return 1;
  84. }

  85. //7 semaphore_v函数将sembuf结构的sem_op部分设置为1,从而信号量变得可用。

  86. static int semaphore_v(void)
  87. {
  88.     struct sembuf sem_b;

  89.     sem_b.sem_num = 0;
  90.     sem_b.sem_op = 1;
  91.     sem_b.sem_flg = SEM_UNDO;
  92.     if(semop(sem_id, &sem_b, 1) == -1)
  93.     {
  94.         fprintf(stderr, "semaphore_v failed\n");
  95.         return 0;
  96.     }
  97.     return 1;
  98. }
复制代码

运行结果
  1. [jyxtec@localhost sem]$ gcc semun.c -o semun
  2. [jyxtec@localhost sem]$ ./semun 1& ./semun
  3. [1] 4402
  4. YYLLYYYYLLYYLLYYLLLLYYLLYYLLYYLLLLYYLL
  5. 4402 - finished
  6. YY
  7. 4403 - finished
  8. [jyxtec@localhost sem]$
  9. [1]+  完成                  ./semun 1
  10. [jyxtec@localhost sem]$
复制代码

Y与L是成对出现的,表明临界区部分被正确的处理了。
论坛ID:yuanlai2010
发表时间:2014-09-01

最新回复

期待下文!  详情 回复 发表于 2014-9-2 08:33
点赞 关注

回复
举报

3028

帖子

0

TA的资源

五彩晶圆(初级)

沙发
 
好啊  静静地等待楼主的下文
 
 

回复

554

帖子

0

TA的资源

版主

板凳
 
期待下文!
 
个人签名My dreams will go on...
http://www.jyxtec.com
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/7 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表