进程间通信(IPC):信号量

本文详细介绍了进程间通信(IPC)中的信号量机制,包括信号量的作用、类型以及如何通过PV操作实现进程同步。通过一个模拟打印机使用的例子,展示了如何使用信号量避免资源冲突,确保临界资源的正确访问。此外,还讲解了信号量的创建、初始化、操作及销毁的相关函数,并提到了系统工具`ipcs`和`ipcrm`用于查看和删除信号量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

进程间通信 IPC :管道、信号量、共享内存、消息队列、套接字

进程间通信,两个进程间传递信息

除了套接字,前面几个主要是在同一台主机上两个进程间通信

进程间通信(IPC):管道_♚陌上花开的博客-CSDN博客

进程间通信(IPC):共享内存_♚陌上花开的博客-CSDN博客

进程间通信( IPC):消息队列_♚陌上花开的博客-CSDN博客

信号量

像现实生活中的红绿灯,用来控制资源的使用

信号量:特殊的变量(一般为正数),代表可用资源的数目

减一 : 代表获取资源(p操作

加一 : 代表释放资源(v操作

(例如商场试衣间是可用资源,若有人使用,可用资源减一,有人出来,可用资源加一)

信号量变为0的时候,代表没有资源可用,再使用资源会阻塞

作用:同步进程(控制进程)

pv操作是一个原子操作,就是不可分割的操作

信号量的值如果只取0,1 ,将其称为二值信号量,如果信号量的值大于1,则称为计数信号量

信号量在内核中,由内核直接管理

临界资源

同一时刻,只允许一个进程或线程访问的资源       eg:打印机

临界区

访问临界资源的代码段

是一段代码,在这段代码中进程将访问共享资源,当另外一个进程已经在这段代码中运行时.这个进程就不能在这段代码中执行

控制对临界资源的访问就是控制程序进入临界区来完成的

操作信号量的接口

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

semget() 创建信号量,或者获取一个已存在的信号

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

semget() 创建或者获取已存在的信号量
semget() 成功返回信号量的 ID, 失败返回-1
key:两个进程使用相同的 key 值,就可以使用同一个信号量
nsems:内核维护的是一个信号量集,在新建信号量时,其指定信号量集中信号量的个数
semflg 可选: IPC_CREAT IPC_EXCL,权限

第三个参数semflg为操作标识,可取如下值:

0:取信号量集标识符,若不存在则函数会报错

IPC_CREAT:当semflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的信号量集,则新建一个信号量集;如果存在这样的信号量集,返回此信号量集的标识符

IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的信号量集,则新建一个消息队列;如果存在这样的信号量集则报错

上述semflg参数为模式标志参数,使用时需要与IPC对象存取权限(如0600)进行|运算来确定信号量集的存取权限

错误代码:

EACCESS:没有权限

EEXIST:信号量集已经存在,无法创建

EIDRM:信号量集已经删除

ENOENT:信号量集不存在,同时semflg没有设置IPC_CREAT标志

ENOMEM:没有足够的内存创建新的信号量集

ENOSPC:超出限制

如果用semget创建了一个新的信号量集对象时,则semid_ds结构成员变量的值设置如下:

        sem_otime设置为0。

        sem_ctime设置为当前时间。

        msg_qbytes设成系统的限制值。

        sem_nsems设置为nsems参数的数值。

        semflg的读写权限写入sem_perm.mode中。

        sem_perm结构的uid和cuid成员被设置成当前进程的有效用户ID,gid和cuid成员被设置成当前进程的有效组ID。
 

semop()  实现p,v操作

int semop(int semid,struct sembuf *sops,unsigned nsops);//struct sembuf *sops 描述对信号量的值做什么样的改动,多个信号量就是个结构体数组 unsigned nsops 指针指向元素的个数,

semop() 对信号量进行改变,做 P 操作或者 V 操作

semop() 成功返回 0,失败返回-1

struct sembuf
{
unsigned short sem_num; //指定信号量集中的信号量下标
short sem_op; //其值为-1,代表 P 操作,其值为 1,代表 V 操作
short sem_flg; 
/*标志位 SEM_UNDO//如果因为程序问题,执行p操作获取资源后,程序崩了,不能执行v操作,
一直占着资源,其他进程无法使用,SEM_UNDO可以记住程序进行了P操作,
就算程序崩溃也可以帮助进行v操作,不会因为程序问题而无法进行v操作,一直阻塞其他进程*/
};

semctl()  对信号量初始化,销毁

int semctl(int semid,int semnum,int cmd,...);//semnum信号量个数,cmd,对它的操作,赋值

semctl() 控制信号量

semctl() 成功返回 0,失败返回-1 

cmd 选项: SETVAL(赋初值) IPC_RMID(删除信号量)

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

此联合体需要用户自己定义。

信号量的使用

例题:进程a和进程b模拟访问打印机,进程a输出第一个字符‘a′表示开始使用打印机,输出第二个字符‘a’表示结束使用,b进程操作与a进程相同。(由于打印机同一时刻只能被一个进程使用,所以输出结果不应该出现 abab),如图所示:

没有使用信号量

a.c文件

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main()
{
    for(int i=0;i<5;i++)
    {
        printf("A");//第一次打印A代表开始使用打印机
        fflush(stdout);
        int n=rand()%3;
        sleep(n);//随机睡眠n
        printf("A");//第二次打印代表结束使用
        fflush(stdout);
        n=rand()%3;
        sleep(n);
    }
    exit(0);

}
                                                         
                                                         

b.c文件

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main()
{
    for(int i=0;i<5;i++)
    {
        printf("B");//第一次打印B代表开始使用打印机
        fflush(stdout);
        int n=rand()%3;
        sleep(n);//随机睡眠n
        printf("B");//第二次打印代表结束使用
        fflush(stdout);
        n=rand()%3;
        sleep(n);
    }
    exit(0);

}
                                                        

让a进程和b进程同时运行,放在后台执行

 没有使用信号量出现一个进程还没结束另一个进程就使用资源的情况,这与我们的期望不符

如何使用pv操作控制进程

使用PV操作

sem.h

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

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

sem.c

#include"sem.h"

static int semid = -1;//信号量id,只在本文件有效

void sem_init()//信号量初始化
{
    semid = semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);//key值为1234,一个信号量,全新创建一个信号量,如果已存在就报错,存取权限0600
    if(semid == -1)//全新创建失败,说明已存在或者创建失败
    {
        semid=semget((key_t)1234,1,0600);
        //semid = semget((key_t)1234,1,0600);//当信号已存在,获取信号量
        if(semid == -1)
        {
            printf("semget err\n");//前面获取失败说明是创建的时候就失败了

        }
    }
    else//全新创建成功,初始化
    {
        union semun a;
        a.val = 1;//信号量的初始值
        if(semctl(semid,0,SETVAL,a)==-1)//0,信号量个数。SETVAL,a,赋初值
        {
            printf("semctl init err\n");//初始化失败打印此语句
        }

    }

}

void sem_p()//P操作
{
    struct sembuf buf;//对信号量的改变
    buf.sem_num=0;//信号量的下标
    buf.sem_op=-1;//对信号量执行的是p操作
    buf.sem_flg = SEM_UNDO;//标志位,如果程序异常可以帮助进行v操作

    if(semop(semid,&buf,1)==-1)
    {
        printf("semop p err\n");
    }

}

void sem_v()//V操作
{

    struct sembuf buf;//对信号量的改变
    buf.sem_num=0;//信号量的下标
    buf.sem_op=1;//对信号量执行的是v操作
    buf.sem_flg = SEM_UNDO;

    if(semop(semid,&buf,1)==-1)
    {
        printf("semop v err\n");
    }

}
                                                                             
void sem_destroy()//销毁
{
    if(semctl(semid,0,IPC_RMID)==-1)//semid 要删除的信号,0 占为,根据semid删除创建的所有信号。IPC_RMID 删除信号量
    {
        printf("semctl destroy err\n");
    }

}

a.c文件

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

int main()
{
    sem_init();//先初始化一个信号量
    for(int i=0;i<5;i++)
    {
        sem_p();//先p操作,获取资源
        printf("A");//第一次打印A代表开始使用打印机
        fflush(stdout);
        int n=rand()%3;
        sleep(n);//随机睡眠n
        printf("A");//第二次打印代表结束使用
        fflush(stdout);
        sem_v();//使用完v操作,释放资源

        n=rand()%3;
        sleep(n);
    }
    sleep(10);//a,b进程使用同一信号量,信号量只需要被销毁一次,我们无法判断a,b哪一个先结束,让a睡眠10s,再销毁信号量
    sem_destroy();

    exit(0);

}

b.c文件

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include"sem.h"
int main()
{
    sem_init();//先创建或获取信号量,如果a先执行,那么b就是获取,反之亦然
    for(int i=0;i<5;i++)
    {
        sem_p();
        printf("B");//第一次打印B代表开始使用打印机
        fflush(stdout);
        int n=rand()%3;
        sleep(n);//随机睡眠n
        printf("B");//第二次打印代表结束使用
        fflush(stdout);
        sem_v();
        n=rand()%3;
        sleep(n);
    }
    exit(0);

}
                                                                                                        

 ipcs

查看系统的消息队列,共享内存,信号量

信号量使用完一定要记得销毁

ipcrm

删除信号量 ipcrm -s 信号量id

若我们单独运行刚才的b.c程序,b.c程序没有销毁信号量的语句,./b运行完信号量就还会存在

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值