普通进程的组调度
  文章一开头提到了希望A、B两个用户在进程数不相同的情况下也能平分CPU的需求,但是上面关于实时进程的组调度策略好像与此不太相干,其实这是普通进程的组调度所要干的事。
  相比实时进程,普通进程的组调度没有这么多讲究。组被看作是跟进程几乎完全相同的实体,它拥有自己的静态优先级、调度程序也动态地调整它的优先级。对于一个组来说,组内进程的优先级并不影响组的优先级,只有这个组被调度程序选中时,这些进程的优先级才被考虑。
  为了设置组的优先级,每个task_group都有一个shares参数(跟前面提到的sched_rt_runtime_us和sched_rt_period_us两个参数并列)。shares并不是优先级,而是调度实体的权重(这是CFS调度器的玩法),这个权重和优先级是有一一对应的关系的。普通进程的优先级也会被转换成其对应调度实体的权重,所以可以说shares代表了优先级。
  shares的默认值跟普通进程默认优先级对应的权重是一样的。所以在默认情况下,组和进程是平分CPU的。
  示例
  (环境:ubuntu 10.04,kernel 2.6.32,Intel Core2 双核)
  挂载一个只划分CPU资源的cgroup,并创建grp_a和grp_b两个子组:
  kouu@kouu-one:~$ sudo mkdir /dev/cgroup/cpu -p
  kouu@kouu-one:~$ sudo mount -t cgroup cgroup -o cpu /dev/cgroup/cpu
  kouu@kouu-one:/dev/cgroup/cpu$ cd /dev/cgroup/cpu/
  kouu@kouu-one:/dev/cgroup/cpu$ mkdir grp_{a,b}
  kouu@kouu-one:/dev/cgroup/cpu$ ls *
  cgroup.procs  cpu.rt_period_us  cpu.rt_runtime_us  cpu.shares  notify_on_release  release_agent  tasks
  grp_a:
  cgroup.procs  cpu.rt_period_us  cpu.rt_runtime_us  cpu.shares  notify_on_release  tasks
  grp_b:
  cgroup.procs  cpu.rt_period_us  cpu.rt_runtime_us  cpu.shares  notify_on_release  tasks
  分别开三个shell,第一个加入grp_a,后两个加入grp_b:
  kouu@kouu-one:~/test/rtproc$ cat ttt.sh
  echo $1 > /dev/cgroup/cpu/$2/tasks
  (为什么要用ttt.sh来写cgroup下的tasks文件呢?因为写这个文件需要root权限,当前shell没有root权限,而sudo只能赋予被它执行的程序的root权限。其实sudo sh,然后再在新开的shell里面执行echo操作也是可以的。)
  kouu@kouu-one:~/test1$ echo $$
  6740
  kouu@kouu-one:~/test1$ sudo sh ttt.sh $$ grp_a
  kouu@kouu-one:~/test2$ echo $$
  9410
  kouu@kouu-one:~/test2$ sudo sh ttt.sh $$ grp_b
  kouu@kouu-one:~/test3$ echo $$
  9425
  kouu@kouu-one:~/test3$ sudo sh ttt.sh $$ grp_b
  回到cgroup目录下,确认这几个shell都被加进去了:
  kouu@kouu-one:/dev/cgroup/cpu$ cat grp_a/tasks
  6740
  kouu@kouu-one:/dev/cgroup/cpu$ cat grp_b/tasks
  9410
  9425
  现在准备在这三个shell下同时执行一个死循环的程序(a.out),为了避免多CPU带来的影响,将进程绑定到第二个核上:
  #define _GNU_SOURCE
  #include
  int main()
  {
  cpu_set_t set;
  CPU_ZERO(&set);
  CPU_SET(1, &set);
  sched_setaffinity(0, sizeof(cpu_set_t), &set);
  while(1);
  return 0;
  }
  编译生成a.out,然后在前面的三个shell中分别运行。三个shell分别会fork出一个子进程来执行a.out,这些子进程都会继承其父进程的cgroup分组信息。然后top一下,可以观察到属于grp_a的a.out占了50%的CPU,而属于grp_b的两个a.out各占25%的CPU(加起来也是50%):
  kouu@kouu-one:/dev/cgroup/cpu$ top -c
  ......
  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
  19854 kouu      20   0  1616  328  272 R   50  0.0   0:11.69 ./a.out
  19857 kouu      20   0  1616  332  272 R   25  0.0   0:05.73 ./a.out
  19860 kouu      20   0  1616  332  272 R   25  0.0   0:04.68 ./a.out
  ......
  接下来再试试实时进程,把a.out程序改造如下:
  #define _GNU_SOURCE
  #include
  int main()
  {
  int prio = 50;
  sched_setscheduler(0, SCHED_FIFO, (struct sched_param*)&prio);
  while(1);
  return 0;
  }
  然后设置grp_a的rt_runtime值:
  kouu@kouu-one:/dev/cgroup/cpu$ sudo sh
  # echo 300000 > grp_a/cpu.rt_runtime_us
  # exit
  kouu@kouu-one:/dev/cgroup/cpu$ cat grp_a/cpu.rt_*
  1000000
  300000
  现在的配置是每秒为一个周期,属于grp_a的实时进程每秒种只能执行300毫秒。运行a.out(设置实时进程需要root权限),然后top看看:
  kouu@kouu-one:/dev/cgroup/cpu$ top -c
  ......
  Cpu(s): 31.4%us,  0.7%sy,  0.0%ni, 68.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
  ......
  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
  28324 root     -51   0  1620  332  272 R   60  0.0   0:06.49 ./a.out
  ......
  可以看到,CPU虽然闲着,但是却不分给a.out程序使用。由于双核的原因,a.out实际的CPU占用是60%而不是30%。
  其他
  前段时间,有一篇“200+行Kernel补丁显著改善Linux桌面性能”的新闻比较火。这个内核补丁能让高负载条件下的桌面程序响应延迟得到大幅度降低。其实现原理是,自动创建基于TTY的task_group,所有进程都会被放置在它所关联的TTY组中。通过这样的自动分组,将桌面程序(Xwindow会占用一个TTY)和其他终端或伪终端(各自占用一个TTY)划分开了。终端上运行的高负载程序(比如make -j64)对桌面程序的影响将大大减少。(根据前面描述的普通进程的组调度的实现可以知道,如果一个任务给系统带来了很高的负载,只会影响到与它同组的进程。这个任务包含一个或是一万个TASK_RUNNING状态的进程,对于其他组的进程来说是没有影响的。)