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

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

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 0X666

typedef 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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值