Linux进程通信之信号量 控制进程同步

目录

1、信号量

2、信号量接口函数

2.1、semget函数

2.2、semctl函数

2.3、semop函数

3、接口封装

4、实例运行

5、ipcs 可以查看消息队列、共享内存、信号量的使用情况,使用 ipcrm 可以进行删除操作。 


        在多进程、多线程系统,程序中通常存在着一部分临界区,我们需要确保只有一个进程或执行线程可以进入这个临界区,并拥有对资源独占式的访问。

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

        临界资源:同一时间只允许被一个进程或线程访问的资源。比如,打印机同时只能被一个人使用;多线程对一个全局变量进行累加(在多处理器,线程并行的情况下,如果不加控制,两个线程同时对同一全局变量进行累加,某个累加操作会被覆盖)。

        我们可以通过信号量同步多进程、线程对资源的访问。

1、信号量

        信号量是一个特殊的变量,一般取正数值。它的值代表允许访问的一类资源的数量信号量与其控制访问的资源之间并无实际代码将其关联,而是在逻辑上一直抽象的,用一个信号量代表一类资源的数量,信号量的拥有的数量值与一类资源的数量相对应。

        P 操作:获取(使用)资源时对信号量的值进行原子减一,代表有一份资源被占用 。当信号量值为 0 时,代表没有资源可用,P 操作会阻塞

        V 操作:释放资源时,对信号量的值进行原子加一

        信号量主要用来同步进程。信号量的值如果只取 0,1,将其称为二值信号量。如果信号量的值大于 1,则称之为计数信号量。

2、信号量接口函数

        信号量函数均在 <sys/sem.h> 头文件中声明。

        Linux系统中的信号量接口提供了比通常所需的更多机制,所有信号量函数都是针对成组的信号量(一组信号量组成信号量集)进行操作。因为实际情况中可能并不只有单一资源需要被控制访问,而是有多个资源需要被同时控制。当然,接口函数中有选项可以选择进行单信号量的控制。

        多个信号量按顺序如数组一般保存在信号量集中,操作时可以通过下标来指定信号量

2.1、semget函数

 函数原型:

int semget(key_t key,int num_sems,int sem_flags);

        semget函数用于创建一个新的信号量集,或者获取一个已有的信号量集的标识符。函数返回信号量集的标识符。其他信号量接口都通过该标识符对信号量进行操作。

        key:一个整数值。多个进程、线程可以通过相同的key值来获取同一个信号量集的标识符。

        num_sems:指定所需信号量的数目(要控制访问的资源类型的数目) 。num_sems==1,代表只对一类资源进行控制访问。

        sem_flags:其中二进制低位9位定义了信号量集的权限(所有者、组和其他人)。可与IPC_CREAT按位或来创建一个信号量集。若信号量集已存在,则为通过key值获取该信号量集的标识符。也可以使用IPC_CREATIPC_EXCL来确保创建出的是一个新的、唯一的信号量集,若信号量集已存在(key值已被使用过),则失败,返回-1

2.2、semctl函数

        用于控制信号量的信息。常用于对信号量集中的一个信号量进行初始化值,或者删除整个信号量集。

函数原型:

int semctl(int sem_id,int sem_num,int command,...);

        sem_id:semget函数返回的信号量集的标识符。

        sem_num:信号量在信号量集中的编号(下标位置)。若只有一个信号量,则这个值取值为0,表示这是第一个也是唯一的一个信号量。

        command:将要进行的操作。常用的两个值:

        (1)IPC_RMID:删除一个不用的信号量集。

        (2)SETVAL:在信号量第一次使用前对其进行值的设置。若使用SETVAL,则函数需要第四个参数 union semun

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

        信号量的值由 union semun 中的val设置。

2.3、semop函数

        用于改变信号量的值:进行P(信号量值-1)、V(信号量值+1)操作。

函数原型:

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

        sem_id:semget函数返回的信号量集的标识符。

        sem_ops:指向一个结构体数组的指针。结构体中保存对指定信号量的操作。数组中可保存多个结构体,可指定同时对多个信号量进行操作。

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

        (1)sem_num:信号量在信号量集中的编号(下标位置)。只有一个信号量时取值为0。

        (2)sem_op:信号量在一次操作中需要改变的数值。通常使用两个值: -1,P操作,占用信号量对应资源的一份;1,V操作,释放一份资源。

        (3)sem_flg:通常被设置为SEM_UNDO。它使得操作系统跟踪当前进程对这个信号量的修改情况,如果这个进程在没有释放该信号量的情况下终止,操作系统将自动释放该进程持有的信号量。

        num_sem_ops:结构体数组中元素的数量(一次操作信号量的个数)。 

3、接口封装

        因为信号量与资源之间并未实际关联,在信号量较少,P/V操作简单时,我们可以将信号量接口函数进行简单封装,使其使用起来更简单、便捷。

        假设目前我们只需要对一类资源进行访问控制,且该资源数量为1。

sem.h: 

#include<stdio.h>
#include<unistd.h>
#include<sys/sem.h>
#include<stdlib.h>

union semun{
    int value;
};

void sem_init();//信号量初始化
void sem_p();    //P操作
void sem_v();    //V操作
void sem_destroy();//信号量释放

sem.c:

#include"sem.h"

static int semid;//静态全局变量 .data区 本文件有效

void sem_init(){
    semid = semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);

    if(semid==-1){//创建失败(已存在,直接获取)
        semid=semget((key_t)1234,1,0600);//已存在,获取
        if(semid==-1) //仍然失败则退出
            exit(1);
    }
    else{//创建信号量集成功,对唯一的信号量进行赋值初始化
        union semun a;
        a.value=1;
        if(semctl(semid,0,SETVAL,a)==-1){//设置值
            perror("semctl err");
            exit(1);
        }
    }
}

void sem_p(){//封装P操作
    struct sembuf buf;
    buf.sem_num=0;//信号量编号为0
    buf.sem_op=-1;//信号量值-1
    buf.sem_flg=SEM_UNDO;//系统帮忙维护

    if(semop(semid,&buf,1)==-1){
        perror("semop err");
        exit(1);
    }
}

void sem_v(){//封装V操作
    struct sembuf buf;
    buf.sem_num=0;
    buf.sem_op=1;//信号量值+1
    buf.sem_flg=SEM_UNDO;

    if(semop(semid,&buf,1)==-1){
        perror("semop err");
        exit(1);
    }
}

void sem_destroy(){//删除信号量集
    if(semctl(semid,0,IPC_RMID)==-1){
        perror("semctl err");
        exit(1);
    }
}

        假设,当前有两个进程要使用打印机,打印机只有一个,一个人使用打印机时不能被打断。 

a.c: 通过信号量进行进程同步,使得a进程与b进程交替打印"aabbaabb......"。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include"sem.h"

int main(){
    sem_init();
    for(int i=0;i<5;i++){
        sem_p();//P操作获取打印机资源
        printf("A");
        fflush(stdout);

        int n=rand()%3;//n可能取值0,1,2
        sleep(n);//模拟打印操作的持续时间
        //在持续时间内由于信号量的存在,
        //其他进程不能进行printf
                   
        printf("A");
        fflush(stdout);
        sem_v();//V操作释放打印机资源

        n=rand()%3;
        sleep(n);
        //使当前进程暂停一下,
        //给其他进程获取到资源的机会
    }
    sleep(5);
    sem_destroy();//删除信号量集
    exit(0);
}

b.c

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include"sem.h"

int main(){
    sem_init();
    for(int i=0;i<5;i++){
        sem_p();//P操作,资源被占用时,阻塞
        printf("B");
        fflush(stdout);

        int n=rand()%3;//0,1,2
        sleep(n);

        printf("B");
        fflush(stdout);
        sem_v();//释放资源

        n=rand()%3;
        sleep(n);
    }
    exit(0);
}

4、实例运行

        通过信号量进行进程同步,使即使某一进程在暂停时,其他进程也不能进行打印。实现了交替按序打印。

5、ipcs 可以查看消息队列、共享内存、信号量的使用情况,使用 ipcrm 可以进行删除操作。 

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值