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

来源:互联网 发布:java软件系统技术合同 编辑:程序博客网 时间:2024/04/30 19:14

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


原创粉丝点击