上次书写了进程间通信的消息队列,这次是IPC中的另一个模块。信号量
信号量是什么?
荷兰计算机科学家Dijkstra把互斥的关键含义抽象称为信号量(semaphore)概念。信号量是一个被保护的量。
信号量的本质是一种数据操作锁,它本身不具有数据交换的功能,而是通过控制其他的
通信资源(文件,外部设备)来实现进程间通信,它本身只是一种外部资源的标识。信号
量在此过程中负责数据操作的互斥、同步等功能。
当请求一个使用信号量来表示的资源时,进程需要先读取信号量的值来判断资源是否可
用。大于0,资源可以请求,等于0,无资源可用,进程会进入睡眠状态直至资源可用。
当进程不再使用一个信号量控制的共享资源时,信号量的值+1,对信号量的值进行的增减
操作均为原子操作,这是由于信号量主要的作用是维护资源的互斥或多进程的同步访问。
而在信号量的创建及初始化上,不能保证操作均为原子性。
其实指的就是我们在操作系统说到的P,V操作;
为什么会有信号量的存在?
其实操作系统都是进程与线程之间的一个大集合,用进程+线程的模式去不断进行逻辑实现,所以用常规的程序来实现进程之间的同步,互斥关系需要复杂的算法,而且会造成“忙等待”,浪费CPU资源,其实就是我们在进程间通信的时候,存在着多个进程去抢占多个共同资源,就会出现进程抢占上的争执与浪费。所以引入了信号量的概念,其实信号量就是一种特殊的变量,它的表面形式就是一个整形变量附加一个队列;而且啊,他只能够被特殊的操作使用,也就是P/V操作,在Linux中,进程对共享资源的互斥访问就是通过信号量机制来实现的。
什么是P/V操作?
其实P/V操作很容易理解,其实P操作是荷兰文的“等待”的首字母:
对于P操作而言我们就是想共享资源中申请一个资源,我这个进程需要占有她了,所以我们需要对当前的临界资源进行申请,也就是-1操作。
P(S):
S = S-1;
若S<0,将该进程的状态设置为等待状态,然后将该进程的PCB插入相应的S信号量等待队列末尾,知道有其他进程在S上执行V操作为止。
V操作是荷兰文的“发信号”的首字母:
对于V操作而言我们进程就是想声明资源我所获取的已经使用完毕,可以放回了。也就是归还临界资源的资源,也就是+1操作。
V(S);
S = S+1;
若S<= 0,释放信号量队列中等待的一个进程,改变其等待转台未就绪态,并将其插入就绪队列。也存在一个上限临界值。
总结一下:
虽然P/V操作可以比较有效地实现进程同步与互斥问题,但是也有这明显的弱点,由于是多个进程进行操作,当一个进程一次使用多个资源,就需要多次的P,多次的V,这个上面就增加了程序的复杂性,也降低了通信效率,严重的可能够会致使进程之间需要相互等待很长的还是件,可能导致死锁的发生。
所以对于PV操作而言,他们的出现于存在必须是成对的,要不然会产生很严重给的问题。
这就涉及到了生产者-消费者问题。读写问题。哲学家就餐问题。还有死锁。还有信号量的原子操作这个我后面补充书写博客,这里就暂时不提。
既然是学习Linux下的信号量,那么我们就来看一下信号量的相关函数
新建信号量函数semget(key_ key,int semnum, int semflg);
其中key为ftok创建的ID,semnum是制定在新的集合中应该创建的信号量数目,
第3个参数就是进程间通信所有实现中都相同的打开方式:
IPC_CREAT:打开,不存在则创建,
IPC_EXCL:单独使用不存在太多意义,与IPC_CREAT一起使用则表示,若信号量集合存在。则操作失败,返回-1.不存在创建一个新的。
这个也会用到下面的联合体结构。
信号量操作函数semop(int semid,sembuf *sops,unsinged nsops);
semid 为semget所创建的信号量ID值。
sembuf是一个指针,指向的是将要在信号量操作上执行的一个数组。
sembuf在Linux/sem.h中定义(sys/sem.h)。
nsops则是操作 +1为V操作,-1为p操作。
sembuf结构
struct sembuf
{
ushort sem_num ; //信号量编号
short sem_op; //信号量操作
short sem_flg; //信号量的操作标志
}
其中sem_flg是信号量的操作标志,sem_op为负,则从信号两种减掉一个值。如果sem_op为真,则从信号量中加上值。如果sem_op为0,则将进程设置为睡眠状态,直到信号量的值为0为止。
在父子进程,也是fork操作中需要把sem_flg设置为0
下面资料来源于网络:
sem_num对应信号集中的信号灯,0对应第一个信号灯。sem_flg可取IPC_NOWAIT以及SEM_UNDO两个标志。如果设置了SEM_UNDO标志,那么在进程结束时,相应的操作将被取消,这是比较重要的一个标志位。如果设置了该标志位,那么在进程没有释放共享资源就退出时,内核将代为释放。如果为一个信号灯设置了该标志,内核都要分配一个sem_undo结构来记录它,为的是确保以后资源能够安全释放。事实上,如果进程退出了,那么它所占用就释放了,但信号灯值却没有改变,此时,信号灯值反映的已经不是资源占有的实际情况,在这种情况下,问题的解决就靠内核来完成。这有点像僵尸进程,进程虽然退出了,资源也都释放了,但内核进程表中仍然有它的记录,此时就需要父进程调用waitpid来解决问题了。
控制信号量参数semctl(int semid,int semnum,int cmd,...);
这个函数的使用涉及到一个结构体。
semctl() 在 semid 标识的信号量集上,或者该集合的第 semnum 个信号量上执行 cmd 指定的控制命令。(信号量集合索引起始于零。)根据 cmd 不同,这个函数有三个或四个参数。当有四个参数时,第四个参数的类型是 union
。调用程序必须按照下面方式定义这个联合体:
union semun {
int val; // 使用的值
struct semid_ds *buf; // IPC_STAT、IPC_SET 使用缓存区
unsigned short *array; // GETALL,、SETALL 使用的数组
struct seminfo *__buf; // IPC_INFO(Linux特有) 使用缓存区
};
注意:该联合体没有定义在任何系统文件中,因此得用户自己声明。 <Centos 下确实是这样,但是UNIX下不同,不需要自己定义声明
好了。关于信号量的操作就这几个函数,我们来看一下代码吧。
//semm.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#define _PATH_ "."
#define _PROJ_ID_ 0x8888
union semun
{
int val;
struct semid_ds* buf;
unsigned short *array;
struct seminfo *__buf;
};
static int _sem_set(int snum,int flags);
static int sem_op(int sem_id,int nsops,int flags);
int create_sem(int snum);
int get_sem(int snum);
int init_sem(int sem_id,int snum,int unit_val);
int sem_p_element(int sem_id,int nsops);
int sem_v_element(int sem_id,int nsops);
int destory_sem_element(int sem_id);
//semm.c
#include"comm.h"
static int _sem_set(int snum,int flags)
{
key_t _key=ftok(_PATH_,_PROJ_ID_);
if(_key<0)
{
perror("ftok");
return -1;
}
int sem_id=-1;
sem_id=semget(_key,snum,flags);
if(sem_id<0)
{
perror("semget");
return -1;
}
return sem_id;
}
int create_sem(int snum)
{
int flags=IPC_CREAT|IPC_EXCL|0666;
int ret= _sem_set(snum,flags);
return ret;
}
int get_sem(int snum)
{
return _sem_set(snum,IPC_CREAT);
}
int init_sem(int sem_id,int snum,int unit_val)
{
union semun _un;
_un.val=unit_val;
if(semctl(sem_id,snum,SETVAL,_un)<0)
{
perror("semctl\n");
return -1;
}
return 0;
}
static int sem_op(int sem_id,int seqnum,int op)
{
struct sembuf _sm;
_sm.sem_num=seqnum;
_sm.sem_op=op;
_sm.sem_flg=0;
if(semop(sem_id,&_sm,1)<0)
{
perror("semop");
return -1;
}
return 0;
}
int sem_p_element(int sem_id,int seqnum)
{
return sem_op(sem_id,seqnum,-1);
}
int sem_v_element(int sem_id,int seqnum)
{
return sem_op(sem_id,seqnum,1);
}
int destory_sem_element(int sem_id)
{
if(semctl(sem_id,IPC_RMID,0,NULL)<0)
{
perror("semctl\n");
return -1;
}
return 0;
}
//test.c
#include"comm.h"
int main()
{
int sem_id=create_sem(1);
if(sem_id<0)
{
printf("error\n");
return -1;
}
init_sem(sem_id,1,1);
pid_t pid=fork();
if(pid<0)
{
perror("pid");
return -1;
}
else if(pid==0)
{
int sem_pid=get_sem(1);
while(1)
{
sem_p_element(sem_pid,0);
printf("A");
sleep(1);
fflush(stdout);
printf("A");
sleep(8);
fflush(stdout);
sem_v_element(sem_pid,0);
}
}
else
{
while(1)
{
sem_p_element(sem_id,0);
sleep(3);
printf("B");
sleep(2);
fflush(stdout);
printf("B");
sleep(5);
fflush(stdout);
sem_v_element(sem_id,0);
}
waitpid(pid,NULL,0);
sestory(sem_id);
}
return 0;
}
//Makefile
.PHONY:all
all:test
test:test.c semm.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f test
好了,就这么简单。
本文出自 “剩蛋君” 博客,请务必保留此出处http://memory73.blog.51cto.com/10530560/1764318