信号量分为以下三种:
1、System V信号量,在内核中维护,可用于进程或线程间的同步,常用于进程的同步。
2、Posix有名信号量,一种来源于POSIX技术规范的实时扩展方案(POSIX Realtime Extension),可用于进程或线程间的同步,常用于线程。
3、Posix基于内存的信号量,存放在共享内存区中,可用于进程或线程间的同步。
为了获得共享资源进程需要执行下列操作:
(1)测试控制该资源的信号量。
(2)若信号量的值为正,则进程可以使用该资源。进程信号量值减1,表示它使用了一个资源单位。此进程使用完共享资源后对应的信号量会加1。以便其他进程使用。
(3)若信号量的值为0,则进程进入休息状态,直至信号量值大于0。进程被唤醒,返回第(1)步。
为了正确地实现信号量,信号量值的测试值的测试及减1操作应当是原子操作(原子操作是不可分割的,在执行完毕前不会被任何其它任务或事件中断)。为此信号量通常是在内核中实现的。
System V IPC机制:信号量。
函数原型:
#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/types.h>
int semget(key_t key,int nsems,int flag);
int semop(int semid,struct sembuf *sops,size_t num_sops);
int semctl(int semid, int semnum, int cmd, …);
一、示例:程序被打断出错
//创建、初始化共享内存
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <sys/shm.h>
#define PROJ_ID 1
#define TIMES 1000000
int main(int argc,char* argv[])
{
if(argc!=2)
{
printf("error args\n");
return -1;
}
key_t skey;
skey=ftok(argv[1],PROJ_ID);
if(-1==skey)
{
perror("ftok");
return -1;
}
printf("the key is %d\n",skey);
int shmid;
shmid=shmget(skey,1<<12,0600|IPC_CREAT);//创建共享内存
if(-1==shmid)
{
perror("shmget");
return -1;
}
printf("the shmid is %d\n",shmid);
int* p;
p=shmat(shmid,NULL,0);
if((int*)-1==p)
{
perror("shmat");
return -1;
}
*p=0;//初始化共享内存
return 0;
}
//add1.c&&add2.c分别将内存中的数加10000000次1
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <sys/shm.h>
#define PROJ_ID 1
#define TIMES 10000000
int main(int argc,char* argv[])
{
if(argc!=2)
{
printf("error args\n");
return -1;
}
key_t skey;
skey=ftok(argv[1],PROJ_ID);
if(-1==skey)
{
perror("ftok");
return -1;
}
// printf("the key is %d\n",skey);
int shmid;
shmid=shmget(skey,1<<12,0600|IPC_CREAT);
if(-1==shmid)
{
perror("shmget");
return -1;
}
// printf("the shmid is %d\n",shmid);
int* p;
p=shmat(shmid,NULL,0);
if((int*)-1==p)
{
perror("shmat");
return -1;
}
int i;
for(i=0;i<TIMES;i++)
{
*p=(*p)+1;
}
printf("the value is %d\n",*p);
return 0;
}
两个相同的程序,功能是将共享内存中的数加10000000次1,正确的结果应该是20000000
同时运行(用脚本实现)后的结果却为:
说明在两个程序并行的时候语句被打断。
二、semget()函数
参数flag是一组标志,其作用与open函数的各种标志很相似。它低端的九个位是该信号量的权限,其作用相当于文件的访问权限。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
int main()
{
int semid;
semid=semget((key_t)1234,10,0666|IPC_CREAT);
printf("the semid is %d\n",semid);
return 0;
}
运行结果:
三、semctl()函数
1.参数semid是由semget返回的信号量标识符。
2.参数semnum为集合中信号量的编号,当要用到成组的信号量时,从0开始。一般取值为0,表示这是第一个也是唯一的一个信号量。
3.参数cmd为执行的操作。通常为:IPC_RMID(立即删除信号集,唤醒所有被阻塞的进程)、GETVAL(根据semun返回第GETVAL-1个信号的值,从0开始,第一个信号量编号为0)、SETVAL(根据semun设定信号的值,从0开始,第一个信号量编号为0)、GETALL(获取所有信号量的值,第二个参数为0,将所有信号的值存入semun.array中)、SETALL(将所有semun.array的值设定到信号集中,第二个参数为0)等。
参数…是一个union semun(需要由程序员自己定义),它至少包含以下几个成员:
union semun{
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
};
通常情况仅使用val,给val赋值为1
在生产者源码里,首先用函数semctl()初始化信号量集合sem_id,它包含两个信号,分别表示生产的数量和空仓库的数量,那么在消费者的进程中用相同的key值就会得到该信号量集合;实现两个进程之间的通信。
在主函数里,设定对两个信号量的PV操作,然后在各自的进程中对两个信号进行操作。
(1)如果只运行生产者进程,则生产10个之后,该进程就会因为在得不到空仓库资源而阻塞,这个时候运行消费者进程,阻塞就会被解除;
(2)如果先运行生产者进程,生产几个产品之后,关闭该进程,则运行消费者进程,当消费完生产的产品后,该进程就会因为在得不到产品资源而阻塞,这个时候运行生产者进程,阻塞就会被解除;
(3)如果同时运行两个进程,由于消费比生产快,因此消费者每次都要等待生产者生产产品之后才能消费;
在每次运行程序之前,一定要先运行生产者进程先初始化信号量。
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>
int main()
{
int semid;
semid=semget((key_t)1234,1,0666|IPC_CREAT);//IPC_CREAT创建信号量
if(-1==semid)
{
perror("semget");
return -1;
}
int ret;
ret=semctl(semid,0,GETVAL);
if(-1==ret)
{
perror("semctl");
return -1;
}
printf("ret is %d\n",ret);
return 0;
}
运行结果:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>
int main()
{
int semid;
semid=semget((key_t)1234,1,0600|IPC_CREAT);
if(-1==semid)
{
perror("semget");
return -1;
}
printf("the semid is %d\n",semid);
int ret;
ret=semctl(semid,0,GETVAL);
if(-1==ret)
{
perror("semctl");
return -1;
}
printf("ret is %d\n",ret);
ret=semctl(semid,0,SETVAL,1);//将信号量的值置1
if(-1==ret)
{
perror("semctl");
return -1;
}
ret=semctl(semid,0,GETVAL);
if(-1==ret)
{
perror("semctl");
return -1;
}
printf("after ret is %d\n",ret);
return 0;
}
//信号量集合操作
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int main()
{
int semid;
semid=semget((key_t)1234,3,0600|IPC_CREAT);
if(-1==semid)
{
perror("semid");
return -1;
}
int ret;
unsigned short arr[3];
ret=semctl(semid,0,GETALL,arr);
if(-1==ret)
{
perror("semctl");
return -1;
}
printf("1sem=%d,2sem=%d,3sem=%d\n",arr[0],arr[1],arr[2]);
return 0;
}
<pre name="code" class="cpp">#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <strings.h>
int main()
{
int semid;
semid=semget((key_t)1234,3,0600|IPC_CREAT);
if(-1==semid)
{
perror("semid");
return -1;
}
int ret;
unsigned short arr[3];
ret=semctl(semid,0,GETALL,arr);
if(-1==ret)
{
perror("semctl_get");
return -1;
}
printf("1sem=%d,2sem=%d,3sem=%d\n",arr[0],arr[1],arr[2]);
arr[0]=1;
arr[1]=2;
arr[2]=3;
ret=semctl(semid,0,SETALL,arr);
if(-1==ret)
{
perror("semctl_set");
return -1;
}
bzero(arr,sizeof(arr));
ret=semctl(semid,0,GETALL,arr);
if(-1==ret)
{
perror("semctl_get");
return -1;
}
printf("1sem=%d,2sem=%d,3sem=%d\n",arr[0],arr[1],arr[2]);
return 0;
}
四、semop()函数
参数semid是由semget返回的信号量标识符。
参数sops是指向一个结构体数组的指针。每个数组元素至少包含以下几个成员:
struct sembuf{
short sem_num; //操作信号量在信号量集合中的编号,第一个信号量的编号是0。
short sem_op; //sem_op成员的值是信号量在一次操作中需要改变的数值。通常只会用到两个值,一个是-1,也就是p操作,它等待信号量变为可用;一个是+1,也就是v操作,它发送信号通知信号量现在可用。
short sem_flg; //通常设为:SEM_UNDO,程序结束,信号量为semop调用前的值。
};
参数nops为sops指向的sembuf结构体数组的大小。
//初始化init.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define PROJ_ID 1
int main(int argc,char* argv[])
{
if(argc!=2)
{
printf("error args\n");
return -1;
}
key_t skey;
skey=ftok(argv[1],PROJ_ID);
if(-1==skey)
{
perror("ftok");
return -1;
}
printf("the key is %d\n",skey);
int shmid;
shmid=shmget(skey,1<<12,0600|IPC_CREAT);
if(-1==shmid)
{
perror("shmget");
return -1;
}
printf("the shmid is %d\n",shmid);
int *p;
p=(int*)shmat(shmid,NULL,0);
if((int*)-1==p)
{
perror("shmat");
return -1;
}
*p=0;
printf("the value is %d\n",*p);
return 0;
}
//两个相同的加法程序add1.c&add2.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <strings.h>
#define TIMES 10000000
#define PROJ_ID 1
int main(int argc,char* argv[])
{
if(argc!=2)
{
printf("error args\n");
return -1;
}
key_t skey;
skey=ftok(argv[1],PROJ_ID);
if(-1==skey)
{
perror("skey");
return -1;
}
printf("the skey is %d\n",skey);
int shmid;
shmid=shmget(skey,1<<12,0600|IPC_CREAT);
if(-1==shmid)
{
perror("shmget");
return -1;
}
printf("the shmid is %d\n",shmid);
int* p;
p=(int*)shmat(shmid,NULL,0);
if((int*)-1==p)
{
perror("shmat");
return -1;
}
int semid;
semid=semget((key_t)1234,0,0600);
if(-1==semid)
{
perror("semget");
return -1;
}
struct sembuf sop;
bzero(&sop,sizeof(sop));
sop.sem_num=0;
sop.sem_op=-1;
sop.sem_flg=SEM_UNDO;
struct sembuf sov;
bzero(&sov,sizeof(sov));
sov.sem_num=0;
sov.sem_op=1;
sov.sem_flg=SEM_UNDO;
int i;
for(i=0;i<TIMES;i++)
{
semop(semid,&sop,1);
*p=(*p)+1;
semop(semid,&sov,1);
}
printf("the value is %d\n",*p);
return 0;
}
输出结果: