Skip navigation.

世界上最遥远的距离(泰戈尔)

By The furthest distance in the world
世界上最遥远的距离
Is not between life and death
不是生与死
But when I stand in front of you
而是 我就站在你面前
Yet you don't know that I love you
你却不知道我爱你
The furthest distance in the world
世界上最遥远的距离
Is not when I stand in front of you
不是 我就站在你面前
Yet you can't see my love
你却不知道我爱你
But when undoubtedly knowing the love from both
而是 明明知道彼此相爱
Yet cannot be together
却不能在一起
The furthest distance in the world
世界上最遥远的距离
Is not being apart while being in love
不是 明明知道彼此相爱 却不能在一起
But when painly cannot resist the yearning
而是 明明无法抵挡这股思念
Yet pretending you have never been in my heart
却还得故意装作丝毫没有把你放在心里
The furthest distance in the world
世界上最遥远的距离
Is not but using one's indifferent heart
不是 明明无法抵挡这股思念 却还得故意装作丝毫没有把你放在心里
To dig an uncrossable river
而是 用自己冷漠的心对爱你的人
For the one who loves you
掘了一条无法跨越的沟渠

开发内核的几条经验

,

作者:王聪@西邮


1. 永远不要使用sleep_on及其变种,那是不安全的,而且即将被剔出。
2. 老的/proc接口是有害的,如果你不能彻底理解它,千万别使用!尽量不要再往/proc里面放东西了,因为那里现在已经够乱了。
3. wait_event的一个变种──wait_event_interruptible_exclusive──是可以执行独占等待的。LDD中这一点错了,不,过时了。
4. 不要使用lock_kernel(),这个锁太大了,会让系统性能下降。
5. 信号量本身就是一种高级的lock,它能比普通的lock,比如spinlock,实现更多的语义。就像分配了内存后必须释放一样,一个进程对一个信号量进行down操作后,必须在退出前执行up。切记:不要在拥有锁时睡眠!
6. Unix的errno具有丰富的含义,请认真小心地处理它们,在用户空间如此,在内核空间更要如此,因为内核空间的一个错误状态直接影响到现有用户程序的表现。
7. oops是严重的错误,不能信任存在oops的内核。
8.
关于补丁:
a) 稳定内核的补丁只能应用于基版本的内核,比如:2.6.17.*补丁只能应用于2.6.17版本的内核;
b) 基版本内核补丁只能应用于上一个版本的内核,比如:2.6.18补丁只能应用于2.6.17;
c) 增量型补丁只能从一个指定的版本应用于另一个指定版本,比如:补丁patch-2.6.17.10-11.bz2只能应用于2.6.17.10,把它升级为2.6.17.11;
d) 如果你需要升级的版本间隔比较大,比如从2.6.17.1到2.6.17.11,可以先从2.6.17.1退回到2.6.17,然后再一次升级到2.6.17.11;
e) 稳定内核的补丁和基版本内核补丁都在源代码目录中,而增量型补丁在一个单独的目录/pub/linux/kernel/v2.6/incr中。

探索内核bug的经历

, ,

04043196 王聪 西安邮电学院计算机系

我们知道,当无符号数上溢时,它会安安静静地绕回,因此,当比较两个无符号数时,不得不考虑绕回的问题。很可能绝大多数情况下不会出现溢出的情况,但是一旦溢出而处理不当就会导致系统进入非预期状态。不幸的是,Linux内核中的kfifo并没有恰当地处理这一问题。

struct kfifo定义在include/linux/kfifo.h中,其成员如下:

struct kfifo {
        unsigned char *buffer;
        unsigned int size;
        unsigned int in;
        unsigned int out;
        spinlock_t *lock;
};
很明显,in和out两个成员都是无符号整型,这主要是为了下面的一个与操作方便。__kfifo_put和__kfifo_get是不带锁的两个接口,分别向循环缓冲区中放数据和取数据,定义如下:
   118  unsigned int __kfifo_put(struct kfifo *fifo,
   119                           unsigned char *buffer, unsigned int len)
   120  {
   121          unsigned int l;
   122
   123          len = min(len, fifo->size - fifo->in + fifo->out);
   ...
   130          smp_mb();
   131
   132          /* first put the data starting from fifo->in to buffer end */
   133          l = min(len, fifo->size - (fifo->in & (fifo->size - 1)));
   134          memcpy(fifo->buffer + (fifo->in & (fifo->size - 1)), buffer, l);   
   135
   136          /* then put the rest (if any) at the beginning of the buffer */
   137          memcpy(fifo->buffer, buffer + l, len - l);
   ...
   144          smp_wmb();
   145
   146          fifo->in += len;
   147
   148          return len;
   149  }
   ...
   164  unsigned int __kfifo_get(struct kfifo *fifo,
   165                           unsigned char *buffer, unsigned int len)
   166  {
   167          unsigned int l;
   168
   169          len = min(len, fifo->in - fifo->out);
   ...
   176          smp_rmb();
   177
   178          /* first get the data from fifo->out until the end of the buffer */
   179          l = min(len, fifo->size - (fifo->out & (fifo->size - 1)));
   180          memcpy(buffer, fifo->buffer + (fifo->out & (fifo->size - 1)), l);
   181
   182          /* then get the rest (if any) from the beginning of the buffer */
   183          memcpy(buffer + l, fifo->buffer, len - l);
   ...
   190          smp_mb();
   191
   192          fifo->out += len;
   193
   194          return len;
   195  }
上面的两个函数在正常情况下可以保证in总是大于等于out,并且它们的差不会超过size。但是当in溢出,而out恰好又没有溢出时,不幸的情况就会发生,in会小于out!这对__kfifo_get影响似乎不大,但对__kfifo_put却是致命地影响!in绕回后会变成一个很小的正数,而out仍然是一个很大的正数,结果(fifo->size - fifo->in + fifo->out)也会变成一个很大的正数。如果内核程序员恰好不小心把一个很大的len作为参数传递给了__kfifo_put(kfifo_put也一样),就会出现指针越界,更严重的会让内核痛苦地oops!

下面一个粗糙的内核模块和用户程序可以展示这个bug。内核模块如下:

     1  #include <linux/kernel.h>
     2  #include <linux/init.h>
     3  #include <linux/module.h>
     4  #include <linux/fs.h>
     5  #include <asm/uaccess.h>
     6  #include <linux/err.h>
     7  #include <linux/gfp.h>
     8  #include <linux/spinlock.h>
     9  #include <linux/kfifo.h>
    10  #include <linux/string.h>
    11
    12  #define LFS_MAGIC 0x19860913
    13  #define NFILES  2
    14  #define TEST_BUF_LEN    64
    15
    16  static struct kfifo *fifo;
    17  static spinlock_t lock;
    18  static char *buf;
    19
    20  static int lfs_open_file(struct inode *inode, struct file *filp)
    21  {
    22          if (inode->i_ino > NFILES)
    23                  return -ENODEV;
    24          return 0;
    25  }
    26
    27  static ssize_t lfs_read_file(struct file *filp, char *buffer,
    28                               size_t count, loff_t * offset)
    29  {
    30          int len;
    31
    32          len = kfifo_get(fifo, buf, count);
    33          if (*offset > len)
    34                  return 0;
    35          if (count > len - *offset)
    36                  count = len - *offset;
    37
    38          if (copy_to_user(buffer, buf + *offset, count))
    39                  return -EFAULT;
    40          *offset += count;
    41          return count;
    42  }
    43
    44  static ssize_t lfs_write_file(struct file *filp, const char *buffer,
    45                                size_t count, loff_t * offset)
    46  {
    47          if (*offset != 0)
    48                  *offset = 0;
    49
    50          if (count >= TEST_BUF_LEN)
    51                  count = TEST_BUF_LEN;
    52
    53          if (copy_from_user(buf, buffer, count))
    54                  return -EFAULT;
    55
    56          return (ssize_t) kfifo_put(fifo, (char *)buffer, count);
    57  }
    58
    59  static int my_atoi(const char *name)
    60  {
    61          int val = 0;
    62
    63          for (;; name++) {
    64                  switch (*name) {
    65                  case '0'...'9':
    66                          val = 10 * val + (*name - '0');
    67                          break;
    68                  default:
    69                          return val;
    70                  }
    71          }
    72  }
    73
    74  static int lfs_open_file2(struct inode *inode, struct file *filp)
    75  {
    76          if (inode->i_ino > NFILES)
    77                  return -ENODEV;
    78          filp->private_data = fifo;
    79          return 0;
    80  }
    81
    82  static ssize_t lfs_read_file2(struct file *filp, char *buffer,
    83                                size_t count, loff_t * offset)
    84  {
    85          int len;
    86          struct kfifo *myfifo = (struct kfifo *)filp->private_data;
    87
    88          len =
    89              snprintf(buf, TEST_BUF_LEN, "in=%u out=%u\n", myfifo->in,
    90                       myfifo->out);
    91          if (*offset > len)
    92                  return 0;
    93          if (count > len - *offset)
    94                  count = len - *offset;
    95
    96          if (copy_to_user(buffer, buf + *offset, count))
    97                  return -EFAULT;
    98          *offset += count;
    99          return count;
   100  }
   101
   102  static ssize_t lfs_write_file2(struct file *filp, const char *buffer,
   103                                 size_t count, loff_t * offset)
   104  {
   105          char *p = buf;
   106          struct kfifo *myfifo = (struct kfifo *)filp->private_data;
   107
   108          if (*offset != 0)
   109                  return -EINVAL;
   110
   111          if (count >= TEST_BUF_LEN)
   112                  return -EINVAL;
   113          memset(buf, 0, TEST_BUF_LEN);
   114          if (copy_from_user(buf, buffer, count))
   115                  return -EFAULT;
   116          p = strchr(buf, ' ');
   117          if (!p)
   118                  return -EINVAL;
   119          *p++ = '\0';
   120          myfifo->in = my_atoi(buf);
   121          myfifo->out = my_atoi(p);
   122          return count;
   123  }
   124
   125  static struct file_operations lfs_file_ops = {
   126          .open = lfs_open_file,
   127          .read = lfs_read_file,
   128          .write = lfs_write_file,
   129  };
   130
   131  static struct file_operations lfs_file2_ops = {
   132          .open = lfs_open_file2,
   133          .read = lfs_read_file2,
   134          .write = lfs_write_file2,
   135  };
   136
   137  struct tree_descr myfiles[] = {
   138          {NULL, NULL, 0},
   139          {.name = "kfifo",
   140           .ops = &lfs_file_ops,
   141           .mode = S_IWUSR | S_IRUGO},
   142          {.name = "debug",
   143           .ops = &lfs_file2_ops,
   144           .mode = S_IWUSR | S_IRUGO},
   145          {"", NULL, 0}
   146  };
   147
   148  static int lfs_fill_super(struct super_block *sb, void *data, int silent)
   149  {
   150          return simple_fill_super(sb, LFS_MAGIC, myfiles);
   151  }
   152
   153  static int lfs_get_super(struct file_system_type *fst,
   154                           int flags, const char *devname, void *data,
   155                           struct vfsmount *mnt)
   156  {
   157          return get_sb_single(fst, flags, data, lfs_fill_super, mnt);
   158  }
   159
   160  static struct file_system_type lfs_type = {
   161          .owner = THIS_MODULE,
   162          .name = "demofs",
   163          .get_sb = lfs_get_super,
   164          .kill_sb = kill_litter_super,
   165  };
   166
   167  static int __init lfs_init(void)
   168  {
   169          spin_lock_init(&lock);
   170          fifo = kfifo_alloc(TEST_BUF_LEN, GFP_KERNEL, &lock);
   171          if (IS_ERR(fifo)) {
   172                  kfifo_free(fifo);
   173                  return -ENOMEM;
   174          }
   175          /*
   176           * We just want the overflow comes soon.
   177           * You can, of course, let fifo->out and fifo->out
   178           * to be 0. And we can let them increase by 'fifo->size'
   179           * in the user space quietly. Sooner or later, they will
   180           * overflow again like this.
   181           */
   182          fifo->in = fifo->out = 0xffffffff - 0x20;
   183          buf = kmalloc(TEST_BUF_LEN, GFP_KERNEL);
   184          if (!buf) {
   185                  kfifo_free(fifo);
   186                  return -ENOMEM;
   187          }
   188          return register_filesystem(&lfs_type);
   189  }
   190
   191  static void __exit lfs_exit(void)
   192  {
   193          unregister_filesystem(&lfs_type);
   194          kfifo_free(fifo);
   195          kfree(buf);
   196  }
   197
   198  module_init(lfs_init);
   199  module_exit(lfs_exit);
   200
   201  MODULE_LICENSE("GPL");
   202  MODULE_VERSION("1.0");
   203  MODULE_AUTHOR("WANG Cong <xiyou.wangcong@gmail.com>");
   204  MODULE_DESCRIPTION("Show the bug of unsigned integer overflow in kfifo.");
   205  MODULE_SUPPORTED_DEVICE("libfs filesystem");
用户程序代码:
     1  #include <sys/types.h>
     2  #include <sys/stat.h>
     3  #include <unistd.h>
     4  #include <fcntl.h>
     5
     6  /*
     7   * Ensure that you name this file 'bugshow.c'
     8   * and put it in the same dir with 'bugshow.sh'.
     9   */
    10  int main(void)
    11  {
    12          int fd;
    13          int i;
    14          char buf[256];
    15
    16          if((fd = open("/mnt/libfs/kfifo", O_RDWR)) == -1)
    17                  return -1;
    18          for(i=0;i< 256;i++)
    19                  buf='0';
    20          /*
    21           * I won't check the return value of write.
    22           * And that's the reason why I don't use 'echo'.
    23           */
    24          write(fd, buf, 256);
    25          return 0;
    26  }
    27

--------------------------------------------------------------------------

     1  #! /bin/bash
     2  #bugshow.sh
     3  #Author: WANG Cong, XIPT. <xiyou.wangcong@gmail.com>
     4  #Usage: ./bugshow.sh install your_module_name.ko
     5  #    OR ./bugshow.sh uninstall your_module_name
     6
     7  if [ $# != "2" ]; then
     8          echo "Usage: ./bugshow.sh install your_module_name.ko"
     9          echo "OR ./bugshow.sh uninstall your_module_name"
    10          exit -1
    11  fi
    12  action="$1"
    13  if [ "$action" = "install" ]; then
    14          module=${!#}
    15          /sbin/insmod $module
    16          mkdir -p /mnt/libfs
    17          mount -t demofs none /mnt/libfs
    18          if find ./ -name bugshow.c
    19          then
    20                  gcc -Wall -o bugshow bugshow.c
    21          else
    22                  echo "Can't find bugshow.c!"
    23                  exit -2
    24          fi
    25          ./bugshow
    26          cat /mnt/libfs/debug
    27          ./bugshow
    28          cat /mnt/libfs/debug
    29  elif [ "$action" = "uninstall" ]; then
    30          module=${!#}
    31          umount none
    32          rmdir /mnt/libfs
    33          /sbin/rmmod $module
    34  else
    35          echo "Bad usage!"
    36          exit -3
    37  fi
    38  exit 0

上面的模块是仔细编写的(虽然没有考虑竞争;-p),所以bug不会导致很严重的问题,只是无法向kfifo中继续写入数据。这个bug影响到所有使用kfifo的内核版本,从2.6.10到2.6.20。

一个简单的补丁如下:

--- kernel/kfifo.c.orig 2007-02-07 19:42:51.000000000 +0800
+++ kernel/kfifo.c      2007-02-07 19:43:31.000000000 +0800
@@ -24,6 +24,7 @@
 #include <linux/slab.h>
 #include <linux/err.h>
 #include <linux/kfifo.h>
+#include <linux/compiler.h>

 /**
  * kfifo_init - allocates a new FIFO using a preallocated buffer
@@ -120,6 +121,12 @@ unsigned int __kfifo_put(struct kfifo *f
 {
        unsigned int l;

+       /*If only fifo->in overflows, let both overflow!*/
+       if (unlikely(fifo->in < fifo->out)) {
+               fifo->out += fifo->size;
+               fifo->in  += fifo->size;
+       }
+
        len = min(len, fifo->size - fifo->in + fifo->out);

        /*
@@ -166,6 +173,12 @@ unsigned int __kfifo_get(struct kfifo *f
 {
        unsigned int l;

+       /*If only fifo->in overflows, let both overflow!*/
+       if (unlikely(fifo->in < fifo->out)) {
+               fifo->out += fifo->size;
+               fifo->in  += fifo->size;
+       }
+
        len = min(len, fifo->in - fifo->out);

        /*
后经过Andrew的指点,发现这不是一个bug。我一开始被/proc接口搞晕了,得出了错误的结论。
教训:千万不用使用老的/proc接口!

Linux内核中的红黑树

,

王聪@西邮

红黑树是平衡二叉树的一种,它有很好的性质,树中的结点都是有序的,而且因为它本身就是平衡的,所以查找也不会出现非常恶劣的情况,基于二叉树的操作的时间复杂度是O(log(n))。Linux内核在管理vm_area_struct时就是采用了红黑树来维护内存块的。

先到include/linux/rbtree.h中看一下rbtree的定义,如下:

struct rb_node
{
        unsigned long  rb_parent_color;
#define RB_RED          0
#define RB_BLACK        1
        struct rb_node *rb_right;
        struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));
struct rb_root只是struct rb_node*的一个包装,这样做的好处是看起来不用传递二级指针了。不错,很简单。再看一下下面几个重要的宏,细心的你一定会发现,rb_parent_color其实没那么简单,Andrea Arcangeli在这里使用了一个小的技巧,不过非常棒。正如名字所暗示,这个成员其实包含指向parent的指针和此结点的颜色!它是怎么做到的呢?很简单,对齐起了作用。既然是sizeof(long)大小的对齐,那么在IA-32上,任何rb_node结构体的地址的低两位肯定都是零,与其空着不用,还不如用它们表示颜色,反正颜色就两种,其实只要一位就够了(估计此处也是照顾16位的机器)。

这样,提取parent指针只要把rb_parent_color成员的低两位清零即可:

#define rb_parent(r)   ((struct rb_node *)((r)->rb_parent_color & ~3))
取颜色只要看最后一位即可:
#define rb_color(r)   ((r)->rb_parent_color & 1)
测试颜色和设置颜色也是水到渠成的事了。需要特别指出的是下面的一个内联函数:
static inline void rb_link_node(struct rb_node * node, struct rb_node * parent, struct rb_node ** rb_link);
它把parent设为node的父结点,并且让rb_link指向node。

我们把重点集中在lib/rbtree.c上,看看一些和红黑树相关的重要算法。开始之前我们一起回忆一下红黑树的规则:

1. 每个结点要么是红色要么是黑色;
2. 根结点必须是黑色;
3. 红结点如果有孩子,其孩子必须都是黑色;
4. 从根结点到叶子的每条路径必须包含相同数目的黑结点。

这四条规则可以限制一棵排序树是平衡的。

__rb_rotate_left是把以root为根的树中的node结点进行左旋,__rb_rotate_right是进行右旋。这两个函数是为后面的插入和删除服务,而不是为外部提供接口。

新插入的结点都设为叶子,染成红色,插入后如果破坏了上述规则,通过调整颜色和旋转可以恢复,二叉树又重新平衡。插入操作的接口函数是

void rb_insert_color(struct rb_node *node, struct rb_root *root);
它把已确定父结点的node结点融入到以root为根的红黑树中,具体算法的分析可以参考MIT的《算法导论》第14.3节,这里的实现和书中的讲解几乎完全一样!怎么确定node的父结点应该在调用rb_insert_color之前通过手工迭带完成。值得指出的一点是,虽然插入操作需要一个循环迭代,但是总的旋转次数不会超过两次!所以效率还是很乐观的。

删除操作多多少少都有点麻烦,它要先执行像普通二叉查找树的“删除”,然后根据删除结点的颜色来判断是否执行进一步的操作。删除的接口是

void rb_erase(struct rb_node *node, struct rb_root *root);
其实它并没有真正删除node,而只是让它和以root为根的树脱离关系,最后它还要判断是否调用__rb_erase_color来调整。具体算法的讲解看参考《算法导论》13.3和14.4节,__rb_erase_color对应书中的RB-DELETE-FIXUP。此处的实现和书上也基本上一致。

其余的几个接口就比较简单了。

struct rb_node *rb_first(struct rb_root *root);
在以root为根的树中找出并返回最小的那个结点,只要从根结点一直向左走就是了。
struct rb_node *rb_last(struct rb_root *root);
是找出并返回最大的那个,一直向右走。
struct rb_node *rb_next(struct rb_node *node);
返回node在树中的后继,这个稍微复杂一点。如果node的右孩子不为空,它只要返回node的右子树中最小的结点即可;如果为空,它要向上查找,找到迭带结点是其父亲的左孩子的结点,返回父结点。如果一直上述到了根结点,返回NULL。
struct rb_node *rb_prev(struct rb_node *node);
返回node的前驱,和rb_next中的操作对称。
void rb_replace_node(struct rb_node *victim, struct rb_node *new, struct rb_root *root);
用new替换以root为根的树中的victim结点。

红黑树接口使用的一个典型例子如下:

static inline struct page * rb_search_page_cache(struct inode * inode,
                                                 unsigned long offset)
{
        struct rb_node * n = inode->i_rb_page_cache.rb_node;
        struct page * page;

        while (n)
        {
                page = rb_entry(n, struct page, rb_page_cache);

                if (offset < page->offset)
                        n = n->rb_left;
                else if (offset > page->offset)
                        n = n->rb_right;
                else
                        return page;
        }
        return NULL;
}

static inline struct page * __rb_insert_page_cache(struct inode * inode,
                                                   unsigned long offset,
                                                   struct rb_node * node)
{
        struct rb_node ** p = &inode->i_rb_page_cache.rb_node;
        struct rb_node * parent = NULL;
        struct page * page;

        while (*p)
        {
                parent = *p;
                page = rb_entry(parent, struct page, rb_page_cache);

                if (offset < page->offset)
                        p = &(*p)->rb_left;
                else if (offset > page->offset)
                        p = &(*p)->rb_right;
                else
                        return page;
        }

        rb_link_node(node, parent, p);

        return NULL;
}

static inline struct page * rb_insert_page_cache(struct inode * inode,
                                                 unsigned long offset,
                                                 struct rb_node * node)
{
        struct page * ret;
        if ((ret = __rb_insert_page_cache(inode, offset, node)))
                goto out;
        rb_insert_color(node, &inode->i_rb_page_cache);
 out:
        return ret;
}

Linux进程管理中队列的使用

,

王聪@西邮

Linux内核中大量使用了队列,这里仅列举它在进程管理中的几处应用。

状态为TASK_RUNNING的进程都会被放入运行队列(runqueue)中,这是通过task_struct(定义在include/linux/sched.h)中的run_list成员来链接的。不过,为了让内核每次都能选取优先级最合适的进程,Linux为每个优先级构建了一个queue!这是通过struct prio_array来实现的,struct prio_array的定义(在kernel/sched.c)大致如下:

struct prio_array {
int nr_active;
unsigned long bitmap[BITMAP_SIZE];
struct list_head queue[MAX_PRIO];
};
queue成员就是队列数组。每个CPU有各自的runqueue,每一个runqueue又有包含两个prio_array,一个是活动队列,一个是时间片耗尽的队列。当运行队列空时,内核便会交换两个队列的指针!原来的耗尽队列就成了新的活动队列!这和prio_array中的bitmap是决定调度算法为O(1)的关键!

状态为TASK_STOPPED,EXIT_ZOMBIE或EXIT_DEAD的进程不会被放入专门的队列中,它们直接通过pid或者通过父进程的孩子队列来访问。

TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE状态的进程会被放入等待队列。不同的是,每个等待事件都会有一个等待队列,该队列中的进程等待同一事件的完成(不要惊慌!“事件”一个动态的过程,不好通过具体的结构来定义一个“事件”。这里等待一个事件就是查看某个条件是否为真,比如某个标志变量是否为真。)。wait_queue_head_t的定义(include/linux/wait.h)如下:

typedef struct _ _wait_queue_head {
        spinlock_t lock;
        struct list_head task_list;
    }wait_queue_head_t;
wait_queue_t的定义如下:
typedef struct _ _wait_queue {
        unsigned int flags;
        struct task_struct * task;
        wait_queue_func_t func;
        struct list_head task_list;
    }wait_queue_t;
进入等待状态的接口有两类: prepare_to_wait*()/finish_wait() wait_event*() 其实wait_event*()内部也是调用prepare_to_wait*(),把它放入一个循环中。而且wait_event*()在事件完成时会自动调用finish_wait()。决定使用哪个要看情况而定。sleep_on*()是遗弃的接口,现在已经不再使用,虽然还支持。等待队列中的进程有两种,一种是exclusive的进程,另一种是nonexclusive的进程。所谓exclusive是指唤醒的进程等待的资源是互斥的,每次只唤醒一个(唤醒多个也可以,不过最后还是只有一个会被唤醒,其余的又被重新添加到等待队列中,这样效率会大打折扣)。一般,等待函数会把进程设为nonexclusive和uninterruptible,带“interruptible”的会专门指定状态为interruptible;而带“timeout”的会在超时后退出,因为它会调用schedule_timeout();带“exclusive”的则会把进程设为exclusive。

唤醒的接口虽然只有wake_up*(),但它内部也分成好几种。带“interruptible”的唤醒函数只会唤醒状态是TASK_INTERRUPTIBLE的进程,而不带的则会唤醒TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE的进程;所有唤醒函数都会把等待同一事件的nonexclusive进程全部唤醒,或者把其中一个exclusive的进程唤醒;而带“nr”的则会唤醒指定个数的exclusive的进程,带“all”的会把全部exclusive进程唤醒。带“sync”会忽略优先级的检查,高优先级的进程可能会被延迟。最后,持有自旋锁时只能调用wait_up_locked()。

关于《三体》


我是在刚刚读完《银河系漫游指南》又重拾《三体》的,于是一口气把它也读完了。之前在杂志和网络上陆陆续续读过一些,《三体》给我的总体印象只是一般,情节和想像根本就没法和国外的优秀科幻相比,不过仍然有一些值得提起的地方。

《三体》中成功塑造了三体文明以及人类对它们疯狂的向往。小说中揭露了人类自私和丑陋的一面,但认识到这一点的人对此却无能为力,只好求助于偶然间联系上的外星三体文明,却没想到三体文明也只是一个自私自利的物种,当它们得知人类的消息后就开始准备占领地球来为自己取得生存之地。

人性本来就是丑陋还是宇宙间的生命皆如此?人类的命运最终会如何?这是作者留给我们的大问题,这没有绝对答案,也没人能知道答案。这警告着我们:可怜的人类,好自为之吧!

和《银河系漫游指南》相比,两者都是嘲笑人类的无知和愚蠢,但差距无疑是很明显的。无论在情节处理还是在语言叙述上,《三体》都明显落后于《银河系漫游指南》,这似乎也是国内科幻的通病。虽然《三体》的一些想像之处也并不落后,但是总体上就明显落后了,它没能像国外那些优秀科幻那样,让读者从头一路吃惊到底;情节上似乎更明显,一些处理不尽如人意,弥合不足,让人读着多多少少有些牵强。不过在国内,《三体》仍然是一部不错的科幻小说。