2
23
2014
11

让我们收养孤儿进程吧

本文来自依云's Blog,转载请注明。

稍微了解一点类 UNIX 系统的进程管理的都知道,当一个进程的父进程死亡之后,它就变成了孤儿进程,会由进程号 1 的 init 进程收养,并且在它死亡时由 init 来收尸。但是,自从使用 systemd 来管理用户级服务进程之后,我发现 systemd --user 管理的进程总是在它之下,即使进程已经 fork 了好几次。systemd 是怎么做到的呢?

对一个软件的实现有不懂的想了解当然是读它的源码了。这种东西可没有另外的文档,因为源码本身即文档。当然之前我也 Google 过,没有得到结果。在又一个全新的源码树里寻寻觅觅一两天之后,终于找到了这个:

        if (arg_running_as == SYSTEMD_USER) {
                /* Become reaper of our children */
                if (prctl(PR_SET_CHILD_SUBREAPER, 1) < 0) {
                        log_warning("Failed to make us a subreaper: %m");
                        if (errno == EINVAL)
                                log_info("Perhaps the kernel version is too old (< 3.4?)");
                }
        }

原来是通过prctl系统调用实现的。于是去翻 prctl 的 man 手册,得知PR_SET_CHILD_SUBREAPER是 Linux 3.4 加入的新特性。把它设置为非零值,当前进程就会变成 subreaper,会像 1 号进程那样收养孤儿进程了。

当然用 C 写不好玩,于是先用 python-cffi 玩了会儿,最后还是写了个 Python 模块,也是抓住机会练习一下 C 啦。有个 python-prctl 模块,但是它没有包含这个调用。

#include<sys/prctl.h>
#include<Python.h>

static PyObject* subreap(PyObject *self, PyObject *args){
  PyObject* pyreaping;
  int reaping;
  int result;

  if (!PyArg_ParseTuple(args, "O!", &PyBool_Type, &pyreaping))
    return NULL;
  reaping = pyreaping == Py_True;

  Py_BEGIN_ALLOW_THREADS
  result = prctl(PR_SET_CHILD_SUBREAPER, reaping);
  Py_END_ALLOW_THREADS

  if(result != 0){
    return PyErr_SetFromErrno(PyExc_OSError);
  }else{
    Py_RETURN_NONE;
  }
}

static PyMethodDef mysysutil_methods[] = {
  {"subreap", subreap, METH_VARARGS},
  {NULL, NULL}    /* Sentinel */
};

static PyModuleDef mysysutil = {
  PyModuleDef_HEAD_INIT,
  "mysysutil",
  "My system utils",
  -1,
  mysysutil_methods,
  NULL, NULL, NULL, NULL
};

PyMODINIT_FUNC PyInit_mysysutil(void){
  PyObject* m;

  m = PyModule_Create(&mysysutil);
  if(m == NULL)
    return NULL;
  return m;
}

编译之后,

>>> import mysysutil
>>> mysysutil.subreap(True)

然后开子进程,不管它 fork 多少次,都依然会在这个 Python 进程之下啦。

但是,这样子不太好玩呢。如果我登陆之后所有启动的子进程都在一个进程之下不是更有意思么?于是我打上了 Awesome 的主意,因为它支持运行任意的 Lua 代码嘛。于是我又给这个 prctl 调用弄了个 Lua 绑定。最终的版本如下:

#include<lua.h>
#include<lualib.h>
#include<lauxlib.h>

#include<sys/prctl.h>
#include<sys/wait.h>
#include<errno.h>
#include<string.h>
#include<signal.h>

static int l_setsubreap(lua_State * L){
  int reap;
  if(lua_isboolean(L, 1)){
    reap = lua_toboolean(L, 1);
  }else{
    return luaL_argerror(L, 1, "not a boolean");
  }
  if(prctl(PR_SET_CHILD_SUBREAPER, reap) != 0){
    return luaL_error(L, "prctl failed: %s", strerror(errno));
  }
  return 0;
}

static int l_ignore_SIGCHLD(lua_State * L){
  signal(SIGCHLD, SIG_IGN);
  return 0;
}

static int l_reap(lua_State * L){
  int pid, st;
  pid = waitpid(-1, &st, WNOHANG);
  lua_pushinteger(L, st);
  lua_pushinteger(L, pid);
  return 2;
}

static const struct luaL_Reg l_lib[] = {
  {"setsubreap", l_setsubreap},
  {"reap", l_reap},
  {"ignore_SIGCHLD", l_ignore_SIGCHLD},
  {NULL, NULL}
};

int luaopen_clua(lua_State * L){
  lua_newtable(L);
  luaL_setfuncs(L, l_lib, 0);
  return 1;
}

除了调用 prctl 外,还增加了显式忽略 SIGCHLD 信号,以及非阻塞地调用 waitpid 收割单个僵尸进程的函数,因为 Awesome 本身没处理子进程退出,我一不小心弄出了好几个僵尸进程……对了,那个 waitpid 要注意给弄成非阻塞的,不然一不小心就会出问题

用的时候就是这样子,可以写到rc.lua里,也可以在 awesome-client 里调用:

package.cpath = package.cpath .. ';/home/lilydjwg/scripts/lua/cmod/?.so'
clua = require('clua')
clua.setsubreap(true)
clua.ignore_SIGCHLD()

最终,我的进程树成了这样子:

htop-awesome-tree

可以看到,由 Awesome 启动的进程已经全部待在 Awesome 进程树之下了。systemd --user 是由 PAM 启动的,所以不在 Awesome 树下。但是,那些 dbus 的东西和 gconfd-2、at-spi 之类的是怎么回事呀……

Category: Linux | Tags: linux python Lua awesome C代码 systemd 窗口管理器 | Read Count: 16425
jiazhoulvke 说:
Feb 23, 2014 05:12:48 PM

刚看标题我还以为你领养了一个小孩呢……

Avatar_small
依云 说:
Feb 23, 2014 06:21:06 PM

…………………………………………………………

eleven.i386 说:
Feb 23, 2014 09:01:47 PM

gnome-shell 下的孤儿, 全部在gnome-shell 这个进程下面, 我只要kill掉gnome-shell 重新启动就好了.

maplebeats 说:
Feb 24, 2014 11:03:04 PM

我想吐槽那神打码,进程居然还要打码。仙子你都在干些什么啊:(

Avatar_small
依云 说:
Feb 25, 2014 02:06:39 PM

我应该把那些地方改成背景色的……

farseerfc 说:
Mar 15, 2014 03:22:23 AM

那個mysysutil怎麼在python2編譯呀,我改了makemod之後編譯報錯了唔……

Avatar_small
依云 说:
Mar 15, 2014 12:37:34 PM

看 Python 2 的 C API 文档吧,关于模块定义的部分,

如果你只是想调用个系统调用的话,直接用 cffi 甚至 ctypes 都是没问题的。

farseerfc 说:
Mar 16, 2014 04:23:02 AM

搞定了~ 我的qtile也會收孤兒啦!
http://i.imgur.com/S71Bu1T.png

代碼在:
https://github.com/farseerfc/qtile-config/blob/master/hooks.py#L24

Avatar_small
依云 说:
Mar 16, 2014 12:07:34 PM

哇,你用 cffi 搞定啦。话说没必要写那么多参数最后全部传零的,另外 waitpid 在 os 模块里有。而且收尸只需要忽略 SIGCHLD 信号即可,不需要开个线程干的。不过可能和 qtile 的信号处理冲突,得先确认一下。

zodiacg 说:
May 11, 2017 09:31:56 AM

不知道从什么时候开始awesome下面多了大把的僵尸进程= =重启awesome会被清掉。以前似乎并没有这个问题。

我现在也是用的arch,awesome版本:
awesome v4.1 (Technologic)
• Compiled against Lua 5.3.4 (running with Lua 5.3)
• D-Bus support: ✔
• execinfo support: ✔
• xcb-randr version: 1.5
• LGI version: 0.9.1

rc.lua调用的部分是参照仙子的rc.lua写的……

Avatar_small
依云 说:
May 11, 2017 11:01:23 AM

我还在用 3.5.9 呢……


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter

| Theme: Aeros 2.0 by TheBuckmaker.com