任务获取一个已经被占用的信号量,信号量将其推进一个等待队列,然后让其睡眠,CPU去执行其他代码。当持有信号量的进程将信号释放,处于等待队列汇总的任务被唤醒,并获取信号量。
信号量会引起睡眠,所以在中断上下文中不能使用信号量。一般在进程上下文中使用。
一个进程占用信号量的同时不能同时占用自旋锁,因为占用信号量会引起睡眠,而占用自旋锁是不允许睡眠的。
信号量不同于自旋锁,不会禁止内核抢占,所以持有信号量的代码可以被抢占。
信号量的操作:
down():信号量计数器减1,若减一后大于等于0,将获得锁并进入临界区;若小于0,任务将会放入等待队列,不进入睡眠。
up():释放信号量,计数器加1。若等待队列不为空,那么队列中的任务在被唤醒的同时会获 得该信号量。
信号量的定义:
- struct semaphore {
- raw_spinlock_t lock;//自旋锁,用于对信号量count进行原子操作
- unsigned int count;//信号量计数器
- struct list_head wait_list;//管理所有在该信号量上睡眠的进程
- };
信号量的使用:
- static DECLARE_MUTEX(mr_sem);
- if(down_interruptible(&mr_sem)){
- /*信号被接收,但信号量还未获取*/
- }
- /*信号已经接收,并进入到临界区*/
- /*释放给定的信号量*/
- up(&mr_sem);
own和down_interruptible函数的区别在于down将进程的状态设置为不可中断的,其他的操作基本相同。
信号量的实现:
- static inline int __sched __down_common(struct semaphore *sem, long state,
- long timeout)
- {
- struct task_struct *task = current;
- struct semaphore_waiter waiter;
- list_add_tail(&waiter.list, &sem->wait_list);
- waiter.task = task;
- waiter.up = false;
- for (;;) {
- if (signal_pending_state(state, task))
- goto interrupted;
- if (unlikely(timeout <= 0))
- goto timed_out;
- __set_task_state(task, 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;
- }
该函数首先会通过semaphore_waiter waiter变量将当前进程放到信号量sem的成员变量wait_list所管理的队列中,接着在一个for循环中把当前进程的状态设置为TASK_INTERRUPTIBLE,再调用schedule_timeout时当前进程进入到睡眠状态。函数将停留在schedule_timeout调用上,知道再次被调度执行。当该进程再一次被调度执行时,schedule_timeout开始返回,接下来根据进程被再次调度的原因进行处理:如果waiter.up不为0,说明进程在信号量sem的wait_list队列中被该信号量的up操作所唤醒,进程可以获得信号量,并返回0.如果进程是因为被用户空间发送的信号所中断或者是超时所引起的唤醒,则返回相应的错误代码。因此对down_interruptible的调用总是应该坚持检查其返回值,以确认函数是已经获得了信号量还是因为操作被中断因而需要特别处理。通常驱动程序对返回的非0值要做的工作是返回-ERESTARTSYS。
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);
- }
- 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);
- }<span style="font-family: Arial, Helvetica, sans-serif;"> </span>
__up函数首先会用 list_first_entry获取wait_list上链表上的第一个waiter节点,然后将其从sem->wait_list上删除。Waiter->up=1最后调用wait_up_process来唤醒waiter节点对应的进程,这样进程将会从之前down_interruptible调用中的timeout = schedule_timeout(timeout)醒来,waiter->up=1,down_interruptible返回0,进程获得信号量,该信号量上sem->wait_list等待列表上的其它进程会继续等待直到有进程释放一个信号量或者被用户空间中断掉。