解析Linux内核的同步与互斥机制(三)

释放双眼,带上耳机,听听看~!

在决定调用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是内部函数,无任何保护,无任何属性设置;而另外两组分别适用于当前是否加锁的两种场合,是对外的接口函数。这种根据适用场合不同提供不同的接口函数的方法值得借鉴。

给TA打赏
共{{data.count}}人
人已打赏
安全运维

WordPress网站专用docker容器环境带Waf

2020-7-18 20:04:44

安全运维

运维安全-Gitlab管理员权限安全思考

2021-9-19 9:16:14

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索