LINUX进程间通信,信号量机制+代码实例

信号量:
一、什么是信号量
为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过某种方法,在任一时刻只能有一个执行进程访问代码的临界区域。临界区域是一种互斥资源。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个进程在访问它,也就是说信号量是用来调协进程对共享资源的访问的。

信号量是一个特殊的变量,相当于一个计数器。程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。

常见的信号量形式为二元信号量,最简单的信号量是只能取0和1的变量。而可以取多个正整数的信号量被称为通用信号量。这里主要讨论二进制信号量。它控制单个资源,一般来讲,信号量的初始值可以是任意一个正值,该值表示公有多少个共享资源单位可供共享应用。
信号量有下面的一些特性:
(1)信号量并非是单个非负值,而必需定义一个含有一个或者多个信号量值的集合。当创建信号量时,要指定集合中信号量值的数量。
信号的
(2)信号量的创建独立于它的初始化,这就导致不能原子的创建一个信号量集合,当然,我们可以用编程的手段很容易的避免。
(3)信号量的生命周期是随内核的,所以可能造成死锁的可能性(一个进程获得临界资源,拿到了信号量的锁,然后非正常原因挂掉,那么其它的进程也不能利用临界资源了)。
这种可能行的存在会带来很糟糕的影响,而undo功能就是为了解决这个糟糕的情况的。
在信号量中,函数semop是自动执行信号量集上的操作数组。函数原型如下:

#incldue<sys/sem.h>
int semop(int semid, struct sembuf semoparray[], size_t nops);
//返回值:若成功,返回0;若出错,返回-1

参数semparry是一个指针,它指向一个由sembuf结构表示的信号量操作数组

struct sembuf{
    unsigned short   sem_num;
    short            sem_op;
    short            sem_flg;
    }

函数semop中参数的意义代表:nops规定该数组中操作的数量(元素个数)
而对集合中每个成员的操作有相应的sem_op来决定。此值可负可正可零。而undo标志对应于相应的sem_flg成员的SEM_UNDO位。
(1)若sem_op为正值,就是进程释放的占用资源数,sem_op值会增加到信号量上,即可操作V操作。如果指定了undo标志,则也可以从该进程此信号量值上减去sem_op
(2)若sem_op是负数值,则表示要获取由该信号量控制的资源数。
如果指定了undo标志,则sem_op的绝对值也可以加到该信号量调整值上。
总结:无论何时只要为信号量操作指定了SEM_UNDO标志,然后分配资源,那么内核就会记住对于该信号量,分配给调用进程多少资源,程序结束时(不论正常或不正常),保证信号值会被重设为semop()调用前的值。这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。
下面介绍有关信号量的一些相关函数
1、semget函数
它的作用是创建一个新信号量或取得一个已有信号量,原型为:

#include<sys/sem.h>
int semget(key_t key, int num_sems, int sem_flags);  

第一个参数key:相当于一个标识符,一般由ftok函数来产生。
第二个参数:num_sems指定需要的信号量数目,它的值几乎总是1。

第三个参数sem_flags:是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。
2、semctl函数
该函数用来直接控制信号量信息,可以通过标志来销毁信号量。它的原型为:

#include<sys/sem.h>
int semctl(int sem_id, int sem_num, int command, ...);  

第四个参数是可选的,是否使用取决于所请求的命令,如果使用该参数,一般类型是

union semun{  
    int val;  
    struct semid_ds *buf;  
    unsigned short *arry;  
};  

前两个参数与前面一个函数中的一样,command通常是下面两个值中的其中一个
SETVAL:用来把信号量初始化为一个已知的值。p 这个值通过union semun中的val成员设置,其作用是在信号量第一次使用前对它进行设置。
IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。
3、函数semop()
此函数前文已经提过。
源代码如下:

//编写Makefile
bin=test_sem
cc=gcc
src=comm.c test_sem.c
$(bin):$(src)
    $(cc) -o $@ $^
.PHONY:clean
clean:
    rm -f $(bin)

//编写comm.h
#ifndef __COMM_
#define __COMM_

#include <stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<string.h>
#include<error.h>

#define PATHNAME "."
#define PROJID 0x666

union semun  
{  
    int val;  
    struct semid_ds *buf;  
    unsigned short *arry;  
};    
int creatSemset(int nums);
int initSem(int semid, int which);
int getSemset(int nums);

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)
{
    key_t  _k = ftok(PATHNAME, PROJID);
    if(_k < 0)
    {
        perror("ftok");
        return -1;
    }
    int semid=semget(_k,nums,flags);
    if(semid<0)
    {
        perror("semget");
        return -2;
    }
    return semid;
}
static int sem_op(int semid, int which, int op)//操作信号量
{
    struct sembuf s;
    s.sem_num = which;
    s.sem_op = op;
    s.sem_flag = 0;
    int ret = semop(semid, &s, 1)//调用
    if(ret < 0)
    {
        perro("semop");
        return -1;
    }
    return ret;
}

int initSemSet(int semid,int which)
{
    union SemUn Un;
    Un.val=1;
    int ret=semctl(semid,which,SETVAL,Un);//
    if(ret<0)
    {
        perror("semctl");
        return -1;
    }
    return 0;
}
int creatSemset(int nums)//创建信号量
{
    int flags=IPC_CREAT | IPC_EXCL | 0666;
    return commSemset(nums,flags);
}
int getSemset(int nums)
{
    return commSemset(0,0);
}
int P(int semid, int which)//p操作
{
    return semop(semid, which, -1);
}
int V(int semid, int which)//V操作
{
    return semop(semid, which, 1);
}
int destorySemset(int semid)//销毁信号量
{
    int ret = semctl(semid, 0, IPC_RMID);
    if(ret<0)
    {
        perror("semctl");
        return -1;
    }
    return 0;   
}
test_sem.c
//sem.c
#include "comm.h"
int main()
{
    int semid=creatSemSet(1);
    initSemSet(semid,0);
    pid_t id=fork();
    if(id==0)
    {
        int semid=getSemSet();
        while(1)
        {
            P(semid,0);    //进入临界区
            printf("A");
            fflush(stdout);
            usleep(10031);
            printf("A");
            fflush(stdout);
            usleep(10021);
            V(semid,0);//释放
        }
    }
    else
    {
        while(1)
        {
            P(semid,0);        //进入临界区
            printf("B");
            fflush(stdout);
            usleep(10051);
            printf("B");
            fflush(stdout);
            usleep(10003);
            V(semid,0);//释放
        }
    }
    destorySemSet(semid);
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值