源出处:http://www.startos.com/linux/tips/2011011921499_3.html
在决定调用sleep_on系列函数到真正调用schedule系列函数期间,若等待的条件为真,若此时继续schedule,相当于丢失了一次唤醒机会。因此sleep_on系列函数会引入竞态,导致系统的不安全。
另外对于interruptible系列函数,其返回时并不能确定是因为资源可用返回还是遇到了signal,因此在程序中用户需要再次判断资源是否可用。如:
static ssize_t at91_mcp2510_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)
{
CanData candata_ret;
retry:
if (mcp2510dev.nCanReadpos != mcp2510dev.nCanRevpos) {// 需求的资源
int count;
count = MCP2510_RevRead(&candata_ret);
if (count) copy_to_user(buffer, (char *)&candata_ret, count);
return count;
} else { //不可用
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
interruptible_sleep_on(&(mcp2510dev.wq));
if (signal_pending(current)) // 遇到信号,返回
return -ERESTARTSYS;
goto retry; //重新判断资源是否可用
}
DPRINTK("read data size=%d\n", sizeof(candata_ret));
return sizeof(candata_ret);
}
综合上述两个因素,sleep_on系列函数应避免在驱动程序中出现,未来的2.7版内核中将删除此类函数。
2.3 等待队列的接口
2.3.1 初始化等待队列
#define DEFINE_WAIT(name) \
wait_queue_t name = { \
.private = current, \
.func = autoremove_wake_function, \
.task_list = LIST_HEAD_INIT((name).task_list), \
}
#define init_wait(wait) \
do { \
(wait)->private = current; \
(wait)->func = autoremove_wake_function; \
INIT_LIST_HEAD(&(wait)->task_list); \
} while (0)
专用的初始化等待队列函数,将当前进程添加到等待队列中,注意和通用的接口 DECLARE_WAITQUEUE及init_waitqueue_entry区别。同时唤醒处理不一样,autoremove_wake_function在default_wake_function的基础之上,将当前进程从等待队列中删除。
2.3.2 添加或从等待队列中删除
添加删除的原始接口
static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new)
{
list_add(&new->task_list, &head->task_list);
}
static inline void __add_wait_queue_tail(wait_queue_head_t *head,
wait_queue_t *new)
{
list_add_tail(&new->task_list, &head->task_list);
}
static inline void __remove_wait_queue(wait_queue_head_t *head,
wait_queue_t *old)
{
list_del(&old->task_list);
}
等待队列是公用资源,但上述接口没有加任何保护措施,适用于已经获得锁的情况下使用。
带锁并设置属性的添加删除
void fastcall add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
{
unsigned long flags;
wait->flags &= ~WQ_FLAG_EXCLUSIVE;
spin_lock_irqsave(&q->lock, flags);
__add_wait_queue(q, wait);
spin_unlock_irqrestore(&q->lock, flags);
}
EXPORT_SYMBOL(add_wait_queue);
void fastcall add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait)
{
unsigned long flags;
wait->flags |= WQ_FLAG_EXCLUSIVE;
spin_lock_irqsave(&q->lock, flags);
__add_wait_queue_tail(q, wait);
spin_unlock_irqrestore(&q->lock, flags);
}
EXPORT_SYMBOL(add_wait_queue_exclusive);
void fastcall remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
{
unsigned long flags;
spin_lock_irqsave(&q->lock, flags);
__remove_wait_queue(q, wait);
spin_unlock_irqrestore(&q->lock, flags);
}
EXPORT_SYMBOL(remove_wait_queue);
此三对函数的特点是调用同名的内部函数,同时添加一些保障安全特性的代码。WQ_FLAG_EXCLUSIVE属性的进程添加到队尾,而非 WQ_FLAG_EXCLUSIVE从队头添加。这样可以保证每次都能唤醒所有的非WQ_FLAG_EXCLUSIVE进程。
无锁但设置属性的添加删除
static inline void add_wait_queue_exclusive_locked(wait_queue_head_t *q,
wait_queue_t * wait)
{
wait->flags |= WQ_FLAG_EXCLUSIVE;
__add_wait_queue_tail(q, wait);
}
/* Must be called with the spinlock in the wait_queue_head_t held.*/
static inline void remove_wait_queue_locked(wait_queue_head_t *q,
wait_queue_t * wait)
{
__remove_wait_queue(q, wait);
}
Locked系列适用于在已经获得锁的情况下调用,通常用于信号量后者complete系列函数中。
上述三组函数,__add_wait_queue是内部函数,无任何保护,无任何属性设置;而另外两组分别适用于当前是否加锁的两种场合,是对外的接口函数。这种根据适用场合不同提供不同的接口函数的方法值得借鉴。