首页 > 解决方案 > Cgroup 意外地将 SIGSTOP 传播到父级

问题描述

我有一个小脚本可以在限制 CPU 时间的 cgroup 中运行命令:

$ cat cgrun.sh
#!/bin/bash

if [[ $# -lt 1 ]]; then
    echo "Usage: $0 <bin>"
    exit 1
fi

sudo cgcreate -g cpu:/cpulimit
sudo cgset -r cpu.cfs_period_us=1000000 cpulimit
sudo cgset -r cpu.cfs_quota_us=100000 cpulimit
sudo cgexec -g cpu:cpulimit sudo -u $USER "$@"
sudo cgdelete cpu:/cpulimit

我让命令运行:./cgrun.sh /bin/sleep 10

然后我从另一个终端发送 SIGSTOP 到 sleep 命令。不知何故,此时父命令,sudocgexec收到了这个信号。然后,我将 SIGCONT 发送到 sleep 命令,它允许 sleep 继续。

但是此时sudocgexec被停止了并且永远不会收获睡眠进程的僵尸。我不明白这怎么会发生?我该如何预防呢?此外,我无法将 SIGCONT 发送到sudoand cgexec,因为我正在从用户发送信号,而这些命令以 root 身份运行。

这是它在 htop 中的样子(省略了一些列):

    PID USER S CPU% MEM%   TIME+  Command
1222869 user S  0.0  0.0  0:00.00 │     │  └─ /bin/bash ./cgrun.sh /bin/sleep 10
1222882 root T  0.0  0.0  0:00.00 │     │     └─ sudo cgexec -g cpu:cpulimit sudo -u user /bin/sleep 10
1222884 root T  0.0  0.0  0:00.00 │     │        └─ sudo -u desertfox /bin/sleep 10
1222887 user Z  0.0  0.0  0:00.00 │     │           └─ /bin/sleep 10

如何以不将 SIGSTOP 反弹到父进程的方式创建 cgroup?

UPD

如果我使用 systemd-run 启动进程,我不会观察到相同的行为:

sudo systemd-run --uid=$USER -t -p CPUQuota=10% sleep 10

标签: linuxbashsignalssudocgroups

解决方案


我不会使用“cg 工具”,而是使用 shell 命令以“硬方式”来创建cpulimit cgroup(它是一个mkdir),设置 cfs 参数(echo在相应的cpu.cfs_ * 文件中使用命令),使用符号创建一个子shell (...),将其移动到cgroup(echo其pid的命令到cgroup的tasks文件中)并在这个子shell中执行请求的命令。

因此,cgrun.sh看起来像这样:

#!/bin/bash

if [[ $# -lt 1 ]]; then
    echo "Usage: $0 <bin>" >&2
    exit 1
fi

CGTREE=/sys/fs/cgroup/cpu

sudo -s <<EOF
[ ! -d ${CGTREE}/cpulimit ] && mkdir ${CGTREE}/cpulimit
echo 1000000 > ${CGTREE}/cpulimit/cpu.cfs_period_us
echo 100000 > ${CGTREE}/cpulimit/cpu.cfs_quota_us
EOF

# Sub-shell in background
(
  # Pid of the current sub-shell
  # ($$ would return the pid of the father process)
  MY_PID=$BASHPID

  # Move current process into the cgroup
  sudo sh -c "echo ${MY_PID} > ${CGTREE}/cpulimit/tasks"

  # Run the command with calling user id (it inherits the cgroup)
  exec "$@"

) &

# Wait for the sub-shell
wait $!

# Exit code of the sub-shell
rc=$?

# Delete the cgroup
sudo rmdir ${CGTREE}/cpulimit

# Exit with the return code of the sub-shell
exit $rc

运行它(在我们获取当前 shell 的 pid 以在另一个终端中显示进程层次结构之前):

$ echo $$
112588
$ ./cgrun.sh /bin/sleep 50

这将创建以下流程层次结构:

$ pstree -p 112588
bash(112588)-+-cgrun.sh(113079)---sleep(113086)

停止sleep进程:

$ kill -STOP 113086

查看 cgroup 以验证sleep命令是否在其中运行(它的 pid 在tasks文件中)并且 CFS 参数设置正确:

$ ls -l /sys/fs/cgroup/cpu/cpulimit/
total 0
-rw-r--r-- 1 root root 0 nov.    5 22:38 cgroup.clone_children
-rw-r--r-- 1 root root 0 nov.    5 22:38 cgroup.procs
-rw-r--r-- 1 root root 0 nov.    5 22:36 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 nov.    5 22:36 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 nov.    5 22:38 cpu.shares
-r--r--r-- 1 root root 0 nov.    5 22:38 cpu.stat
-rw-r--r-- 1 root root 0 nov.    5 22:38 cpu.uclamp.max
-rw-r--r-- 1 root root 0 nov.    5 22:38 cpu.uclamp.min
-r--r--r-- 1 root root 0 nov.    5 22:38 cpuacct.stat
-rw-r--r-- 1 root root 0 nov.    5 22:38 cpuacct.usage
-r--r--r-- 1 root root 0 nov.    5 22:38 cpuacct.usage_all
-r--r--r-- 1 root root 0 nov.    5 22:38 cpuacct.usage_percpu
-r--r--r-- 1 root root 0 nov.    5 22:38 cpuacct.usage_percpu_sys
-r--r--r-- 1 root root 0 nov.    5 22:38 cpuacct.usage_percpu_user
-r--r--r-- 1 root root 0 nov.    5 22:38 cpuacct.usage_sys
-r--r--r-- 1 root root 0 nov.    5 22:38 cpuacct.usage_user
-rw-r--r-- 1 root root 0 nov.    5 22:38 notify_on_release
-rw-r--r-- 1 root root 0 nov.    5 22:36 tasks
$ cat /sys/fs/cgroup/cpu/cpulimit/tasks 
113086  # This is the pid of sleep
$ cat /sys/fs/cgroup/cpu/cpulimit/cpu.cfs_*
1000000
100000

sleep进程发送 SIGCONT 信号:

$ kill -CONT 113086

该过程完成并且 cgroup 被销毁:

$ ls -l /sys/fs/cgroup/cpu/cpulimit
ls: cannot access '/sys/fs/cgroup/cpu/cpulimit': No such file or directory

完成后获取脚本的退出代码(它是已启动命令的退出代码):

$ echo $?
0

推荐阅读