Linux中的信号量

来源:互联网 发布:网络工程学什么 编辑:程序博客网 时间:2024/06/07 14:46

为什么要使用信号量:
当我们在多用户系统、或者多进程系统下编程时,我们经常会发现我们有段临界代码,在此处我们需要保
证当前仅有一个进程访问它,为了阻止多个程序同时访问一个共享资源引起的问题,我们需要一种方法生
成一个标记从而保证在临界区部分一次只有一个进程执行。而这种方法就是信号量
一、信号量:
信号量的本质是一种数据操作锁,说的通俗一点就是一个计数器。它本身不具有数据交换的功能,而是通
过控制其他的通信资源(文件、外部设备)来实现进程之间的通信,它本身只是一种外部资源的标识。信
号量在此过程中负责数据操作的互斥、同步功能。
当请求一个使用信号量来表示的资源时,进程要先读取信号量的值来判断资源是否可用。大于0
资源可以请求,等于0,无资源可用进程会进入睡眠状态直至资源可用

当进程不再使用一个信号量控制的共享资源时,信号量的值+1,对信号量的值进行的增减操作均为原子操
作,这是由于信号量的主要作用是维护资源的互斥或多进程的访问。而在信号量的创建及初始化上,不能
保证操作均为原子性

原子性:如果一个程序,它要么完整的被执行,要么完全不执行。这种特性就叫做原子性
互斥:就是说两个进程只能在某一时刻执行一个,这种结果可能是因为共同争夺资源而产生的
同步:就是进程之间可以同时运行,之间不存在“利益冲突”
二、信号量的工作原理(申请减一。释放加一)
信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv)
P(sv):如果sv的值大于0,就给他减一,如果它的值为0,就挂起该进程的执行;
V(sv):如果有其他进程因等待sv被挂起,就让它恢复运行,如果没有进程因等待sv而挂起就加1;

例如:就是两个进程共享信号量sv,一个进程执行了p(sv)操作,它将得到信号量并进入临界区,
是sv减一。而第二个进程将被阻止进入临界区,因为当它试图执行p(sv),sv为0.它会挂起等待,
当第一个进程离开临界区域并执行V(sv)释放信号量,这时第二个进程就可以恢复执行。

三、信号量机制
1.创建/获取一个信号量

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

返回值:成功返回信号量集合的semid,失败返回-1;
key:第一个参数key是一个用来允许不相关的进程访问相同信号量的整数值。可以由ftok函数获得

key_t ftok(const char* pathname,int proj_id)

nsems:这个参数表示你要创建的信号量集合中信号量的个数。信号量只能以集合的方式创建。
semflg:同时使用IPC_CREAT和_IPC_EXCL则会创建一个新的信号量,若已经存在则返回-1,
单独使用IPC_CREAT则会返回一个新的或者已经存在的信号量。

测试信号量的创建:

//Makefiletest_sem: test_sem.c comm.c    gcc -o $@ $^.PHONY:cleanclean:    rm -f test_sem//comm.h头文件#ifndef __COMM_H__#define __COMM_H__#include<stdio.h>#include<sys/types.h>#include<sys/ipc.h>#include<sys/sem.h>#define PATHNAME "."#define PROJ_ID 0x6666int creatSem(int nums);int getSem(int nums);#endif//comm.c信号量的接口#include "comm.h"static int commSet(int nums,int flags){    key_t _key=ftok(PATHNAME,PROJ_ID);    if(_key<0)    {        perror("ftok()");        return -1;    }    int semid=semget(_key,nums,flags);    if(semid<0)    {        perror("semget");        return -2;    }    return 0;}int creatSem(int nums){    return commSet(nums,IPC_CREAT|IPC_EXCL|0666);}//test_sem.c测试#include "comm.h"int main(){    int semid=creatSem(1);    sleep(4);    printf(" Sem proc is done!\n");    return 0;}

我们可以使用

ipcs -s //查看系统当中的信号量
ipcrm -s semid //删除信号量

这里写图片描述

2.初始化/删除信号量

int 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特有)使用缓存区
};

sem_id:是信号量集合的标识符。
semnum:信号量在集合中的标号

这两个通常的cmd值为:
SETVAL:用于初始化信号量为一个已知的值。所需要的值作为联合semun的val成员来传递。在信号量第一次使用之前需要设置信号量。
IPC_RMID:当信号量不再需要时用于删除一个信号量标识。

3.信号量结合的操作

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

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

第一个参数,sem_id,是由semget函数所返回的信号量标识符。
第二个参数,sem_ops,是一个指向结构数组的指针,其中的每一个结构至少包含下列成员:

struct sembuf{      unsigned short sem_num;  /* semaphore number */      short          sem_op;   /* semaphore operation */      short          sem_flg;  /* operation flags */};

sem_num: 为信号量是以集合的形式存在的,就相当所有信号量在一个数组里边,
sem_num表示信号量在集合中的编号。 通常设为0;

sem_op:表示该信号量的操作(P操作还是V操作)。通常情况下中使用两个值,-1是我们的P操作,用来等待一个信号量变得可用,而+1是我们的V操作,用来通知一个信号量可用。

sem_flg:信号操作标志,它的取值有两种。IPC_NOWAIT和SEM_UNDO。
IPC_NOWAIT:对信号量的操作不能满足时,semop()不会阻塞,而是立即返回,同时设定错误信息。

SEM_UNDO: 这会使得操作系统跟踪当前进程对信号量所做的改变,而且如果进程终止而没有释放这个信号量, 如果信号量为这个进程所占有,这个标记可以使得操作系统自动释放这个信号量。将sem_flg设置为SEM_UNDO是一个好习惯,除非我们需要不同的行 为。如果我们确实变我们需要一个不同的值而不是SEM_UNDO,一致性是十分重要的,否则我们就会变得十分迷惑,当我们的进程退出时,内核是否会尝试清 理我们的信号量。

nsops:表示要操作信号量的个数。因为信号量是以集合的形式存在,所以第二个参数可以传一个数组,同时对一个集合中的多个信号量进行操作。

例子:
父进程中打印AA,子进程中打印BB。利用信号量使得AA和BB之间不出现混叠。因为打印的内容都要输入到显示器上,要不混叠的话,显示器就是临界资源。我们需要在父子进程的临界区进行加锁。

//comm.h#include<sys/types.h>#include<sys/ipc.h>#include<sys/sem.h>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                                                                                  (Linux-specific) */                                               };#define PATHNAME "."#define PROJ_ID 0x6666int creatSemSet(int nums); //创建int getSemSet(int nums);  //接受int initSem(int semid,int nums,int initval);  //初始化int P(int semid,int which);  int V(int semid,int which);int destorySemSet(int semid);#endif//comm.c#include "comm.h"static int commSemSet(int nums,int flags)//nums表示创建信号集中的个数{    key_t _key=ftok(PATHNAME,PROJ_ID);    if(_key<0)    {        perror("ftok");        return -1;    }    int semid=semget(_key,nums,flags);    if(semid<0)    {        perror("semget");        return -2;    }    return semid;}int creatSemSet(int nums){    return commSemSet(nums,IPC_CREAT|IPC_EXCL|0666);}int getSemSet(int nums){    return commSemSet(nums,IPC_CREAT);}int initSem(int semid,int nums,int initVal) //nums信号集中的标号 initval初始化值{    union semun _un;    _un.val=initVal;    if(semctl(semid,nums,SETVAL, _un)<0) //SETVAL表示初始化信号量    {        perror("Semctlinit");        return -1;    }    return 0;}static int SemPV(int semid,int which,int op) //which表示哪个信号量  op指的是P/V操作{    struct sembuf _sf;    _sf.sem_num=which;    _sf.sem_op=op;    _sf.sem_flg=SEM_UNDO;    if(semop(semid,&_sf,1)<0)//这里的1指的是对一个信号量进行操作    {        perror("semop");        return -1;    }    return 0;}int P(int semid,int which){    return SemPV(semid,which,-1);}int V(int semid,int which){    return SemPV(semid,which,1);}int destorySemSet(int semid){    if(semctl(semid,0,IPC_RMID)<0)    {        perror("semctl");        return -1;    }    return 0;}

测试:不加锁

#include "comm.h"int main(){    int semid=creatSemSet(1);    initSem(semid,0,1);    pid_t id=fork();    if(id==0)    {        //child;        while(1)        {            usleep(100000);            printf("A");            usleep(300000);            fflush(stdout);            printf("A");            usleep(500000);            fflush(stdout);        }    }    else    {        //parent;        while(1)        {            printf("B");            usleep(103000);            fflush(stdout);            printf("B");            usleep(200000);            fflush(stdout);        }        pid_t ret=waitpid(id,NULL,0);        if(ret>0)        {            printf("wait Success!\n");        }    }    destory(semid);    return 0;}

结果:
这里写图片描述
AB打印无规律

测试:加锁

#include "comm.h"int main(){    int semid=creatSemSet(1);    initSem(semid,0,1);    pid_t id=fork();    if(id==0)    {        //child;        while(1)        {            int _semid=getSemSet(0);            P(_semid,0);            usleep(100000);            printf("A");            usleep(300000);            fflush(stdout);            printf("A");            usleep(500000);            fflush(stdout);            V(_semid,0);        }    }    else    {        //parent;        while(1)        {            P(semid,0);            printf("B");            usleep(103000);            fflush(stdout);            printf("B");            usleep(200000);            fflush(stdout);            V(semid,0);        }        pid_t ret=waitpid(id,NULL,0);        if(ret>0)        {            printf("wait Success!\n");        }    }    destory(semid);    return 0;}

结果:
这里写图片描述
要么就不打印打印就打印两个;

原创粉丝点击