信号量:
信号量(也叫信号灯)是一种用于提供不同进程间或一个给定进程的不同线程间同步手段的原语。信号量是进程/线程同步的一种方式,有时候我们需要保护一段代码,使它每次只能被一个执行进程/线程运行,这种工作就需要一个二进制开关;有时候需要限制一段代码可以被多少个进程/线程执行,这就需要用到关于计数信号量。信号量开关是二进制信号量的一种逻辑扩展,两者实际调用的函数都是一样。 |
信号量分为以下三种:
1、System V信号量,在内核中维护,可用于进程或线程间的同步,常用于进程的同步。
2、Posix有名信号量,一种来源于POSIX技术规范的实时扩展方案(POSIX Realtime Extension),可用于进程或线程间的同步,常用于线程。
3、Posix基于内存的信号量,存放在共享内存区中,可用于进程或线程间的同步。
为了获得共享资源进程需要执行下列操作:
(1)测试控制该资源的信号量。
(2)若信号量的值为正,则进程可以使用该资源。进程信号量值减1,表示它使用了一个资源单位。此进程使用完共享资源后对应的信号量会加1。以便其他进程使用。
(3)若信号量的值为0,则进程进入休息状态,直至信号量值大于0。进程被唤醒,返回第(1)步。
为了正确地实现信号量,信号量值的加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, …);
|
|
函数semget创建一个信号量集或访问一个已存在的信号量集。返回值:成功时,返回一个称为信号量标识符的整数,semop 和 semctl 会使用它;出错时,返回-1.
参数key是唯一标识一个信号量的关键字,如果为 IPC_PRIVATE 值为0,创建一个只有创建者进程才可以访问的信号量,通常用于父子进程之间;非0值的key(可以通过ftok函数获得 )表示创建一个可以被多个进程共享的信号量;
参数nsems指定需要使用的信号量数目。如果是创建新集合,则必须指定nsems。如果引用一个现存的集合,则将nsems指定为0.
参数flag是一组标志,其作用与open函数的各种标志很相似。它低端的九个位是该信号量的权限,其作用相当于文件的访问权限。此外,它们还可以与键值IPC_CREAT按位或操作,以创建一个新的信号量。即使在设置了IPC_CREAT标志后给出的是一个现有的信号量的键字,也并不是一个错误。我们也可以通过 IPC_CREAT 和 IPC_EXCL 标志的联合使用确保自己将创建出一个新的独一无二的信号量来,如果该信号量已经存在,就会返回一个错误。
|
|
函数semop用于改变信号量对象中各个信号量的状态。返回值:成功时,返回0;失败时,返回-1.
参数semid是由semget返回的信号量标识符。
参数sops是指向一个结构体数组的指针。每个数组元素至少包含以下几个成员:
|
|
函数semctl用来直接控制信号量信息。函数返回值:成功时,返回0;失败时,返回-1.
参数semid是由semget返回的信号量标识符。
参数semnum为集合中信号量的编号,当要用到成组的信号量时,从0开始。一般取值为0,表示这是第一个也是唯一的一个信号量。
参数cmd为执行的操作。通常为:IPC_RMID(立即删除信号集,唤醒所有被阻塞的进程(后面一个参数就为NULL))、GETVAL(根据semun返回信号量的值,从0开始,第一个信号量编号为0)、SETVAL(根据semun设定信号的值,从0开始,第一个信号量编号为0)、GETALL(获取所有信号量的值,第二个参数为0,将所有信号的值存入semun.array中)、SETALL(将所有semun.array的值设定到信号集中,第二个参数为0)等。( IPC_STAT 获取信号量信息,IPC_SET 设置信号量信息)
参数…是一个union semun(需要由程序员自己定义),它至少包含以下几个成员:
|
add1.c add2.c | printf.c | start.sh(脚本文件) | zero.c |
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
int main()
{
int shmid=shmget((key_t)1234,20,IPC_CREAT|0600);
if(-1==shmid)
{
perror("shmget");
return -1;
}
int *i;
i=(int *)shmat(shmid,NULL,0);
int j;
for(j=0;j<1e7;j++)
{
(*i)++;
}
return 0;
}
|
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
int main()
{
int shmid=shmget((key_t)1234,20,IPC_CREAT|0600);
if(-1==shmid)
{
perror("shmget");
return -1;
}
int *i;
i=(int *)shmat(shmid,NULL,0);
printf("%d\n",*i);
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
|
./zero
./add1 &(后台运行)
./add2
sleep 1;
./printf
(运行前要加chmod +x start.sh )
|
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
int main()
{
int shmid=shmget((key_t)1234,20,IPC_CREAT|0600);
if(-1==shmid)
{
perror("shmget");
return -1;
}
int *i;
i=(int *)shmat(shmid,NULL,0);
*i=0;
return 0;
}
|
ipcrm - s 信号量id 删除信号量
semget.c | |
#include<sys/sem.h>
#include<sys/ipc.h>
#include<sys/types.h>
#include<stdio.h>
int main()
{
int semid=semget((key_t)1234,1,IPC_CREAT|0600); //创建信号量集,就一个信号量,没有就创建,权限为0600,以后要引用就是0
if(-1==semid)
{
perror("semget");
return -1;
}
int ret=semctl(semid,0,SETVAL,5); //0为信号量编号(从0开始),表示第一个,也是唯一的信号量,设置信号量值为5
if(-1==ret)
{
perror("semctl");
}
printf("ret=%d\n",ret);
ret =semctl(semid,0,GETVAL,NULL); //查看刚才设置的值是不是为1,NULL可以不写
printf("ret=%d\n",ret);
return 0;
}
|
改进:
add1.c add2.c | |
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/sem.h>
int main()
{
int shmid=shmget((key_t)1234,20,IPC_CREAT|0600);
if(-1==shmid)
{
perror("shmget");
return -1;
}
int *i;
i=(int *)shmat(shmid,NULL,0);
//*i=0;
int j;
int semid=semget((key_t)1234,1,IPC_CREAT|0600); //创建一个信号量集,信号量个数为1
struct sembuf sp;
memset(&sp,0,sizeof(sp));
sp.sem_num=0; //信号量在信号量集合中的编号,从0开始编号
sp.sem_op=-1; //信号量在一次操作中要改变的值,-1表示p操作
sp.sem_flg=SEM_UNDO; //表示程序结束,信号量为调用前的值
struct sembuf sv;
memset(&sv,0,sizeof(sv));
sv.sem_num=0;
sv.sem_op=1; //v操作
sv.sem_flg=SEM_UNDO;
int ret = semctl(semid,0,SETVAL,1); //两个加操作只要一个中设置这个就行了,不设置也可以。好像默认1
//必须人为设定
for(j=0;j<200000;j++)
{
semop(semid,&sp,1); //原子操作,不能被打断;(最后一个形参表示前面结构体的个数)
(*i)++;
semop(semid,&sv,1);
}
return 0;
}
|
//运行的时候start.sh中去掉./print(因为两个进程不一定能一起结束),最后在执行./print |
传递多个信号量
semget_more.c | |
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/sem.h>
//创建一个信号量集合,但是集合中有多个信号量
int main()
{
int semid=semget((key_t)1234,2,IPC_CREAT|0600); //2表示两个信号量
if(-1==semid)
{
perror("semget");
return -1;
}
unsigned short array[2]={1,2}; //信号量集,里面有两个信号量,它们的初值分别是1和2
int ret=semctl(semid,0,SETALL,array);
if(-1==ret)
{
perror("semctl");
return -1;
}
memset(array,0,sizeof(array)); //先清空,打算后面再重新获取,看SETALL有没有成功
ret=semctl(semid,0,GETALL,array);
if(-1==ret)
{
perror("semctl");
return -1;
}
printf("array[0]=%d,array[1]=%d\n",array[0],array[1]);
ret = semctl(semid,0,IPC_RMID); //删除信号量 ,最后一个参数不用写
return 0;
}
|
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/sem.h>
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
}arg;
int main()
{
int semid=semget((key_t)1234,2,IPC_CREAT|0600);
if(-1==semid)
{
perror("semget");
return -1;
}
unsigned short array[2]={1,2};
arg.array = array;
//semctl(semid,0,SETALL,arg);
//semctl(semid,0,SETVAL,*(arg.array));
//semctl(semid,1,SETVAL,*(arg.array+1));
semctl(semid,0,SETALL,array);
memset(array,0,sizeof(array));
printf("after memset array[0]=%d,array[1]=%d\n",array[0],array[1]);
int ret=semctl(semid,0,GETALL,array);
if(-1==ret)
{
perror("semctl");
return -1;
}
printf("after get array[0]=%d,array[1]=%d\n",array[0],array[1]);
printf("sem1's value = %d\n",semctl(semid,0,GETVAL));
printf("sem2's value = %d\n",semctl(semid,1,GETVAL));
ret = semctl(semid,0,IPC_RMID);
return 0;
}
|
获取信号量信息打印
semget_more.c | |
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/sem.h>
//创建一个信号量集合,但是集合中有多个信号量
int main()
{
int semid=semget((key_t)1234,2,IPC_CREAT|0600);
if(-1==semid)
{
perror("semget");
return -1;
}
struct semid_ds buf;
memset(&buf,0,sizeof(buf));
int ret=semctl(semid,0,IPC_STAT,&buf);
if(-1==ret)
{
perror("semctl");
return -1;
}
printf("uid=%d,cuid=%d,mode=%o,nsems=%ld\n", buf.sem_perm.uid , buf.sem_perm.cuid , buf.sem_perm.mode , buf.sem_nsems);
return 0;
}
|
printf函数后面添加下列代码修改perm | |
buf.sem_perm.mode=0666;
ret=semctl(semid,0,IPC_SET,&buf);
|
//有亲缘关系的信号量进程间通信 #include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/sem.h>
int main(void)
{
int semid = semget(IPC_PRIVATE,1,IPC_CREAT|0600);
semctl(semid,0,SETVAL,1); //这段代码放到外面和放到各自的进程中都一样,因为它们的semid一样,有点像共享内存
if(fork())
{
// semctl(semid,0,SETVAL,1);
printf("I am father. sem'val =%d\n",semctl(semid,0,GETVAL));
while(1);
}
else
{
// semctl(semid,0,SETVAL,1);
printf("I am father. sem'val =%d\n",semctl(semid,0,GETVAL));
while(1);
}
return 0;
}
|
在生产者源码里,首先用函数semctl()初始化信号量集合sem_id,它包含两个信号,分别表示生产的数量和空仓库的数量,那么在消费者的进程中用相同的key值就会得到该信号量集合;实现两个进程之间的通信。在主函数里,设定对两个信号量的PV操作,然后在各自的进程中对两个信号进行操作。
(1)如果只运行生产者进程,则生产10个之后,该进程就会因为在得不到空仓库资源而阻塞,这个时候运行消费者进程,阻塞就会被解除;
(2)如果先运行生产者进程,生产几个产品之后,关闭该进程,则运行消费者进程,当消费完生产的产品后,该进程就会因为在得不到产品资源而阻塞,这个时候运行生产者进程,阻塞就会被解除;
(3)如果同时运行两个进程,由于消费比生产快,因此消费者每次都要等待生产者生产产品之后才能消费;
在每次运行程序之前,一定要先运行生产者进程先初始化信号量。
|
pro.c | cus.c |
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/sem.h>
int set_val(int semid)
{
unsigned short array[2]={0,10};
int ret=semctl(semid,0,SETALL,array);
if(-1==ret)
{
perror("semctl");
return -1;
}
memset(array,0,sizeof(array));
ret=semctl(semid,0,GETALL,array);
if(-1==ret)
{
perror("semctl");
return -1;
}
printf("array[0]=%d,array[1]=%d\n",array[0],array[1]);
}
int main()
{
int semid=semget((key_t)1234,2,IPC_CREAT|0600);
if(-1==semid)
{
perror("semget");
return -1;
}
set_val(semid);
struct sembuf sp[2];
sp[0].sem_num=0;
sp[0].sem_op=1;
sp[0].sem_flg=SEM_UNDO; //或者等于0
sp[1].sem_num=1;
sp[1].sem_op=-1;
sp[1].sem_flg=SEM_UNDO;
while(1)
{
printf("product num=%d,space num=%d\n",semctl(semid,0,GETVAL),semctl(semid,1,GETVAL));
semop(semid,&sp[1],1);
printf("------------------------\n");
printf("I will produce\n");
semop(semid,&sp[0],1);
printf("product num=%d,space num=%d\n",semctl(semid,0,GETVAL),semctl(semid,1,GETVAL));
printf("************************\n\n\n");
sleep(3);
}
return 0;
}
|
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/sem.h>
int main()
{
int semid=semget((key_t)1234,2,IPC_CREAT|0600);
if(-1==semid)
{
perror("semget");
return -1;
}
struct sembuf sp[2];
sp[0].sem_num=0;
sp[0].sem_op=-1;
sp[0].sem_flg=SEM_UNDO;
sp[1].sem_num=1;
sp[1].sem_op=1;
sp[1].sem_flg=SEM_UNDO;
while(1)
{
printf("product num=%d,space num=%d\n",semctl(semid,0,GETVAL),semctl(semid,1,GETVAL));
semop(semid,&sp[0],1);
printf("------------------------\n");
printf("I will consume\n");
semop(semid,&sp[1],1);
printf("product num=%d,space num=%d\n",semctl(semid,0,GETVAL),semctl(semid,1,GETVAL));
printf("************************\n\n\n");
sleep(3);
}
return 0;
}
|
Makefile |
pro:pro.c cus.c
gcc pro.c -o pro
gcc cus.c -o cus
.PHONY:clean
clean:
rm pro cus
|