上次书写了进程间通信的消息队列,这次是IPC中的另一个模块。信号量
信号量是什么?
荷兰计算机科学家Dijkstra把互斥的关键含义抽象称为信号量(semaphore)概念。信号量是一个被保护的量。
信号量的本质是一种数据操作锁,它本身不具有数据交换的功能,而是通过控制其他的
通信资源(文件,外部设备)来实现进程间通信,它本身只是一种外部资源的标识。信号
量在此过程中负责数据操作的互斥、同步等功能。
当请求一个使用信号量来表示的资源时,进程需要先读取信号量的值来判断资源是否可
用。大于0,资源可以请求,等于0,无资源可用,进程会进入睡眠状态直至资源可用。
当进程不再使用一个信号量控制的共享资源时,信号量的值+1,对信号量的值进行的增减
操作均为原子操作,这是由于信号量主要的作用是维护资源的互斥或多进程的同步访问。
而在信号量的创建及初始化上,不能保证操作均为原子性。
其实指的就是我们在操作系统说到的P,V操作;
为什么会有信号量的存在?
其实操作系统都是进行与线程之间的一个大集合,用进程+线程的模式去不断进行逻辑实现,所以用常规的程序来实现进程之间的同步,互斥关系需要复杂的算法,而且会造成“忙等待”,浪费CPC资源,其实就是我们在进程间通信的时候,存在着多个进程去抢占多个共同资源,就会出现进程抢占上的争执与浪费。所以引入了信号量的概念,其实信号量就是一种特殊的变量,它的表面形式就是一个整形变量附加一个队列;而且啊,他只能够被特殊的操作使用,也就是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 [email protected] $^ .PHONY:clean clean: rm -f test
好了,就这么简单。