通常对于一个正在执行的进程而言,我们会关注进程的内存/CPU占用,网络连接,启动参数,映像路径,线程,堆栈等信息。
而通过类似任务管理器,命令行等方式可以轻松获取到这些信息。但是,这些信息究竟是从何而来呢?
/proc/[PID]/
:这是每个正在运行的进程都有一个对应的目录,其中[PID]
是进程的ID号。
DIR *proc = opendir("/proc");
if (proc) {
struct dirent *entry;
while ((entry = readdir(proc)) != NULL) {
if (entry->d_type == DT_DIR) {
std::string dir_name(entry->d_name);
if (dir_name.find_first_not_of("0123456789") == std::string::npos) {
int32_t pid = atoi(entry->d_name);
}
}
}
closedir(proc);
}
windows平台获取进程列表的方式比较多,只需从中选择任意一种即可。
在这里,采用第一种方式遍历进程。
当然,由于CreateToolhelp32Snapshot底层实际上同样是通过NtQuerySystemInformation实现的。因此,若追求高效率的话,可以使用第三种方案。
HANDLE hSnapshot =
CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD, 0);
if (hSnapshot) {
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(pe32);
if (Process32First(hSnapshot, &pe32)) {
do {
std::string path;
W2A(pe32.szExeFile, &path);
Proc detail_info;
detail_info.pid = pe32.th32ProcessID;
detail_info.name = path;
// GetDetailInfo
proc_map->emplace(pe32.th32ProcessID, detail_info);
} while (Process32Next(hSnapshot, &pe32));
}
CloseHandle(hSnapshot);
}
另附第三种方案实现:
typedef NTSTATUS(NTAPI *P_NT_QUERY_SYSTEM_INFORMATION)(
SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SystemInformation,
ULONG SystemInformationLength, PULONG ReturnLength);
HMODULE hNtdll = GetModuleHandle(L"ntdll.dll");
P_NT_QUERY_SYSTEM_INFORMATION pfnNtQueryInformationProcess = NULL;
if (hNtdll) {
pfnNtQueryInformationProcess =
reinterpret_cast<P_NT_QUERY_SYSTEM_INFORMATION>(
GetProcAddress(hNtdll, "NtQuerySystemInformation"));
ULONG length = 0;
PUCHAR pInfo = NULL;
do {
DWORD result = pfnNtQueryInformationProcess(SystemProcessInformation, pInfo,
length, &length);
#define STATUS_INFO_LENGTH_MISMATCH 0xC0000004
if (result != 0) {
if (result == STATUS_INFO_LENGTH_MISMATCH) {
pInfo = new UCHAR[length];
continue;
}
break;
}
PSYSTEM_PROCESS_INFORMATION _ProcessInfo;
ULONG Offset = 0;
do {
_ProcessInfo = (PSYSTEM_PROCESS_INFORMATION)&pInfo[Offset];
int64_t pid = HandleToLong(_ProcessInfo->UniqueProcessId);
// ToDo
Offset += _ProcessInfo->NextEntryOffset;
} while (_ProcessInfo->NextEntryOffset);
break;
} while (true);
if (pInfo) {
delete pInfo;
}
}
SYSTEM_THREAD_INFORMATION具体结构请参考:SYSTEM_PROCESS_INFORMATION
/proc/[PID]/cmdline
:这个文件包含了启动该进程的命令行参数。参数之间使用null字符(‘\0’)分隔。
char cmd_path[PROCPATHLEN];
sprintf(cmd_path, "/proc/%d/cmdline", pid);
FILE *fp = fopen(cmd_path, "r");
if (fp) {
char line[4096];
if (fgets(line, sizeof(line), fp)) {
imagepath->assign(line);
startparamater->assign(line);
int32_t offset = startparamater->size() + 1;
while (line[offset]) {
startparamater->append(" ");
startparamater->append(line + offset);
offset += strlen(line + offset) + 1;
}
}
fclose(fp);
}
windows平台没有直接提供获取进程启动参数的接口,但是可以通过解析进程的PEB(进程环境块)地址,获取信息。
typedef NTSTATUS(NTAPI *NT_QUERY_INFORMATION_PROCESS)(HANDLE, PROCESSINFOCLASS,
PVOID, ULONG, PULONG);
HMODULE hNtdll = GetModuleHandle(L"Ntdll");
if (hNtdll) {
NT_QUERY_INFORMATION_PROCESS pNtQueryInformationProcess =
(NT_QUERY_INFORMATION_PROCESS)GetProcAddress(hNtdll,
"NtQueryInformationProcess");
if (pNtQueryInformationProcess) {
// 只读打开进程句柄
HANDLE hProcess =
OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
if (hProcess) {
PWSTR buffer = NULL;
do {
PROCESS_BASIC_INFORMATION pbi = {0};
// 读取进程基本信息RTL_USER_PROCESS_PARAMETERS
if (pNtQueryInformationProcess(
hProcess, ProcessBasicInformation, (PVOID)&pbi,
sizeof(PROCESS_BASIC_INFORMATION), NULL)) {
break;
}
if (NULL == pbi.PebBaseAddress) {
break;
}
PEB peb;
SIZE_T dwDummy;
// 从PEB地址中读取PEB结构
if (!ReadProcessMemory(hProcess, pbi.PebBaseAddress, &peb, sizeof(peb),
&dwDummy)) {
break;
}
RTL_USER_PROCESS_PARAMETERS para;
// 从参数结构地址读取RTL_USER_PROCESS_PARAMETERS结构
if (!ReadProcessMemory(hProcess, peb.ProcessParameters, ¶,
sizeof(para), &dwDummy)) {
break;
}
// 从映像文件地址读取映像文件路径
LPVOID lpAddress = para.ImagePathName.Buffer;
DWORD dwSize = para.ImagePathName.Length;
buffer = new WCHAR[dwSize / sizeof(WCHAR) + 1];
buffer[dwSize / sizeof(WCHAR)] = 0x00;
if (!ReadProcessMemory(hProcess, lpAddress, buffer, dwSize, &dwDummy)) {
break;
}
W2A(buffer, imagepath);
delete[] buffer;
buffer = NULL;
// 从参数列表地址读取参数列表
lpAddress = para.CommandLine.Buffer;
dwSize = para.CommandLine.Length;
buffer = new WCHAR[dwSize / sizeof(WCHAR) + 1];
buffer[dwSize / sizeof(WCHAR)] = 0x00;
if (!ReadProcessMemory(hProcess, lpAddress, buffer, dwSize, &dwDummy))
break;
W2A(buffer, startparamater);
delete[] buffer;
buffer = NULL;
result = true;
} while (false);
if (buffer) {
delete[] buffer;
}
CloseHandle(hProcess);
}
}
}
/proc/[PID]/maps
:这个文件包含了进程的内存映射信息,显示了进程所使用的内存地址范围及其对应的权限。
char map_path[PROCPATHLEN];
sprintf(map_path, "/proc/%d/maps", pid);
FILE *fp = fopen(map_path, "r");
if (fp) {
char line[1024];
char filename[1024];
std::unordered_set<std::string> module_sets;
while (fgets(line, sizeof(line), fp)) {
sscanf(line, "%*s %*s %*s %*s %*ld %s", filename);
if (filename[0] == '/') {
module_sets.emplace(filename);
}
}
fclose(fp);
for (std::unordered_set<std::string>::const_iterator itr =
module_sets.begin();
itr != module_sets.end(); itr++) {
ptable->push_back(*itr);
}
}
windows平台可以直接使用Module32First族函数遍历所有进程加载的模块。
HANDLE hSnapshot =
CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid);
if (hSnapshot) {
MODULEENTRY32 md32;
md32.dwSize = sizeof(md32);
if (Module32First(hSnapshot, &md32)) {
do {
std::string filepath;
W2A(md32.szExePath, &filepath);
ptable->push_back(filepath);
} while (Module32Next(hSnapshot, &md32));
}
CloseHandle(hSnapshot);
}
进程的Cpu利用率无法直接从系统获取,而需要通过时间片算法来计算。在这里,我们参考top命令实现简单的进程Cpu利用率计算。
算法:
hertz_ = sysconf(_SC_CLK_TCK)
FILE *fp = fopen("/proc/uptime", "r");
if (fp) {
char line[1024];
fgets(line, sizeof(line), fp);
fclose(fp);
sscanf(line, "%lf", uptime);
}
float et = uptime_cur - uptime_save_;
if (et < 0.01) {
et = 0.005;
}
uptime_save_ = uptime_cur;
frame_etscale_ = 100.0f / ((float)hertz_ * (float)et * 1);
char stat_path[PROCPATHLEN];
sprintf(stat_path, "/proc/%d/stat", pid);
FILE *fp = fopen(stat_path, "r");
if (fp) {
char line[1024];
if (fgets(line, sizeof(line), fp)) {
int64_t u_time, s_time, wait_u_time, wait_s_time, start_time;
sscanf(line,
"%*d %*s %*c %*d %*d %*d %*d %*d "
"%*lu %*lu %*lu %*lu %*lu"
"%llu %llu %llu %llu"
"%*ld %*ld "
"%*d "
"%*ld "
"%llu ", /* start_time */
&u_time, &s_time, &wait_u_time, &wait_s_time, &start_time);
cpu_time->s_time = s_time;
cpu_time->u_time = u_time;
cpu_time->start_time = start_time;
}
fclose(fp);
}
tics = process.new_time - process.old_time;
cpu_usage = tics * etscale_;
windows平台的算法与Linux类似。
算法:
FILETIME idleTime, kernelTime, userTime;
if (!GetSystemTimes(&idleTime, &kernelTime, &userTime)) {
return;
}
ULARGE_INTEGER cpu_time;
cpu_time.LowPart = kernelTime.dwLowDateTime + userTime.dwLowDateTime;
cpu_time.HighPart = kernelTime.dwHighDateTime + userTime.dwHighDateTime;
time = cpu_time.QuadPart;
HANDLE hProcess =
OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
if (hProcess) {
FILETIME start_time, exit_time, s_time, u_time;
if (!GetProcessTimes(hProcess, &start_time, &exit_time, &s_time, &u_time)) {
return false;
}
FileTimeToInt64(s_time, cpu_time->s_time);
FileTimeToInt64(u_time, cpu_time->u_time);
FileTimeToInt64(start_time, cpu_time->start_time);
CloseHandle(hProcess);
}
*cpu_usage = (process.new_time - process.old_time) * 1000 /
(system_time_.new_time - system_time_.old_time);
/proc/[PID]/status
:这个文件包含了有关进程状态的各种信息,如进程ID、父进程ID、运行状态、内存使用情况等。
// 获取物理内存总量,单位Byte
const char *meminfo_path = "/proc/meminfo";
FILE *fp = fopen(meminfo_path, "r");
if (fp) {
char line[4096];
while (fgets(line, sizeof(line), fp)) {
if (strncmp(line, "MemTotal:", 9) == 0) {
sscanf(line, "%*s:%d", sys_mem_size_);
break;
}
}
fclose(fp);
}
//获取进程物理内存占用,单位KB
char status_path[PROCPATHLEN];
sprintf(status_path, "/proc/%d/status", pid);
FILE *fp = fopen(status_path, "r");
if (fp) {
char line[4096];
while (fgets(line, sizeof(line), fp)) {
if (strncmp(line, "VmRSS:", 6) == 0) {
sscanf(line, "%*s:%d", mem_used_size);
break;
}
}
fclose(fp);
}
MEMORYSTATUSEX mem_info;
mem_info.dwLength = sizeof(mem_info);
// 获取物理内存总量,单位Byte
if (GlobalMemoryStatusEx(&mem_info)) {
sys_mem_size_ = mem_info.ullTotalPhys;
}
SYSTEM_INFO si;
GetSystemInfo(&si);
HANDLE hProcess =
OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
if (hProcess) {
PSAPI_WORKING_SET_INFORMATION workset_info = NULL;
// 查询进程内存工具集
if (!QueryWorkingSet(hProcess, &workset_info, sizeof(workset_info))) {
if (GetLastError() == ERROR_BAD_LENGTH) {
// 计算所需内存,由于工具集长度动态变化,因此在此多申请64个工具集空间。
size_t length =
sizeof(PSAPI_WORKING_SET_INFORMATION) +
sizeof(PSAPI_WORKING_SET_BLOCK) * (workset_info.NumberOfEntries + 64);
PPSAPI_WORKING_SET_INFORMATION p_workset_info =
(PPSAPI_WORKING_SET_INFORMATION) new char[length];
if (QueryWorkingSet(hProcess, p_workset_info, length)) {
*mem_used_size = 0;
for (int i = 0; i < p_workset_info->NumberOfEntries; i++) {
// 判断工具集是否共享
if (p_workset_info->WorkingSetInfo[i].Flags &&
p_workset_info->WorkingSetInfo[i].Shared == 0) {
*mem_used_size += si.dwPageSize;
}
}
}
delete[] (char *)p_workset_info;
} else {
return false;
}
}
// Byte单位转换KB单位
*mem_used_size = (*mem_used_size / 1024);
return true;
}
/proc/[PID]/status
char status_path[PROCPATHLEN];
sprintf(status_path, "/proc/%d/status", pid);
FILE *fp = fopen(status_path, "r");
if (fp) {
char line[4096];
while (fgets(line, sizeof(line), fp)) {
if (strncmp(line, "Uid:", 4) == 0) {
int32_t uid = 0;
sscanf(line, "%*s:%d", &uid);
struct passwd *_passwd;
_passwd = getpwuid(uid);
if (_passwd) {
username = _passwd->pw_name;
}
}
}
fclose(fp);
}
HANDLE handle = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
if (handle != 0x00) {
HANDLE token;
// 打开进程令牌
if (OpenProcessToken(handle, TOKEN_QUERY, &token)) {
DWORD token_size = 0;
PTOKEN_USER p_token_user = NULL;
SID_NAME_USE sn;
// 从进程令牌中获取用户令牌
if (!GetTokenInformation(token, TokenUser, p_token_user, token_size,
&token_size)) {
if (ERROR_INSUFFICIENT_BUFFER == GetLastError()) {
p_token_user = (PTOKEN_USER)HeapAlloc(GetProcessHeap(), 0, token_size);
if (p_token_user) {
if (GetTokenInformation(token, TokenUser, p_token_user, token_size,
&token_size)) {
TCHAR szUserName[MAX_PATH] = {0};
DWORD dwUserSize = MAX_PATH;
TCHAR szDomain[MAX_PATH] = {0};
DWORD dwDomainSize = MAX_PATH;
// 根据用户令牌查询用户名
if (LookupAccountSid(NULL, ((PTOKEN_USER)p_token_user)->User.Sid,
szUserName, &dwUserSize, szDomain,
&dwDomainSize, &sn)) {
W2A(szUserName, username);
ret = true;
}
}
HeapFree(GetProcessHeap(), 0, p_token_user);
}
}
}
CloseHandle(token);
}
CloseHandle(handle);
}
/proc/net/tcp /proc/net/tcp6 /proc/net/udp /proc/net/udp6
: 提供了当前 TCP /TCP6/UDP/UDP6套接字的详细信息。
/proc/[PID]/fd/
:这是一个文件夹,包含了进程当前打开的文件描述符列表。
const char *tcp_file[] = {"/proc/net/tcp", "/proc/net/tcp6"};
for (int i = 0; i < 2; i++) {
FILE *fp = fopen(tcp_file[i], "r");
if (!fp) {
return;
}
char line[1024];
while (fgets(line, sizeof(line), fp)) {
unsigned long rxq, txq, time_len, retr, inode;
int num, local_port, remote_port, d, state, uid, timer_run, timeout;
char rem_addr[128], local_addr[128];
// 解析TCP信息
num = sscanf(line,
"%d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X %X %lX:%lX "
"%X:%lX %lX %d %d %lu %*s\n",
&d, local_addr, &local_port, rem_addr, &remote_port, &state,
&txq, &rxq, &timer_run, &time_len, &retr, &uid, &timeout,
&inode);
if (num < 11) {
continue;
}
// ipv6地址
if (strlen(local_addr) > 8) {
char addr6[INET6_ADDRSTRLEN];
struct in6_addr in6;
sscanf(local_addr, "%08X%08X%08X%08X", &in6.s6_addr32[0],
&in6.s6_addr32[1], &in6.s6_addr32[2], &in6.s6_addr32[3]);
// 端口序转换
inet_ntop(AF_INET6, &in6, addr6, sizeof(addr6));
sscanf(rem_addr, "%08X%08X%08X%08X", &in6.s6_addr32[0], &in6.s6_addr32[1],
&in6.s6_addr32[2], &in6.s6_addr32[3]);
inet_ntop(AF_INET6, &in6, addr6, sizeof(addr6));
} else {
// ipv4地址
char addr[INET_ADDRSTRLEN];
struct in_addr in;
sscanf(local_addr, "%X", &in.s_addr);
inet_ntop(AF_INET, &in, addr, sizeof(addr));
sscanf(rem_addr, "%X", &in.s_addr);
inet_ntop(AF_INET, &in, addr, sizeof(addr));
}
}
fclose(fp);
}
const char *udp_file[] = {"/proc/net/udp", "/proc/net/udp6"};
for (int i = 0; i < 2; i++) {
FILE *fp = fopen(udp_file[i], "r");
if (!fp) {
return false;
}
char line[1024];
while (fgets(line, sizeof(line), fp)) {
unsigned long rxq, txq, time_len, retr, inode;
int num, local_port, remote_port, d, state, uid, timer_run, timeout;
char rem_addr[128], local_addr[128];
// 解析UDP信息
num = sscanf(line,
"%d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X %X %lX:%lX "
"%X:%lX %lX %d %d %lu %*s\n",
&d, local_addr, &local_port, rem_addr, &remote_port, &state,
&txq, &rxq, &timer_run, &time_len, &retr, &uid, &timeout,
&inode);
if (num < 10) {
continue;
}
if (strlen(local_addr) > 8) {
// ipv6地址
char addr6[INET6_ADDRSTRLEN];
struct in6_addr in6;
sscanf(local_addr, "%08X%08X%08X%08X", &in6.s6_addr32[0],
&in6.s6_addr32[1], &in6.s6_addr32[2], &in6.s6_addr32[3]);
inet_ntop(AF_INET6, &in6, addr6, sizeof(addr6));
} else {
// ipv4地址
char addr[INET_ADDRSTRLEN];
struct in_addr in;
sscanf(local_addr, "%X", &in.s_addr);
inet_ntop(AF_INET, &in, addr, sizeof(addr));
}
}
fclose(fp);
}
直接使用GetExtendedTcpTable和GetExtendedUdpTable函数查询TCP和UDP信息。
DWORD bufferSize;
MIB_TCPTABLE_OWNER_PID *net_table = NULL;
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData)) {
return;
}
do {
SOCKADDR_IN v4 = {AF_INET};
SOCKADDR_IN6 v6 = {AF_INET6};
int32_t ip[] = {AF_INET6, AF_INET};
char ipaddress[INET6_ADDRSTRLEN];
DWORD ipaddress_length = 0;
for (int i = 0; i < 2; i++) {
bufferSize = 0;
if (GetExtendedTcpTable(NULL, &bufferSize, FALSE, ip[i],
TCP_TABLE_OWNER_PID_ALL,
0) == ERROR_INSUFFICIENT_BUFFER) {
BYTE *net_table = new BYTE[bufferSize];
if (net_table != NULL) {
if (GetExtendedTcpTable(net_table, &bufferSize, FALSE, ip[i],
TCP_TABLE_OWNER_PID_ALL, 0) == NO_ERROR) {
if (i == 0) {
MIB_TCP6TABLE_OWNER_PID *net_table_v6 =
(MIB_TCP6TABLE_OWNER_PID *)net_table;
for (DWORD i = 0; i < net_table_v6->dwNumEntries; i++) {
MIB_TCP6ROW_OWNER_PID row = net_table_v6->table[i];
// local addr info
ipaddress_length = INET6_ADDRSTRLEN;
memcpy(v6.sin6_addr.s6_addr, row.ucLocalAddr,
sizeof(row.ucLocalAddr));
if (!WSAAddressToStringA((LPSOCKADDR)&v6, sizeof(v6), NULL,
ipaddress, &ipaddress_length)) {
local_host = ipaddress;
}
local_port = ntohs(row.dwLocalPort);
// remote addr info
ipaddress_length = INET6_ADDRSTRLEN;
memcpy(v6.sin6_addr.s6_addr, row.ucRemoteAddr,
sizeof(row.ucRemoteAddr));
if (!WSAAddressToStringA((LPSOCKADDR)&v6, sizeof(v6), NULL,
ipaddress, &ipaddress_length)) {
remote_host = ipaddress;
}
remote_port = ntohs(row.dwRemotePort);
}
} else {
MIB_TCPTABLE_OWNER_PID *net_table_v4 =
(MIB_TCPTABLE_OWNER_PID *)net_table;
for (DWORD i = 0; i < net_table_v4->dwNumEntries; i++) {
MIB_TCPROW_OWNER_PID row = net_table_v4->table[i];
// local addr info
ipaddress_length = INET_ADDRSTRLEN;
v4.sin_addr.s_addr = row.dwLocalAddr;
if (!WSAAddressToStringA((LPSOCKADDR)&v4, sizeof(v4), NULL,
ipaddress, &ipaddress_length)) {
local_host = ipaddress;
}
local_port = ntohs(row.dwLocalPort);
// remote addr info
ipaddress_length = INET_ADDRSTRLEN;
v4.sin_addr.s_addr = row.dwRemoteAddr;
if (!WSAAddressToStringA((LPSOCKADDR)&v4, sizeof(v4), NULL,
ipaddress, &ipaddress_length)) {
remote_host = ipaddress;
}
remote_port = ntohs(row.dwRemotePort);
}
}
}
delete[] net_table;
}
}
}
for (int i = 0; i < 2; i++) {
bufferSize = 0;
if (GetExtendedUdpTable(NULL, &bufferSize, FALSE, ip[i],
UDP_TABLE_OWNER_PID,
0) == ERROR_INSUFFICIENT_BUFFER) {
BYTE *net_table = new BYTE[bufferSize];
if (net_table != NULL) {
if (GetExtendedUdpTable(net_table, &bufferSize, FALSE, ip[i],
UDP_TABLE_OWNER_PID, 0) == NO_ERROR) {
if (i == 0) {
MIB_UDP6TABLE_OWNER_PID *net_table_v6 =
(MIB_UDP6TABLE_OWNER_PID *)net_table;
for (DWORD i = 0; i < net_table_v6->dwNumEntries; i++) {
MIB_UDP6ROW_OWNER_PID row = net_table_v6->table[i];
// local addr netInfo
ipaddress_length = INET6_ADDRSTRLEN;
memcpy(v6.sin6_addr.s6_addr, row.ucLocalAddr,
sizeof(row.ucLocalAddr));
if (!WSAAddressToStringA((LPSOCKADDR)&v6, sizeof(v6), NULL,
ipaddress, &ipaddress_length)) {
local_host = ipaddress;
}
local_port = ntohs(row.dwLocalPort);
}
} else {
MIB_UDPTABLE_OWNER_PID *net_table_v4 =
(MIB_UDPTABLE_OWNER_PID *)net_table;
for (DWORD i = 0; i < net_table_v4->dwNumEntries; i++) {
MIB_UDPROW_OWNER_PID row = net_table_v4->table[i];
// local addr netInfo
ipaddress_length = INET_ADDRSTRLEN;
v4.sin_addr.s_addr = row.dwLocalAddr;
if (!WSAAddressToStringA((LPSOCKADDR)&v4, sizeof(v4), NULL,
ipaddress, &ipaddress_length)) {
local_host = ipaddress;
}
local_port = ntohs(row.dwLocalPort);
}
}
}
delete[] net_table;
}
}
}
} while (false);
WSACleanup();
……