POISX线程信号量的实现原理

  • sem结构体:
struct semaphore {
	raw_spinlock_t		lock;
	unsigned int		count;
	struct list_head	wait_list;
};
struct semaphore_waiter {
	struct list_head list;
	struct task_struct *task;
	bool up;
};
  • sem_post和sem_wait就是在加减count

  • 在加减count前后, 会获取和释放lock

  • wait_list保存信号量上等待的线程, 元素是semaphore_waiter对象, 通过list_head双向链表连接

  • task: task_struct类似于一个抽象类, 保存的是当前线程的运行信息

  • up: 如果等待队列只有一个线程, 那么__up函数唤醒该线程时设置up为true, 避免其在__down_common中一直阻塞

  • sem_post的源码对应的是up, 如下:

    void up(struct semaphore *sem)
    {
      unsigned long flags;
    
      raw_spin_lock_irqsave(&sem->lock, flags);
      if (likely(list_empty(&sem->wait_list)))
        sem->count++;
      else
        __up(sem);
      raw_spin_unlock_irqrestore(&sem->lock, flags);
    }
    
    • 先获取锁
    • 如果当前信号量的等待队列是空, 直接对count进行加操作
    • 否则调用__up函数
    • 释放锁
    • __up函数的源码如下:
      static noinline void __sched __up(struct semaphore *sem)
      {
        struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list,
                  struct semaphore_waiter, list);
        list_del(&waiter->list);
        waiter->up = true;
        wake_up_process(waiter->task);
      }
      
      • 取信号量的等待队列中的第一个线程
      • 移除等待队列中该线程元素
      • 系统调用wake_up_process激活waiter中保存的线程
  • sem_wait的源码对应的是down系列, 包括down_killable, down_interruptible, down_timeout, down_trylock, 对于前面三个, 底层实现是__down_common函数, 只是参数略有不同, __down_common源码如下:

    static inline int __sched __down_common(struct semaphore *sem, long state,
                    long timeout)
    {
      struct semaphore_waiter waiter;
    
      list_add_tail(&waiter.list, &sem->wait_list);
      waiter.task = current;
      waiter.up = false;
    
      for (;;) {
        if (signal_pending_state(state, current))
          goto interrupted;
        if (unlikely(timeout <= 0))
          goto timed_out;
        __set_current_state(state);
        raw_spin_unlock_irq(&sem->lock);
        timeout = schedule_timeout(timeout);
        raw_spin_lock_irq(&sem->lock);
        if (waiter.up)
          return 0;
      }
    
    timed_out:
      list_del(&waiter.list);
      return -ETIME;
    
    interrupted:
      list_del(&waiter.list);
      return -EINTR;
    }
    
    • timed_out, interrupted用来返回超时和被中断
    • 创建一个waiter对象, 并添加到信号量等待队列队尾
    • 启动阻塞循环, 退出循环的条件有:
      • 查看当前线程的未决信号, 如果有就跳转到interrupted并返回
      • 查看当前的剩余等待时间是否<=0, 如果是就跳转到超时并返回
      • 某线程up了信号量, 并唤醒了等待队列的第一个线程, 而当前线程就是等待队列的第一个线程, 则返回0, 表示获取信号量(减信号量值)成功
    • 重新设置当前线程状态
    • 解锁
    • schedule_timeout让当前线程睡眠timeout, 该函数定义在kernel\time\time.c中, 返回值0表示超时到期, >0表示剩余超时时间, 一般大于0是因为被信号中断
    • 尝试获取当前信号量的锁
    • 查看线程是否被唤醒
  • 可见, 信号量和互斥量不同, 互斥量是保护共享资源的访问, 而信号量自己就是共享资源, 保护的是信号量值本身, 更多的作用是控制线程之间的执行顺序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值