linux c++ status 获取进程名称不完整问题记录

本文探讨了Linux下C++获取进程名称时遇到的15字符限制问题,提出通过读取/proc/$pid/exe、cmdline和ps命令三种方法来避免进程名称截断。作者详细介绍了每种方法的实现过程和应用场景,包括VIM中的特殊字符处理和awk命令的使用。

前期提要

之前整理了一篇 linux 下使用 C++ 根据提供的 PID 查找 进程名称 或者根据 进程名称PID, 当时的策略是读取 /proc/$pid/status 文件,因为该文件第一行信息就是 Name,所以对第一行数据进行切割解析就可以了

但是!

最近在实际使用过程中发现,这种方式并 不保险,因为 status 文件中保存的名称长度只有 15 个字符长度,当名称长度大于 15 的时候,保存的名称会被截断

简单写个 demo 证明一下,编写 CMakeLists.txt , 这里设置的程序名称是 “test0123456789_123456789”

cmake_minimum_required(VERSION 3.4)

set(project_name test0123456789_123456789)
project(${project_name})

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_executable(${project_name} main.cpp)

main.cpp 中也简单点

#include <iostream>
#include <unistd.h>

using namespace std;

#define MAX_PATH 260
#define BUF_SIZE 1024

bool findNameByPid(const int &pid, std::string &servername)
{
    char buf[BUF_SIZE];
    char name[MAX_PATH];
    char filepath[MAX_PATH];

    sprintf(filepath, "/proc/%d/status", pid);
    FILE *fp = fopen(filepath, "r");
    if (nullptr == fp)
        return false;

    if (fgets(buf, BUF_SIZE - 1, fp) == nullptr)
    {
        fclose(fp);
        return false;
    }
    fclose(fp);

    sscanf(buf, "%*s%s", name);
    servername = std::string(name);
    return true;
}

int main()
{
    std::string servername;
    findNameByPid(getpid(), servername);
    std::cout << getpid() << " : " << servername << std::endl;
    // 方便查看 cat /proc/$pid/status 
    sleep(1000);
    return 0;
}

如果按照预期的 15 个字符长度的话, “_” 下划线之后应该都没有的, 实际运行结果和预期一样存在截断

在这里插入图片描述

新的思路

在网络上找了一圈为什么 linux 在 “写” status 文件时进程名称要做一次截断的原因,只找到下面这篇文章

https://stackoverflow.com/questions/14176058/why-is-the-name-of-a-process-in-proc-pid-status-not-matching-package-name-or-ps

在这里插入图片描述

大致意思就是:

这归结于一个 linux 内核特性:一个进程有两个不同的名称。

  • 其中一个名称是 可执行文件路径 的最后一部分组成,但是内核会将其截断为 15 个字符
  • 另一个名称是 /proc/$pid/cmdline 中的内容,也就是 ps 显示的内容

说了一个结果,可是没有解释为什么内核会将名称截断为 15 个字符,但是也提供了 2 个解题思路

  • 获取可执行文件路径
  • 读取 /proc/$pid/cmdline

既然 ps -ef 中可以看到进程名称等信息,那是不是可以通过命令行的形式来获取需要的信息,所以这里就有 3 种思路,下面对 3 种思路分别实现一下

1. 通过获取可执行文件路径

linux 对一个进程的描述信息,基本都在 /proc/$pid 文件夹下,获取可执行文件路径可以通过 “readlink /proc/$pid/exe”, 获取到可执行文件路径之后自行截取最后一个 “/” 后面的进程名称即可

代码如下:

bool findNameByPid(const int &pid, std::string &servername)
{
    char buf[BUF_SIZE] = {0};
    char filepath[MAX_PATH] = {0};

    sprintf(filepath, "/proc/%d/exe", pid);
    int ret_count = readlink(filepath, buf, BUF_SIZE);
    if (ret_count < 0 || ret_count >= BUF_SIZE)
        return false;

    string exe_name = buf;
    std::cout << "read from /proc/$pid/exe info : " << exe_name << std::endl; 
    size_t pos = exe_name.rfind("/");
    if (pos != string::npos)
        servername = exe_name.substr(pos + 1, exe_name.length());

    return true;
}

执行结果如下图:

在这里插入图片描述

2. 通过 cmdline 中获取

cmdline 中不带参数测试

#include <iostream>
#include <fstream>

bool findNameByPid(const int &pid, std::string &servername)
{
    std::string path = std::string("/proc/") + std::to_string(pid) + "/cmdline";
    ifstream file(path);
    std::string cmdline{};
    getline(file, cmdline, '\0');
    size_t pos = cmdline.rfind("/");
    if (pos != string::npos)
        servername = cmdline.substr(pos + 1, cmdline.length());

    return true;
}

在这里插入图片描述

这里使用 vi /proc/12320/cmdline 查看是因为文件中存在特殊字符 ^@ (其实可以理解成结束符或者输入参数之间的分隔符,详细看附录),如果使用 “cat /proc/12320/cmdline” 特殊字符不会显示,当然也可以使用 “cat -A” 命令来查看,下面带参数测试的部分有图示意

cmdline 带参数测试

假设有时候我们还需要读取启动的时候携带的参数, cmdline 文件其实也有保存

在这里插入图片描述

传入的参数也能通过 getline(file, args, '\0') 一个一个解析出来,简单改一下代码,测试一下读取 args 是否可以

bool findNameByPid(const int &pid, std::string &servername)
{
    std::string path = std::string("/proc/") + std::to_string(pid) + "/cmdline";
    ifstream file(path);
    std::string cmdline{};
    getline(file, cmdline, '\0');
    size_t pos = cmdline.rfind("/");
    if (pos != string::npos)
        servername = cmdline.substr(pos + 1, cmdline.length());

    std::string args{};
    while (getline(file, args, '\0'))
        std::cout << args << std::endl;

    return true;
}

程序运行的结果如下:

在这里插入图片描述

3. 通过命令行命令获取

bool findNameByPid(const int &pid, std::string &servername)
{
    bool result = false;
    FILE *fp;
    char cmd[MAX_PATH] = {0};

    sprintf(cmd, "ps -ef | grep %d | awk '$2~%d {print $8}'", pid, pid);
    if ((fp = popen(cmd, "r")) != NULL)
    {
        char buf[BUF_SIZE] = {0};
        if (fgets(buf, BUF_SIZE, fp) != NULL)
        {
            string readinfo{buf};
            size_t pos = readinfo.rfind("/");
            size_t length = readinfo.rfind("\n") - pos - 1;
            servername = readinfo.substr(pos + 1, length);
            result = true;
        }
    }
    pclose(fp);
    return result;
}

简单解释一下 cmd 命令

  • "ps -ef | grep $pid" 来检索所有满足条件的数据,然后通过 awk 对数据做进一步的筛选
  • "$2~$pid" 筛选上面得到结果中第二列数据 (ps -ef 第二列是 pid 信息) 等于给定 pid 的数据
  • "{print $8}" 获取第8列数据,第8列也就是前面提到了 cmdline 的信息

将命令通过 popen() 去执行,然后解析数据,切割得到最后的结果 (popen() 以及 awk 更多介绍参考附录)

这种思路还是比较简单的,写好执行的命令扔给命令行执行,然后对结果进行解析,也是一种思路,毕竟命令行有时候会有很多好用的自带工具

比如根据进程名称获取进程ID,如果根据前 2 种实现的思路,其实需要遍历 /proc/ 下所有进程的文件夹,而实际上 linux 是存在 pidof 这样的命令,逻辑实现起来可以很简单

总结

简单总结一下,大概可以用以下 3 种方式替换 /proc/$pid/status 来避免截断的问题

  • 通过执行 readlink 接口获取 /proc/$pid/exe 结果
  • 以文件的形式读取 /proc/$pid/cmdline 中的结果
  • 通过 popen() 执行 ps -ef | grep $pid | awk '$2~$pid {print $8}'

附录

VIM 中的特殊字符

详细参见这篇大佬的博客 https://www.cnblogs.com/LubinLew/p/vim-escape-character.html

linux 下的 popen()

详细参考这边文章: http://c.biancheng.net/cpp/html/351.html

注意事项:在编写具 SUID/SGID 权限的程序时请尽量避免使用 popen()popen()会继承环境变量,通过环境变量可能会造成系统安全的问题。

awk 常用命令

推荐看一下这位大佬的博客 https://cloud.tencent.com/developer/article/1159061

很详细,常用的都有举例

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

会偷懒的程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值