【Linux】进程间通信——信号量

目录

一、概念

1.定义

2.目的

3.专业名词

二、信号量接口

(1)semget

(2)semop

(3)semctl

(4)删除信号量

三、信号量使用

代码

编译执行结果​编辑

四、查看信号量信息


一、概念

1.定义

  • 用于管理对资源的访问。相当于红绿灯:管理进程间争夺的使用权,未得到使用权的一方只能等待
  • 信号量是一个原子操作。它是一个特殊的变量,一般取正数值。它的值代表允许访问的资源数目,获取资源时,需要对信号量的值进行原子-1,该操作称为P操作。当信号量的值为0时,代表没有资源可用,p操作会阻塞。释放资源时,需要对信号量的值进行原子加一,该操作被称为V操作。信号量主要用来同步进行。
    • P:用于等待---获取资源--- -1
    • V:用于操作---释放资源--- +1
    •  PV操作:

      假设一个信号量变量sv,则如下表的两个操作定义: 

2.目的

        保证同一时刻只能有一个进程对某个资源进行访问,防止因多个程序同时访问一个共享资源而引发的问题,使得在任意时刻只有一个执行线程访问代码的临界区域。而一个进程要持续不断的运行以等待某个内存位置被改变。

3.专业名词

  • 二值信号量:信号量的值如果只取0,1。
  • 计数信号量:信号量的值大于1.
  • 临界资源:计算机的软硬件资源;同一时刻只允许进程或者线程访问的资源(打印机)
  • 临界区::访问临界资源的代码段

二、信号量接口

头文件:#include<sys/sem.h>

(1)semget

  • 作用:创建一个新信号量或者获取一个已有的信号量的键:
  • int semget(key_t key,int num_sems,int sem_flags);
  •         第一个参数key是整数值,不相关的进程可以通过它访问同一个信号量。程序对所有信号量的访问都是间接的,它先提供一个键,再由系统生成一个相应的信号量标识符。只有semget函数才直接使用信号量键,所有其他的信号量函数都是由semget函数返回信号量的标识符。
  •         第二个参数num_sems:创建信号量的数目,一般取值为1。
  •         第三个参数sem_flags:标志位,如果未创建时IPC_CREAT;如果为全新创建,不知道是否有人创建过,则为IPC_CREAT|IPC_EXCL
  •         返回值:成功返回一个整数(非零)值,是其他信号量函数将用到的信号量标识符。如果失败,返回-1。

(2)semop

  • 作用:对信号量进行改变,做p或者v操作
  • int semop(int sem_id,struct sembuf * sem_ops,sieze_t num_sem_ops);
  •         第一个参数是返回的信号量标识符。表明是p操作还是v操作,p:-1,v:+1。
  •         第二个参数是结构体指针,指向sembuf,sembuf内有三个结构体成员:
    • struct sembuf{
          short sem_num;
          short sem_op;
          short sem_flg;
      }
    • sem_flg一般等于SEM_UNDO。这样的目的是:不论当前进程是否正常退出,都将还原此操作的 sem_op 值。用于以防万一异常结束的进程。
  •         第三个参数是数组长度

(3)semctl

  • 作用:用来直接控制信号量信息
  • int semctl(int sem_id,int sem_num,int command,...);
  • 第一个参数是信号量标识符
  • 第二个参数是信号量编号
  • 第三个信号是将要采取的动作
    • SETVAL:初始化信号量,该值通过下面union semun中的val成员设置,其作用是再信号量第一次使用之前对它进行设置。
    • IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。
  • 如果有第四个参数,它会是一个union semun的联合体:
    • union semun{
          int val;
          struct semid_ds *buf;
          unsigned short * array;
      }
      该结构体一般需要我们程序员自己定义
  • 返回值:成功返回0,失败返回-1。

(4)删除信号量

ipcrm -s id

三、信号量使用

        信号量的使用只需用一个最简单的二进制信号量即可,在下面例子讲述用完整编程接口为二进制信号量创建一个非常简单的pv类型接口

例子:

        用进程a和进程b模拟访问打印机,进程a输出第一个字符”A“表示开始使用打印机,输出第二个字符”A“表示结束使用,b进程操作与a进程相同。如下图示:

sleep----开始使用

printf-----使用结束

1.编写a和b的代码:

  • n=rand()%3; sleep(n);//随机睡眠3秒钟
  • 在后台可以实现ab进程同时运行(后台&实现同时运行多进程),执行结果如下:
  • 如果不加控制,AB交替出现,说明AB是在交替使用,A在sleep时B会打印出B,就不符合信号量的要求——A在使用时B不能使用。

        由于打印机在同一时刻只能被一个进程使用,所以输出结果不应该出现abab这样交替的结果,下面使用信号量对其进行控制,使AB成对出现:

2.实现A在sleep时B不能使用,A和B是成对出现的:

代码

头文件:seem.h

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

//程序员自己定义semun联合体
union semun
{
        int val;
};

void sem_init();
void sem_p(); //P操作
void sem_v(); //V操作
void sem_destory();//销毁
~                           

seem.cpp文件:

//初始化
void sem_init()
{
        //semget:创建一个新信号量或者获取一个已有的信号量的键
        semid=semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);//1234:房间号,0600->文件权限
        if(semid==-1)   //全新创建失败,获取已经存在的信号量
        {
                semid=semget((key_t)1234,1,0600);
                if(semid==-1)
                {
                        perror("semget error");
                }
                else
                {
                        //初始化
                        union semun a;//定义联合体
                        a.val=1;//初始化设置为1,假设刚开始可以使用
                        //semctl:用来直接控制信号量信息
                        if(semctl(semid,0,SETVAL,a)==-1)//编号从0开始
                        {
                                perror("semctl init error");
                        }
                }
        }
}

void sem_p()//p操作
{
        struct sembuf buf;
        buf.sem_num=0;
        buf.sem_op=-1;
        buf.sem_flg=SEM_UNDO;//一般都这样设置,是让操作系统记住使用了P操作
        //semop:对信号量进行改变,做p或者v操作
        if(semop(semid,&buf,1)==-1)//1:长度,只有一个信号量
                perror("p perror");
}

void sem_v()//v操作
{
        struct sembuf buf;
        buf.sem_num=0;
        buf.sem_op=1;
        buf.sem_flg=SEM_UNDO;//一般都这样设置,是让操作系统记住使用了P操作
        if(semop(semid,&buf,1)==-1)//1:长度,只有一个信号量
                perror("v perror");
}
//销毁
void sem_destory()
{
        if(semctl(semid,0,IPC_RMID)==-1)
                perror("destory sem error");
}

a.c:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<assert.h>
#include"seem.h"

int main()
{
        int i=0;
        //信号量初始化
        sem_init();
        for(;i<5;i++)
        {
                sem_p();
                printf("A");
                fflush(stdout);
                int n=rand()%3;
                sleep(n);
                printf("A");
                fflush(stdout);
                sem_v();
                n=rand()%3;
                sleep(n);
        }
        sleep(10);
        sem_destory();
}

b.c:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<assert.h>
#include"seem.h"

int main()
{
        int i=0;
        sem_init();
        for(;i<5;i++)
        {
                sem_p();
                printf("B");
                fflush(stdout);
                int n=rand()%3;
                sleep(n);
                printf("B");
                fflush(stdout);
                sem_v();
                n=rand()%3;
                sleep(n);
        }
}

编译执行结果

 此时,AB是成对打印的。

四、查看信号量信息

  • ipcs:查看信号量和消息队列以及共享内存

  • ipcs -s:查看信号量
  • ipcs -m:查看共享内存
  • ipcs -q:只查看消息队列
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
信号是一种用于进程通信和同步的机制。它是一个计数器,用于保证在共享资源上的互斥访问。在Linux系统中,可以使用信号来实现进程的同步和互斥。以下是信号的基本概念: - 计数器:信号的值是一个计数器,它可以被多个进程共享。 - P操作:当一个进程需要访问共享资源时,它必须执行P操作,该操作会将信号的值减1。如果信号的值为0,则进程将被阻塞,直到信号的值大于0。 - V操作:当一个进程使用完共享资源后,它必须执行V操作,该操作会将信号的值加1。如果有进程正在等待该信号,则唤醒其中一个进程继续执行。 在ZUCC中,可以使用信号来实现进程的同步和互斥。首先,需要使用semget函数创建一个信号集合,并使用semctl函数对信号进行初始化。然后,可以使用semop函数执行P和V操作。例如,下面是一个简单的示例程序,用于演示信号的使用: ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/sem.h> #define SEM_KEY 1234 union semun { int val; struct semid_ds *buf; unsigned short *array; }; int main() { int semid, pid; union semun arg; struct sembuf sb; // 创建信号集合 semid = semget(SEM_KEY, 1, IPC_CREAT | 0666); if (semid == -1) { perror("semget"); exit(EXIT_FAILURE); } // 初始化信号 arg.val = 1; if (semctl(semid, 0, SETVAL, arg) == -1) { perror("semctl"); exit(EXIT_FAILURE); } // 创建子进程 pid = fork(); if (pid == -1) { perror("fork"); exit(EXIT_FAILURE); } else if (pid == 0) { // 子进程执行P操作 sb.sem_num = 0; sb.sem_op = -1; sb.sem_flg = SEM_UNDO; if (semop(semid, &sb, 1) == -1) { perror("semop P"); exit(EXIT_FAILURE); } printf("Child process\n"); // 子进程执行V操作 sb.sem_num = 0; sb.sem_op = 1; sb.sem_flg = SEM_UNDO; if (semop(semid, &sb, 1) == -1) { perror("semop V"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } else { // 父进程执行P操作 sb.sem_num = 0; sb.sem_op = -1; sb.sem_flg = SEM_UNDO; if (semop(semid, &sb, 1) == -1) { perror("semop P"); exit(EXIT_FAILURE); } printf("Parent process\n"); // 父进程执行V操作 sb.sem_num = 0; sb.sem_op = 1; sb.sem_flg = SEM_UNDO; if (semop(semid, &sb, 1) == -1) { perror("semop V"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } return 0; } ``` 在上述代码中,创建了一个信号集合,并将其初始化为1。然后,创建了一个子进程和一个父进程,它们分别执行P和V操作。由于信号的初始值为1,因此父进程和子进程都可以顺利地执行。如果将信号的初始值改为0,那么父进程和子进程都将被阻塞,直到有一个进程执行V操作为止。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值