获取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相关的产品的时候,比如淘宝我要联系店家点击旺旺图标的时候能够自动启动阿里旺旺进行聊天.之前很奇怪为什么网页端能够自动启动客户端程序,最近在开发吉特仓储管理系统的时候也遇到一个类似的 ...
随机推荐
- java高并发核心要点|系列4|CPU内存指令重排序(Memory Reordering)
今天,我们来学习另一个重要的概念. CPU内存指令重排序(Memory Reordering) 什么叫重排序? 重排序的背景 我们知道现代CPU的主频越来越高,与cache的交互次数也越来越多.当CP ...
- nginx代理证书使用方法
一.证书购买 一般情况,申请证书时需要添加DNS解析,具体的步骤一般运营商都会给予详细说明.当然,也需要填写证书保护的处理的域名,一般非免费的证书可以支持多个域名处理,免费的一般只能支持一个域名的设置 ...
- java 对象与类
类与类之间的关系 一.继承关系 继承指的是一个类(称为子类.子接口)继承另外的一个类(称为父类.父接口)的功能,并可以增加它自己的新功能的能力.在Java中继承关系通过关键字extends明 ...
- git提示Please enter a commit message to explain why this merge is necessary
Please enter a commit message to explain why this merge is necessary. 请输入提交消息来解释为什么这种合并是必要的(提交信息) gi ...
- iOS画线段
CGContextRef context = UIGraphicsGetCurrentContext(); //设置线条类型 CGContextSetLineCap(context, kCGLineC ...
- Docker(四):Docker常用命令
除过以上我们使用的Docker命令外,Docker还有一些其它常用的命令 拉取docker镜像 docker pull image_name 查看宿主机上的镜像,Docker镜像保存在/var/lib ...
- JAVA笔记24-IO流(2)-节点流举例
节点流类型 例1: import java.io.*; public class TestFileInputStream{ public static void main(String args[]) ...
- CSS布局之flexbox
参考链接: https://www.cnblogs.com/qingchunshiguang/p/8011103.html 练习代码 <!DOCTYPE html> <html la ...
- C#之扩展方法 default(T)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...
- vs中free内存失败
关于vs中free内存失败: 主要有以下两个原因: 1. 函数参数调用写错.特别是传指针进去的时候,若形参与实参不对应,会出错. 2. 内存分配不够,这个原因主要是因为程序中访问到了内存外的地址,即使 ...