为什么if(fork()==0){getpid()}和popen()进程返回相同的进程id

Why is if (fork() == 0) { getpid() } and a popen() process returning the same process id?

本文关键字:进程 返回 id popen getpid fork if 为什么      更新时间:2023-10-16

据我所知,当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原子的可执行文件。

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

"你知道吗,由于你的-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返回中添加了-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;
}

使用上面的helper函数,同时用它替换原始代码中的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++;
}

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

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