内核同步措施

本文介绍了Linux内核中用于同步的三种机制:原子操作,如atomic_set和atomic_inc;自旋锁,用于多处理器环境的短时同步,并通过spin_lock和spin_unlock操作;以及信号量,包括读写信号量,当资源不可用时,进程会被挂起。down和up函数用于信号量的获取和释放。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

内核同步措施


1.内核同步的引入

假设一种情况,只有一个cpu并且cpu处理任务是串行的,也就是严格地执行完一个再执行下一个。这种情况下内核中的共享数据不需要特殊的规则也能达到我们预期。然而这种情况肯定是不能满足我们对计算机的要求,因为它太慢而且如果有一个任务出问题了那么后面任务将无法执行。现实的情况是多CPU并行处理任务,即使是单核cpu,任务也是可调度的。那么对于共享数据的操作就必须遵循一定的访问规则。否则能会出现意想不到的错误。避免并发带来的竞争称为同步。下面介绍几种内核同步措施。

2.原子操作

原子操作可以保证指令以原子的方式被执行,也就是执行过程不能被打断。linux内核提供一种原子类型atomic_t。

  1. typedef struct {
  2. volatile int counter;
  3. } atomic_t;

并为这种类型提供了一些专门的操作,常见操作如下: 
atomic_set(v,i):把*v置为i 
atomic_add(i,v):把*v增加i 
atomic_inc(v):把*v加1 
atomic_add(i,v)操作:

  1. static inline void atomic_add(int i, atomic_t *v)
  2. {
  3. asm volatile(LOCK_PREFIX "addl %1,%0"
  4. : "=m" (v->counter)
  5. : "ir" (i), "m" (v->counter));
  6. }

其中LOCK_PREFIX保证了操作的原子性。

3.自旋锁

自旋锁的实现

  1. typedef struct raw_spinlock {
  2. unsigned int slock;
  3. } raw_spinlock_t;
  1. typedef struct {
  2. raw_spinlock_t raw_lock;
  3. #ifdef CONFIG_GENERIC_LOCKBREAK
  4. unsigned int break_lock;
  5. #endif
  6. #ifdef CONFIG_DEBUG_SPINLOCK
  7. unsigned int magic, owner_cpu;
  8. void *owner;
  9. #endif
  10. #ifdef CONFIG_DEBUG_LOCK_ALLOC
  11. struct lockdep_map dep_map;
  12. #endif
  13. } spinlock_t;

其实最简单的自旋锁的实现只需要一个字段,就是volatile unsigned int lock 
自旋锁的使用 
定义一个自旋锁:DEFINE_SPINLOCK(mr_lock); 
获取自旋锁:spin_lock(&mr_lock); 
释放自旋锁:spin_unlock(&mr_lock); 
自旋锁是专门为防止多处理器并行而引入的一种锁,多用于中断处理,自旋锁最多只能被一个内核任务持有。由于当自旋锁被持有时,等待它的任务进行自旋,所以自旋锁被持有的时间不能太久,如果需要长时间锁定可以用信号量。 
如果是开中断的情况下使用自旋锁要特别小心,因为中断可能随时会中断正在运行的程序,如果中断打断的是一段自旋锁保护的代码,那么其他申请同一自旋锁的cpu停顿。 
所以一般申请自旋锁都得关中断,内核提供spin_lock_irqsave和spin_unlock_irqrestore两个函数用于关中断并使用自旋锁

4.信号量

信号量也是一种锁,与自旋锁不同的地方是:当资源不可用时,信号量会将进程挂起,而自旋锁则让等待着忙等(特别浪费处理器时间),一般自旋锁用于中断上下文,信号量用于进程上下文。

4.1信号量的定义:

  1. struct semaphore {
  2. spinlock_t lock;
  3. unsigned int count;
  4. struct list_head wait_list;
  5. };

lock:自旋锁,为了防止多处理器并行造成错误。 
count:如果count大于0,那么表示资源空闲可用,如果count等于0,表示信号量正被使用,但没有其他进程等待,如果count小于0则表示有其他进程等待信号量。 
wait_list:等待信号量的进程链表。

4.2信号量的使用:

获取信号量函数:down()和down_interruptible(),down函数获取信号量时,如果信号量不可用则进程进入睡眠等待并且不可被信号中断。而使用down_interruptible进程在等待过程中可以被信号打断

  1. void down(struct semaphore *sem)
  2. {
  3. unsigned long flags;
  4. spin_lock_irqsave(&sem->lock, flags);//加锁,使信号量的操作在关闭中断下
  5. if (likely(sem->count > 0)) //信号量是否空闲可用,如果可用则count减1
  6. sem->count--;
  7. else
  8. __down(sem); // 如果不可用则将进程插入等待链表中
  9. spin_unlock_irqrestore(&sem->lock, flags); //解锁
  10. }
  1. static noinline void __sched __down(struct semaphore *sem)
  2. {
  3. __down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
  4. }

TASK_UNINTERRUPTIBLE表示进程处于不可中断状态,MAX_SCHEDULE_TIMEOUT表示没有超时,进程会一直等待。

  1. static inline int __sched __down_common(struct semaphore *sem, long state,
  2. long timeout)
  3. {
  4. struct task_struct *task = current;
  5. struct semaphore_waiter waiter;
  6. list_add_tail(&waiter.list, &sem->wait_list);/*将节点添加到信号量的等待队列尾*/
  7. waiter.task = task; //等待节点就是当前进程
  8. waiter.up = 0;
  9. for (;;) { /*这里使用一个死循环,是考虑到等待过程可能被信号打断,导致睡眠时间不足*/
  10. if (signal_pending_state(state, task)) /*如果是TASK_INTERRUPTIBLE或TASK_WAKEKILL状态,并且有信号将进程唤醒,则将当前进程从等待队列中取出并退出*/
  11. goto interrupted;
  12. if (timeout <= 0) //如果不是被信号唤醒的,而是等待超时的话
  13. goto timed_out;
  14. __set_task_state(task, state); //设置进程状态
  15. spin_unlock_irq(&sem->lock); //释放自旋锁
  16. timeout = schedule_timeout(timeout); // 进程切换
  17. spin_lock_irq(&sem->lock); //加锁
  18. if (waiter.up) /*进程被唤醒了,可能是睡眠时间已经到达,或者获得了信号量,或者被信号唤醒了,所以要进行判断若是被其他进程释放信号量而唤醒的,则返回。*/
  19. return 0;
  20. }
  21. timed_out://进程获取信号量等待超时时返回
  22. list_del(&waiter.list);
  23. return -ETIME;
  24. interrupted://进程等待获取信号量时被信号中断
  25. list_del(&waiter.list);
  26. return -EINTR;
  27. }

semaphore_waiter结构体:

  1. struct semaphore_waiter {
  2. struct list_head list;
  3. struct task_struct *task;
  4. int up;
  5. };

获取信号量函数还有 
down_killable:获取信号量,但可被致命信号唤醒 
down_trylock:获取信号量,如果信号量不可用,则返回失败而不等待 
down_timeout:获取信号量,如果在指定的时间内没有被信号量唤醒,则返回失败 
释放信号量函数up:

  1. void up(struct semaphore *sem)
  2. {
  3. unsigned long flags;
  4. spin_lock_irqsave(&sem->lock, flags); // 获得保护信号量的自旋锁
  5. if (likely(list_empty(&sem->wait_list))) //没有进程等待此信号量
  6. sem->count++;
  7. else
  8. __up(sem); //唤醒等待链表中的进程
  9. spin_unlock_irqrestore(&sem->lock, flags);//释放自旋锁
  10. }
  1. static noinline void __sched __up(struct semaphore *sem)
  2. {
  3. struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list,
  4. struct semaphore_waiter, list); //取得等待链表中的第一个任务
  5. list_del(&waiter->list); //将第一个任务从等待链表中删除
  6. waiter->up = 1; //设置up为1,表示进程是被信号量唤醒的,而不是信号
  7. wake_up_process(waiter->task); //唤醒进程
  8. }

4.3读写信号量

读写信号量类似于信号量,但它可以允许有多个读者,在不能获得信号量时,当前进程会调度出去。

  1. struct rw_semaphore {
  2. __s32 activity;
  3. spinlock_t wait_lock;
  4. struct list_head wait_list;
  5. #ifdef CONFIG_DEBUG_LOCK_ALLOC
  6. struct lockdep_map dep_map;
  7. #endif
  8. };

activity:如果activity为0,那么没有读者或写者, 如果activity>0,表示当前的并发读者数。 如果activity为-1,表示有一个写者。 
wait_lock:自旋锁 
wait_list:等待信号量的等待队列链表。 
获取读信号量函数down_read:

  1. void __sched down_read(struct rw_semaphore *sem)
  2. {
  3. might_sleep();
  4. rwsem_acquire_read(&sem->dep_map, 0, 0, _RET_IP_);
  5. LOCK_CONTENDED(sem, __down_read_trylock, __down_read);
  6. }

这个函数没看懂,不过它是调用 __down_read完成加读者的具体操作

  1. void __sched __down_read(struct rw_semaphore *sem)
  2. {
  3. struct rwsem_waiter waiter;
  4. struct task_struct *tsk;
  5. spin_lock_irq(&sem->wait_lock);
  6. //加锁并关中断
  7. if (sem->activity >= 0 && list_empty(&sem->wait_list)) {
  8. //如果有0或者多个读者且等待队列为空获得读写信号量
  9. /* granted */
  10. sem->activity++;//读者数加1
  11. spin_unlock_irq(&sem->wait_lock);//解锁
  12. goto out;
  13. }
  14. //若不能获得sem,将当前进程加入等待队列
  15. tsk = current;
  16. set_task_state(tsk, TASK_UNINTERRUPTIBLE);
  17. /* set up my own style of waitqueue */
  18. waiter.task = tsk;
  19. waiter.flags = RWSEM_WAITING_FOR_READ;//表示读写等待操作
  20. get_task_struct(tsk);//进程使用计数加1
  21. list_add_tail(&waiter.list, &sem->wait_list);
  22. /* we don't need to touch the semaphore struct anymore */
  23. spin_unlock_irq(&sem->wait_lock);
  24. /* wait to be given the lock */
  25. for (;;) {//读着等待sem
  26. if (!waiter.task)
  27. break;
  28. schedule();//调度出去,并等待被唤醒
  29. set_task_state(tsk, TASK_UNINTERRUPTIBLE);
  30. }
  31. tsk->state = TASK_RUNNING; //获得sem
  32. out:
  33. ;
  34. }

释放读信号量函数up_read:

  1. void up_read(struct rw_semaphore *sem)
  2. {
  3. rwsem_release(&sem->dep_map, 1, _RET_IP_);
  4. __up_read(sem);
  5. }

__up_read:

  1. void __up_read(struct rw_semaphore *sem)
  2. {
  3. unsigned long flags;
  4. spin_lock_irqsave(&sem->wait_lock, flags);
  5. if (--sem->activity == 0 && !list_empty(&sem->wait_list))
  6. //如果没有读者持有信号且等待队列有写者则唤醒等待的写者
  7. sem = __rwsem_wake_one_writer(sem);//只唤醒第1个写者
  8. spin_unlock_irqrestore(&sem->wait_lock, flags);
  9. }
  10. static inline struct rw_semaphore *
  11. __rwsem_wake_one_writer(struct rw_semaphore *sem)
  12. {
  13. struct rwsem_waiter *waiter;
  14. struct task_struct *tsk;
  15. sem->activity = -1;//将activity置-1,标志写者持有sem
  16. waiter = list_entry(sem->wait_list.next, struct rwsem_waiter, list);//获得第一个写者
  17. list_del(&waiter->list);//将其从队列中删除
  18. tsk = waiter->task;
  19. smp_mb();
  20. waiter->task = NULL;
  21. wake_up_process(tsk);//唤醒进程
  22. put_task_struct(tsk);
  23. return sem;
  24. }

释放与获得写信号量操作与读信号量相似 
获得写信号量:

  1. void __sched down_write(struct rw_semaphore *sem)
  2. {
  3. might_sleep();
  4. rwsem_acquire(&sem->dep_map, 0, 0, _RET_IP_);
  5. LOCK_CONTENDED(sem, __down_write_trylock, __down_write);
  6. }
  1. static inline void __down_write(struct rw_semaphore *sem)
  2. {
  3. __down_write_nested(sem, 0);
  4. }
  1. void __sched __down_write_nested(struct rw_semaphore *sem, int subclass)
  2. {
  3. struct rwsem_waiter waiter;
  4. struct task_struct *tsk;
  5. spin_lock_irq(&sem->wait_lock);
  6. if (sem->activity == 0 && list_empty(&sem->wait_list)) { //没有读者和写者,并且没有其他处于等待状态的读者和写者
  7. /* granted */
  8. sem->activity = -1;//将activity置-1,标志写者持有sem
  9. spin_unlock_irq(&sem->wait_lock);
  10. goto out;
  11. }
  12. //运行到这里,说明有读者或者写者,将当前任务挂起
  13. tsk = current;
  14. set_task_state(tsk, TASK_UNINTERRUPTIBLE);
  15. /* set up my own style of waitqueue */
  16. waiter.task = tsk;
  17. waiter.flags = RWSEM_WAITING_FOR_WRITE;/*这个标志表示等待者是一个写者*/
  18. get_task_struct(tsk);
  19. list_add_tail(&waiter.list, &sem->wait_list);
  20. /* we don't need to touch the semaphore struct anymore */
  21. spin_unlock_irq(&sem->wait_lock);
  22. /* wait to be given the lock */
  23. for (;;) {
  24. if (!waiter.task)
  25. break;
  26. schedule();
  27. set_task_state(tsk, TASK_UNINTERRUPTIBLE);
  28. }
  29. tsk->state = TASK_RUNNING;
  30. out:
  31. ;
  32. }
  1. void up_write(struct rw_semaphore *sem)
  2. {
  3. rwsem_release(&sem->dep_map, 1, _RET_IP_);
  4. __up_write(sem);
  5. }
  1. void __up_write(struct rw_semaphore *sem)
  2. {
  3. unsigned long flags;
  4. spin_lock_irqsave(&sem->wait_lock, flags);
  5. sem->activity = 0;
  6. /*因为仅仅只能有一个写者,也不能有读者能够与写者同在,因此写者释放信号量时,可以将activity设置为0,表示没有写者和读者了*/
  7. if (!list_empty(&sem->wait_list))
  8. sem = __rwsem_do_wake(sem, 1);
  9. spin_unlock_irqrestore(&sem->wait_lock, flags);
  10. }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值