在學習 Unix/Linux 系統編程時,光看書往往不夠,動手實作才能真正掌握核心概念。本文記錄了如何從零開始,利用 C 語言遍歷 /proc 檔案系統,實現一個簡易版的 ps 命令,用於顯示當前系統進程的快照資訊。
ps 命令的本質是讀取內核透過 /proc 虛擬檔案系統暴露的進程資訊。每個進程在 /proc 下都有一個以 PID 命名的目錄,裡面的 stat、status 等檔案包含了進程狀態、父進程 ID、使用者等關鍵數據。
/proc 目錄,篩選出純數字命名的子目錄(即 PID)。/proc/[PID]/stat 獲取進程名、狀態、PPID 等欄位。stat 系統呼叫取得目錄擁有者的 UID,再用 getpwuid() 轉換為使用者名稱。
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <dirent.h>
#include <pwd.h>
#define MAX_LEN 20
typedef struct ps_info {
char pname[MAX_LEN];
char user[MAX_LEN];
int pid;
int ppid;
char state;
struct ps_info *next;
} mps;
/* 判斷字串是否為純數字 */
int is_num(char *str) {
if (!str || !*str) return -1;
for (int i = 0; str[i]; i++)
if (str[i] < '0' || str[i] > '9') return -1;
return 0;
}
/* UID 轉使用者名稱 */
void uid_to_name(uid_t uid, mps *node) {
struct passwd *pw = getpwuid(uid);
if (pw) strcpy(node->user, pw->pw_name);
else sprintf(node->user, "%d", uid);
}
/* 讀取單一進程資訊 */
int read_info(const char *d_name, mps *node) {
char path[64];
struct stat st;
FILE *fp;
snprintf(path, sizeof(path), "/proc/%s", d_name);
if (stat(path, &st) == -1) return -1;
uid_to_name(st.st_uid, node);
snprintf(path, sizeof(path), "/proc/%s/stat", d_name);
if (!(fp = fopen(path, "r"))) return -1;
/* stat 格式:pid (comm) state ppid ... */
if (fscanf(fp, "%d %s %c %d", &node->pid, node->pname, &node->state, &node->ppid) != 4) {
fclose(fp);
return -1;
}
fclose(fp);
return 0;
}
/* 遍歷 /proc 目錄建立鏈表 */
mps *trav_dir(const char *dir) {
DIR *dp = opendir(dir);
if (!dp) return NULL;
mps *head = NULL, *tail = NULL, *cur;
struct dirent *ep;
while ((ep = readdir(dp))) {
if (is_num(ep->d_name)) continue; // 非數字目錄跳過
cur = malloc(sizeof(mps));
if (!cur) { closedir(dp); return head; }
cur->next = NULL;
if (read_info(ep->d_name, cur) == 0) {
if (!head) head = tail = cur;
else { tail->next = cur; tail = cur; }
} else free(cur);
}
closedir(dp);
return head;
}
/* 列印進程表 */
void print_ps(mps *head) {
printf("%-10s %6s %6s %5s %s\n", "USER", "PID", "PPID", "STAT", "COMMAND");
for (mps *p = head; p; p = p->next)
printf("%-10s %6d %6d %5c %s\n", p->user, p->pid, p->ppid, p->state, p->pname);
}
int main(void) {
mps *list = trav_dir("/proc");
if (!list) { fprintf(stderr, "遍歷 /proc 失敗\n"); return 1; }
print_ps(list);
/* 釋放鏈表 */
while (list) {
mps *tmp = list;
list = list->next;
free(tmp);
}
return 0;
}
USER PID PPID STAT COMMAND root 1 0 S systemd root 2 0 S kthreadd ...
這個簡易版 ps 展示了 Linux 進程資訊的獲取機制。實際生產環境中的 ps 遠比這複雜,支援多種輸出格式、執行緒顯示、篩選條件等。若要深入,可嘗試:
/proc/[PID]/status 獲取更詳細的記憶體、信號等資訊。-e、-f、-u 等常見參數。libproc 或直接讀取 /proc 的更高效方式。動手實作是理解作業系統原理的最佳途徑,歡迎在留言區交流改進想法!