目录

一、信号量和P、V原语

1.1 信号量常规理解

1.2 信号量值含义

1.3 信号量结构体伪代码

1.4 P原语

1.5 V原语

1.6 信号量集结构体

二、信号量操作接口

2.1 semget

2.2 semctl

2.3 semop

三、封装Sem


一、信号量和P、V原语

信号量和P、V原语由Dijkstra(迪杰斯特拉)提出。

1.1 信号量常规理解

  • 执行流间互斥和同步

1.2 信号量值含义

  • S>0:S 表示可用资源的个数
  • S=0:表示无资源可用,无等待进程
  • S<0:|S| 表示等待队列中进程个数

1.3 信号量结构体伪代码

// 信号量本质是一个计数器
struct semaphore
{
    int value;
    pointer_PCB queue;
}

1.4 P原语

P(s)
{
    s.value = s.value--;
    if (s.value < 0)
    {
        // 该进程状态置为等待状状态
        // 将该进程的PCB插⼊⼊相应的等待队列s.queue末尾
    }
}

1.5 V原语

V(s)
{
    s.value = s.value++;
    if (s.value > 0)
    {
        // 唤醒相应等待队列s.queue中等待的⼀⼀个进程
        // 改变其状态为就绪态
        // 并将其插⼊OS就绪队列
    }
}

1.6 信号量集结构体

The semid_ds data structure is defined in as follows :
struct semid_ds
{
    struct ipc_perm sem_perm; /* Ownership and permissions */
    time_t sem_otime;         /* Last semop time */
    time_t sem_ctime;         /* Last change time */
    unsigned long sem_nsems;  /* No. of semaphores in set */
};
The ipc_perm structure is defined as follows(the highlighted fields are settable using IPC_SET) :
struct ipc_perm
{
    key_t __key;          /* Key supplied to semget(2) */
    uid_t uid;            /* Effective UID of owner */
    gid_t gid;            /* Effective GID of owner */
    uid_t cuid;           /* Effective UID of creator */
    gid_t cgid;           /* Effective GID of creator */
    unsigned short mode;  /* Permissions */
    unsigned short __seq; /* Sequence number */
};

二、信号量操作接口

2.1 semget

NAME

semget - get a System V semaphore set identifier

SYNOPSIS

#include <sys/types.h>

#include <sys/sem.h>

#include <sys/ipc.h>

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

RETURN VALUE

        On success, semget() returns the semaphore set identifier (a nonnegative integer).  On failure, -1 is returned, and errno is set to indicate the error.

参数介绍:

  • key:信号量集的键值,同消息队列和共享内存
  • nsems:信号量集中信号量的个数
  • semflg:同消息队列和共享内存

2.2 semctl

NAME

semctl - System V semaphore control operations

SYNOPSIS

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

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

This function has three or four arguments, depending on op.  When there are four, the fourth has the type union semun.  The calling program must define this union as follows:

           union semun {
               int              val;    /* Value for SETVAL */
               struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
               unsigned short  *array;  /* Array for GETALL, SETALL */
               struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                           (Linux-specific) */
           };

RETURN VALUE
       On success, semctl() returns a nonnegative value depending on op as follows:

       GETNCNT
              the value of semncnt.

       GETPID the value of sempid.

       GETVAL the value of semval.

       GETZCNT
              the value of semzcnt.

       IPC_INFO
              the index of the highest used entry in the kernel's internal array recording information about all semaphore sets.  (This  information  can  be  used  with  repeated  SEM_STAT  or
              SEM_STAT_ANY operations to obtain information about all semaphore sets on the system.)

       SEM_INFO
              as for IPC_INFO.

       SEM_STAT
              the identifier of the semaphore set whose index was given in semid.

       SEM_STAT_ANY
              as for SEM_STAT.

 All other op values return 0 on success.

On failure, semctl() returns -1 and sets errno to indicate the error.

参数介绍:

1. semid:

  • 信号量集的标识符(由 semget() 创建)

2. semnum:

  • 信号量在集合中的索引(从0开始)
  • 对单个信号量操作时:指定目标信号量
  • 对整组操作时(如 IPC_RMID):此参数被忽略

3. cmd:控制命令

常用命令及作用:

命令功能是否需要第四个参数
IPC_STAT获取信号量集状态信息是(struct semid_ds*
IPC_SET修改信号量集权限是(struct semid_ds*
IPC_RMID立即删除信号量集
GETVAL获取单个信号量值
SETVAL设置单个信号量值是(int
GETALL获取所有信号量值是(unsigned short*
SETALL设置所有信号量值是(unsigned short*
GETPID获取最后操作进程PID
GETNCNT获取等待信号量增加的进程数
GETZCNT获取等待信号量为零的进程数

4. 第四个参数(可变参数):

  • 类型:联合体 semun(需用户自定义)
  • // 定义示例
    union semun {
        int              val;    // SETVAL 使用
        struct semid_ds *buf;    // IPC_STAT/IPC_SET 使用
        unsigned short  *array;  // GETALL/SETALL 使用
    };
  • 作用:根据 cmd 传递特定数据

2.3 semop

NAME

semop, semtimedop - System V semaphore operations

SYNOPSIS

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

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

RETURN VALUE

        On success, semop() and semtimedop() return 0.  On failure, they return -1, and set errno to indicate the error.

参数介绍:

1. semid:

  • 信号量集的标识符(由 semget() 创建)
  • 需确保进程有操作权限(通过 semget() 的权限位设置)

2. sops(操作结构体指针):

  • 类型:struct sembuf *
  • 结构体定义:
  • struct sembuf {
      unsigned short sem_num;  // 信号量在集合中的索引
      short          sem_op;   // 操作类型(见下文)
      short          sem_flg;  // 操作标志
    };

成员详解:

1. sem_num:

  • 信号量在信号量集中的索引(从 0 开始)
  • 例如:集合包含 3 个信号量时,有效值为 012

2. sem_op(操作类型):

操作说明原子性保证
正数增加信号量值(释放资源),例如 sem_op=3 使信号量值 $S \leftarrow S + 3$立即执行,不阻塞
负数减少信号量值(请求资源),例如 sem_op=-2 需满足 $S \geq 2$,否则阻塞不满足条件时阻塞进程
0等待信号量值变为 $0$,若 $S \neq 0$ 则阻塞常用于同步操作

3. sem_flg(操作标志):

标志说明
IPC_NOWAIT非阻塞模式:若操作无法立即完成(如资源不足),直接返回错误 EAGAIN
SEM_UNDO进程退出时自动撤销操作(防止死锁),例如进程崩溃后自动恢复信号量原始值

3. nsops(操作数量):

  • 作用:指定sops,数组的长度(即要执行的操作数量)

三、封装Sem

// Sem.hpp
#pragma once
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define GET_SEM IPC_CREAT // 不给权限
#define BUILD_SEM (IPC_CREAT | IPC_EXCL | 0X666)
const std::string pathname = "/tmp";
int project_id = 99;
// 只关注使用和删除
class Semaphore
{
public:
    Semaphore(int semid, int flag)
        :_semid(semid), _flag(flag){}
    void P() {
        struct sembuf bs; // 该结构体系统提供
        bs.sem_num = 0;
        bs.sem_op = -1;
        bs.sem_flg = SEM_UNDO;
        int n = ::semop(_semid, &bs, 1);
        if(n < 0) {
            perror("semop-P");
        }
    }
    void V() {
        struct sembuf bs; // 该结构体系统提供
        bs.sem_num = 0;
        bs.sem_op = 1;
        bs.sem_flg = SEM_UNDO;
        int n = ::semop(_semid, &bs, 1);
        if(n < 0) {
            perror("semop-V");
        }
    }
    ~Semaphore() {
        if(_flag == GET_SEM) return;
        // 让信号量自动销毁
        // 如果销毁信号量集合:The argument semnum is ignored
        int n = ::semctl(_semid, 0, IPC_RMID);
        if(n < 0) {
            perror("semctl");
        }
        std::cout << "sem set destory!" << std::endl;
    }
private:
    int _semid;
    int _flag;
};
using sem_ptr = std::shared_ptr;
// 使用简单的建造者模式,用它来构建一个sem
class SemaphoreBuilder
{
public:
    SemaphoreBuilder():_val(-1){}
    SemaphoreBuilder& SetVal(int val) {
        _val = val;
        return *this; // 支持连续访问
    }
    sem_ptr Build(int flag) {
        if(_val < 0) {
            std::cout << "you must init first" << std::endl;
            return nullptr;
        }
        // 1. 申请key值
        key_t key = ::ftok(pathname.c_str(), project_id);
        if(key < 0) {
            perror("ftok");
            exit(1);
        }
        // 2. 根据初始值,创建一个信号量集
        int semid = ::semget(key, 1, flag);
        if(semid < 0) {
            perror("semget");
            exit(2);
        }
        if(BUILD_SEM == flag) {
            // 3. 初始化信号量
            union semun { // 该联合体系统不提供,需要自己定义
                int val;
                struct semid_ds *buf;
                unsigned short *array;
                struct seminfo *__buf;
            }un;
            un.val = _val; // 设置初始值
            int n = ::semctl(semid, 0, SETVAL, un);
            if(n < 0) {
                perror("semctl-set");
                exit(3);
            }
        }
        return std::make_shared(semid, flag);
    }
    ~SemaphoreBuilder(){}
private:
    int _val;
};
// Write.cc
#include 
#include 
#include 
#include "Sem.hpp"
int main()
{
    SemaphoreBuilder sab;
    auto fsem = sab.SetVal(1).Build(BUILD_SEM); // 创建信号量集合,只有一个信号量,初始化为1,就是当成锁来用
    if (fork() == 0)
    {
        auto csem = sab.Build(GET_SEM);
        int cnt = 9;
        while (cnt--)
        {
            csem->P();
            std::cout << "c, sleep" << std::endl;
            sleep(1);
            std::cout << "csem: " << cnt << std::endl;
            sleep(1);
            csem->V();
        }
        exit(0);
    }
    int cnt = 16;
    while (cnt--)
    {
        fsem->P();
        std::cout << "f, sleep" << std::endl;
        sleep(1);
        std::cout << "fsem: " << cnt << std::endl;
        sleep(1);
        fsem->V();
    }
    return 0;
}