【问题标题】:Why is if (fork() == 0) { getpid() } and a popen() process returning the same process id?为什么 if (fork() == 0) { getpid() } 和 popen() 进程返回相同的进程 ID?
【发布时间】:2019-11-09 23:52:53
【问题描述】:

据我所知,当 fork() 中的 getpid() 与 popen() 生成的进程不同时,我想知道为什么这两个进程 id 匹配。

我被告知我的代码只能工作,因为我解释可能是基于 Ubuntu 的发行版的错误,例如 Xubuntu、Lubuntu 和 KDE neon(这是我迄今为止测试过的发行版)。您可以从此处轻松编译和测试代码:https://github.com/time-killer-games/XTransientFor 如果您不信任,请忽略该链接中可用的 x64 二进制文件。如果这对他们不起作用,特别欢迎 Arch、RedHat 等测试人员提供反馈。

这里有一个更简单的方法来演示这个问题:

// USAGE: xprocesstest [command]

#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>

#include <unistd.h>

#include <thread>
#include <chrono>

#include <iostream>
#include <string>

using std::string;

static inline Window XGetActiveWindow(Display *display) {
  unsigned long window;
  unsigned char *prop;

  Atom actual_type, filter_atom;
  int actual_format, status;
  unsigned long nitems, bytes_after;

  int screen = XDefaultScreen(display);
  window = RootWindow(display, screen);

  filter_atom = XInternAtom(display, "_NET_ACTIVE_WINDOW", True);
  status = XGetWindowProperty(display, window, filter_atom, 0, 1000, False, AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after, &prop);

  unsigned long long_property = prop[0] + (prop[1] << 8) + (prop[2] << 16) + (prop[3] << 24);
  XFree(prop);

  return (Window)long_property;
}

static inline pid_t XGetActiveProcessId(Display *display) {
  unsigned long window = XGetActiveWindow(display);
  unsigned char *prop;

  Atom actual_type, filter_atom;
  int actual_format, status;
  unsigned long nitems, bytes_after;

  filter_atom = XInternAtom(display, "_NET_WM_PID", True);
  status = XGetWindowProperty(display, window, filter_atom, 0, 1000, False, AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after, &prop);

  unsigned long long_property = prop[0] + (prop[1] << 8) + (prop[2] << 16) + (prop[3] << 24);
  XFree(prop);

  return (pid_t)(long_property - 1);
}

int main(int argc, const char **argv) {
  if (argc == 2) {
    char *buffer = NULL;
    size_t buffer_size = 0;
    string str_buffer;

    FILE *file = popen(argv[1], "r");

    if (fork() == 0) {
      Display *display = XOpenDisplay(NULL);
      Window window;

      unsigned i = 0;
      while (i < 10) {
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
        if (XGetActiveProcessId(display) == getpid()) {
          window = XGetActiveWindow(display);
          break;
        }
        i++;
      }

      if (window == XGetActiveWindow(display)) 
      std::cout << "process id's match!" << std::endl;
      else std::cout << "process id's don't match!" << std::endl;

      XCloseDisplay(display);
      exit(0);
    }

    while (getline(&buffer, &buffer_size, file) != -1)
      str_buffer += buffer;

    std::cout << str_buffer;
    free(buffer);
    pclose(file);
  }
}

编译:

cd "${0%/*}"
g++ -c -std=c++17 "xprocesstest.cpp" -fPIC -m64
g++ "xprocesstest.o" -o "xprocesstest" -fPIC -lX11

运行:

cd "${0%/*}"
./xprocesstest "kdialog --getopenfilename"

您可以将引号中的命令替换为设置 _NET_WM_PID 原子的任何可执行文件。

【问题讨论】:

  • 这真的是minimal reproducible example吗?不可以去掉一半的代码来重现吗? Xubuntu、Lubuntu 和 Ubuntu 是具有不同桌面环境的相同发行版。
  • 不错,我会处理的,对此感到抱歉。
  • 虽然值得注意的是不同的桌面环境可能意味着不同的默认窗口管理器和包,但我不确定这是否有任何区别,这就是我发布该内容的原因。
  • 我认为问题如Is it possible to get active window & executable names in X11/Xlib? 所述。我在 openSUSE 或 Archlinux 上使用 g++ -Wall -Wextra -pedantic -std=gnu++11 -Ofast -lX11 -o bin/xprocesstest xprocesstest.cpp 构建没有问题,并且使用 kdialogzenity 成功运行。在这两种情况下,我都会得到"process id's don't match!",根据链接,这并不意外。
  • 您是否知道,由于您的- 1,您实际上正在检查两个进程是否具有sequential pids?这在 Linux 上并不奇怪。发行版之间的差异将取决于 sh 是否优化了它用于运行命令的额外分叉

标签: c++ linux x11 xlib


【解决方案1】:

@thatotherguy 在他的评论中解释了答案:

“您是否知道由于您的 - 1 您实际上正在检查两个进程是否具有顺序 pid?这在 Linux 上并不令人惊讶。发行版之间的差异将取决于 sh 是否优化了它使用的额外分叉运行命令”

static inline pid_t XGetActiveProcessId(Display *display) {
  unsigned long window = XGetActiveWindow(display);
  unsigned char *prop;

  Atom actual_type, filter_atom;
  int actual_format, status;
  unsigned long nitems, bytes_after;

  filter_atom = XInternAtom(display, "_NET_WM_PID", True);
  status = XGetWindowProperty(display, window, filter_atom, 0, 1000, False, AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after, &prop);

  unsigned long long_property = prop[0] + (prop[1] << 8) + (prop[2] << 16) + (prop[3] << 24);
  XFree(prop);

  return (pid_t)(long_property - 1);
}

我最初添加的进程 id return 中的 - 1 是因为我当时认为它返回了错误的进程 id,因为我在写的时候认为 fork() 应该与 popen 具有相同的进程 id ,后来我发现事实并非如此。我减去了一个,从而使两个不同的否则正确的进程 id 不正确地相等。

这是在我的原始代码中做我打算做的事情的正确方法,这导致我提出这个问题;我想知道如何检测 fork 和 popen 子进程是否来自一个共同的父进程(同时从 GetActiveProcessId() 函数的返回中删除一个减法):

#include <proc/readproc.h>
#include <cstring>

static inline pid_t GetParentPidFromPid(pid_t pid) {
  proc_t proc_info; pid_t ppid;
  memset(&proc_info, 0, sizeof(proc_info));
  PROCTAB *pt_ptr = openproc(PROC_FILLSTATUS | PROC_PID, &pid);
  if(readproc(pt_ptr, &proc_info) != 0) { 
    ppid = proc_info.ppid;
    string cmd = proc_info.cmd;
    if (cmd == "sh")
      ppid = GetParentPidFromPid(ppid);
  } else ppid = 0;
  closeproc(pt_ptr);
  return ppid;
}

使用上面的辅助函数,同时替换原始代码中的while循环,用这个,让我可以做我想做的事情:

  while (i < 10) {
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
    if (GetParentPidFromPid(XGetActiveProcessId(display)) == GetParentPidFromPid(getpid()) ||
      GetParentPidFromPid(GetParentPidFromPid(XGetActiveProcessId(display))) == GetParentPidFromPid(getppid())) {
      window = XGetActiveWindow(display);
      break;
    }
    i++;
  }

正如@thatotherguy 还指出的那样,一些发行版将返回不同的父进程,因为 sh cmd 将直接使用 run。为了解决这个问题,我在 if 语句中执行或检查,以查看父进程或“祖父”进程 id 是否返回相等,同时尝试跳过任何具有 sh cmd 值的父进程。

如果您在基于 Debian 的系统上,辅助函数需要安装 -lprocps 链接器标志和 libprocps-dev 软件包。包名在其他发行版上会有所不同。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-11-01
    • 2014-01-30
    • 1970-01-01
    • 2011-09-28
    • 1970-01-01
    • 2017-02-14
    • 2012-08-02
    • 1970-01-01
    相关资源
    最近更新 更多