system v 信号量的使用 2019.8.25

代码000:叮铃铃,摇晃的铃铛。PV操作,生产者,消费者。

int semget(key_t key,int nsems,int semflg);//得到关键做名字,须定个数许定权。

西姆拿着钥匙,可以打开很多个数门,但是有的门上有几把锁,有多把锁的有的可以打开,有的打不开。

int semctl(int semid,int semnum,int cmd,union semun);//控制标志数定位,十命不同改枚举。枚举四值值为首,信号结构排第二。多个数值用数组,信号信息存最后。命令图像:号角

她冲进指定的房间,吹响命令的号角,打开装着梅子的包。

int semop (int semid,struct sembuf *sops,unsigned nsops);//操作序号要指定,缓存结构须写明,最后一个可写0。

她拿着钥匙缓了口气,吃了个蛋。

缓冲结构要定位,排行第几要说清,信号量值变多少,sem_op来说明,最后一个标志位,阻塞撤销可指定。

union semun  //尺量结构,组长缓冲。
{
   int val;  //值 -- 瓦妞
   struct semid_ds* buf;//信号量结构(semid_ds data structure)
   unsigned short* array;//值数组
   struct seminfo *buf;//IPC_INFO缓存
}

struct semid_ds  //拳打OC(日月)数个数。
{
   struct ipc_perm sem_perm;//权限
   time_t  sem_otime;// 记录最后一次执行PV操作的时间(调用semop)
   time_t  sem_ctime;//信号量集最后一次改变的时间(调用semctl)
   unsigned long sem_nsems;//信号量集中信号量的个数
};

struct sembuf
{
 short sem_num;//要操作的第几个信号量
 short sem_op;//是P操作还是V操作
 short sem_flg;//可以是IPC_NOWAIT,SEM_UNDO,0
};


============================================================================

信号量是由迪杰斯特拉提出来的,没错就是那个搞算法的家伙。

PV操作

提到信号量,不得不说PV操作:

信号量的PV操作:

P(s)
{
  s.value--; //消耗资源?
  if(s.value<0)
    {
        将当前进程置位等待状态,
        并插入等待队列s.queue;
    }
}

V(s)
{
s.value++;//归还资源
if(s.value <=0)
  {

      唤醒相应等待的队列s.queue中的一个进程,
      将其改变为就绪态,
      并将其插入就绪队列。
  }
}

生产者与消费者示例:
semaphore full=0;//有多少可用资源
semaphore empty=BUFFER_SIZE;//资源放置容器,满后不能生产资源
semaphore mutex=1;//独占锁
Producer(item)
{
    P(empty);
    P(mutex);
    生产资源
    V(mutex);
    V(full);
}
Consumer()
{
    P(full);
    P(mutex);
    消耗资源
    V(mutex);
    V(empty);
}

信号量的PV操作:

在同一个进程中主要是为了解决资源互斥的问题,在不同的进程中时为了解决同步的问题。

信号量的含义:

S>0:S表示可以使用的资源的数量。

S=0;表示无可用资源,也无等待进程。

S<0;S的绝对值就是表示在等待资源的进程数量。

信号量函数

Int semget(key_t key,int nsems,int semflg);

Key:信号集的名字

Nsems:信号集中信号量的个数

Semflg:由九个权限标志构成

返回值:失败返回-1,成功返回信号量集标志码(这个标志码是怎么来的?)

Semctl函数用于控制信号量集:

Int semctl(int semid,int semnum,int cmd,…);

 

参数:

Semid:由semget返回的信号量的标志码,semget返回值

Semnum:信号量集中的信号量的数量

Cmd:将要采取的动作:(有10个可以取的值)

命令

解   释

IPC_STAT

从信号量集上检索semid_ds结构,并存到semun联合体参数的成员buf的地址中

IPC_SET

设置一个信号量集合的semid_ds结构中ipc_perm域的值,并从semun的buf中取出值

IPC_RMID

从内核中删除信号量集合

GETALL

从信号量集合中获得所有信号量的值,并把其整数值存到semun联合体成员的一个指针数组中

GETNCNT

返回当前等待资源的进程个数

GETPID

返回最后一个执行系统调用semop()进程的PID

GETVAL

返回信号量集合内单个信号量的值

GETZCNT

返回当前等待100%资源利用的进程个数

SETALL

与GETALL正好相反

SETVAL

用联合体中val成员的值设置信号量集合中单个信号量的值

…:最后一个参数根据命令不同而不同。

传入的结构体为:

union semun 
{
  int              val;    /* Value for SETVAL 最大为32767*/ 
  struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
  unsigned short  *array;  /* Array for GETALL, SETALL */
  struct seminfo  *__buf;  /* Buffer for IPC_INFO*/
};

struct semid_ds

{

struct ipc_perm sem_perm;

time_t  sem_otime;// 记录最后一次执行PV操作的时间

time_t  sem_ctime;//信号量集最后一次改变的时间

unsigned long sem_nsems;//信号量集中信号量的个数

};

返回值:成功0,失败-1;

semop函数:用来创建和访问一个信号量集,这个就是进行PV操作的。

 semop (int semid,struct sembuf *sops,unsigned nsops);

 semid:由semget返回的信号量的标志码,semget返回值

 sops 是一个指向一个结构数值struct sembuf的指针

 nsops:信号量的个数

返回值:0成功,-1失败。

这个函数可以传入一个枚举变量:

struct sembuf
{
 short sem_num;//要操作的第几个信号量
 short sem_op;//是P操作还是V操作
 short sem_flg;//可以是IPC_NOWAIT,SEM_UNDO,0
};

这个结构体可以在信号量上执行各种操作:

在一个信号量上可以
执行各种操作(即系统调用),包括:
1.将信号量设置成一个绝对值;
2.在信号量当前值的基础上加上一个数量;
3.在信号量当前值的基础上减去一个数量;
4.等待信号量的值等于 0;

后两个可能会导致调用进程阻塞,如果使用参数IPC_NOWAIT的话就直接返回失败。

使用SEM_UNDO会在进程结束的时候撤销PV操作。(这里有坑,如果在执行semop后又使用semctl设置了信号量的值呢?)

在多个同级进程中,如果使用信号量,一般是先semget创建,semctl初始化,再之后各个进程semop,那么这样就会形成一个竞态。

可以使用semid_ds 数据结构中的 sem_otime 字段的初始化。在一个信号量集首次被创建时, sem_otime
字段会被初始化为 0,并且只有后续的 semop()调用才会修改这个字段的值。因此可以利用这
个特性来消除上面描述的竞争条件。

还有多个信号量之间发生阻塞时,会使用银行家算法来避免死锁问题。

下面的例子是使用信号量作为互斥锁:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
#include <stdio.h>
#include <thread>

union semun 
{
  int              val;    /* Value for SETVAL */ // 这个才是要设置的值
  struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
  unsigned short  *array;  /* Array for GETALL, SETALL */
  struct seminfo  *__buf;  /* Buffer for IPC_INFO*/
};

class sem_lock{
	public:
	sem_lock():m_semid(-1){};
		
	int sem_create(key_t key);
	int sem_setval(int val);
	int sem_getmode();
	int sem_getval();
	int sem_p();
	int sem_v();
	~sem_lock()
	{
	  semctl(m_semid, 0, IPC_RMID);	
	}
	private:
	  int m_semid;
};


int sem_lock::sem_getval()
{
	int ret;
	ret=semctl(m_semid,0,GETVAL,0);//semid表示信号量标志码,0:对信号量集中第0个信号量进行操作,GETVAL:表示要设置值,直接返回信号的值。
	
	if(ret==-1)
	{
	  	perror("sem_getval failed");
	}
	return ret;
}

int sem_lock::sem_create(key_t key)
{
	m_semid=semget(key,1,IPC_CREAT|IPC_EXCL|0666);
	
	if(m_semid==-1)
	{
	    perror("sem_create failed");
	}
	return m_semid;
}
int sem_lock::sem_setval(int val)
{
	union semun su;
	su.val=val;
	int ret;
	ret=semctl(m_semid,0,SETVAL,su);//semid表示信号量标志码,0:对信号量集中第0个信号量进行操作,SETVAL:表示要设置值,su:设置的值。
	
	if(ret==-1)
	{
	    perror("sem_setval failed");
	}
	return ret;
}

int sem_lock::sem_p()
{
	struct sembuf sops={0,-1,0};//0:对信号量集中的第一个信号量进行操作,-1:P操作,
	int ret;
	ret = semop(m_semid,&sops,1);//第二个参数可以指向一个数组,就是可以对多个信号量进行操作。第三个参数是表示只操作一个信号量
	if(ret==-1)
	{
	    perror("sem_p failed");
	}	
	return ret;	
}
	
int sem_lock::sem_v()
{
	struct sembuf sops={0,1,0};//0:对信号量集中的第一个信号量进行操作,1:V操作,
	int ret;
	ret = semop(m_semid,&sops,1);//第二个参数可以指向一个数组,就是可以对多个信号量进行操作。第三个参数是表示只操作一个信号量
	if(ret==-1)
	{
	    perror("sem_v failed");
	}	
	return ret;		
}

int sem_lock::sem_getmode()
{
	union semun su;
	struct semid_ds sem;
	su.buf=&sem;
	int ret =semctl(m_semid,0,IPC_STAT,su);
	
	if(ret == -1)
	{
	    perror("sem_getmode failed");
	}
		
	printf("permissions: %o",su.buf->sem_perm.mode);
}

int g_res=99999;
int g_first=0;
int g_second=0;
int g_semid=0;

sem_lock lock;


void proc_first()
{
        while(g_res>0)
        {
          lock.sem_p();
          g_res?--g_res,++g_first:0;
          lock.sem_v();
        }
}

void proc_second()
{
    while(g_res>0)
        {
          lock.sem_p();
          g_res?--g_res,++g_second:0;
          lock.sem_v();
        }
}


int main()
{

    lock.sem_create(1234);
    lock.sem_setval(1);
    std::thread first(proc_first);
    std::thread second(proc_second);
    first.join();
    second.join();
    printf("%d\n",g_first+g_second);
    return 0;	
}

编译输出:

g++ -std=c++11   sem.cpp -o sem -lpthread

./sem 

99999

===============助记

1.semget-广场:有名字,大小,权限(是否公开)

2.semctl:天空:领空的名字,控制哪个烟花,何时炸(将要采取的动作),炸包含的信息

union semun

{

   int val;  //

struct semid_ds* buf;//信号量结构(semid_ds data structure)

unsigned short* array;//值数组

struct seminfo *buf;//IPC_INFO缓存

}

 

尺量结构,组长缓冲。

 

信号量结构:

struct semid_ds

{

       struct ipc_perm sem_perm;//权限

time_t  sem_otime;// 记录最后一次执行PV操作的时间(调用semop)

time_t  sem_ctime;//信号量集最后一次改变的时间(调用semctl)

unsigned long sem_nsems;//信号量集中信号量的个数

};

拳打OC(日月)数个数。

 

3.semop (int semid,struct sembuf *sops,unsigned nsops);

  看烟花的人:

  看哪个烟花,

struct sembuf

{

 short sem_num;//要操作的第几个信号量

 short sem_op;//是P操作还是V操作

 short sem_flg;//可以是IPC_NOWAIT,SEM_UNDO,0

};

定位PV方式:

看烟花要定位看哪个,人多的地方看还是人少的地方看,有人挡着怎么办(是否等待锁)?

复习:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值