首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C >> 關於C

從零開始:用 C 語言實現簡易版 ps 命令

     編輯:關於C

前言

在學習 Unix/Linux 系統編程時,光看書往往不夠,動手實作才能真正掌握核心概念。本文記錄了如何從零開始,利用 C 語言遍歷 /proc 檔案系統,實現一個簡易版的 ps 命令,用於顯示當前系統進程的快照資訊。

核心思路

ps 命令的本質是讀取內核透過 /proc 虛擬檔案系統暴露的進程資訊。每個進程在 /proc 下都有一個以 PID 命名的目錄,裡面的 stat、status 等檔案包含了進程狀態、父進程 ID、使用者等關鍵數據。

關鍵步驟

  1. 掃描 /proc 目錄,篩選出純數字命名的子目錄(即 PID)。
  2. 對每個 PID,讀取 /proc/[PID]/stat 獲取進程名、狀態、PPID 等欄位。
  3. 透過 stat 系統呼叫取得目錄擁有者的 UID,再用 getpwuid() 轉換為使用者名稱。
  4. 將收集到的資訊串聯成鏈表,最後格式化輸出。

核心程式碼解析

#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 的更高效方式。

動手實作是理解作業系統原理的最佳途徑,歡迎在留言區交流改進想法!

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved