一、定义:
linux/include/linux/completion.h
13struct completion { 14 unsigned int done; 15 wait_queue_head_t wait; 16}; |
二、作用:
虽然信号量可以用于实现同步,但往往可能会出现一些不好的结果。例如:当进程A分配了一个临时信号量变量,把它初始化为关闭的MUTEX,并把其地址传递给进程B,然后在A之上调用down(),进程A打算一旦被唤醒就撤销给信号量。随后,运行在不同CPU上的进程B在同一个信号量上调用up()。然而,up()和down()的目前实现还允许这两个函数在同一个信号量上并发。因此,进程A可以被唤醒并撤销临时信号量,而进程B还在运行up()函数。结果up()可能试图访问一个不存在的数据结构。这样就会出现错误。为了防止发生这种错误就专门设计了completion机制专门用于同步。
三、个字段详解:
1、unsigned int done;
指示等待的事件是否完成。初始化时为0。如果为0,则表示等待的事件未完成。大于0表示等待的事件已经完成。
2、wait_queue_head_t wait;
存放等待该事件完成的进程队列。
四、操作:
1、定义和初始化:
(1)
struct completion completion;
init_completion(&completion);
直接定义并调用init_completion()初始化。init_completion()会将done字段初始化为0,wait字段的自旋锁为未锁,等待队列为空。这说明调用该完成量的进程必须等待某事件完成(即另外一进程必须先调用completiom()唤醒该完成量)。
(2)
DECLARE_COMPLETION(completion);
直接定义并初始化completion完成量,效果等同于以上定义方式。
2、等待完成量:
(1)wait_for_completion()函数,/linux/kernel/sched.c
3731void fastcall __sched wait_for_completion(struct completion *x) 3732{ 3733 might_sleep(); 3734 3735 spin_lock_irq(&x->wait.lock); 3736 if (!x->done) { 3737 DECLARE_WAITQUEUE(wait, current); 3738 3739 wait.flags |= WQ_FLAG_EXCLUSIVE; 3740 __add_wait_queue_tail(&x->wait, &wait); 3741 do { 3742 __set_current_state(TASK_UNINTERRUPTIBLE); 3743 spin_unlock_irq(&x->wait.lock); 3744 schedule(); 3745 spin_lock_irq(&x->wait.lock); 3746 } while (!x->done); 3747 __remove_wait_queue(&x->wait, &wait); 3748 } 3749 x->done--; 3750 spin_unlock_irq(&x->wait.lock); 3751} |
该函数相当于信号量中的down()操作。不过在操作中对使用其自身的自旋锁。如果done为0,则说明等待的事件没有完成,则调用DECLARE_WAITQUEUE()定义等待队列wait并将当前进程添加进等待队列wait。然后将wait添加进该完成量的等待队列的末尾,进入循环。设置当前进程为不可中断状态(TASK_UNINTERRUPTIBLE),释放自旋锁并让当前进程进入睡眠状态。一旦进程被调度唤醒据又获得自旋锁并查看等待的事件是否完成。如果完成(大于0),则从完成量的等待队列中删除等待的进程,并自减
3754unsigned long fastcall __sched 3755wait_for_completion_timeout(struct completion *x, unsigned long timeout) 3756{ 3757 might_sleep(); 3758 3759 spin_lock_irq(&x->wait.lock); 3760 if (!x->done) { 3761 DECLARE_WAITQUEUE(wait, current); 3762 3763 wait.flags |= WQ_FLAG_EXCLUSIVE; 3764 __add_wait_queue_tail(&x->wait, &wait); 3765 do { 3766 __set_current_state(TASK_UNINTERRUPTIBLE); 3767 spin_unlock_irq(&x->wait.lock); 3768 timeout = schedule_timeout(timeout); 3769 spin_lock_irq(&x->wait.lock); 3770 if (!timeout) { 3771 __remove_wait_queue(&x->wait, &wait); 3772 goto out; 3773 } 3774 } while (!x->done); 3775 __remove_wait_queue(&x->wait, &wait); 3776 } 3777 x->done--; 3778out: 3779 spin_unlock_irq(&x->wait.lock); 3780 return timeout; 3781}
也是等待完成量。与wait_for_completion()最大的区别是它等待超时的情况下返回。也就是说如果经过给定的时间该完成量还没有被唤醒,就直接返回。这样最大的好处是经过一定的时间该进程已经不需要等待某事件,那么就可以直接被唤醒继续执行。
(3)wait_for_completion_interruptible()函数,/linux/kernel/sched.c
3784int fastcall __sched wait_for_completion_interruptible(struct completion *x) 3785{ 3786 int ret = 0; 3787 3788 might_sleep(); 3789 3790 spin_lock_irq(&x->wait.lock); 3791 if (!x->done) { 3792 DECLARE_WAITQUEUE(wait, current); 3793 3794 wait.flags |= WQ_FLAG_EXCLUSIVE; 3795 __add_wait_queue_tail(&x->wait, &wait); 3796 do { 3797 if (signal_pending(current)) { 3798 ret = -ERESTARTSYS; 3799 __remove_wait_queue(&x->wait, &wait); 3800 goto out; 3801 } 3802 __set_current_state(TASK_INTERRUPTIBLE); 3803 spin_unlock_irq(&x->wait.lock); 3804 schedule(); 3805 spin_lock_irq(&x->wait.lock); 3806 } while (!x->done); 3807 __remove_wait_queue(&x->wait, &wait); 3808 } 3809 x->done--; 3810out: 3811 spin_unlock_irq(&x->wait.lock); 3812 3813 return ret; 3814} |
可见这中等待完成量的方式是可以被信号打断的。如果当前进程收到 如果收到TIF_SIGPENDING信号,则等待该完成量的进程会被从等待队列中删除,并返回ERESTARTSYS。
(4)wait_for_completion_interruptible_timeout()函数,/linux/kernel/sched.c
3817unsigned long fastcall __sched 3818wait_for_completion_interruptible_timeout(struct completion *x, 3819 unsigned long timeout) 3820{ 3821 might_sleep(); 3822 3823 spin_lock_irq(&x->wait.lock); 3824 if (!x->done) { 3825 DECLARE_WAITQUEUE(wait, current); 3826 3827 wait.flags |= WQ_FLAG_EXCLUSIVE; 3828 __add_wait_queue_tail(&x->wait, &wait); 3829 do { 3830 if (signal_pending(current)) { 3831 timeout = -ERESTARTSYS; 3832 __remove_wait_queue(&x->wait, &wait); 3833 goto out; 3834 } 3835 __set_current_state(TASK_INTERRUPTIBLE); 3836 spin_unlock_irq(&x->wait.lock); 3837 timeout = schedule_timeout(timeout); 3838 spin_lock_irq(&x->wait.lock); 3839 if (!timeout) { 3840 __remove_wait_queue(&x->wait, &wait); 3841 goto out; 3842 } 3843 } while (!x->done); 3844 __remove_wait_queue(&x->wait, &wait); 3845 } 3846 x->done--; 3847out: 3848 spin_unlock_irq(&x->wait.lock); 3849 return timeout; 3850} |
可中断的并且可超时返回的等待完成量。3、唤醒完成量:
(1)completion()函数:/linux/kernel/sched.c
3707void fastcall complete(struct completion *x) 3708{ 3709 unsigned long flags; 3710 3711 spin_lock_irqsave(&x->wait.lock, flags); 3712 x->done++; 3713 __wake_up_common(&x->wait, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 3714 1, 0, NULL); 3715 spin_unlock_irqrestore(&x->wait.lock, flags); 3716 |
3719void fastcall complete_all(struct completion *x) 3720{ 3721 unsigned long flags; 3722 3723 spin_lock_irqsave(&x->wait.lock, flags); 3724 x->done += UINT_MAX/2; 3725 __wake_up_common(&x->wait, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 3726 0, 0, NULL); 3727 spin_unlock_irqrestore(&x->wait.lock, flags); 3728
唤醒所有等待给完成量的进程。