270 lines
11 KiB
Python
270 lines
11 KiB
Python
#!/usr/bin/env python3
|
||
import json
|
||
import os
|
||
import subprocess
|
||
import sys
|
||
import threading
|
||
import time
|
||
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
|
||
from pathlib import Path
|
||
from urllib.parse import parse_qs, urlparse
|
||
|
||
|
||
ROOT = Path(__file__).resolve().parent
|
||
STATE_DIR = ROOT / "data"
|
||
STATE_FILE = STATE_DIR / "state.json"
|
||
WEB_DIR = ROOT / "web"
|
||
BIN = ROOT / ("process_manager.exe" if os.name == "nt" else "process_manager")
|
||
|
||
|
||
class CProcessBridge:
|
||
"""
|
||
把浏览器请求转换成 C 程序命令,并读取 C 程序生成的 JSON 状态。
|
||
|
||
这个类相当于“桥接层”:
|
||
浏览器不会直接运行 C 函数,而是请求 Python API;
|
||
Python 把 API 翻译成命令行文本,写入 C 程序 stdin;
|
||
C 程序执行命令后写 state.json;
|
||
Python 再读取 state.json 返回给浏览器。
|
||
"""
|
||
|
||
def __init__(self):
|
||
self.process = None
|
||
self.lock = threading.Lock()
|
||
|
||
def start(self):
|
||
STATE_DIR.mkdir(exist_ok=True)
|
||
if self.process and self.process.poll() is None:
|
||
return
|
||
|
||
# C 程序以 --json 模式启动后,会把系统状态持续写入 data/state.json。
|
||
# previous_mtime 用来判断启动后是否真的产生了新的状态文件。
|
||
previous_mtime = STATE_FILE.stat().st_mtime_ns if STATE_FILE.exists() else 0
|
||
self.process = subprocess.Popen(
|
||
[str(BIN), "--json", str(STATE_FILE)],
|
||
cwd=str(ROOT),
|
||
stdin=subprocess.PIPE,
|
||
stdout=subprocess.DEVNULL,
|
||
stderr=subprocess.DEVNULL,
|
||
text=True,
|
||
bufsize=1,
|
||
)
|
||
self._wait_for_state(previous_mtime)
|
||
|
||
def stop(self):
|
||
if self.process and self.process.poll() is None:
|
||
# 优先发送 exit,让 C 程序正常退出;超时后再 terminate。
|
||
if self.process.stdin is not None:
|
||
self.process.stdin.write("exit\n")
|
||
self.process.stdin.flush()
|
||
try:
|
||
self.process.wait(timeout=1)
|
||
except subprocess.TimeoutExpired:
|
||
self.process.terminate()
|
||
|
||
def reset(self):
|
||
with self.lock:
|
||
# 重置时销毁旧 C 进程和旧 JSON,再启动一个全新的模拟系统。
|
||
self.stop()
|
||
if STATE_FILE.exists():
|
||
STATE_FILE.unlink()
|
||
self.process = None
|
||
self.start()
|
||
|
||
def command(self, line):
|
||
with self.lock:
|
||
self.start()
|
||
if self.process.stdin is None:
|
||
raise RuntimeError("C process stdin is unavailable")
|
||
# 记录命令前的修改时间,随后等待 state.json 被 C 程序重新写入。
|
||
# lock 保证同一时刻只有一个浏览器请求在写 C 程序 stdin,避免命令交错。
|
||
previous_mtime = STATE_FILE.stat().st_mtime_ns if STATE_FILE.exists() else 0
|
||
self.process.stdin.write(line + "\n")
|
||
self.process.stdin.flush()
|
||
self._wait_for_state(previous_mtime)
|
||
|
||
def state(self):
|
||
with self.lock:
|
||
self.start()
|
||
self._wait_for_state()
|
||
# state.json 是 C 程序输出的完整系统快照,Python 只负责转发。
|
||
with STATE_FILE.open("r", encoding="utf-8") as file:
|
||
return json.load(file)
|
||
|
||
def _wait_for_state(self, previous_mtime=0):
|
||
# 简单轮询足够应对此项目:C 程序每次命令执行完都会写完整 JSON。
|
||
# 50 * 0.02s 最多等待约 1 秒,避免浏览器请求无限卡住。
|
||
for _ in range(50):
|
||
if (STATE_FILE.exists() and
|
||
STATE_FILE.stat().st_size > 0 and
|
||
STATE_FILE.stat().st_mtime_ns > previous_mtime):
|
||
return
|
||
time.sleep(0.02)
|
||
|
||
|
||
bridge = CProcessBridge()
|
||
|
||
|
||
def first(query, name, default=""):
|
||
values = query.get(name)
|
||
if not values:
|
||
return default
|
||
return values[0]
|
||
|
||
|
||
def integer(query, name, default=-1):
|
||
try:
|
||
# API 查询参数都是字符串,这里集中做整数转换和默认值处理。
|
||
return int(first(query, name, str(default)))
|
||
except ValueError:
|
||
return default
|
||
|
||
|
||
class WebUIHandler(SimpleHTTPRequestHandler):
|
||
def __init__(self, *args, **kwargs):
|
||
super().__init__(*args, directory=str(WEB_DIR), **kwargs)
|
||
|
||
def log_message(self, fmt, *args):
|
||
sys.stderr.write("%s\n" % (fmt % args))
|
||
|
||
def do_GET(self):
|
||
parsed = urlparse(self.path)
|
||
if parsed.path.startswith("/api/"):
|
||
# /api/* 是操作接口,其它路径交给 SimpleHTTPRequestHandler 返回静态页面。
|
||
# 这样一个 Python 文件同时承担静态资源服务和后端 API。
|
||
self.handle_api(parsed.path, parse_qs(parsed.query))
|
||
return
|
||
super().do_GET()
|
||
|
||
def send_json(self, payload, status=200):
|
||
body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
|
||
self.send_response(status)
|
||
self.send_header("Content-Type", "application/json; charset=utf-8")
|
||
self.send_header("Content-Length", str(len(body)))
|
||
self.end_headers()
|
||
self.wfile.write(body)
|
||
|
||
def handle_api(self, path, query):
|
||
try:
|
||
# 每个 API 分支只做参数整理,然后拼成 C 程序已经支持的命令。
|
||
# 真实业务逻辑仍在 C 代码里,避免 Python 和 C 各维护一套状态。
|
||
if path == "/api/state":
|
||
pass
|
||
elif path == "/api/create":
|
||
name = first(query, "name", "process").replace(" ", "_")
|
||
priority = integer(query, "priority", 1)
|
||
service_time = integer(query, "service_time", 1)
|
||
bridge.command(f"create {name} {priority} {service_time}")
|
||
elif path == "/api/fork":
|
||
bridge.command(f"fork {integer(query, 'pid')}")
|
||
elif path == "/api/schedule":
|
||
algo = first(query, "type", "fcfs")
|
||
quantum = integer(query, "quantum", 2)
|
||
bridge.command(f"schedule {algo} {quantum}")
|
||
elif path == "/api/step":
|
||
bridge.command("step")
|
||
elif path == "/api/run":
|
||
bridge.command("run")
|
||
elif path == "/api/block":
|
||
bridge.command(f"block {integer(query, 'pid')}")
|
||
elif path == "/api/wakeup":
|
||
bridge.command(f"wakeup {integer(query, 'pid')}")
|
||
elif path == "/api/kill":
|
||
bridge.command(f"kill {integer(query, 'pid')}")
|
||
elif path == "/api/killtree":
|
||
bridge.command(f"killtree {integer(query, 'pid')}")
|
||
elif path == "/api/send":
|
||
from_pid = integer(query, "from")
|
||
to_pid = integer(query, "to")
|
||
# 命令行协议用空格分隔参数,因此这里把换行去掉,避免破坏 C 端解析。
|
||
content = first(query, "content", "").replace("\n", " ").strip()
|
||
bridge.command(f"send {from_pid} {to_pid} {content}")
|
||
elif path == "/api/recv":
|
||
bridge.command(f"recv {integer(query, 'pid')}")
|
||
elif path == "/api/sem_create":
|
||
name = first(query, "name", "sem").replace(" ", "_")
|
||
value = integer(query, "value", 1)
|
||
bridge.command(f"sem_create {name} {value}")
|
||
elif path == "/api/sem_wait":
|
||
bridge.command(f"sem_wait {integer(query, 'sem_id')} {integer(query, 'pid')}")
|
||
elif path == "/api/sem_signal":
|
||
bridge.command(f"sem_signal {integer(query, 'sem_id')}")
|
||
elif path == "/api/shm_create":
|
||
bridge.command(f"shm_create {integer(query, 'size', 64)}")
|
||
elif path == "/api/shm_attach":
|
||
bridge.command(f"shm_attach {integer(query, 'shm_id')} {integer(query, 'pid')}")
|
||
elif path == "/api/shm_write":
|
||
shm_id = integer(query, "shm_id")
|
||
pid = integer(query, "pid")
|
||
offset = integer(query, "offset", 0)
|
||
# 共享内存写入内容作为命令末尾文本传给 C 程序。
|
||
data = first(query, "data", "").replace("\n", " ").strip()
|
||
bridge.command(f"shm_write {shm_id} {pid} {offset} {data}")
|
||
elif path == "/api/shm_read":
|
||
bridge.command(
|
||
f"shm_read {integer(query, 'shm_id')} {integer(query, 'pid')} {integer(query, 'offset', 0)} {integer(query, 'length', 32)}"
|
||
)
|
||
elif path == "/api/shm_destroy":
|
||
bridge.command(f"shm_destroy {integer(query, 'shm_id')}")
|
||
elif path == "/api/pipe_create":
|
||
bridge.command("pipe_create")
|
||
elif path == "/api/pipe_write":
|
||
pipe_id = integer(query, "pipe_id")
|
||
pid = integer(query, "pid")
|
||
data = first(query, "data", "").replace("\n", " ").strip()
|
||
bridge.command(f"pipe_write {pipe_id} {pid} {data}")
|
||
elif path == "/api/pipe_read":
|
||
bridge.command(f"pipe_read {integer(query, 'pipe_id')} {integer(query, 'pid')}")
|
||
elif path == "/api/pipe_close":
|
||
bridge.command(
|
||
f"pipe_close {integer(query, 'pipe_id')} {integer(query, 'close_reader', 0)} {integer(query, 'close_writer', 0)}"
|
||
)
|
||
elif path == "/api/resource_add":
|
||
name = first(query, "name", "R").replace(" ", "_")
|
||
bridge.command(f"resource_add {name} {integer(query, 'total', 1)}")
|
||
elif path == "/api/resource_request":
|
||
bridge.command(
|
||
f"request {integer(query, 'pid')} {integer(query, 'resource_id')} {integer(query, 'amount', 1)}"
|
||
)
|
||
elif path == "/api/resource_release":
|
||
bridge.command(
|
||
f"release {integer(query, 'pid')} {integer(query, 'resource_id')} {integer(query, 'amount', 1)}"
|
||
)
|
||
elif path == "/api/detect_deadlock":
|
||
bridge.command("detect_deadlock")
|
||
elif path == "/api/reset":
|
||
bridge.reset()
|
||
else:
|
||
self.send_json({"error": "unknown api"}, status=404)
|
||
return
|
||
|
||
# 每个操作后都返回最新状态,前端不需要再额外请求一次。
|
||
self.send_json(bridge.state())
|
||
except Exception as exc:
|
||
# 出错时返回 JSON,前端可以统一显示错误信息。
|
||
self.send_json({"error": str(exc)}, status=500)
|
||
|
||
|
||
def main():
|
||
if not BIN.exists():
|
||
raise SystemExit("process_manager not found. Run `make` first.")
|
||
|
||
# PORT 可通过环境变量覆盖,默认使用 8080,方便本地演示。
|
||
port = int(os.environ.get("PORT", "8080"))
|
||
bridge.start()
|
||
server = ThreadingHTTPServer(("127.0.0.1", port), WebUIHandler)
|
||
print(f"Python WebUI server: http://127.0.0.1:{port}")
|
||
print(f"C JSON state file: {STATE_FILE}")
|
||
|
||
try:
|
||
server.serve_forever()
|
||
except KeyboardInterrupt:
|
||
pass
|
||
finally:
|
||
bridge.stop()
|
||
server.server_close()
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|