Linux--进程间通信之信号量

来源:互联网 发布:崩坏3舰团矩阵buff 编辑:程序博客网 时间:2024/05/17 04:25

现在已经写了两种进程间通信方式

Linux--进程间通信之匿名管道及命名管道:http://blog.csdn.net/sayhello_world/article/details/59556670

Linux--进程间通信之消息队列:http://blog.csdn.net/sayhello_world/article/details/59690231


今天我们来说第三种进程间通信方式--信号量。

一.什么是信号量?

信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)所拥有。

信号量的值为正的时候,说明它空闲。所测试的线程可以锁定使用它。若为0,说明它被占用,测试的线程要进入睡眠队列中,等待被唤醒。

 

二.为什么要有信号量?

当我们进行进程间通信的时候,若写端向其写hello world,读端读取,则很有可能写端还未写完hello world,读端就已经读取了。这样子便会导致数据不一致问题,为了避免这一问题,便有了信号量。

 

介绍几个概念:

原子性:通俗的来说就是一件事要么做了,要么没做,如果他做了一定是做完了。

临界资源:不同的进程看到共同的资源。

临界区:共同访问临界资源的那段代码。

所以这里的保护机制,保护的是临界区,因为我们只能控制代码。

 

三.信号量两种操作

由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:

P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行

V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂

起,就给它加1.

 

那么我们可以知道,PV操作为原子性的,二元信号量本身就为一把锁。

同步:访问临界资源并以特定的顺序访问。(一般同步都在互斥的情况下)

互斥:在任意时刻只允许一个人进入临界区访问临界资源。

饥饿:长时间没有得到资源。

 

那么就会有一个问题:

如果有一个全局变量,他能让两个进程同时看到,它能取代信号量吗?

     不能,因为变量开始存于内存中,但是加减运算在CPU中,若要给此变量加减1,则应该先将他取到CPU中,CPU中加一,再返给内存中,需要三步。任意一步都可能会被中断,所以与另一进程不具有互斥性。

 

那么我们来实现一下信号量:

思路:两个进程两行打印输出A和B,如果没有信号量之前,则可能是AB可能是乱序输出,如果加了PV操作,则应该是成对输出。

需要用到的函数:

信号量的创建:

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

key:与消息队列中key值含义相同,用ftok生成

nsems:信号量特有的参数,在system V下申请信号量是以信号量集的方式,次变量表示信号量的个数是多少个。

semflg:与消息队列中相同,两个参数IPC_CREAT与IPC_EXCL

详情见:http://blog.csdn.net/sayhello_world/article/details/59690231


信号量的获取:

与消息队列创建相同,只是semflg传的参数不同,获取时只需传IPC_CREAT


信号量的销毁:

int semctl(int semid,int semnum,int cmd,.....)

返回值:失败返回-1

semnum:操作信号量集中的哪一个信号量

cmd:销毁时用IPC_RMID


信号量的初始化:

与信号量的销毁相同,只是这时传的值不同。

semctl(semid,which,SETVAL,un)

这里un为一个结构体如下

union semun {
int val; // 使用的值
struct semid_ds *buf; // IPC_STAT、IPC_SET 使用缓存区
unsigned short *array; // GETALL,、SETALL 使用的数组
struct seminfo *__buf; // IPC_INFO(Linux特有) 使用缓存区
};


PV操作:

int semop(int sem_id, struct sembuf *sem_opa, size_t  num_sem_ops);

函数参数:第一个参数,sem_id,是由semget函数所返回的信号量标识符。

第二个参数,sem_ops,是一个指向结构数组的指针,其中的每一个结构至少包含下列成员:

struct sembuf{      short sem_num;//除非使用一组信号量,否则它为0,一般从0,1,...num_secs-1      short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作, 一个是+1,即V(释放信号)操作。      short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,并在进程没有释放该信号量而终止时,操作系统释放信号量  };  

着重说一下第三个成员:sem_flg:通常设置为SEM_UNDO,这会使得操作系统跟踪当前进程对信号量所做的改变,而且如果进程终止而没有释放这个信号量, 如果信号量为这个进程所占有,这个标记可以使得操作系统自动释放这个信号量。

第三个参数num_sem_ops,表示进行操作信号量的个数,即sops结构变量的个数,需大于或等于1。最常见设置此值等于1,只完成对一个信号量的操作。

函数返回值:成功:返回信号量集的标识符,错误,返回-1

comm.h

#ifndef __COMM_H__#define __COMM_H__#include<stdio.h>#include<stdlib.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/sem.h>#include<unistd.h>#include<string.h>#define PATHNAME "."#define PROJ_ID 0X666typedef 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) */}_semun;int create_sem(int semnum);int get_sem();int init_sem(int semid,int which,int _val);int destroy_sem(int semid);int P(int semid,int which);int V(int semid,int which);#endif

comm.c

#include"comm.h"static int comm_sem(int semnum,int flags){        key_t k = ftok(PATHNAME,PROJ_ID);        int semid = semget(k,semnum,flags);        if(semid < 0)        {                perror("semget\n");  return -1;        }        return semid;}int creat_sem(int semnum){        return comm_sem(semnum,IPC_CREAT|IPC_EXCL|0666);}int get_sem(){        return comm_sem(0,IPC_CREAT);}int destroy_sem(int semid){        if(semctl(semid,0,IPC_RMID) < 0)        {                perror("semctl");                return -1;        }        return 0;}int init_sem(int semid,int which,int _val){ _semun un;        un.val = _val;        if(semctl(semid,which,SETVAL,un) < 0)        {                perror("init_sem");                return -1;        }        return 0;}static int comm_op(int semid,int which,int op){        struct sembuf sbuf;        sbuf.sem_num = which;        sbuf.sem_op = op;        sbuf.sem_flg = 0;        return semop(semid,&sbuf,1);}int P(int semid,int which){        return comm_op(semid,which,-1);}int V(int semid,int which){        return comm_op(semid,which,1);}     

测试代码:

comm_test.c

#include"comm.h"int main(){        int semid = creat_sem(1);        init_sem(semid,0,1);        int status = 0;        pid_t id = fork();        if(id< 0){                perror("fork");                return -1;        }        else if(id == 0){//child                while(1){                        int _semid = get_sem();                        P(_semid,0);                        printf("A");                        usleep(12345);                        fflush(stdout);                        printf("A");                        usleep(55787);                        fflush(stdout);                        V(_semid,0);                }                exit(1);        }else{//father                while(1)                {                        P(semid,0);                        printf("B");                        usleep(16745);                        fflush(stdout);                        printf("B");                        usleep(59887);                        fflush(stdout);                        V(semid,0);                }                wait(&status);                destroy_sem(semid);        }        return 0;}

未添加信号量之前:有单个出现的


用信号量之后:都是成双出现的


1 0
原创粉丝点击