linux c++ status 获取进程名称不完整问题记录
前期提要
之前整理了一篇 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 文件时进程名称要做一次截断的原因,只找到下面这篇文章

大致意思就是:
这归结于一个 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
很详细,常用的都有举例
本文探讨了Linux下C++获取进程名称时遇到的15字符限制问题,提出通过读取/proc/$pid/exe、cmdline和ps命令三种方法来避免进程名称截断。作者详细介绍了每种方法的实现过程和应用场景,包括VIM中的特殊字符处理和awk命令的使用。
2547

被折叠的 条评论
为什么被折叠?



