进程间通讯——信号量

 信号量:我们先不要把信号和信号量混在一起,信号是系统预先定义好的一些特定的事件。
                而信号量是对于一个资源能被几个进程同时访问的计数器。

举个生活中的例子便于理解这个计数器:就好比一个停车场,门口会有个停车计时系统,进程就好比车子,假设停车场的容量只有100辆车,那我们把这个容量当成可以占用的资源,停一辆车,资源就会被占用,计数器就减1。所以如果停车场已经停了100辆车,那101辆车是不是就不能停了,资源是有限的嘛。除非停车场有车子开出去,不占用这个车位,计数器就加1,101辆车就可以停车了。
信号量 相当于 记录   资源同时被 几个进程访问。
  
 我们先了解底下这几个概念:
1、临界资源:同一时刻,只能被一个进程访问的资源。
2、临界区:访问临界资源的代码区域。
3、原子操作:任何情况下都不能被打断的操作。
4、内核对象:用于对进程间通讯时,多进程能够访问同一资源的记录。

信号量的作用:进程间同步控制
那么进程间同步是什么呢?



















如上面的图所示,A进程的先执行1,然后发现B进程占据了资源,那就只能执行2,等B进程执行完以后,再执行3。就是进程间同步了。那么信号量是如何控制的呢?

我们先看看跟信号量有关的操作:
(1)创建或获取:如果是创建,必须初始化,如果是获取,不用初始化。
          #include<sys/sem.h>
          int     semget ( key_t key,    int nsems,    int flag ) ;

               第一个参数 :key_t key
          每个进程间通讯都用一个非负整数的标识符加以引用,那么这个标识符的身份证就是key,称为“键”。
          键key的数据类型是  key_t,在头文件#include<sys/types.h> 中被定义为长整型。

                当你定义了键,  键由内核会变成标识符去使用。

               那么如何或取键值呢?
           #include<sys/ipc.h>
           key_t ftok(const char *path,int id);
           ftok提供的唯一服务就是由一个路径名和项目id这两个值产生一个键。id是0-255之间的字符值。
        
         第二个参数:表示信号量值的数量
        
         第三个参数:flag  权限以及控制:
                                                      IPC_CREAT  如果存在信号量,则获取。
                                                      如果不存在,则创建。

(2)p 和 v  操作(减一和加一操作)
          #include<sys/sem.h>
            int  semop  ( int smid, struct sembuf *buf ,  int  lenth )
            第一个参数:semget()函数的返回值,也就是信号量ID。
            第二个参数:是一个结构体指针,指向信号量操作数组,那么这个结构体是:
                                 struct  sembuf
                                  {
                                          short    sem_num;//信号量在信号量集中的索引,0代表第一个信号量,1代表第二个...
                                          short    sem_op; //操作类型
                                          short    sem_flg;//操作标志
                                  }


          第三个参数:数组元素的个数。
(3)删除和初始化操作
         #include <sys/sem.h>
         int  semctl(int semid,   int semun  ,int cmd,    /*union semun arg*/)
          第一个参数:semget()函数的返回值,也就是信号量ID。
          第二个参数:是联合体semun 的成员val:
                                  union   semun
                                    {
                                           int val;
                                           struct   semid_ds  *buf;
                                           unsigned short    *array;
                                    }

         第三个参数:cmd  常用的为初始化使用:  IPC_STAT
                                                      删除使用   :   IPC_RMID
         第四个参数:这个参数是可选用的,如果使用的话,就是联合体 union semun。

       信号量基本的操作就是这些了,你一定要先搞懂这些函数的参数含义,才能使用。
 实例:在A进程中用户循环输入,当输入ok的时候,B进程打印100以内的素数,然后用户在A进程中输入end结束操作。

   那我们肯定会用到信号量的东西,你想当用户输入ok,B进程才输出,那么信号量该如何操控呢?是不是跟我们前面画的进程同步图是一样的操作呢?
  哦,那如果A进程用户输入ok的时候,A进程不占用资源,B进程占用资源,等B进程把资源释放,或者B进程执行完毕,A进程再接着执行。
     那是不是会用到 P  V  操作呢?
          p操作:就是占用一个资源,如果p操作成功,申请一个空闲资源(信号量-1),如果失败,这个进程会被阻塞。
         v操作:就是释放一个被所占用的资源(信号量+1),负责把一个被阻塞的进程唤醒。
        我们先思考思考。


代码实现:封装信号量操作函数
sem.h
#include<stdio.h>
#include<assert.h>
#include<string.h>
#include<stdlib.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<sys/types.h>

int semmid;
union semun
{
    int val;
	struct semid_ds  *buf;
    unsigned short *arry;
};

void sem_get();//获取和创建信号量

void sem_p();//p操作

void sem_v();//v操作

void sem_del();//删除信号量



sem.c
#include "sem.h"
int semid;
void sem_get()
{
    semid=semget((key_t)1234,1,0664);//用长整型数据1234,然后强转为key_t.设置信号量个数为1
	if(semid==-1)
	{
	    semid=semget((key_t)1234,1,0664|IPC_CREAT);//如果没有找到信号量,再次创建,必须初始化
		assert(semid!=-1);

		union semun a;           
		a.val=0;
		if(semctl(semid,0,SETVAL,a)==-1)  
		{
		    perror("error\n");
			exit(0);
		}
	}
}

void sem_p()
{
    struct sembuf buf;
	buf.sem_num=0;//表示第一个信号量
	buf.sem_op=-1;//信号操作类型
	buf.sem_flg=SEM_UNDO;//操作标志

	if(semop(semid,&buf,1)==-1)
	{
	    perror("error\n");
		exit(0);
	}
}

void sem_v()
{
    struct sembuf buf;
	buf.sem_num=0;
	buf.sem_op=1;
	buf.sem_flg=SEM_UNDO;

	if(semop(semid,&buf,1)==-1)
	{
	    perror("error\n");
		exit(0);
	}
}


void sem_del()//删除信号量
{
    if(semctl(semid,0,IPC_RMID)==-1)
	{
	    perror("error\n");
		exit(0);
	}

}

进程A:
#include "sem.h"
#include<string.h>
#include<stdio.h>
#include<unistd.h>

int main()
{
	sem_get();
	char buff[128]={0};
    while(1)
	{
	    fgets(buff,128,stdin);
	    buff[strlen(buff)-1]=0;
		
		if(strncmp(buff,"ok",2)==0)
		{
		    sem_v();
		}

		if(strncmp(buff,"end",3)==0)
		{
		    break;
		}
	}
}

进程B
#include "sem.h"
#include<stdio.h>
#include<string.h>
#include<unistd.h>

int main()
{
    sem_get();
    sem_p();
	int i;
	int j;
	int flag=0;
	for(i=1;i<100;i++)
	{
		flag=0;
	    for(j=2;j<i;j++)
		{
		    if(i%j==0)
			{
			    flag=1;
				break;
			}
		}
		if(flag==0)
		
		    printf("%d  ",i);
		
	}
	sem_v();
}

结果测试:





那么进程间异步呢?

还记得上面那个union   semun  a 联合体的val成员吗?当a.val=0时,A进程服务B进程,当a.val>0时,A,B进程之间是竞争状态。也就是A,B进程同时进行P操作。


猜你喜欢

转载自blog.csdn.net/lyt15829797751/article/details/78341935