Linux信号量实现互斥与同步

2023年 7月 25日 23.8k 0

1、什么是信号量

从概念上来说,信号量是一个由内核维护的非负整数计数器。

信号量(Semaphore)是一种实现任务间通信的机制,可以实现任务之间同步或者临界资源的互斥访问,常用于协助一组相互竞争的任务来访问临界资源。是Linux中进程主要的同步与互斥手段就是信号量。

  • 互斥 : 同一时刻只有一个进程访问临界资源
  • 同步 : 在互斥的基础上增加了进程对临界资源的访问顺序

信号可以执行如下操作:

将信号量设置成一个具体的值。
在信号量当前值的基础上加上一个数值。
在信号量当前值的基础上减上一个数值。
等待信号量的值为 0。

在 Linux 系统中查询信号量使用 ipcs -s , 删除某个信号量使用 ipcrm -s

2、信号量的使用

  Linux 内核提供了一套对信号量的操作,包括创建,初始化,操作信号量。

2.1、创建信号量

创建信号量集合调用 semget 函数。

//头文件
#include 
#include 
#include 

//函数原型
int semget(key_t key, int nsems, int semflg);

函数参数

  • key : 由 ftok() 函数生成
  • nsems : 信号量的数量
  • semflg : 信号量集合的标志
    • IPC_CREAT : 创建标志
    • IPC_EXCL : 与 IPC_CREAT 标志一起使用,如果信号量集合存在就报错
    • 权限标志

函数返回值

  • 成功 : 返回信号量集合的 id
  • 失败 : -1,并设置 errno

2.2、初始化信号量

初始化信号量调用 semctl 函数,设置一个信号量的初始值。在 semid 标识的信号量集上,或者该集合的第semnum 个信号量上执行 cmd 指定的控制命令。

#include 
#include 
#include 

int semctl(int semid, int semnum, int cmd, ...);

函数参数

  • semid : 信号量集合的id

  • semnum : 信号量的编号, 信号量的编号从 0 开始

  • cmd : 命令控制字

    • SETVAL: 设置信号量的值
    • GETVAL: 获取信号量的值
  • … : 后面是属于可变参参数列表,根据不同的命令有不同的参数

函数返回值

  • 成功 : 根据不同的命令有不同的返回值,可以查看帮助文档关于 RETURN 的说明
    • GETNCNT the value of semncnt
    • GETPID the value of sempid
    • GETVAL the value of semval
    • GETZCNT the value of semzcnt.
    • All other cmd values return 0 on success.

失败 : 返回 -1,并设置 errno

2.3、操作信号量

操作信号量调用 semop 函数。

#include 
#include 
#include 

int semop(int semid, struct sembuf *sops, size_t nsops);

函数参数

  • semid : 信号量集合id
  • sops : 信号量操作结构体指针
  • nsops : 操作的信号量的数量

函数返回值

  • 成功 : 返回 0
  • 失败 : 返回 -1,并设置 errno

3、Demo实例:实现进程间同步

在进程间实现同步操作,按顺序打印 充电 玩手机 没电了。

父进程:

  • 占用 SEM_CONTROL_P,此时父进程正常运行,输出 充电
  • 释放 SEM_CONTROL_C,占用 SME_CONTROL_P,此时父进程阻塞,子进程继续执行
  • 当子进程输出 玩手机 之后,释放 SEM_CONTROL_P,父进程继续执行,输出 没电了
  • 父进程 释放 SEM_CONTROL_P 循环结束

子进程:

  • 占用 SEM_CONTROL_C ,此时子进程阻塞
  • 当父进程释放 SEM_CONTROL_C 时, 子进程输出 玩手机 ,释放 SEM_CONTROL_P
  • 循环占用 SEM_CONTROL_C,由于之前已经占用,此时进入子进程阻塞,等待父进程释放 SEM_CONTROL_C
#include 
#include 
#include 
#include 
#include 

#include "sem_handle.h"

#define SEM_CONTROL_P 0
#define SEM_CONTROL_C 1

int main(void)
{
    pid_t cpid;
    int semid;
    unsigned short values[2] = {1,0};

    //创建一个信号量集合,包含 2个信号量
    // 一个信号量 编号为 0(SEM_CONTROL_P)控制父进程的运行与暂停
    // 一个信号量 编号为 1(SEM_CONTROL_C) 控制子进程的运行与暂停
    semid = sem_create(2, values);
    if (semid == -1)
        exit(EXIT_FAILURE);

    cpid = fork();

    if (cpid == -1){
        perror("[ERROR] fork(): ");
        exit(EXIT_FAILURE);
    } else if (cpid == 0){
        while(1){
            sem_p(semid,SEM_CONTROL_C);
            printf(" 玩手机 ");
            fflush(stdout);
            sem_v(semid,SEM_CONTROL_P);
        }
    } else {
        while(1){
            sem_p(semid,SEM_CONTROL_P);
            printf(" 充电 ");
            fflush(stdout);
            sem_v(semid,SEM_CONTROL_C);

            sem_p(semid,SEM_CONTROL_P);
            printf(" 没电了 ");
            fflush(stdout);
            sem_v(semid,SEM_CONTROL_P);

            sleep(1);
            putchar('n');
        }

        wait(NULL);
    }

    return 0;
}

运行结果

sem_handle.h 封装的代码

#ifndef CDEMO_SEM_HANDLE_H
#define CDEMO_SEM_HANDLE_H

#include 
#include 
#include 
#include 

extern int sem_create(int nsems,unsigned short values[]); //创建信号量集合
extern int sem_p(int semid,int semnum);// 占用资源
extern int sem_v(int semid,int semnum);// 释放资源
extern int sem_del(int semid); // 删除信号量集合

#endif
#include "sem_handle.h"

#define SEM_PATHNAME "."
#define SEM_PRO_ID 100

union semun{
    int val;
    unsigned short *array;
};

/**
 * 创建信号量集合
 * @param nsems 信号量的数量
 * @param values 信号量的值
 * @return 成功 :信号量集合的id 失败 : -1
 */
int sem_create(int nsems, unsigned short values[])
{
    int semid,ret;
    key_t key;
    union semun s;

    key = ftok(SEM_PATHNAME,SEM_PRO_ID); // 创建 key
    if (key == -1){
        perror("[ERROR] ftok() : ");
        return -1;
    }

    semid = semget(key, nsems, IPC_CREAT|0666); // 创建信号量集合
    if (semid == -1){
        perror("[ERROR] semget() : ");
        return -1;
    }

    s.array = values;

    ret = semctl(semid,0,SETALL,s); // 设置信号量的值
    if (ret == -1){
        perror("[ERROR] semctl() : ");
        return -1;
    }

    return semid;
}


/**
 * 占用信号量资源
 * @param semid 信号量集合的id
 * @param semnum 操作的信号量编号
 * @return
 */
int sem_p(int semid, int semnum)
{
    struct sembuf sops;

    sops.sem_num = semnum;
    sops.sem_op = -1;
    sops.sem_flg = SEM_UNDO;// 进程终止,会自动释放

    return semop(semid,&sops,1);
}

/**
 * 释放信号量资源
 * @param semid 信号量集合的id
 * @param semnum 操作的信号量编号
 * @return
 */
int sem_v(int semid, int semnum)
{
    struct sembuf sops;

    sops.sem_num = semnum;
    sops.sem_op = 1;
    sops.sem_flg = SEM_UNDO;

    return semop(semid, &sops, 1);
}

/**
 * 删除信号量集合
 * @param semid 信号量集合的id
 * @return
 */
int sem_del(int semid)
{
    return semctl(semid, 0, IPC_RMID,NULL);
}

相关文章

服务器端口转发,带你了解服务器端口转发
服务器开放端口,服务器开放端口的步骤
产品推荐:7月受欢迎AI容器镜像来了,有Qwen系列大模型镜像
如何使用 WinGet 下载 Microsoft Store 应用
百度搜索:蓝易云 – 熟悉ubuntu apt-get命令详解
百度搜索:蓝易云 – 域名解析成功但ping不通解决方案

发布评论