linux下的信号量

来源:互联网 发布:近几年大学生就业数据 编辑:程序博客网 时间:2024/05/16 17:15

一、什么是信号量???
信号量的本质是一种数据操作锁、用来负责数据操作过程中的互斥、同步等功能。
信号量用来管理临界资源的。它本身只是一种外部资源的标识、不具有数据交换功能,而是通过控制其他的通信资源实现进程间通信。
可以这样理解,信号量就相当于是一个计数器。当有进程对它所管理的资源进行请求时,进程先要读取信号量的值,大于0,资源可以请求,等于0,资源不可以用,这时进程会进入睡眠状态直至资源可用。当一个进程不再使用资源时,信号量+1(对应的操作称为V操作),反之当有进程使用资源时,信号量-1(对应的操作为P操作)。对信号量的值操作均为原子操作。

二、
1、什么是临界资源?什么是临界区???
临界资源:一次只允许一个进程使用的资源。
临界区:访问临界资源的程序代码片段。

2、为什么要使用信号量???
为了防止多个进程在访问共享资源为引发的问题。信号量可以协调进程对共享资源的访问,也就是用来保护临界资源的。任一时刻只能有一个执行线程进入临界区。

三、linux信号量机制
3.1、头文件

#include <sys/types.h>#include <sys/ipc.h>#include <sys/sem.h>

3.2、创建/获取一个信号量集合

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

返回值:成功返回信号量集合的semid。失败返回-1。
key:可以用key_t ftok(const char* pathname,int proj_id)获取。
nsems:这个参数表示你要创建的信号量集合中的信号量的个数。信号量只能以集合的形式创建。
semflg:同时使用IPC_CREAT和IPC_EXCL则会创建一个新的信号量集合。若已经存在的话则返回-1。单独使用IPC_CREAT的话会返回一个新的或者已经存在的信号量集合。

3.3、信号量结合的操作

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

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

semid:信号量结合的idstruct sembuf *sops:struct sembuf{      unsigned short sem_num;  /* semaphore number */      short          sem_op;   /* semaphore operation */      short          sem_flg;  /* operation flags */};

sem_num: 为信号量是以集合的形式存在的,就相当所有信号量在一个数组里边,sem_num表示信号量在集合中的编号。
sem_op:表示该信号量的操作(P操作还是V操作)。如果其值为正数,该值会加到现有的信号内含值中。通常用于释放所控资源的使用权;如果sem_op的值为负数,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值。通常用于获取资源的使用权
sem_flg:信号操作标志,它的取值有两种。IPC_NOWAIT和SEM_UNDO。
IPC_NOWAIT:对信号量的操作不能满足时,semop()不会阻塞,而是立即返回,同时设定错误信息。
SEM_UNDO: 程序结束时(不管是正常还是不正常),保证信号值会被设定semop()调用之前的值。这样做的目的在于避免程序在异常的情况下结束未将锁定的资源解锁(死锁),造成资源永远锁定。
nsops:表示要操作信号量的个数。因为信号量是以集合的形式存在,所以第二个参数可以传一个数组,同时对一个集合中的多个信号量进行操作。

3.4、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特有)使用缓存区};返回值:失败返回-1。成功返回0。semid:信号量集合的编号。semnum:信号量在集合中的标号。

3.5、命令:ipcs -s //查看创建的信号量集合的个数
ipcrm -s semid //删除一个信号量集合

四、信号量的生命周期随内核。IPC种的信号量一般指的是二元信号量,他就相当于互斥锁,当一个进程在等待资源时,该进程会被挂起,也就是修改这个进程PCB中的状态,让它从r跳到其他状态。然后把这个进程的PCB放到信号量集合的等待队列中。

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

//代码://Makefilecommh=comm.hsrc=sem.c comm.cdst=semcc=gcc$(dst):$(src) $(commh)    $(cc) -o $@ $<.PHONY:cleanclean:    rm -rf $(dst)//comm.h#ifndef __COMM_H__#define __COMM_H__#include<stdio.h>#include<unistd.h>#include<sys/types.h>#include<sys/ipc.h>#include<sys/sem.h>#define PATHNAME "."#define PROJID 0666union SemUn{    int val;    struct semid_ds *buf;    unsigned short *array;    struct seminfo *__buf;};int creatSemSet(int nums);int getSemSet();int initSemSet(int semid,int which);int P(int semid);int V(int semid);int destorySemSet(int semid);#endif   //comm.c#include"comm.h"static int commSemSet(int nums,int flags){    key_t _k=ftok(PATHNAME,PROJID);    if(_k==-1)    {        perror("ftok");        return -1;    }    int semid=semget(_k,nums,flags);    if(semid<0)    {        perror("semget");        return -1;    }    return semid;}int creatSemSet(int nums){    return commSemSet(nums,IPC_CREAT | IPC_EXCL | 0666);}int getSemSet(){    return commSemSet(0,0);}int initSemSet(int semid,int which){    union SemUn _SemUn;    _SemUn.val=1;    if(semctl(semid,which,SETVAL,_SemUn)<0)    {        perror("semctl");        return -1;    }    return 0;}static int SemOp(int semid,int op,int which){    struct sembuf buf[1];    buf[0].sem_op=op;    buf[0].sem_num=which;    if(semop(semid,buf,1)==-1)    {        perror("semop");        return -1;    }    return 0;}int P(int semid){    return SemOp(semid,-1,0);}int V(int semid){    return SemOp(semid,1,0);}int destorySemSet(int semid){    if(semctl(semid,0,IPC_RMID,NULL)==-1)    {        perror("semctl");        return -1;    }    return 0;}//sem.c#include"comm.c"int main(){    int semid=creatSemSet(1);    initSemSet(semid,0);    pid_t id=fork();    if(id==0)    {        int semid=getSemSet();        while(1)        {            P(semid);    //进入临界区            printf("A");            fflush(stdout);            usleep(10031);            printf("A");            fflush(stdout);            usleep(10021);            V(semid);        }    }    else    {        while(1)        {            P(semid);        //进入临界区            printf("B");            fflush(stdout);            usleep(10051);            printf("B");            fflush(stdout);            usleep(10003);            V(semid);        }    }    destorySemSet(semid);}
4 0