一、信号灯简介
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 .读共享内存- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <errno.h>
- #include <unistd.h>
- #include <sys/ipc.h>
- #include <sys/shm.h>
- #include <sys/types.h>
- #include <sys/sem.h>
- #define READ 0
- #define WRITE 1
- #define N 2
- union semun{
- int val;
- struct semid_ds *buf;
- unsigned short *array;
- struct seminfo *__buf;
- };
- //初始化信号灯集中的信号灯的值
- int my_sem_init(int semid)
- {
- int i = 0;
- union semun mysemun;
- for(i = 0;i < N;i ++)
- {
- mysemun.val = i;
- if(semctl(semid,i,SETVAL,mysemun) < 0)
- {
- perror("Fail to semctl");
- return -1;
- }
- }
- return 0;
- }
- //信号灯集中的信号灯释放资源
- int my_sem_post(int semid,int sem_num)
- {
- struct sembuf op;
- op.sem_num = sem_num;
- op.sem_op = 1;
- op.sem_flg = 0;
- if(semop(semid,&op,1) < 0)
- {
- perror("Fail to semop");
- return -1;
- }
- return 0;
- }
- //信号灯集中的信号灯申请资源
- 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(semid,&op,1) < 0)
- {
- perror("Fail to semop");
- return -1;
- }
- return 0;
- }
- //读共享内存
- int read_share_memory(int semid,char *addr)
- {
- int n;
- while(1)
- {
- my_sem_wait(semid,READ);
-
- printf("Read : %s.\n",addr);
- my_sem_post(semid,WRITE);
- if(strncmp(addr,"quit",4) == 0)
- {
- if(shmdt((void *)addr) < 0)
- {
- perror("Fail to semdt");
- return -1;
- }
-
- break;
- }
- }
-
- return 0;
- }
- int main(int argc,char *argv[])
- {
- key_t key;
- void *shmaddr;
- int shmid,semid;
-
- if(argc < 2)
- {
- fprintf(stderr,"usage : %s argv[1].\n",argv[0]);
- exit(EXIT_FAILURE);
- }
-
- //获取键值
- if((key = ftok(argv[1],'a')) < 0)
- {
- perror("Fail to ftok");
- exit(EXIT_FAILURE);
- }
-
- //创建共享内存
- if((shmid = shmget(key,1024,IPC_CREAT | 0666)) < 0)
- {
- perror("Fail to shmget");
- exit(EXIT_FAILURE);
- }
- //映射共享内存到进程地址空间
- if((shmaddr = shmat(shmid,NULL,0)) == (void *)-1)
- {
- perror("Fail to shmat");
- exit(EXIT_FAILURE);
- }
-
- //创建含有2个的信号灯的信号灯集
- if((semid = semget(key,N,IPC_CREAT | 0666)) < 0)
- {
- perror("Fail to shmget");
- exit(EXIT_FAILURE);
- }
- //初始化信号灯集中的信号灯
- my_sem_init(semid);
-
- read_share_memory(semid,(char *)shmaddr);
- exit(EXIT_SUCCESS);
- }
写共享内存- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <errno.h>
- #include <unistd.h>
- #include <sys/ipc.h>
- #include <sys/shm.h>
- #include <sys/types.h>
- #include <sys/sem.h>
- #define READ 0
- #define WRITE 1
- #define N 2
- union semun{
- int val;
- struct semid_ds *buf;
- unsigned short *array;
- struct seminfo *__buf;
- }mysemun;
- //初始化信号灯集中的信号灯的值
- int my_sem_init(int semid)
- {
- int i = 0;
- for(i = 0;i < N;i ++)
- {
- mysemun.val = i;
- if(semctl(semid,i,SETVAL,mysemun) < 0)
- {
- perror("Fail to semctl");
- return -1;
- }
- }
- return 0;
- }
- //信号灯集中的信号灯释放资源
- int my_sem_post(int semid,int sem_num)
- {
- struct sembuf op;
- op.sem_num = sem_num;
- op.sem_op = 1;
- op.sem_flg = 0;
- if(semop(semid,&op,1) < 0)
- {
- perror("Fail to semop");
- return -1;
- }
- return 0;
- }
- //信号灯集中的信号灯申请资源
- 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(semid,&op,1) < 0)
- {
- perror("Fail to semop");
- return -1;
- }
- return 0;
- }
- //读共享内存
- int read_share_memory(int semid,char *addr)
- {
- int n;
- while(1)
- {
- my_sem_wait(semid,WRITE);
- printf(">");
- fgets(addr,1024,stdin);
- addr[strlen(addr)-1] = '\0';
- my_sem_post(semid,READ);
- if(strncmp(addr,"quit",4) == 0)
- {
- if(shmdt((void *)addr) < 0)
- {
- perror("Fail to semdt");
- return -1;
- }
-
- break;
- }
- }
-
- return 0;
- }
- int main(int argc,char *argv[])
- {
- key_t key;
- void *shmaddr;
- int shmid,semid;
-
- if(argc < 2)
- {
- fprintf(stderr,"usage : %s argv[1].\n",argv[0]);
- exit(EXIT_FAILURE);
- }
-
- //获取键值
- if((key = ftok(argv[1],'a')) < 0)
- {
- perror("Fail to ftok");
- exit(EXIT_FAILURE);
- }
-
- //创建共享内存
- if((shmid = shmget(key,1024,IPC_CREAT | 0666)) < 0)
- {
- perror("Fail to shmget");
- exit(EXIT_FAILURE);
- }
- //映射共享内存到进程地址空间
- if((shmaddr = shmat(shmid,NULL,0)) == (void *)-1)
- {
- perror("Fail to shmat");
- exit(EXIT_FAILURE);
- }
-
- //创建含有2个的信号灯的信号灯集
- if((semid = semget(key,N,IPC_CREAT | 0666)) < 0)
- {
- perror("Fail to shmget");
- exit(EXIT_FAILURE);
- }
- //初始化信号灯集中的信号灯
- my_sem_init(semid);
-
- read_share_memory(semid,(char *)shmaddr);
- if(shmctl(shmid,IPC_RMID,NULL) < 0)
- {
- perror("Fail to shmctl");
- return -1;
- }
- if(semctl(semid,0,IPC_RMID,0) < 0)
- {
- perror("Fail to semctl WRITE");
- return -1;
- }
- exit(EXIT_SUCCESS);
- }
阅读(1094) | 评论(0) | 转发(0) |