Linux内核机制—semaphore
一、信号量相关结构
1. 信号量描述结构体
struct semaphore { /* 保护信号量的spinlock锁 */ raw_spinlock_t lock; /* * 对于二值信号量,为1表示没有任务在临界区,为0表示只有1个任 * 务在临界区,没有任务等待 在改信号量上,为-n表示有n个任务等 * 待在该信号量上 */ unsigned int count; /* * 获取不到信号量的任务对应的semaphore_waiter结构会串联在这个 * 链表上,先入先出。 */ struct list_head wait_list; };
2. 信号量等待任务描述结构体
struct semaphore_waiter { /* 通过此成员挂在semaphore结构的wait_list链表上 */ struct list_head list; /* 等待信号量的任务 */ struct task_struct *task; /* * 被释放信号量的行为唤醒的情况下为真,其它比如 * 信号、超时而导致的唤醒为假。 */ bool up; };
二、信号量相关函数
1. 初始化函数
//定义并初始化一个二值信号量 DEFINE_SEMAPHORE(sem1); //初始化一个二值或非二值的信号量 void sema_init(struct semaphore *sem, int val);
2. 获取信号量函数
//获取信号量,若是获取不到进入 TASK_UNINTERRUPTIBLE 状态 void down(struct semaphore *sem); //同 down(),区别是若获取不到进入 TASK_INTERRUPTIBLE 状态,支持被信号唤醒 int __must_check down_interruptible(struct semaphore *sem); //同 down(),区别是若获取不到进入 TASK_KILLABLE(TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)状态,表示只可被 SIGKILL 信号唤醒 int __must_check down_killable(struct semaphore *sem); //尝试获取信号量,不会阻塞,返回1成功,0失败 int __must_check down_trylock(struct semaphore *sem); //同 down(),区别是若获取不到不会一直等待,而是有一个超时时间,到期后自动唤醒 int __must_check down_timeout(struct semaphore *sem, long jiffies);
3. 释放信号量函数
void up(struct semaphore *sem);
三、获取信号量函数代码走读
我们基于 down() 函数进行解读:
/* * down - 获取信号量 * * 获取信号量函数,若此信号量不允许更多任务获取它时,此函数会让任务进入休眠状态 * 直到有人释放信号量。 * * 此函数已经被弃用了,请使用 down_interruptible() 或 down_killable() 替代。 */ void down(struct semaphore *sem) //kernel/locking/semaphore.c { unsigned long flags; raw_spin_lock_irqsave(&sem->lock, flags); /* 或信号量值大于0,直接将去1然后返回,信号量值小于等于0时才会阻塞 */ if (likely(sem->count > 0)) sem->count--; else __down(sem); raw_spin_unlock_irqrestore(&sem->lock, flags); } EXPORT_SYMBOL(down); static noinline void __sched __down(struct semaphore *sem) { /* * down() 获取不到信号量会进入D状态,而且无限超时。 * * down() 的其它衍生函数都会调用到 __down_common() 只是传参不同。 */ __down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT); }
__down_common() 是所有down的衍生函数都要调用的一个公共函数,只是传参不同。
/* * __down 传参:(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT) 还可以指定超时时间。 */ static inline int __sched __down_common(struct semaphore *sem, long state, long timeout) { struct semaphore_waiter waiter; /* * 将当前任务放在链表末尾部,up初始化为假(唤醒后,被释放信号量的行 * 为唤醒的情况下为真,被信号、等待超时而导致的唤醒为假) */ list_add_tail(&waiter.list, &sem->wait_list); waiter.task = current; waiter.up = false; for (;;) { /* * down()的三个衍生函数响应不同: * down() 不起作用,D状态不支持信号唤醒。 * down_interruptible() 这里可能被信号唤醒。 * down_killable() 这里只可能被SIGKILL信号唤醒。 */ if (signal_pending_state(state, current)) goto interrupted; /* 定时时间到期,只有 down_timeout() 可能从这里退出 */ if (unlikely(timeout <= 0)) goto timed_out; /* 将当前进程的状态设置为非RUNNING状态,准备休眠 */ __set_current_state(state); raw_spin_unlock_irq(&sem->lock); /* 触发切换,当前进程让出CPU */ timeout = schedule_timeout(timeout); raw_spin_lock_irq(&sem->lock); /* 若是被释放信号量的行为唤醒的,返回0,见 up() 的讲解 */ if (waiter.up) return 0; } timed_out: /* 对于 down_timeout() 等待超时后,将当前任务从链表上删除,返回-ETIME */ list_del(&waiter.list); return -ETIME; interrupted: /* 对于down_interruptible()和down_killable()被信号唤醒返回-EINTR */ list_del(&waiter.list); return -EINTR; }
检测信号的 signal_pending_state():
static inline int signal_pending_state(long state, struct task_struct *p) { /* 若state传参 TASK_INTERRUPTIBLE, 这里直接就退出了 */ if (!(state & (TASK_INTERRUPTIBLE | TASK_WAKEKILL))) return 0; if (!signal_pending(p)) return 0; /*对于 TASK_KILLABLE(TASK_WAKEKILL | TASK_UNINTERRUPTIBLE),只能被SIGKILL唤醒*/ return (state & TASK_INTERRUPTIBLE) || __fatal_signal_pending(p); }
触发进程切换的 schedule_timeout() 函数:
/* * schedule_timeout - 睡眠直到超时 * @timeout: 超时值,单位 jiffies * * 使当前任务休眠,直到 @timeout jiffies 过去。函数行为取决于当前任务状态(另 * 见 set_current_state() 描述): * * %TASK_RUNNING - 调度程序被调用,但任务根本不休眠。发生这种情况是因为 sched_submit_work() * 对处于 %TASK_RUNNING 状态的任务不执行任何操作。 * * %TASK_UNINTERRUPTIBLE - 保证至少在 @timeout jiffies 之后才返回,除非当前任务被显式唤醒 * (例如通过wake_up_process())。 * * %TASK_INTERRUPTIBLE - 如果信号被传递到当前任务或当前任务被显式唤醒,可能会提前返回。 * * 当此函数返回时,当前任务状态保证为 %TASK_RUNNING。 * * @timeout 若被指定为 %MAX_SCHEDULE_TIMEOUT,将直接切走而不受超时限制。在这种情况下, * 返回值将是 %MAX_SCHEDULE_TIMEOUT。 * * 当定时器超时返回 0,否则将返回剩余的 jiffies 时间。在所有情况下,返回值都保证为非负数。 */ signed long __sched schedule_timeout(signed long timeout) //kernel/time/timer.c { struct process_timer timer; unsigned long expire; switch (timeout) { case MAX_SCHEDULE_TIMEOUT: /* 若指定的是无限超时,直接切走,不会触发定时器定时 */ schedule(); goto out; default: /* 这个情况永远也不应该发生,打印一个报错提示 */ if (timeout < 0) { printk(KERN_ERR "schedule_timeout: wrong timeout value %lx\n", timeout); dump_stack(); current->state = TASK_RUNNING; goto out; } } /* 定时器到期时间指定为当前jiffies值加上指定的到期时间 */ expire = timeout + jiffies; /* 指定为当前将被阻塞的任务 */ timer.task = current; timer_setup_on_stack(&timer.timer, process_timeout, 0); /* 启动定时器 */ __mod_timer(&timer.timer, expire, MOD_TIMER_NOTPENDING); /* 将当前任务切走 */ schedule(); /* 当前任务被唤醒后继续执行 */ del_singleshot_timer_sync(&timer.timer); /* Remove the timer from the object tracker */ destroy_timer_on_stack(&timer.timer); /* 检查超时时间是否到期,到期返回0否则返回剩余的jiffies时间 */ timeout = expire - jiffies; out: return timeout < 0 ? 0 : timeout; } EXPORT_SYMBOL(schedule_timeout); /* 超时唤醒函数 */ static void process_timeout(struct timer_list *t) //kernel/time/timer.c { struct process_timer *timeout = from_timer(timeout, t, timer); wake_up_process(timeout->task); }
四、释放信号量函数代码走读
static noinline void __sched __up(struct semaphore *sem) //kernel/locking/semaphore.c { /* 从链表首取待唤醒的任务进行唤醒 */ struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list, struct semaphore_waiter, list); /* 先从wait链表上取下来 */ list_del(&waiter->list); /* 被up唤醒的任务up成员为true */ waiter->up = true; /* 执行唤醒选核流程 */ wake_up_process(waiter->task); }
五、信号量使用场景
1. 二值信号量做成的锁,没有像mutex那样的乐观自旋,因此适合于保护较长的临界区。
2. 如果被保护的共享资源有多份,并不只是互斥访问的,那非常适合使用信号量。
六、总结
1. 信号量分为二值信号量和非二值信号量。前者可以当做锁来使用,主要用于保护临界区较大执行时间较长的代码段。后者可以用于多份资源的生产消费同步。
2. 可以指定获取不到信号量时任务的状态,Sleep、D、killable状态。还可以指定获取不到信号量时的等待超时时间,超时后自动退出等待。
posted on 2022-05-22 20:10 Hello-World3 阅读(882) 评论(0) 编辑 收藏 举报