OS79.【Linux】POSIX信号量

目录

1.知识回顾

2.在信号量的保护下访问临界资源

3.POSIX信号量的系统调用

sem_init

sem_destroy

sem_wait

sem_trywait

sem_post

4.glibc-2.43源码分析sem_t类型

sem_t实际上是联合体

POSIX信号量的系统调用把联合体当结构体使用


1.知识回顾

之前在OS55.【Linux】理解信号量(不是信号)文章介绍过信号量,也简单提到过System V有关于信号量的系统调用

这里摘一点重要的内容:

  1. 申请计数器成功,就表示具有访问资源的权限了

  2. 申请了计数器资源,当前可能尚未访问资源,即申请是资源的预订机制

  3. 计数器可以有效保证进入共享资源的执行流的数量

  4. 所以每一个执行流,想访问共享资源中的一部分的时候.不是直接访问,而是先申请计数器资源,类比看电影的先买票

计算机科学中,把这个"计数器"称为信号量

信号量本身也需要被保护,那么这把计数器的本质是什么?

信号量是保证PV操作原子性的一把计数器

1.申请信号量,本质是对计数器做减1操作,即P操作

2.释放资源,释放信号量,本质是对计数器进行加1操作,即V操作

3.申请和释放称为PV操作,且操作是原子的

由于System V信号量现在已经不常用了,本文介绍POSIX标准下的信号量

之前在https://zhangcoder.blog.csdn.net/article/details/158321006?spm=1011.2415.3001.5331文章写过基于阻塞队列的多生产者-多消费者模型的代码

cpp 复制代码
#include <pthread.h>
#include <unistd.h>
#include <cstdio>
#include <cstdlib>
#include <queue>
#define PRODUCER_CNT 5
#define COMSUMER_CNT 5
template <class T>
class blocking_queue
{
public:
    blocking_queue(size_t max_capacity=20)
    :_max_capacity(max_capacity)
    {
        pthread_mutex_init(&_lock,nullptr);
        pthread_cond_init(&_p_cond,nullptr);
        pthread_cond_init(&_c_cond,nullptr);
    }
    T& pop()
    {
        pthread_mutex_lock(&_lock);
        while (_q.empty())
            pthread_cond_wait(&_c_cond,&_lock);
        auto& obj=_q.front();
        _q.pop();
        pthread_cond_broadcast(&_p_cond);
        pthread_mutex_unlock(&_lock);
        return obj;
    }
    void push(const T& obj)
    {
        pthread_mutex_lock(&_lock);
        while (_q.size()>=_max_capacity)
            pthread_cond_wait(&_p_cond,&_lock);
        _q.push(obj);
        pthread_cond_broadcast(&_c_cond);
        pthread_mutex_unlock(&_lock);
    }
    ~blocking_queue()
    {
        pthread_mutex_destroy(&_lock);
        pthread_cond_destroy(&_p_cond);
        pthread_cond_destroy(&_c_cond);
        while(!_q.empty())
            _q.pop();
    }
private:
    std::queue<T> _q;
    size_t _max_capacity;
    pthread_mutex_t _lock;
    pthread_cond_t _p_cond;//生产者的条件变量
    pthread_cond_t _c_cond;//消费者的条件变量
};

void* produce(void* args)
{
    pthread_detach(pthread_self());
    blocking_queue<int>* bq=static_cast<blocking_queue<int>*>(args);
    for (;;)
    {
        int data=rand()%10;
        bq->push(data);
        printf("生产者生产了数据%d\n",data);
    }
    return nullptr;
}

void* consume(void* args)
{
    pthread_detach(pthread_self());
    blocking_queue<int>* bq=static_cast<blocking_queue<int>*>(args);
    for (;;)
    {
        int data=bq->pop();
        printf("消费者消费了数据%d\n",data);
    }
    return nullptr;
}

int main()
{
    srand((unsigned int)time(NULL));
    blocking_queue<int>* bq=new blocking_queue<int>();
    pthread_t producer,consumer;
    for (int i=0;i<PRODUCER_CNT;i++)
        pthread_create(&producer,nullptr,produce,bq);
    for (int i=0;i<COMSUMER_CNT;i++)
        pthread_create(&consumer,nullptr,consume,bq);
    for(;;){sleep(100);}
    delete bq;
    return 0;
}

注意到生产者和消费者只能有一个能访问_q队列,那么_q队列这个共享资源是当成整体使用的

也可以把_q队列这个共享资源拆成多份,每个线程只访问其中的一份,可以用信号量控制,本文后面将实现

2.在信号量的保护下访问临界资源

总共3步: 申请临界资源(P操作) → 访问资源 → 释放临界资源(V操作)

申请信号量如果成功,那么申请临界资源就成功(不需要手动判断临界资源,P操作内部自带计数和阻塞逻辑)

释放信号量如果成功,那么释放临界资源就成功

用伪代码表示:

cpp 复制代码
//锁: 手动判断资源是否就绪
加锁();
while (资源不可用) 
{  
    解锁();
    放条件变量下的等待队列中休眠,等待被唤醒();    
    加锁();
}
使用资源();           
解锁();

//信号量: 无需手动判断资源是否就绪
P操作();              
使用资源();            
V操作();             

结论: PV操作之间不需要像锁那样判断资源是否就绪

3.POSIX信号量的系统调用

sem_init

cpp 复制代码
int sem_init(sem_t *sem, int pshared, unsigned int value);

作用: 初始化信号量

参数sem: 信号量指针

参数pshared: 0表示线程间共享,非零表示进程间共享

参数value: 提供信号量的初始值,上面提过了信号量是保证PV操作原子性的一把计数器

sem_destroy

cpp 复制代码
int sem_destroy(sem_t *sem);

作用: 销毁信号量

sem_wait

cpp 复制代码
int sem_wait(sem_t *sem);

作用: 等待信号量,如果信号量的值不为0(当前有空闲的临界资源),就会将信号量的值减1,即P操作

如果当前没有有空闲的临界资源,那么阻塞等待

sem_trywait

作用: 等待信号量,但等待的逻辑和sem_wait不同,sem_trywait是非阻塞等待,即申请不到信号量就里面出错返回,不像sem_wait在那里一直等

sem_post

cpp 复制代码
int sem_post(sem_t *sem);

作用: 发布信号量,表示申请到的临界资源使用完毕,可以归还该资源了,将信号量值加1,即V操作

4.glibc-2.43源码分析sem_t类型

sem_t实际上是联合体

sem_t实际上是一个联合体,在glibc-2.43的/sysdeps/unix/sysv/linux/bits/semaphore.h中定义了:

cpp 复制代码
#ifndef _SEMAPHORE_H
# error "Never use <bits/semaphore.h> directly; include <semaphore.h> instead."
#endif

#include <bits/wordsize.h>

#if __WORDSIZE == 64
# define __SIZEOF_SEM_T	32
#else
# define __SIZEOF_SEM_T	16
#endif

/* Value returned if `sem_open' failed.  */
#define SEM_FAILED      ((sem_t *) 0)


typedef union
{
  char __size[__SIZEOF_SEM_T];
  long int __align;
} sem_t;

__WORDSIZE是字长,和系统架构有关

如果字长是64位,那么__size数组为32字节;

如果字长是32位,那么__size数组为16字节;

__align保证sem_t联合体对齐

POSIX信号量的系统调用把联合体当结构体使用

以sem_init为例说明,定义在/nptl/sem_init.c中:

sem_init和__new_sem_init是同一个函数

cpp 复制代码
int
__new_sem_init (sem_t *sem, int pshared, unsigned int value)
{
  ASSERT_PTHREAD_INTERNAL_SIZE (sem_t, struct new_sem);

  /* Parameter sanity check.  */
  if (__glibc_unlikely (value > SEM_VALUE_MAX))
    {
      __set_errno (EINVAL);
      return -1;
    }

  /* Map to the internal type.  */
  struct new_sem *isem = (struct new_sem *) sem;

  /* Use the values the caller provided.  */
#if USE_64B_ATOMICS_ON_SEM_T
  isem->data = value;
#else
  isem->value = value << SEM_VALUE_SHIFT;
  /* pad is used as a mutex on pre-v9 sparc and ignored otherwise.  */
  isem->pad = 0;
  isem->nwaiters = 0;
#endif

  isem->private = (pshared == 0 ? FUTEX_PRIVATE : FUTEX_SHARED);

  return 0;
}
versioned_symbol (libc, __new_sem_init, sem_init, GLIBC_2_34);

#if OTHER_SHLIB_COMPAT(libpthread, GLIBC_2_1, GLIBC_2_34)
compat_symbol (libpthread, __new_sem_init, sem_init, GLIBC_2_1);
#endif

sem_init先通过struct new_sem *isem = (struct new_sem *) sem,把联合体指针转为结构体指针,

再初始化各个字段: isem->data、isem->value、......

其中struct new_sem定义在/sysdeps/nptl/internaltypes.h中:

cpp 复制代码
/* Semaphore variable structure.  */
struct new_sem
{
#if USE_64B_ATOMICS_ON_SEM_T
  /* The data field holds both value (in the least-significant 32 bits) and
     nwaiters.  */
# if __BYTE_ORDER == __LITTLE_ENDIAN
#  define SEM_VALUE_OFFSET 0
# elif __BYTE_ORDER == __BIG_ENDIAN
#  define SEM_VALUE_OFFSET 1
# else
# error Unsupported byte order.
# endif
# define SEM_NWAITERS_SHIFT 32
# define SEM_VALUE_MASK (~(unsigned int)0)
  uint64_t data;
  int private;
  int pad;
#else
# define SEM_VALUE_SHIFT 1
# define SEM_NWAITERS_MASK ((unsigned int)1)
  unsigned int value;
  int private;
  int pad;
  unsigned int nwaiters;
#endif
};

sem_init把sem_init提供的信号量的值存到了struct new_sem的value字段

结论: POSIX信号量的系统调用把sem_t联合体当结构体使用,通过强制类型转换访问内部成员,使用联合体为了对外隐藏结构

相关推荐
公众号:fuwuqiBMC6 小时前
(转自“服务器BMC”)服务器BMC功能——DDR故障预测
运维·服务器·人工智能
会助力智能会务6 小时前
会务系统供应商怎么选?会助力智能会务系统,一站式数字化会务服务商
大数据·运维·人工智能
CodingPioneer7 小时前
03零刻Pro-LS2K3000-Loongnix 20.7.rc1硬盘分区、格式化、挂载与卸载
linux·运维·服务器·硬盘挂载
出门吃三碗饭7 小时前
如何从0到1构建自动化的咖啡和冰激凌机器人?
运维·机器人·自动化
ShineWinsu7 小时前
对于Linux:TCPsocket编程基础的解析
linux·网络协议·tcp/ip·面试·笔试·进程·远程指令
dddwjzx7 小时前
嵌入式Linux C应用编程——tslib库的使用
linux·嵌入式
Huangjin007_7 小时前
【Linux 系统篇(三)】入门基础指令详解(三)
linux·服务器
言乐67 小时前
Python实现基本搜索引擎
java·linux·开发语言·python·搜索引擎
酷可达拉斯8 小时前
Linux操作系统-简单内核优化
linux·git·php