1. 项目概述
在麒麟操作系统(Kylin)环境下使用C语言获取系统进程信息,是系统编程和运维监控中的常见需求。这个项目主要探讨如何通过gcc编译器,在国产麒麟平台上开发能够获取并分析系统进程状态的实用工具。
作为国产操作系统的代表,麒麟OS基于Linux内核,但在系统调用和部分API实现上有其特殊性。我们需要特别注意系统兼容性问题,同时充分利用Linux提供的/proc文件系统和相关系统调用来实现功能。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与工具链配置
2.1 麒麟系统基础环境
银河麒麟V10作为国产操作系统的代表,其软件生态与常规Linux发行版存在一定差异。在开始开发前,需要确保系统环境配置正确:
- 确认系统版本:
bash复制cat /etc/os-release
- 安装基本开发工具链:
bash复制sudo apt-get update
sudo apt-get install build-essential
注意:麒麟系统的软件源配置可能与Ubuntu不同,若遇到依赖问题,可尝试从麒麟官方镜像站获取特定版本的软件包。
2.2 GCC编译器配置
麒麟系统默认可能不包含最新版gcc,我们需要确认编译器版本并进行必要升级:
- 检查当前gcc版本:
bash复制gcc --version
- 若需升级(以gcc-9为例):
bash复制sudo apt-get install gcc-9 g++-9
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 90
- 验证编译器工作状态:
bash复制gcc -v
3. 获取进程信息的核心方法
3.1 通过/proc文件系统
Linux系统将进程信息以文件形式暴露在/proc目录下,这是最直接可靠的获取方式:
c复制#include <stdio.h>
#include <dirent.h>
void list_processes() {
DIR *dir;
struct dirent *entry;
dir = opendir("/proc");
if (dir == NULL) {
perror("opendir failed");
return;
}
while ((entry = readdir(dir)) != NULL) {
// 只处理数字目录名(进程ID)
if (entry->d_type == DT_DIR && atoi(entry->d_name) > 0) {
printf("Process ID: %s\n", entry->d_name);
// 可进一步读取/proc/[pid]/status等文件获取详细信息
char path[256];
snprintf(path, sizeof(path), "/proc/%s/status", entry->d_name);
FILE *fp = fopen(path, "r");
if (fp) {
char line[256];
while (fgets(line, sizeof(line), fp)) {
if (strncmp(line, "Name:", 5) == 0) {
printf(" %s", line);
}
}
fclose(fp);
}
}
}
closedir(dir);
}
3.2 使用系统调用
对于更底层的控制,可以直接使用系统调用:
c复制#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
void get_process_info(pid_t pid) {
// 获取进程状态
char stat_path[256];
snprintf(stat_path, sizeof(stat_path), "/proc/%d/stat", pid);
FILE *stat_file = fopen(stat_path, "r");
if (stat_file) {
char stat_line[1024];
if (fgets(stat_line, sizeof(stat_line), stat_file)) {
// 解析stat文件内容
int pid;
char comm[256];
char state;
int ppid;
sscanf(stat_line, "%d %s %c %d", &pid, comm, &state, &ppid);
printf("PID: %d\nCommand: %s\nState: %c\nParent PID: %d\n",
pid, comm, state, ppid);
}
fclose(stat_file);
}
}
4. 进程信息的高级获取与处理
4.1 获取完整进程树
构建进程树关系有助于理解系统运行状态:
c复制#include <stdlib.h>
#include <string.h>
typedef struct ProcessNode {
pid_t pid;
pid_t ppid;
char name[256];
struct ProcessNode *children;
struct ProcessNode *next;
} ProcessNode;
ProcessNode* build_process_tree() {
DIR *dir;
struct dirent *entry;
ProcessNode *root = NULL;
ProcessNode **nodes = NULL;
int count = 0;
// 第一次遍历:收集所有进程
dir = opendir("/proc");
while ((entry = readdir(dir)) != NULL) {
if (entry->d_type == DT_DIR && atoi(entry->d_name) > 0) {
pid_t pid = atoi(entry->d_name);
char stat_path[256];
snprintf(stat_path, sizeof(stat_path), "/proc/%d/stat", pid);
FILE *stat_file = fopen(stat_path, "r");
if (stat_file) {
char stat_line[1024];
if (fgets(stat_line, sizeof(stat_line), stat_file)) {
char comm[256];
char state;
pid_t ppid;
sscanf(stat_line, "%d %s %c %d", &pid, comm, &state, &ppid);
ProcessNode *node = malloc(sizeof(ProcessNode));
node->pid = pid;
node->ppid = ppid;
strncpy(node->name, comm, sizeof(node->name));
node->children = NULL;
node->next = NULL;
nodes = realloc(nodes, sizeof(ProcessNode*) * (count + 1));
nodes[count++] = node;
}
fclose(stat_file);
}
}
}
closedir(dir);
// 第二次遍历:构建树结构
for (int i = 0; i < count; i++) {
if (nodes[i]->ppid == 0) {
root = nodes[i];
} else {
for (int j = 0; j < count; j++) {
if (nodes[j]->pid == nodes[i]->ppid) {
nodes[i]->next = nodes[j]->children;
nodes[j]->children = nodes[i];
break;
}
}
}
}
free(nodes);
return root;
}
4.2 实时监控进程状态变化
通过轮询方式实现简单的进程监控:
c复制#include <time.h>
void monitor_process(pid_t pid, int interval) {
char stat_path[256];
snprintf(stat_path, sizeof(stat_path), "/proc/%d/stat", pid);
while (1) {
FILE *stat_file = fopen(stat_path, "r");
if (stat_file) {
char stat_line[1024];
if (fgets(stat_line, sizeof(stat_line), stat_file)) {
char comm[256];
char state;
unsigned long utime, stime;
sscanf(stat_line, "%*d %s %c %*d %*d %*d %*d %*d %*u %*u %*u %*u %lu %lu",
comm, &state, &utime, &stime);
time_t now = time(NULL);
printf("[%.24s] PID %d: %s State=%c CPU=%lu+%lu\n",
ctime(&now), pid, comm, state, utime, stime);
}
fclose(stat_file);
} else {
printf("Process %d no longer exists\n", pid);
break;
}
sleep(interval);
}
}
5. 麒麟系统特殊考量
5.1 系统调用差异
麒麟系统基于Linux内核,但部分系统调用可能有特殊行为:
- 进程信息获取API的兼容性测试
- 安全策略可能限制普通用户访问某些/proc条目
- 系统工具链版本可能影响头文件可用性
5.2 性能优化建议
在资源受限的国产平台上,性能优化尤为重要:
- 减少频繁的文件打开/关闭操作
- 使用缓冲读取代替逐字符读取
- 对/proc文件系统的访问进行缓存
- 避免不必要的字符串处理
c复制// 优化后的进程状态读取示例
void read_process_status(pid_t pid) {
static char buf[4096];
char path[64];
snprintf(path, sizeof(path), "/proc/%d/status", pid);
int fd = open(path, O_RDONLY);
if (fd == -1) return;
ssize_t n = read(fd, buf, sizeof(buf)-1);
close(fd);
if (n > 0) {
buf[n] = '\0';
// 高效解析缓冲区内容
char *line = strtok(buf, "\n");
while (line) {
if (strncmp(line, "State:", 6) == 0) {
printf("%s\n", line);
}
line = strtok(NULL, "\n");
}
}
}
6. 完整示例程序
以下是一个综合性的进程信息查看工具实现:
c复制#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#define MAX_PROCESSES 1024
typedef struct {
pid_t pid;
pid_t ppid;
char name[256];
char state;
unsigned long utime;
unsigned long stime;
long rss;
} ProcessInfo;
int get_process_info(pid_t pid, ProcessInfo *info) {
char stat_path[64];
snprintf(stat_path, sizeof(stat_path), "/proc/%d/stat", pid);
FILE *stat_file = fopen(stat_path, "r");
if (!stat_file) return 0;
char stat_line[1024];
if (!fgets(stat_line, sizeof(stat_line), stat_file)) {
fclose(stat_file);
return 0;
}
// 解析stat文件内容
char comm[256];
sscanf(stat_line, "%d %s %c %d %*d %*d %*d %*d %*u %*u %*u %*u %lu %lu %*d %*d %*d %*d %*d %*d %*u %*u %ld",
&info->pid, comm, &info->state, &info->ppid,
&info->utime, &info->stime, &info->rss);
// 处理命令名中的括号
if (comm[0] == '(' && comm[strlen(comm)-1] == ')') {
memmove(comm, comm+1, strlen(comm)-2);
comm[strlen(comm)-2] = '\0';
}
strncpy(info->name, comm, sizeof(info->name)-1);
fclose(stat_file);
return 1;
}
void print_process_table() {
DIR *dir;
struct dirent *entry;
ProcessInfo processes[MAX_PROCESSES];
int count = 0;
dir = opendir("/proc");
if (!dir) {
perror("Failed to open /proc");
return;
}
// 收集所有进程信息
while ((entry = readdir(dir)) != NULL && count < MAX_PROCESSES) {
if (entry->d_type == DT_DIR && atoi(entry->d_name) > 0) {
pid_t pid = atoi(entry->d_name);
if (get_process_info(pid, &processes[count])) {
count++;
}
}
}
closedir(dir);
// 打印表头
printf("%-6s %-6s %-8s %-20s %-12s %-12s %s\n",
"PID", "PPID", "STATE", "NAME", "USER TIME", "SYSTEM TIME", "RSS");
// 打印进程信息
for (int i = 0; i < count; i++) {
ProcessInfo *p = &processes[i];
printf("%-6d %-6d %-8c %-20s %-12lu %-12lu %ldKB\n",
p->pid, p->ppid, p->state, p->name,
p->utime, p->stime, p->rss * 4); // 页大小通常为4KB
}
}
int main(int argc, char *argv[]) {
printf("Kylin Process Viewer - Compiled with GCC %d.%d.%d\n",
__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);
print_process_table();
return 0;
}
编译命令:
bash复制gcc -o process_viewer process_viewer.c -Wall -O2
7. 常见问题与解决方案
7.1 权限问题
在麒麟系统上,普通用户可能无法访问某些进程的信息:
- 使用sudo运行程序
- 调整系统安全策略(需管理员权限)
- 检查SELinux或AppArmor配置
7.2 编译错误处理
常见编译问题及解决方法:
-
头文件缺失:
bash复制sudo apt-get install linux-headers-$(uname -r) -
链接错误:
bash复制
gcc -o program program.c -Wall -Wextra -
版本不兼容:
bash复制
gcc -std=gnu11 -D_GNU_SOURCE program.c
7.3 运行时问题
-
进程信息不完整:
- 检查/proc文件系统是否正常挂载
- 确认目标进程是否存在
-
性能问题:
- 减少不必要的/proc访问
- 使用批量读取代替多次小读取
-
内存泄漏:
- 使用valgrind检测
bash复制
valgrind --leak-check=full ./process_viewer
8. 扩展功能建议
8.1 图形界面集成
使用GTK或Qt为工具添加图形界面:
c复制// 简单GTK示例
#include <gtk/gtk.h>
void on_activate(GtkApplication *app) {
GtkWidget *window = gtk_application_window_new(app);
gtk_window_set_title(GTK_WINDOW(window), "Kylin Process Viewer");
gtk_window_set_default_size(GTK_WINDOW(window), 800, 600);
GtkWidget *text_view = gtk_text_view_new();
GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_view));
// 获取进程信息并显示
char info[4096];
// ... 获取进程信息的代码 ...
gtk_text_buffer_set_text(buffer, info, -1);
gtk_window_set_child(GTK_WINDOW(window), text_view);
gtk_widget_show(window);
}
int main(int argc, char *argv[]) {
GtkApplication *app = gtk_application_new("org.example.processviewer", G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect(app, "activate", G_CALLBACK(on_activate), NULL);
int status = g_application_run(G_APPLICATION(app), argc, argv);
g_object_unref(app);
return status;
}
8.2 网络功能扩展
将进程信息通过HTTP API暴露:
c复制#include <microhttpd.h>
int answer_to_connection(void *cls, struct MHD_Connection *connection,
const char *url, const char *method,
const char *version, const char *upload_data,
size_t *upload_data_size, void **con_cls) {
char *page = "<html><body><pre>";
// ... 获取进程信息并追加到page ...
strcat(page, "</pre></body></html>");
struct MHD_Response *response = MHD_create_response_from_buffer(strlen(page),
(void*)page, MHD_RESPMEM_MUST_COPY);
int ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
MHD_destroy_response(response);
return ret;
}
int main() {
struct MHD_Daemon *daemon = MHD_start_daemon(MHD_USE_AUTO | MHD_USE_INTERNAL_POLLING_THREAD,
8080, NULL, NULL, &answer_to_connection, NULL, MHD_OPTION_END);
if (NULL == daemon) return 1;
getchar(); // 按任意键停止
MHD_stop_daemon(daemon);
return 0;
}
8.3 系统资源监控
扩展功能以监控CPU、内存等系统资源:
c复制void read_system_stats() {
// CPU使用率
FILE *stat = fopen("/proc/stat", "r");
if (stat) {
char line[256];
while (fgets(line, sizeof(line), stat)) {
if (strncmp(line, "cpu ", 4) == 0) {
unsigned long user, nice, system, idle;
sscanf(line + 5, "%lu %lu %lu %lu", &user, &nice, &system, &idle);
printf("CPU: user=%lu nice=%lu system=%lu idle=%lu\n",
user, nice, system, idle);
}
}
fclose(stat);
}
// 内存信息
FILE *meminfo = fopen("/proc/meminfo", "r");
if (meminfo) {
char line[256];
while (fgets(line, sizeof(line), meminfo)) {
if (strncmp(line, "MemTotal:", 9) == 0 ||
strncmp(line, "MemFree:", 8) == 0 ||
strncmp(line, "Buffers:", 8) == 0 ||
strncmp(line, "Cached:", 7) == 0) {
printf("%s", line);
}
}
fclose(meminfo);
}
}
在实际开发中,我发现麒麟系统对/proc文件系统的访问速度有时会比主流Linux发行版稍慢,特别是在处理大量进程时。一个实用的优化技巧是减少不必要的字符串操作,直接使用二进制读取和内存映射来处理/proc文件内容。另外,银河麒麟V10的安全策略较为严格,开发时需要特别注意权限管理,避免因权限问题导致功能异常。
