iT邦幫忙

2026 iThome 鐵人賽

DAY 28
0
Software Development

打造 OS Kernel:從 OS in 1,000 Lines 到 xv6系列 第 28 篇

Day 28|第一次擴充 xv6 API:實作自己的 pstat System Call

  • 分享至 

  • xImage
  •  

到目前為止,統計多半直接印在核心主控台上。
今天改成讓使用者程式取得一份結構化資料,自己決定如何顯示或比較。
這也是從「加幾行除錯文字」走向「設計核心 API」的第一步。

先決定 API 的範圍

本次 pstat() 只回傳呼叫者自己的資料,不列出任意 PID 的全部欄位。
因此不必在別的行程修改位址空間時,跨行程讀取未受相同鎖保護的 sz。
Day 30 的多工作負載由各個工作自行回報,再由外層整理,不宣稱得到全系統同一瞬間的快照。

這也有一個限制:呼叫時行程正在執行,回報的 state 通常是 RUNNING。
SLEEPING 的直接觀察仍使用 Day 24 的事件紀錄或核心除錯,不假裝 self-query 可以看見自己正在睡眠。

定義跨越邊界的資料格式

在 xv6 checkout 新增 kernel/pstat.h:

#ifndef XV6_PSTAT_H
#define XV6_PSTAT_H
#define PSTAT_VERSION 1
struct pstat {
  uint version;
  uint bytes;
  int pid;
  int state;
  uint64 size_bytes;
  uint64 scheduled;
  uint64 syscalls;
  uint64 sleeps;
  uint64 wakes;
  uint64 yields;
};
#endif

包含這個 header 前要先包含 kernel/types.h,核心檔案則使用自身目錄的 types.h。
不要把 struct proc 原樣複製給使用者,因為它含有核心指標、鎖與不屬於公開介面的欄位。
version 與 bytes 用來辨識這份 ABI,但目前仍要求 user/kernel 使用同一版 header,尚未實作跨版本協商。

把呼叫接到每一層

沿用 Day 27 的編號配置,在 kernel/syscall.h 新增 #define SYS_pstat 24。
完成以下接線:

檔案 新增內容
kernel/syscall.c 宣告區 extern uint64 sys_pstat(void);
kernel/syscall.c 的 syscalls[] [SYS_pstat] = sys_pstat,
user/user.h struct pstat; 與 int pstat(struct pstat *, int);
user/usys.pl entry("pstat");

在 kernel/sysproc.c 加入 #include "pstat.h",並新增完整 handler:

uint64
sys_pstat(void)
{
  uint64 dst;
  int bytes;
  argaddr(0, &dst);
  argint(1, &bytes);
  if (bytes != sizeof(struct pstat))
    return -1;
  struct proc *p = myproc();
  struct pstat s;
  memset(&s, 0, sizeof(s));
  s.version = PSTAT_VERSION;
  s.bytes = sizeof(s);
  s.size_bytes = p->sz;
  acquire(&p->lock);
  s.pid = p->pid;
  s.state = p->state;
  s.scheduled = p->scheduled;
  s.syscalls = p->syscalls;
  s.sleeps = p->sleeps;
  s.wakes = p->wakes;
  s.yields = p->yields;
  release(&p->lock);
  return copyout(p->pagetable, p->sz, dst, (char *)&s, sizeof(s));
}

這裡的 copyout() 有五個參數,第二個是使用者記憶體大小,對應本系列固定的 xv6 版本。
argaddr() 只取出數值,不驗證指標可寫,真正的位址與權限檢查由 copyout 路徑完成。
清零整個結構,也能避免把未初始化的 padding 帶到使用者空間。

先取得本行程資料,再釋放 p->lock 後複製,不持有這把 spinlock 執行可能涉及頁面配置的 copyout。
這是一次觀察,不承諾返回時所有計數仍與取樣瞬間完全一樣。

寫一個包含失敗路徑的使用者測試

新增 user/pstat.c,並將 $U/_pstat\ 加入 Makefile 的 UPROGS:

#include "kernel/types.h"
#include "kernel/pstat.h"
#include "user/user.h"

int
main(void)
{
  struct pstat s;
  if (pstat(&s, sizeof(s)) < 0 || s.version != PSTAT_VERSION)
    exit(1);
  printf("pid=%d state=%d size=%lu scheduled=%lu syscalls=%lu\n",
         s.pid, s.state, s.size_bytes, s.scheduled, s.syscalls);
  printf("sleep=%lu wake=%lu yield=%lu\n", s.sleeps, s.wakes, s.yields);
  if (pstat(&s, 1) != -1)
    exit(1);
  if (pstat((struct pstat *)~(uint64)0, sizeof(s)) != -1)
    exit(1);
  printf("pstat checks=ok\n");
  exit(0);
}

在 30-days-os-kernel/examples/xv6-riscv/ 執行 make TOOLPREFIX=riscv64-linux-gnu- CPUS=1 qemu,於 xv6 Shell 執行 pstat。
應看到自己的欄位與 pstat checks=ok,大小不符及超出範圍的位址都應返回 −1,而不是讓核心 panic。

查詢本身也算一個系統呼叫

Day 26 的計數器在 handler 前增加,所以這次 pstat 也會包含在 syscalls 中。
此外,列印需要 write,程式結束需要 exit,不能把程式總呼叫數直接等同於手寫的 getpid 次數。
做差值比較時,要明確定義哪兩個取樣點之間包含了哪些操作。

本日的檔案變更包括 pstat.h、sysproc.c、syscall 接線與 user/pstat.c。

day28: expose versioned self-process statistics through pstat

下一篇加上可選擇的追蹤開關,把前面零散的觀察整理成一個可以控制的工具。

參考資料


上一篇
Day 27|多核心 Kernel 為什麼需要 Lock?親手製造一個 Race Condition
下一篇
Day 29|打造 Kernel Observer:把 Process、Syscall、Scheduler 資訊整合起來
系列文
打造 OS Kernel:從 OS in 1,000 Lines 到 xv6 共 29 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言