获取fork+exec启动的程序的PID值
问题背景
业务中有个场景需要自动起一个A程序(由于A程序与 sublime_text 启动后遇到的问题有相似之处,后文就用 sublime_text 来替代A程序,当A程序与 sublime_text 的现象有所差异的时候,恢复使用 A 程序),并在适当的场景下杀死它,自然而然想到 fork + exec 的方式来启动它。但是启动后,在获取程序 pid 的时候却遇到了一点问题。以下是启动的代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int create_process(char *name, char *argv[])
{
int pid = fork();
if (0 == pid)
{
execv(name, argv);
exit(127);
}
else if (0 < pid)
{
return pid;
}else
{
return -1;
}
}
int main()
{
char *name = "/opt/sublime_text/sublime_text";
char *argv[] = {"/opt/sublime_text/sublime_text", (char *)0};
int pid = create_process(name, argv);
printf("pid = %d\n",pid);
return 0;
}
程序执行结果如下,从下图我们可以清晰的看到通过 fork + exec 启动的程序的 pid 与最后通过 ps进程查看器查询得到的 pid 是不一致的。
尽管它们的 pid 值只差了1,但是这个结果还是让我感到非常疑惑。
问题分析
一般的,在子进程中使用 exec 函数并不会改变子进程的 pid 值,而得到的结果确确实实改变了。一开始怀疑是与 pid 的分配方式有关,因为多次得到的结果其 pid 都只差1(有兴趣的可以自行了解 pid 位图分配策略),但没有太多的信息进行佐证,最后怀疑是要启动的程序的问题。
通过strace
来跟踪 sublime_text 进程中的系统调用:
从上面的结果我们可以看出,sublime_text 的真实 pid 与 strace得到的结果中 clone 一行的结果相对应。从这个信息中,我们可以发现 sublime_text 内部通过 clone 自己创建了一个子进程来启动程序。因此推测通过 fork 得到的子进程在完成自己的任务后就退出了,启动程序的事情交给了 sublime_text 内部通过 clone 起的子进程去做。
问题解决
从上面的问题分析得知,sublime_text 真实的 pid 是 clone 创建的子进程的 pid,而这个 clone 创建的子进程是 sublime_text 内部启动的。那么如何获取启动的程序的 pid 呢。一开始想到方法如下:在启动程序A之前,记录下环境中已启动的程序A的 pid,然后启动 count 个A程序,扣除掉之前记录的就是现在启动的(sublime_text 启动多次只有一个程序实例,而 A 程序启动多次有多个程序实例,因此此处恢复为A程序的描述);但是这种方法存在极小概率会出错,环境并不是只有一个用户,也就是我在记录完环境中已有的程序A的 pid 后,启动 n 个程序A,此时如果有另一个用户也起了 m 个程序A,那么我就会认为这 n + m 个A程序都是我起的,后期杀死的时候破坏了他人启动的程序。因此这种方式并不适用,在论坛与人讨论后查找资论发现可以使用ptrace
来解决,其实也就是模拟strace
来跟踪进程中的系统调用。
#define _POSIX_C_SOURCE 200112L
/* C standard library */
#include <errno.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
/* POSIX */
#include <unistd.h>
#include <sys/user.h>
#include <sys/wait.h>
/* Linux */
#include <syscall.h>
#include <sys/ptrace.h>
#define FATAL(...) \
do { \
fprintf(stderr, "strace: " __VA_ARGS__); \
fputc('\n', stderr); \
exit(EXIT_FAILURE); \
} while (0)
int
main(int argc, char **argv)
{
if (argc <= 1)
FATAL("too few arguments: %d", argc);
pid_t pid = fork();
switch (pid) {
case -1: /* error */
FATAL("%s", strerror(errno));
case 0: /* child */
ptrace(PTRACE_TRACEME, 0, 0, 0);
execvp(argv[1], argv + 1);
FATAL("%s", strerror(errno));
}
/* parent */
waitpid(pid, 0, 0); // sync with PTRACE_TRACEME
ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_EXITKILL);
for (;;) {
/* Enter next system call */
if (ptrace(PTRACE_SYSCALL, pid, 0, 0) == -1)
FATAL("%s", strerror(errno));
if (waitpid(pid, 0, 0) == -1)
FATAL("%s", strerror(errno));
/* Gather system call arguments */
struct user_regs_struct regs;
if (ptrace(PTRACE_GETREGS, pid, 0, ®s) == -1)
FATAL("%s", strerror(errno));
long syscall = regs.orig_rax;
/* Print a representation of the system call */
fprintf(stderr, "%ld(%ld, %ld, %ld, %ld, %ld, %ld)",
syscall,
(long)regs.rdi, (long)regs.rsi, (long)regs.rdx,
(long)regs.r10, (long)regs.r8, (long)regs.r9);
/* Run system call and stop on exit */
if (ptrace(PTRACE_SYSCALL, pid, 0, 0) == -1)
FATAL("%s", strerror(errno));
if (waitpid(pid, 0, 0) == -1)
FATAL("%s", strerror(errno));
/* Get system call result */
if (ptrace(PTRACE_GETREGS, pid, 0, ®s) == -1) {
fputs(" = ?\n", stderr);
if (errno == ESRCH)
exit(regs.rdi); // system call was _exit(2) or similar
FATAL("%s", strerror(errno));
}
/* Print system call result */
fprintf(stderr, " = %ld\n", (long)regs.rax);
/*clone 系统调用号的特判
if (56 == syscall){
printf("%ld\n", (long)regs.rax);
}
*/
}
}
程序的主体主要是关于ptrace
的用法,本文不对ptrace
的用法进行详细阐述,具体可参见文末资料。上述程序是一个小型的strace
,它将拦截所有的系统调用,并输出相应的信息,如果取消代码尾处对于 clone 系统调用号的特判的注释,那么其打印出来的信息,就是 sublime_text 的 pid,此时我们的问题也得到了解决。对于系统调用号,可在/usr/include/x86_64-linux-gnu/asm/unistd_64.h
查找,也可查看文末资料,此处针对64位机器。
参考资料
Searchable Linux Syscall Table for x86 and x86_64
ptrace-examples
Programming with PTRACE, Part2 - 系统调用入门
使用 Ptrace 拦截和模拟 Linux 系统调用
获取fork+exec启动的程序的PID值的更多相关文章
- 【Appnium+C#+Winform自动化测试系列】一、获取本机连接的设备、启动多个Appnium和获取本机启动的Appnium
本系列内容,准备根据所完成的项目为基线,一步一步的把整个设计和实现过程梳理. 先从基本的一些环境问题入手,梳理清楚关于手机设备和Appnium.因为我们在后面的建立Appnium连接时,需要设备名字和 ...
- 一起学android之怎样获取手机程序列表以及程序相关信息并启动指定程序 (26)
效果图: 程序列表: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaGFpX3FpbmdfeHVfa29uZw==/font/5a6L5L2T/fonts ...
- MFC VC++ 根据文件名获取程序的Pid
环境:PC Win7 VS VC++ .MFC 使用,输入文件名即可获取程序的pid,进而可以对程序进行操作,比如关闭Porcess等. 头文件: #include <TlHelp32.h> ...
- linux第1天 fork exec 守护进程
概念方面 文件是对I/O设备的抽象表示.虚拟存储器是对主存和磁盘I/O设备的抽象表示.进程则是对处理器.主存和I/O设备的抽象表示 中断 早期是没有进程这个概念,当出现中断技术以后才出现进程这个概念 ...
- fork+exec 与system,popen区别
1.fork + exec fork用来创建一个子进程.一个程序一调用fork函数,系统就为一个新的进程准备了前述三个段,首先,系统让新的进程与旧的进程使用同一个代码段,因为它们的程序还是相同的,对于 ...
- shell调用另一个脚本的三种方式fork/exec/source
exec和source都属于bash内部命令(builtins commands),在bash下输入man exec或man source可以查看所有的内部命令信息. bash shell的命令分为两 ...
- Linux fork exec等
http://www.cnblogs.com/leoo2sk/archive/2009/12/11/talk-about-fork-in-linux.html http://www.cnblogs.c ...
- docker-compose exec时 出现"fork/exec /proc/self/exe: no such file or directory" 报错
问题:跟往常一样执行docker-compos exec redis sh时出现如下错误,而容器是运行状态中. # docker-compose exec redis sh rpc error: co ...
- 吉特仓库管理系统(开源)-如何在网页端启动WinForm 程序
在逛淘宝或者使用QQ相关的产品的时候,比如淘宝我要联系店家点击旺旺图标的时候能够自动启动阿里旺旺进行聊天.之前很奇怪为什么网页端能够自动启动客户端程序,最近在开发吉特仓储管理系统的时候也遇到一个类似的 ...
随机推荐
- 在win32中使用SetWindowSubclass阻止Enter键
使用阻止Enter键的编辑控件的简单子类来完成此操作: LRESULT CALLBACK EditSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, L ...
- 【长郡NOIP2014模拟10.22】搞笑的代码
题目 在OI界存在着一位传奇选手--QQ,他总是以风格迥异的搞笑代码受世人围观 某次某道题目的输入是一个排列,他使用了以下伪代码来生成数据 while 序列长度<n do { 随机生成一个整数属 ...
- 【NOIP2016提高A组模拟9.15】Osu
题目 分析 考虑二分答案, 二分小数显然是不可取的,那么我们将所有可能的答案求出来,记录在一个数组上,排个序(C++调用函数很容易超时,手打快排,时间复杂度约为\(O(>8*10^7)\),但相 ...
- vue和electron做的聊天应用表情包处理
表情包库: https://apps.timwhitlock.info/emoji/tables/unicode <template> <div @click.stop> &l ...
- python2---输出1--100之间的偶数
#!/usr/bin/env python方法1:num = 1while num <= 100: if num % 2 == 0: print(num) num += 1方法2: #!/usr ...
- html area标签 语法
html area标签 语法 作用:带有可点击区域的图像映射 说明:<img> 中的 usemap 属性可引用 <map> 中的 id 或 name 属性(由浏览器决定),所以 ...
- ASP.NET实现文件断点续传
IE的自带下载功能中没有断点续传功能,要实现断点续传功能,需要用到HTTP协议中鲜为人知的几个响应头和请求头. 一. 两个必要响应头Accept-Ranges.ETag 客户端每次提交下载请求时,服务 ...
- Codeforces Round #303 (Div. 2) E. Paths and Trees Dijkstra堆优化+贪心(!!!)
E. Paths and Trees time limit per test 3 seconds memory limit per test 256 megabytes input standard ...
- [模板][快速排序&归并排序]
不得不说,手写的快排真的好菜.(即使开了随机数...) 快速排序 #include<iostream> #include<cstdio> #include<cstring ...
- Luogu P4550 收集邮票
题目链接:Click here Solution: 本题直接推价格似乎很难,考虑先从购买次数入手 设购买次数\(g(i)\)为当前有\(i\)种不同的邮票,要买到\(n\)种的期望购买次数 可以由期望 ...