Skip navigation.

Posts tagged with "Linux Kernel"

Linux内核中的几个概念

,

王聪@西邮


软中断(softirq)是内核使用的一种推后执行任务的一种机制,由于一些中断处理必须要在短期内完成,所以内核不得不把一些相对不重要的工作推后执行,软中断就是专门用来执行这种后退的工作。它在某种程度上有点像硬件中断,来得“随时随地”,而且不在进程上下文之中。千万不要把它和“软件中断(software interrupts)”这个概念混了,后者是因为在编程中使用了中断指令(比如:int 0x80)而产生一个硬件上实际存在的中断信号,而前者更本就不和硬件扯关系。

小任务(tasklet)是在软中断的基础上实现的一种后推执行方式。软中断是在编译时就已经确定好的,而小任务则可以在运行时分配和初始化;软中断可以在不同的CPU上同时执行,而同一类型的tasklet只能在调度它们的同一CPU上运行,因此软中断函数必须是可重入的,而小任务的函数则没有这个必要。

工作队列(work queue)是另一种后推方式,但它和小任务有着很大的区别,小任务是在中断上下文中执行的,而工作队列是在进程上下文中执行的,所以工作队列是可以休眠的,也就不一定是原子的。执行工作队列的线程是ksoftirqd/n(n是cpu的编号,在UP是ksoftirqd/0),这是一个内核线程,因此也不能访问用户内存。

下半部(bottom half)是后推执行任务的一个统称,它主要是完成上半部未完成的一些工作。一般来说,在处理中断时,在中断处理例程(上半部)中做的工作越少越好,其余一些相对不那么迫切的工作可以后推给下半部来完成,当然了,下半部可以是小任务,也可以是工作队列。

两个和gcc相关的脚本

,

Linux内核源代码中有这么一个脚本文件:scripts/gcc-version.sh。它的主要代码如下:

     9  compiler="$*"
    10
    11  MAJOR=$(echo __GNUC__ | $compiler -E -xc - | tail -n 1)
    12  MINOR=$(echo __GNUC_MINOR__ | $compiler -E -xc - | tail -n 1)
    13  printf "%02d%02d\\n" $MAJOR $MINOR
    14
从上面我们很容易看出它的用法,它要带一个参数,指明该平台上 GNU C编译器的命令(可能有些平台是cc)。它会给出GNU C编译器的版本号,以如下格式:XXYY,其中XX是主版本号,YY是次版本号,比如gcc-4.1的版本号就是0401。

这个脚本的实现很简单,它通过把GNU C编译器预先定义的宏__GNUC__和__GNUC_MINOR__展开后交给编译器的预处理器处理,处理后其实就应该是想要的结果了,但gcc会自动在前面插入自己的一些东西,所以,要截取最后一行才是真正的结果。-E选项是指明只进行预处理,注意:如果没有-o,-E默认的输出是标准输出;-x选项是要指明所使用的语言,这里指明的是c;-是说明输入来自标准输入,这主要是照顾管道。

另一个和gcc相关的脚本是scripts/gcc-x86_64-has-stack-protector.sh,它用来测试x86_64(x86_64是AMD的,IA-64才是Intel的)上是不是有堆栈保护,代码如下:

     3  echo "int foo(void) { char X[200]; return 3; }" | $1 -S -xc -c -O0 -mcmodel=kernel -fstack-protector - -o - 2> /dev/null | grep -q "%gs"
     4  if [ "$?" -eq "0" ] ; then
     5          echo $2
     6  fi
-fstack-protector选项是指明要检查堆栈是否会溢出,这是为了保护程序免于缓冲区溢出的攻击;-mcmodel=kernel是指明要为内核模式生成代码,Linux内核似乎要使用此选项。第3行的命令使用得更妙,还把中间命令的错误重定向到了/dev/null,而且还为grep开启了安静模式。似乎是从生成的汇编中找到"%gs"这个寄存器就说明有堆栈保护,但原理还是不明白;-(。

开发内核的几条经验

,

作者:王聪@西邮


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()。