Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2758585
  • 博文数量: 102
  • 博客积分: 1444
  • 博客等级: 中尉
  • 技术积分: 13891
  • 用 户 组: 普通用户
  • 注册时间: 2012-03-29 10:58
文章分类

全部博文(102)

文章存档

2014年(29)

2013年(14)

2012年(59)

分类: LINUX

2012-06-01 07:11:15

一、信号灯简介

Linux支持System V的信号灯(semaphore),是一种进程间通信的方式,只不过它和管道、FIFO或者共享内存不一样,信号灯主要用于同步或者互斥对共享资源的访问,它的发明来源于火车运行系统中的"信号灯",利用信号灯可以实现"PV"操作这种进程间同步进制。P操作时获得资源,将信号灯的值减1,如果结果不为负则执行完毕,进程获得资源,否则进程睡眠以等待的进程释放;V操作则是释放资源,给信号灯的值加1, 唤醒一个因执行P操作而等待的进程。

二、信号灯的两种类型

A、二值信号灯

最简单的信号灯形式,信号灯的值只能取0或1,类似互斥锁。

注意:虽然二值信号灯能够实现互斥锁的功能,但两者的关注内容不同。信号灯强调共享资源,只要共享资源可用,其他进程同样可以修改信号灯的值;互斥锁更强调进程,占用资源的进程使用完资源后,必须由进程本身来解锁。

B.计数信号灯

信号灯的值可以取任意非负值(当然受内核本省的约束),用来统计资源,其值就代表可用资源的个数。

注意:通常所说的系统V信号灯指的是计数信号灯集

三、相关API

A.创建一个信号灯集



功能:创建一个信号灯集并返回这个信号灯集的ID 或直接返回一个已经存在的信号灯集的ID

参数说明:

key:如果key值为IPC_PRIVATE或key为0并且semflg设置了IPC_CREAT,此时调用此函数总是创建一个新的信号灯集(我们在共享内存的时候验证过,还记的吗?其实system v 的ipc对象的创建机制都很类似。

nsems:指定这个信号灯集中信号灯的个数(每个信号灯代表了某一类资源)。

semflg:可以指定为IPC_CREAT | 0666,其含义为,不存在则创建,访问权限为0666。我们也可以通过IPC _CREAT | IPC_EXCL一起使用的时候确定要创建的信号灯集是否存在,如果存在此时这个函数放回-1 。



B.控制信号灯集



参数说明:

semid : 信号灯集ID

semnum:要修改的信号灯编号(创建信号灯集时,信号灯的编号从0开始)

cmd:

IPC_STAT  获取信号灯信息,信息由arg.buf(即第四个参数)返回
GETVAL  : 获取semnum信号灯的值
SETVAL  : 设置semnum信号灯的值
IPC_RMID : 从系统中删除semnum所代表的信号灯  

注意:semctl是可变参数,cmd为GETVAL或SETVAL时,需要传递第四个参数,其参数类型为 union semun。这个结构体的类型必须在应用程序中定义,定义如下:



<1>设置信号灯集中第一个信号灯的数值为1(即某一类资源的个数)

a.必须在应用程序中定义如下类型

union semun{
  int   val;
  struct  semid_ds  *buf;
  unsigned  short  *array;
  struct  seminfo  *  __buf;
};

b.定义一个union semun变量,并赋值

union semun mysemun;
mysemun.val = 1;

c.调用semctl函数

//第一个信号灯的编号为0
if ( ( semctl (  semid  ,  0  ,  SETVAL,mysemun)) < 0)
{
    perror("Fail to semctl");
    exit(EXIT_FAILURE);
}

<2>删除一个信号灯

if(  semctl  (  semid  ,  0  ,  IPC_RMID  ,  0 ) < 0 )
{
    perror("Fail to semctl  IPC_RMID");
    exit(EXIT_FAILURE);
}



C.操作信号灯



功能:semop系统调用可以实现对由semid标志的信号等集中的某一个指定信号灯的一系列操作

参数说明:

semid信号灯集的标识ID。

sops :指向结构体sembuf的指针,设置信号灯集中某一个信号灯的工作方式。



<1>sem_num对应信号灯集中的信号灯,0代表第一个信号灯

<2>sem_op值决定了对sem_num的三种不同操作:

a.sem_op = 0,调用者阻塞等待,直到信号灯的值等于0时返回。可以用来测试共享资源是否已用完。

b.sem_op = 1,释放资源,V操作

c.sem_op = -1,分配资源,P操作

<3>sem_flg可取0,IPC_NOWAIT以及SEM_UNDO三个标志

a.   0  代表阻塞调用(资源不满足阻塞)
b.   IPC_NOWAIT  代表非阻塞调用
c.   如果设置了SEM_UNDO标志,那么在进程结束时,相应的操作将被取消,这是一个比较重要的一个标志。

案例:封装一个P操作和一个V操作

//p操作
int my_sem_wait(int semid,int sem_num)
{
    struct sembuf  op;
     
    op.sem_num = sem_num;
    op.sem_op = -1;
    op.sem_flg = 0;
    
    if(semop(sem_id,&op,1) < 0)
    {
perror("fail to semop");
exit(-1);
    }

    return 0;
}

//V操作
int my_sem_wait(int semid,int sem_num)
{
    struct sembuf  op;
     
    op.sem_num = sem_num;
    op.sem_op = 1;
    op.sem_flg = 0;
    
    if(semop(sem_id,&op,1) < 0)
    {
perror("fail to semop");
exit(-1);
    }

    return 0;
}

案例探究(用信号灯集实现共享内存间的同步):

A .读共享内存

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <errno.h>
  5. #include <unistd.h>
  6. #include <sys/ipc.h>
  7. #include <sys/shm.h>
  8. #include <sys/types.h>
  9. #include <sys/sem.h>

  10. #define READ 0
  11. #define WRITE 1

  12. #define N 2

  13. union semun{
  14.     int val;
  15.     struct semid_ds *buf;
  16.     unsigned short *array;
  17.     struct seminfo *__buf;
  18. };

  19. //初始化信号灯集中的信号灯的值
  20. int my_sem_init(int semid)
  21. {
  22.     int i = 0;
  23.     union semun mysemun;

  24.     for(i = 0;i < N;i ++)
  25.     {
  26.         mysemun.val = i;
  27.         if(semctl(semid,i,SETVAL,mysemun) < 0)
  28.         {
  29.             perror("Fail to semctl");
  30.             return -1;
  31.         }
  32.     }

  33.     return 0;
  34. }

  35. //信号灯集中的信号灯释放资源
  36. int my_sem_post(int semid,int sem_num)
  37. {
  38.     struct sembuf op;

  39.     op.sem_num = sem_num;
  40.     op.sem_op = 1;
  41.     op.sem_flg = 0;

  42.     if(semop(semid,&op,1) < 0)
  43.     {
  44.         perror("Fail to semop");
  45.         return -1;
  46.     }

  47.     return 0;
  48. }

  49. //信号灯集中的信号灯申请资源
  50. int my_sem_wait(int semid,int sem_num)
  51. {
  52.     struct sembuf op;

  53.     op.sem_num = sem_num;
  54.     op.sem_op = -1;
  55.     op.sem_flg = 0;

  56.     if(semop(semid,&op,1) < 0)
  57.     {
  58.         perror("Fail to semop");
  59.         return -1;
  60.     }

  61.     return 0;
  62. }

  63. //读共享内存
  64. int read_share_memory(int semid,char *addr)
  65. {
  66.     int n;

  67.     while(1)
  68.     {
  69.         my_sem_wait(semid,READ);
  70.         
  71.         printf("Read : %s.\n",addr);

  72.         my_sem_post(semid,WRITE);

  73.         if(strncmp(addr,"quit",4) == 0)
  74.         {
  75.             if(shmdt((void *)addr) < 0)
  76.             {
  77.                 perror("Fail to semdt");
  78.                 return -1;
  79.             }
  80.             
  81.             break;
  82.         }

  83.     }
  84.     
  85.     return 0;
  86. }

  87. int main(int argc,char *argv[])
  88. {
  89.     key_t key;
  90.     void *shmaddr;
  91.     int shmid,semid;
  92.     
  93.     if(argc < 2)
  94.     {
  95.         fprintf(stderr,"usage : %s argv[1].\n",argv[0]);
  96.         exit(EXIT_FAILURE);
  97.     }
  98.     
  99.     //获取键值
  100.     if((key = ftok(argv[1],'a')) < 0)
  101.     {
  102.         perror("Fail to ftok");
  103.         exit(EXIT_FAILURE);
  104.     }
  105.     
  106.     //创建共享内存
  107.     if((shmid = shmget(key,1024,IPC_CREAT | 0666)) < 0)
  108.     {
  109.         perror("Fail to shmget");
  110.         exit(EXIT_FAILURE);
  111.     }

  112.     //映射共享内存到进程地址空间
  113.     if((shmaddr = shmat(shmid,NULL,0)) == (void *)-1)
  114.     {
  115.         perror("Fail to shmat");
  116.         exit(EXIT_FAILURE);
  117.     }
  118.     
  119.     //创建含有2个的信号灯的信号灯集
  120.     if((semid = semget(key,N,IPC_CREAT | 0666)) < 0)
  121.     {
  122.         perror("Fail to shmget");
  123.         exit(EXIT_FAILURE);
  124.     }

  125.     //初始化信号灯集中的信号灯
  126.     my_sem_init(semid);
  127.     
  128.     read_share_memory(semid,(char *)shmaddr);

  129.     exit(EXIT_SUCCESS);
  130. }
写共享内存

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <errno.h>
  5. #include <unistd.h>
  6. #include <sys/ipc.h>
  7. #include <sys/shm.h>
  8. #include <sys/types.h>
  9. #include <sys/sem.h>

  10. #define READ 0
  11. #define WRITE 1

  12. #define N 2

  13. union semun{
  14.     int val;
  15.     struct semid_ds *buf;
  16.     unsigned short *array;
  17.     struct seminfo *__buf;
  18. }mysemun;

  19. //初始化信号灯集中的信号灯的值
  20. int my_sem_init(int semid)
  21. {
  22.     int i = 0;

  23.     for(i = 0;i < N;i ++)
  24.     {
  25.         mysemun.val = i;
  26.         if(semctl(semid,i,SETVAL,mysemun) < 0)
  27.         {
  28.             perror("Fail to semctl");
  29.             return -1;
  30.         }
  31.     }

  32.     return 0;
  33. }

  34. //信号灯集中的信号灯释放资源
  35. int my_sem_post(int semid,int sem_num)
  36. {
  37.     struct sembuf op;

  38.     op.sem_num = sem_num;
  39.     op.sem_op = 1;
  40.     op.sem_flg = 0;

  41.     if(semop(semid,&op,1) < 0)
  42.     {
  43.         perror("Fail to semop");
  44.         return -1;
  45.     }

  46.     return 0;
  47. }

  48. //信号灯集中的信号灯申请资源
  49. int my_sem_wait(int semid,int sem_num)
  50. {
  51.     struct sembuf op;

  52.     op.sem_num = sem_num;
  53.     op.sem_op = -1;
  54.     op.sem_flg = 0;

  55.     if(semop(semid,&op,1) < 0)
  56.     {
  57.         perror("Fail to semop");
  58.         return -1;
  59.     }

  60.     return 0;
  61. }

  62. //读共享内存
  63. int read_share_memory(int semid,char *addr)
  64. {
  65.     int n;

  66.     while(1)
  67.     {
  68.         my_sem_wait(semid,WRITE);

  69.         printf(">");
  70.         fgets(addr,1024,stdin);
  71.         addr[strlen(addr)-1] = '\0';

  72.         my_sem_post(semid,READ);

  73.         if(strncmp(addr,"quit",4) == 0)
  74.         {
  75.             if(shmdt((void *)addr) < 0)
  76.             {
  77.                 perror("Fail to semdt");
  78.                 return -1;
  79.             }
  80.             
  81.             break;
  82.         }

  83.     }
  84.     
  85.     return 0;
  86. }

  87. int main(int argc,char *argv[])
  88. {
  89.     key_t key;
  90.     void *shmaddr;
  91.     int shmid,semid;
  92.     
  93.     if(argc < 2)
  94.     {
  95.         fprintf(stderr,"usage : %s argv[1].\n",argv[0]);
  96.         exit(EXIT_FAILURE);
  97.     }
  98.     
  99.     //获取键值
  100.     if((key = ftok(argv[1],'a')) < 0)
  101.     {
  102.         perror("Fail to ftok");
  103.         exit(EXIT_FAILURE);
  104.     }
  105.     
  106.     //创建共享内存
  107.     if((shmid = shmget(key,1024,IPC_CREAT | 0666)) < 0)
  108.     {
  109.         perror("Fail to shmget");
  110.         exit(EXIT_FAILURE);
  111.     }

  112.     //映射共享内存到进程地址空间
  113.     if((shmaddr = shmat(shmid,NULL,0)) == (void *)-1)
  114.     {
  115.         perror("Fail to shmat");
  116.         exit(EXIT_FAILURE);
  117.     }
  118.     
  119.     //创建含有2个的信号灯的信号灯集
  120.     if((semid = semget(key,N,IPC_CREAT | 0666)) < 0)
  121.     {
  122.         perror("Fail to shmget");
  123.         exit(EXIT_FAILURE);
  124.     }

  125.     //初始化信号灯集中的信号灯
  126.     my_sem_init(semid);
  127.     
  128.     read_share_memory(semid,(char *)shmaddr);

  129.     if(shmctl(shmid,IPC_RMID,NULL) < 0)
  130.     {
  131.         perror("Fail to shmctl");
  132.         return -1;
  133.     }

  134.     if(semctl(semid,0,IPC_RMID,0) < 0)
  135.     {
  136.         perror("Fail to semctl WRITE");
  137.         return -1;
  138.     }

  139.     exit(EXIT_SUCCESS);
  140. }
阅读(11534) | 评论(10) | 转发(16) |
给主人留下些什么吧!~~

月鸟2014-12-18 15:05:33

创建一个信号灯集(semget)并将它初始化(semctl)需两次函数调用

leishouxue2014-06-06 12:44:28

草根老师:呵呵!是的,应该判断信号灯集是否已经存在,然后在决定是否在进行初始化

哈哈…… 很高兴认识。

回复 | 举报

草根老师2014-06-04 20:52:25

leishouxue:草根老师,你讲的很好!我特意将程序运行了一遍,出现错了。
通过调试发现:
1.你初始化了信号灯两遍,也会是说WRITE信号的初始值变成了2。
2.write需要等read操作完后才可以销毁信号灯和共享内催。
以上是自己拙见。

呵呵!是的,应该判断信号灯集是否已经存在,然后在决定是否在进行初始化

回复 | 举报

leishouxue2014-05-31 10:43:13

草根老师,你讲的很好!我特意将程序运行了一遍,出现错了。
通过调试发现:
1.你初始化了信号灯两遍,也会是说WRITE信号的初始值变成了2。
2.write需要等read操作完后才可以销毁信号灯和共享内催。
以上是自己拙见。

liujunwei12342012-07-09 22:04:00

分析的很好,学习了!