feat: 进程调度模拟

This commit is contained in:
2026-04-19 18:17:20 +08:00
parent 8d5f879021
commit b53a15c86a
4 changed files with 1194 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
module os-course-design
go 1.25.1
+81
View File
@@ -0,0 +1,81 @@
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strconv"
)
type SimRequest struct {
Algorithm string `json:"algorithm"`
TimeSlice int `json:"timeSlice"`
Processes []ProcInput `json:"processes"`
}
type ProcInput struct {
Name string `json:"name"`
Priority int `json:"priority"`
ArrivalTime int `json:"arrivalTime"`
NeedTime int `json:"needTime"`
}
func main() {
port := "8080"
if len(os.Args) > 1 {
p, err := strconv.Atoi(os.Args[1])
if err == nil && p > 0 && p < 65536 {
port = os.Args[1]
}
}
fs := http.FileServer(http.Dir("./static"))
http.Handle("/", fs)
http.HandleFunc("/api/simulate", handleSimulate)
fmt.Printf("进程调度模拟器已启动: http://localhost:%s\n", port)
if err := http.ListenAndServe(":"+port, nil); err != nil {
fmt.Fprintf(os.Stderr, "服务器启动失败: %v\n", err)
os.Exit(1)
}
}
func handleSimulate(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var req SimRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request: "+err.Error(), http.StatusBadRequest)
return
}
procs := make([]*PCB, len(req.Processes))
for i, p := range req.Processes {
procs[i] = NewPCB(p.Name, p.Priority, p.ArrivalTime, p.NeedTime)
}
var result SimResult
if req.Algorithm == "mlfq" {
ts := [3]int{1, 2, 4}
if req.TimeSlice > 0 {
ts[0] = req.TimeSlice
ts[1] = req.TimeSlice * 2
ts[2] = req.TimeSlice * 4
}
result = SimulateMLFQ(procs, ts)
} else {
ts := 2
if req.TimeSlice > 0 {
ts = req.TimeSlice
}
result = SimulatePriorityRR(procs, ts)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(result)
}
+506
View File
@@ -0,0 +1,506 @@
package main
import "sort"
const (
StateReady = "R"
StateExecute = "E"
StateWait = "W"
StateFinish = "F"
)
type PCB struct {
Name string `json:"name"`
Priority int `json:"priority"`
InitPriority int `json:"initPriority"`
ArrivalTime int `json:"arrivalTime"`
NeedTime int `json:"needTime"`
UsedTime int `json:"usedTime"`
State string `json:"state"`
StartTime int `json:"startTime"`
FinishTime int `json:"finishTime"`
QueueLevel int `json:"queueLevel"`
WaitUntil int `json:"waitUntil"`
SliceUsedTime int `json:"sliceUsedTime"`
}
func (p *PCB) Clone() PCB {
return *p
}
func (p *PCB) Remaining() int {
return p.NeedTime - p.UsedTime
}
type Snapshot struct {
Time int `json:"time"`
Processes []PCB `json:"processes"`
Running string `json:"running"`
Queues [][]string `json:"queues"`
Event string `json:"event"`
}
type SimResult struct {
Snapshots []Snapshot `json:"snapshots"`
Algorithm string `json:"algorithm"`
AvgTurnaround float64 `json:"avgTurnaround"`
Deadlock bool `json:"deadlock"`
}
func NewPCB(name string, priority, arrivalTime, needTime int) *PCB {
return &PCB{
Name: name,
Priority: priority,
InitPriority: priority,
ArrivalTime: arrivalTime,
NeedTime: needTime,
UsedTime: 0,
State: StateReady,
StartTime: -1,
FinishTime: -1,
QueueLevel: 0,
WaitUntil: -1,
SliceUsedTime: 0,
}
}
func allFinished(procs []*PCB) bool {
for _, p := range procs {
if p.State != StateFinish {
return false
}
}
return true
}
func allWaitingOrFinished(procs []*PCB) bool {
for _, p := range procs {
if p.State != StateWait && p.State != StateFinish {
return false
}
}
return true
}
func anyWillWake(procs []*PCB, time int) bool {
for _, p := range procs {
if p.State == StateWait && p.WaitUntil > time {
return true
}
}
return false
}
func collectReady(procs []*PCB, time int) []*PCB {
var ready []*PCB
for _, p := range procs {
if p.State == StateReady && p.ArrivalTime <= time {
ready = append(ready, p)
}
}
return ready
}
func findRunning(procs []*PCB) *PCB {
for _, p := range procs {
if p.State == StateExecute {
return p
}
}
return nil
}
func calcAvgTurnaround(procs []*PCB) float64 {
total := 0
count := 0
for _, p := range procs {
if p.State == StateFinish && p.FinishTime > 0 {
total += p.FinishTime - p.ArrivalTime
count++
}
}
if count == 0 {
return 0
}
return float64(total) / float64(count)
}
func makeSnap(time int, procs []*PCB, running string, queues [][]string, event string) Snapshot {
var copies []PCB
for _, p := range procs {
copies = append(copies, p.Clone())
}
return Snapshot{
Time: time,
Processes: copies,
Running: running,
Queues: queues,
Event: event,
}
}
func SimulatePriorityRR(processes []*PCB, timeSlice int) SimResult {
procs := make([]*PCB, len(processes))
for i, p := range processes {
c := *p
procs[i] = &c
}
var snapshots []Snapshot
var readyQueue []*PCB
time := 0
maxTime := 0
for _, p := range procs {
end := p.ArrivalTime + p.NeedTime*2 + 10
if end > maxTime {
maxTime = end
}
}
for time <= maxTime {
for _, p := range procs {
if p.State == StateWait && p.WaitUntil <= time {
p.State = StateReady
p.WaitUntil = -1
readyQueue = append(readyQueue, p)
}
if p.ArrivalTime == time && p.State == StateReady {
found := false
for _, q := range readyQueue {
if q == p {
found = true
break
}
}
if !found {
readyQueue = append(readyQueue, p)
}
}
}
running := findRunning(procs)
if running != nil {
for _, p := range readyQueue {
if p.Priority < running.Priority {
running.State = StateReady
running.SliceUsedTime = 0
readyQueue = append(readyQueue, running)
running = nil
break
}
}
}
if running == nil {
if len(readyQueue) == 0 {
if allFinished(procs) {
snapshots = append(snapshots, makeSnap(time, procs, "", nil, "✓ 所有进程执行完毕"))
break
}
if allWaitingOrFinished(procs) {
if !anyWillWake(procs, time) {
snapshots = append(snapshots, makeSnap(time, procs, "", nil, "✗ 死锁:所有未完成进程都在等待"))
return SimResult{Snapshots: snapshots, Algorithm: "基于优先级的时间片轮转", AvgTurnaround: calcAvgTurnaround(procs), Deadlock: true}
}
snapshots = append(snapshots, makeSnap(time, procs, "", nil, "无就绪进程,等待唤醒..."))
time++
continue
}
snapshots = append(snapshots, makeSnap(time, procs, "", nil, "无就绪进程"))
time++
continue
}
sort.Slice(readyQueue, func(i, j int) bool {
if readyQueue[i].Priority != readyQueue[j].Priority {
return readyQueue[i].Priority < readyQueue[j].Priority
}
return readyQueue[i].ArrivalTime < readyQueue[j].ArrivalTime
})
selected := readyQueue[0]
readyQueue = readyQueue[1:]
selected.State = StateExecute
selected.SliceUsedTime = 0
if selected.StartTime == -1 {
selected.StartTime = time
}
running = selected
}
queueNames := buildQueueNames(readyQueue)
event := running.Name + " 执行 (优先级=" + itoa(running.Priority) + ", 剩余=" + itoa(running.Remaining()) + ")"
snapshots = append(snapshots, makeSnap(time, procs, running.Name, queueNames, event))
running.UsedTime++
running.SliceUsedTime++
if running.UsedTime >= running.NeedTime {
running.State = StateFinish
running.FinishTime = time + 1
snapshots = append(snapshots, makeSnap(time+1, procs, "", nil, running.Name+" 执行完毕 ✓"))
} else if running.SliceUsedTime >= timeSlice {
running.Priority += 3
if running.UsedTime%3 == 0 && running.UsedTime < running.NeedTime {
running.State = StateWait
running.WaitUntil = time + 2
running.SliceUsedTime = 0
snapshots = append(snapshots, makeSnap(time+1, procs, "", nil,
running.Name+" 等待I/O (优先级→"+itoa(running.Priority)+", 唤醒于t="+itoa(running.WaitUntil)+")"))
} else {
running.State = StateReady
running.SliceUsedTime = 0
readyQueue = append(readyQueue, running)
snapshots = append(snapshots, makeSnap(time+1, procs, "", buildQueueNames(readyQueue),
running.Name+" 时间片用完 (优先级→"+itoa(running.Priority)+")"))
}
} else {
queueNames2 := buildQueueNames(readyQueue)
snapshots = append(snapshots, makeSnap(time+1, procs, running.Name, queueNames2, running.Name+" 继续执行"))
}
time++
}
return SimResult{
Snapshots: snapshots,
Algorithm: "基于优先级的时间片轮转",
AvgTurnaround: calcAvgTurnaround(procs),
Deadlock: false,
}
}
func SimulateMLFQ(processes []*PCB, queueTimeSlices [3]int) SimResult {
procs := make([]*PCB, len(processes))
for i, p := range processes {
c := *p
c.QueueLevel = 0
c.SliceUsedTime = 0
procs[i] = &c
}
var snapshots []Snapshot
queues := [3][]*PCB{}
time := 0
maxTime := 0
for _, p := range procs {
end := p.ArrivalTime + p.NeedTime*3 + 20
if end > maxTime {
maxTime = end
}
}
for time <= maxTime {
for _, p := range procs {
if p.State == StateWait && p.WaitUntil <= time {
p.State = StateReady
p.WaitUntil = -1
queues[p.QueueLevel] = append(queues[p.QueueLevel], p)
}
if p.ArrivalTime == time && p.State == StateReady {
p.QueueLevel = 0
p.SliceUsedTime = 0
found := false
for _, q := range queues[0] {
if q == p {
found = true
break
}
}
if !found {
queues[0] = append(queues[0], p)
}
}
}
running := findRunning(procs)
if running != nil {
preempted := false
for q := 0; q < 3; q++ {
sort.Slice(queues[q], func(i, j int) bool {
return queues[q][i].Priority < queues[q][j].Priority
})
if len(queues[q]) > 0 && (q < running.QueueLevel || (q == running.QueueLevel && queues[q][0].Priority < running.Priority)) {
running.State = StateReady
queues[running.QueueLevel] = insertSortedByPriority(queues[running.QueueLevel], running)
running = nil
preempted = true
break
}
}
_ = preempted
}
if running == nil {
selected := selectFromMLFQ(queues)
if selected == nil {
if allFinished(procs) {
snapshots = append(snapshots, makeMLFQSnap(time, procs, "", queues, "✓ 所有进程执行完毕"))
break
}
if allWaitingOrFinished(procs) {
if !anyWillWake(procs, time) {
snapshots = append(snapshots, makeMLFQSnap(time, procs, "", queues, "✗ 死锁:所有未完成进程都在等待"))
return SimResult{Snapshots: snapshots, Algorithm: "多级反馈队列轮转", AvgTurnaround: calcAvgTurnaround(procs), Deadlock: true}
}
snapshots = append(snapshots, makeMLFQSnap(time, procs, "", queues, "无就绪进程,等待唤醒..."))
time++
continue
}
snapshots = append(snapshots, makeMLFQSnap(time, procs, "", queues, "无就绪进程"))
time++
continue
}
removeFromQueue(&queues, selected)
selected.State = StateExecute
if selected.StartTime == -1 {
selected.StartTime = time
}
running = selected
}
event := running.Name + " 从Q" + itoa(running.QueueLevel) + "执行 (片内已用=" + itoa(running.SliceUsedTime) + "/" + itoa(queueTimeSlices[running.QueueLevel]) + ")"
snapshots = append(snapshots, makeMLFQSnap(time, procs, running.Name, queues, event))
running.UsedTime++
running.SliceUsedTime++
qLevel := running.QueueLevel
ts := queueTimeSlices[qLevel]
if running.UsedTime >= running.NeedTime {
running.State = StateFinish
running.FinishTime = time + 1
snapshots = append(snapshots, makeMLFQSnap(time+1, procs, "", queues, running.Name+" 执行完毕 ✓"))
} else if running.SliceUsedTime >= ts {
shouldWait := running.UsedTime%3 == 0 && running.UsedTime < running.NeedTime
if shouldWait {
running.State = StateWait
running.WaitUntil = time + 2
running.SliceUsedTime = 0
snapshots = append(snapshots, makeMLFQSnap(time+1, procs, "", queues,
running.Name+" 等待I/O (唤醒于t="+itoa(running.WaitUntil)+")"))
} else if qLevel < 2 {
running.QueueLevel = qLevel + 1
running.State = StateReady
running.SliceUsedTime = 0
queues[qLevel+1] = append(queues[qLevel+1], running)
snapshots = append(snapshots, makeMLFQSnap(time+1, procs, "", queues,
running.Name+" 降级→Q"+itoa(qLevel+1)))
} else {
running.State = StateReady
running.SliceUsedTime = 0
queues[2] = append(queues[2], running)
snapshots = append(snapshots, makeMLFQSnap(time+1, procs, "", queues,
running.Name+" 重新加入Q2"))
}
} else {
snapshots = append(snapshots, makeMLFQSnap(time+1, procs, running.Name, queues, running.Name+" 继续执行"))
}
time++
}
return SimResult{
Snapshots: snapshots,
Algorithm: "多级反馈队列轮转",
AvgTurnaround: calcAvgTurnaround(procs),
Deadlock: false,
}
}
func selectFromMLFQ(queues [3][]*PCB) *PCB {
for q := 0; q < 3; q++ {
sort.Slice(queues[q], func(i, j int) bool {
if queues[q][i].Priority != queues[q][j].Priority {
return queues[q][i].Priority < queues[q][j].Priority
}
return queues[q][i].ArrivalTime < queues[q][j].ArrivalTime
})
if len(queues[q]) > 0 {
return queues[q][0]
}
}
return nil
}
func removeFromQueue(queues *[3][]*PCB, target *PCB) {
for q := 0; q < 3; q++ {
for i, p := range queues[q] {
if p == target {
queues[q] = append(queues[q][:i], queues[q][i+1:]...)
return
}
}
}
}
func insertSortedByPriority(queue []*PCB, p *PCB) []*PCB {
queue = append(queue, p)
sort.Slice(queue, func(i, j int) bool {
if queue[i].Priority != queue[j].Priority {
return queue[i].Priority < queue[j].Priority
}
return queue[i].ArrivalTime < queue[j].ArrivalTime
})
return queue
}
func buildQueueNames(queue []*PCB) [][]string {
var names []string
for _, p := range queue {
names = append(names, p.Name)
}
if names == nil {
names = []string{}
}
return [][]string{names}
}
func buildMLFQQueueNames(queues [3][]*PCB) [][]string {
var qs [][]string
for q := 0; q < 3; q++ {
var names []string
for _, p := range queues[q] {
names = append(names, p.Name)
}
if names == nil {
names = []string{}
}
qs = append(qs, names)
}
return qs
}
func makeMLFQSnap(time int, procs []*PCB, running string, queues [3][]*PCB, event string) Snapshot {
return makeSnap(time, procs, running, buildMLFQQueueNames(queues), event)
}
func itoa(n int) string {
if n == 0 {
return "0"
}
neg := false
if n < 0 {
neg = true
n = -n
}
s := ""
for n > 0 {
s = string(rune('0'+n%10)) + s
n /= 10
}
if neg {
s = "-" + s
}
return s
}
func minInt(a, b int) int {
if a < b {
return a
}
return b
}
+604
View File
@@ -0,0 +1,604 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>进程调度模拟器</title>
<style>
:root {
--bg: #0f172a; --card: #1e293b; --border: #334155;
--text: #e2e8f0; --muted: #94a3b8; --accent: #38bdf8;
--green: #4ade80; --yellow: #fbbf24; --red: #f87171;
--purple: #a78bfa; --orange: #fb923c;
}
* { margin:0; padding:0; box-sizing:border-box; }
body { font-family:'Segoe UI',system-ui,sans-serif; background:var(--bg); color:var(--text); min-height:100vh; }
.app { max-width:1400px; margin:0 auto; padding:16px; }
h1 { text-align:center; font-size:1.6rem; margin-bottom:12px; background:linear-gradient(135deg,var(--accent),var(--purple)); -webkit-background-clip:text; -webkit-text-fill-color:transparent; }
.tabs { display:flex; gap:4px; margin-bottom:16px; }
.tab { padding:8px 20px; border-radius:8px 8px 0 0; border:1px solid var(--border); border-bottom:none; background:var(--card); color:var(--muted); cursor:pointer; font-size:.9rem; transition:.2s; }
.tab.active { background:var(--bg); color:var(--accent); border-color:var(--accent); }
.panel { display:none; }
.panel.active { display:block; }
.card { background:var(--card); border:1px solid var(--border); border-radius:10px; padding:16px; margin-bottom:12px; }
.card h3 { font-size:.95rem; color:var(--accent); margin-bottom:10px; }
.form-row { display:flex; gap:10px; flex-wrap:wrap; align-items:end; margin-bottom:10px; }
.form-group { display:flex; flex-direction:column; gap:3px; }
.form-group label { font-size:.8rem; color:var(--muted); }
.form-group input, .form-group select { background:var(--bg); border:1px solid var(--border); color:var(--text); padding:6px 10px; border-radius:6px; font-size:.85rem; width:100px; }
.btn { padding:7px 18px; border:none; border-radius:6px; cursor:pointer; font-size:.85rem; font-weight:600; transition:.2s; }
.btn-primary { background:var(--accent); color:#000; }
.btn-primary:hover { filter:brightness(1.15); }
.btn-success { background:var(--green); color:#000; }
.btn-warn { background:var(--yellow); color:#000; }
.btn-danger { background:var(--red); color:#000; }
.btn-sm { padding:4px 12px; font-size:.8rem; }
table { width:100%; border-collapse:collapse; font-size:.82rem; }
th,td { padding:6px 8px; text-align:center; border-bottom:1px solid var(--border); }
th { color:var(--accent); font-weight:600; position:sticky; top:0; background:var(--card); }
.process-table { max-height:200px; overflow-y:auto; }
.badge { display:inline-block; padding:2px 8px; border-radius:10px; font-size:.75rem; font-weight:700; }
.badge-E { background:var(--green); color:#000; }
.badge-R { background:var(--accent); color:#000; }
.badge-W { background:var(--yellow); color:#000; }
.badge-F { background:var(--muted); color:#000; }
.controls { display:flex; gap:8px; align-items:center; flex-wrap:wrap; margin-bottom:12px; }
.speed-ctrl { display:flex; align-items:center; gap:6px; font-size:.85rem; color:var(--muted); }
.speed-ctrl input[type=range] { width:100px; }
.event-log { font-size:.85rem; color:var(--yellow); min-height:24px; padding:6px 0; }
.queues-viz { display:flex; gap:16px; flex-wrap:wrap; }
.queue-col { flex:1; min-width:200px; }
.queue-col h4 { font-size:.82rem; color:var(--purple); margin-bottom:6px; }
.queue-items { min-height:40px; background:rgba(0,0,0,.2); border-radius:8px; padding:8px; display:flex; flex-wrap:wrap; gap:6px; align-items:center; }
.queue-item { padding:4px 10px; border-radius:6px; font-size:.8rem; font-weight:600; color:#000; animation:slideIn .3s ease; }
.running-box { text-align:center; padding:12px; margin-bottom:12px; }
.running-name { font-size:1.3rem; font-weight:700; color:var(--green); animation:pulse 1s infinite; }
.running-info { font-size:.85rem; color:var(--muted); margin-top:4px; }
.gantt-row { display:flex; align-items:center; margin-bottom:4px; }
.gantt-label { width:50px; font-size:.78rem; color:var(--muted); text-align:right; padding-right:6px; flex-shrink:0; }
.gantt-bar { display:flex; height:22px; border-radius:4px; overflow:hidden; }
.gantt-cell { height:100%; display:flex; align-items:center; justify-content:center; font-size:.7rem; color:#000; font-weight:600; transition:width .3s; min-width:20px; }
.result-table { margin-top:10px; }
.avg { font-size:.9rem; color:var(--green); margin-top:8px; font-weight:600; }
.deadlock-msg { color:var(--red); font-weight:700; font-size:1rem; }
.compare-card { display:flex; gap:20px; flex-wrap:wrap; }
.compare-card > div { flex:1; min-width:250px; }
.color-P0 { background:#38bdf8; } .color-P1 { background:#a78bfa; } .color-P2 { background:#fb923c; }
.color-P3 { background:#4ade80; } .color-P4 { background:#f87171; } .color-P5 { background:#fbbf24; }
.color-P6 { background:#2dd4bf; } .color-P7 { background:#e879f9; } .color-P8 { background:#f472b6; }
.color-P9 { background:#818cf8; } .color-idle { background:#475569; }
@keyframes slideIn { from { opacity:0; transform:translateY(-8px); } to { opacity:1; transform:translateY(0); } }
@keyframes pulse { 0%,100% { opacity:1; } 50% { opacity:.6; } }
.step-info { font-size:.85rem; color:var(--muted); margin-left:12px; }
.grid-2 { display:grid; grid-template-columns:1fr 1fr; gap:12px; }
@media(max-width:900px) { .grid-2 { grid-template-columns:1fr; } }
.progress-bar { width:100%; height:6px; background:var(--border); border-radius:3px; margin-top:4px; }
.progress-fill { height:100%; border-radius:3px; background:var(--green); transition:width .3s; }
</style>
</head>
<body>
<div class="app">
<h1>进程调度模拟器</h1>
<div class="tabs">
<div class="tab active" data-tab="setup">参数设置</div>
<div class="tab" data-tab="prr">优先级时间片轮转</div>
<div class="tab" data-tab="mlfq">多级反馈队列轮转</div>
<div class="tab" data-tab="compare">算法对比</div>
</div>
<div class="panel active" id="panel-setup">
<div class="card">
<h3>进程参数</h3>
<div class="form-row">
<div class="form-group"><label>调度算法</label>
<select id="sel-algo"><option value="rr">优先级时间片轮转</option><option value="mlfq">多级反馈队列轮转</option><option value="both">两种算法对比</option></select>
</div>
<div class="form-group"><label>时间片大小</label><input type="number" id="inp-ts" value="2" min="1" max="20"></div>
<div class="form-group"><label>进程数量</label><input type="number" id="inp-count" value="5" min="2" max="10"></div>
<button class="btn btn-primary" onclick="genTable()">生成进程表</button>
</div>
</div>
<div class="card">
<h3>进程信息表</h3>
<div class="process-table">
<table><thead><tr><th>进程名</th><th>优先级(1-100)</th><th>到达时间</th><th>运行时间</th></tr></thead>
<tbody id="proc-tbody"></tbody></table>
</div>
<div style="margin-top:10px;display:flex;gap:8px">
<button class="btn btn-success" onclick="runSim()">开始模拟</button>
<button class="btn btn-warn" onclick="fillDefault()">填入默认值</button>
</div>
</div>
</div>
<div class="panel" id="panel-prr">
<div class="card">
<h3>优先级时间片轮转 - 动态调度过程</h3>
<div class="controls">
<button class="btn btn-primary btn-sm" onclick="playPause('prr')">▶ 播放</button>
<button class="btn btn-warn btn-sm" onclick="stepBack('prr')">◀</button>
<button class="btn btn-success btn-sm" onclick="stepFwd('prr')">▶|</button>
<button class="btn btn-danger btn-sm" onclick="resetAnim('prr')">⟲ 重置</button>
<div class="speed-ctrl"><label>速度:</label><input type="range" id="speed-prr" min="1" max="10" value="5"><span id="speedVal-prr">5</span></div>
<span class="step-info" id="stepinfo-prr">步骤 0/0</span>
</div>
<div class="event-log" id="event-prr">等待模拟开始...</div>
</div>
<div class="grid-2">
<div>
<div class="card">
<h3>当前执行</h3>
<div class="running-box" id="running-prr">-</div>
</div>
<div class="card">
<h3>就绪队列</h3>
<div class="queue-items" id="queue-prr">-</div>
</div>
</div>
<div class="card">
<h3>进程状态表</h3>
<table><thead><tr><th>进程</th><th>优先级</th><th>状态</th><th>已用</th><th>剩余</th><th>进度</th></tr></thead>
<tbody id="ptable-prr"></tbody></table>
</div>
</div>
<div class="card">
<h3>甘特图</h3>
<div id="gantt-prr"></div>
</div>
<div class="card">
<h3>调度结果</h3>
<div id="result-prr"></div>
</div>
</div>
<div class="panel" id="panel-mlfq">
<div class="card">
<h3>多级反馈队列轮转 - 动态调度过程</h3>
<div class="controls">
<button class="btn btn-primary btn-sm" onclick="playPause('mlfq')">▶ 播放</button>
<button class="btn btn-warn btn-sm" onclick="stepBack('mlfq')">◀</button>
<button class="btn btn-success btn-sm" onclick="stepFwd('mlfq')">▶|</button>
<button class="btn btn-danger btn-sm" onclick="resetAnim('mlfq')">⟲ 重置</button>
<div class="speed-ctrl"><label>速度:</label><input type="range" id="speed-mlfq" min="1" max="10" value="5"><span id="speedVal-mlfq">5</span></div>
<span class="step-info" id="stepinfo-mlfq">步骤 0/0</span>
</div>
<div class="event-log" id="event-mlfq">等待模拟开始...</div>
</div>
<div class="grid-2">
<div>
<div class="card">
<h3>当前执行</h3>
<div class="running-box" id="running-mlfq">-</div>
</div>
<div class="card">
<h3>就绪队列 (Q0 / Q1 / Q2)</h3>
<div class="queues-viz" id="queues-mlfq">
<div class="queue-col"><h4>Q0 (最高优先)</h4><div class="queue-items" id="q0-mlfq">-</div></div>
<div class="queue-col"><h4>Q1</h4><div class="queue-items" id="q1-mlfq">-</div></div>
<div class="queue-col"><h4>Q2 (最低优先)</h4><div class="queue-items" id="q2-mlfq">-</div></div>
</div>
</div>
</div>
<div class="card">
<h3>进程状态表</h3>
<table><thead><tr><th>进程</th><th>优先级</th><th>队列</th><th>状态</th><th>已用</th><th>剩余</th><th>进度</th></tr></thead>
<tbody id="ptable-mlfq"></tbody></table>
</div>
</div>
<div class="card">
<h3>甘特图</h3>
<div id="gantt-mlfq"></div>
</div>
<div class="card">
<h3>调度结果</h3>
<div id="result-mlfq"></div>
</div>
</div>
<div class="panel" id="panel-compare">
<div class="card">
<h3>算法性能对比</h3>
<div id="compare-content" style="color:var(--muted)">请先运行模拟(选择"两种算法对比")</div>
</div>
</div>
</div>
<script>
const state = {
prr: { data:null, step:0, timer:null, ganttCells:[] },
mlfq: { data:null, step:0, timer:null, ganttCells:[] },
procColors: {}
};
const COLORS = ['color-P0','color-P1','color-P2','color-P3','color-P4','color-P5','color-P6','color-P7','color-P8','color-P9'];
function getGanttColor(name, idx) {
if (!name || name === '') return 'color-idle';
return COLORS[idx % COLORS.length];
}
document.querySelectorAll('.tab').forEach(t => {
t.onclick = () => {
document.querySelectorAll('.tab').forEach(x=>x.classList.remove('active'));
document.querySelectorAll('.panel').forEach(x=>x.classList.remove('active'));
t.classList.add('active');
document.getElementById('panel-'+t.dataset.tab).classList.add('active');
};
});
document.getElementById('speed-prr').oninput = function(){ document.getElementById('speedVal-prr').textContent=this.value; };
document.getElementById('speed-mlfq').oninput = function(){ document.getElementById('speedVal-mlfq').textContent=this.value; };
function genTable() {
const n = parseInt(document.getElementById('inp-count').value) || 5;
const tbody = document.getElementById('proc-tbody');
tbody.innerHTML = '';
for (let i=0; i<n; i++) {
const tr = document.createElement('tr');
tr.innerHTML = `<td>P${i}</td><td><input type="number" class="prio" value="${Math.floor(Math.random()*50)+1}" min="1" max="100" style="width:70px"></td><td><input type="number" class="arr" value="${i}" min="0" style="width:70px"></td><td><input type="number" class="need" value="${Math.floor(Math.random()*6)+2}" min="1" style="width:70px"></td>`;
tbody.appendChild(tr);
}
}
function fillDefault() {
const defaults = [
{name:'P0',prio:3,arr:0,need:5},
{name:'P1',prio:8,arr:1,need:3},
{name:'P2',prio:5,arr:2,need:6},
{name:'P3',prio:12,arr:3,need:4},
{name:'P4',prio:20,arr:4,need:2},
];
document.getElementById('inp-count').value = 5;
const tbody = document.getElementById('proc-tbody');
tbody.innerHTML = '';
defaults.forEach(d => {
const tr = document.createElement('tr');
tr.innerHTML = `<td>${d.name}</td><td><input type="number" class="prio" value="${d.prio}" min="1" max="100" style="width:70px"></td><td><input type="number" class="arr" value="${d.arr}" min="0" style="width:70px"></td><td><input type="number" class="need" value="${d.need}" min="1" style="width:70px"></td>`;
tbody.appendChild(tr);
});
}
function getProcs() {
const rows = document.querySelectorAll('#proc-tbody tr');
const procs = [];
rows.forEach((tr,i) => {
const name = tr.cells[0].textContent;
const prio = parseInt(tr.querySelector('.prio').value)||50;
const arr = parseInt(tr.querySelector('.arr').value)||0;
const need = parseInt(tr.querySelector('.need').value)||3;
procs.push({name,priority:Math.min(100,Math.max(1,prio)),arrivalTime:Math.max(0,arr),needTime:Math.max(1,need)});
});
return procs;
}
async function runSim() {
const algo = document.getElementById('sel-algo').value;
const ts = parseInt(document.getElementById('inp-ts').value)||2;
const procs = getProcs();
if (procs.length < 2) { alert('至少需要2个进程'); return; }
procs.forEach((p,i) => state.procColors[p.name] = COLORS[i % COLORS.length]);
if (algo === 'rr' || algo === 'both') {
const res = await doSim('rr', ts, procs);
state.prr.data = res;
state.prr.step = 0;
state.prr.ganttCells = [];
buildGantt('prr', res);
renderStep('prr');
switchTab('prr');
}
if (algo === 'mlfq' || algo === 'both') {
const res = await doSim('mlfq', ts, procs);
state.mlfq.data = res;
state.mlfq.step = 0;
state.mlfq.ganttCells = [];
buildGantt('mlfq', res);
renderStep('mlfq');
if (algo === 'mlfq') switchTab('mlfq');
}
if (algo === 'both') {
buildCompare();
setTimeout(()=>switchTab('compare'), 100);
}
}
async function doSim(algo, ts, procs) {
const resp = await fetch('/api/simulate', {
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({algorithm:algo, timeSlice:ts, processes:procs})
});
return resp.json();
}
function switchTab(tab) {
document.querySelectorAll('.tab').forEach(t => {
t.classList.toggle('active', t.dataset.tab === tab);
});
document.querySelectorAll('.panel').forEach(p => {
p.classList.toggle('active', p.id === 'panel-'+tab);
});
}
function renderStep(key) {
const s = state[key];
if (!s.data) return;
const snap = s.data.snapshots[s.step];
if (!snap) return;
document.getElementById('stepinfo-'+key).textContent = `步骤 ${s.step}/${s.data.snapshots.length-1} | 时间 t=${snap.time}`;
document.getElementById('event-'+key).textContent = snap.event || '';
const runningEl = document.getElementById('running-'+key);
if (snap.running) {
const rp = snap.processes.find(p=>p.name===snap.running);
runningEl.innerHTML = `<div class="running-name" style="color:var(--green)">${rp.name}</div><div class="running-info">优先级=${rp.priority} | 已用=${rp.usedTime} | 剩余=${rp.needTime-rp.usedTime}${key==='mlfq'?' | 队列=Q'+rp.queueLevel:''}</div>`;
} else {
runningEl.innerHTML = `<div style="color:var(--muted)">无进程执行</div>`;
}
if (key === 'prr') {
const qEl = document.getElementById('queue-prr');
const q = snap.queues && snap.queues[0] ? snap.queues[0] : [];
qEl.innerHTML = q.length ? q.map(n=>`<span class="queue-item ${state.procColors[n]||''}">${n}</span>`).join('') : '<span style="color:var(--muted)">空</span>';
} else {
for (let qi=0; qi<3; qi++) {
const el = document.getElementById('q'+qi+'-mlfq');
const q = snap.queues && snap.queues[qi] ? snap.queues[qi] : [];
el.innerHTML = q.length ? q.map(n=>`<span class="queue-item ${state.procColors[n]||''}">${n}</span>`).join('') : '<span style="color:var(--muted)">空</span>';
}
}
const tbody = document.getElementById('ptable-'+key);
tbody.innerHTML = '';
snap.processes.forEach(p => {
const rem = p.needTime - p.usedTime;
const pct = Math.round(p.usedTime/p.needTime*100);
const colorCls = state.procColors[p.name]||'';
const extra = key==='mlfq' ? `<td>Q${p.queueLevel}</td>` : '';
tbody.innerHTML += `<tr>
<td><span class="queue-item ${colorCls}" style="font-size:.78rem">${p.name}</span></td>
<td>${p.priority}</td>${extra}
<td><span class="badge badge-${p.state}">${p.state}</span></td>
<td>${p.usedTime}</td>
<td>${rem}</td>
<td><div class="progress-bar"><div class="progress-fill" style="width:${pct}%"></div></div></td>
</tr>`;
});
updateGantt(key, s.step);
if (s.step === s.data.snapshots.length - 1) {
showResult(key, s.data);
}
}
function buildGantt(key, data) {
const el = document.getElementById('gantt-'+key);
el.innerHTML = '';
const names = data.snapshots[0].processes.map(p=>p.name);
const maxTime = data.snapshots[data.snapshots.length-1].time;
state[key].ganttCells = {};
names.forEach((name,idx) => {
const row = document.createElement('div');
row.className = 'gantt-row';
row.innerHTML = `<div class="gantt-label">${name}</div><div class="gantt-bar" id="gbar-${key}-${idx}"></div>`;
el.appendChild(row);
for (let t=0; t<maxTime; t++) {
const cell = document.createElement('div');
cell.className = 'gantt-cell color-idle';
cell.style.width = Math.max(20, 800/maxTime) + 'px';
cell.dataset.time = t;
cell.textContent = '';
row.querySelector('.gantt-bar').appendChild(cell);
if (!state[key].ganttCells[t]) state[key].ganttCells[t] = {};
state[key].ganttCells[t][name] = cell;
}
});
const idleRow = document.createElement('div');
idleRow.className = 'gantt-row';
idleRow.innerHTML = `<div class="gantt-label" style="color:var(--muted)">CPU</div><div class="gantt-bar" id="gbar-${key}-cpu"></div>`;
el.appendChild(idleRow);
for (let t=0; t<maxTime; t++) {
const cell = document.createElement('div');
cell.className = 'gantt-cell color-idle';
cell.style.width = Math.max(20, 800/maxTime) + 'px';
cell.dataset.time = t;
cell.textContent = '·';
idleRow.querySelector('.gantt-bar').appendChild(cell);
if (!state[key].ganttCells[t]) state[key].ganttCells[t] = {};
state[key].ganttCells[t]['__cpu'] = cell;
}
}
function updateGantt(key, step) {
const s = state[key];
const data = s.data;
if (!s.ganttCells || !data) return;
for (let t=0; t<=step && t<data.snapshots.length; t++) {
const snap = data.snapshots[t];
snap.processes.forEach((p,idx) => {
const cell = s.ganttCells[t] && s.ganttCells[t][p.name];
if (!cell) return;
cell.className = 'gantt-cell ' + (state.procColors[p.name]||'');
if (p.state === 'E' && snap.running === p.name) {
cell.textContent = '▶';
} else if (p.state === 'F') {
cell.textContent = '✓';
} else if (p.state === 'W') {
cell.textContent = 'W';
cell.style.opacity = '0.4';
} else if (p.state === 'R') {
cell.textContent = '·';
cell.style.opacity = '0.3';
}
});
const cpuCell = s.ganttCells[t] && s.ganttCells[t]['__cpu'];
if (cpuCell) {
if (snap.running) {
cpuCell.className = 'gantt-cell ' + (state.procColors[snap.running]||'');
cpuCell.textContent = snap.running;
cpuCell.style.opacity = '1';
} else {
cpuCell.className = 'gantt-cell color-idle';
cpuCell.textContent = '-';
cpuCell.style.opacity = '0.3';
}
}
}
}
function showResult(key, data) {
const el = document.getElementById('result-'+key);
const lastSnap = data.snapshots[data.snapshots.length-1];
const procs = lastSnap.processes;
let html = '';
if (data.deadlock) {
html += '<div class="deadlock-msg">检测到死锁!</div>';
}
html += '<table class="result-table"><thead><tr><th>进程</th><th>到达</th><th>开始</th><th>结束</th><th>周转时间</th></tr></thead><tbody>';
procs.forEach(p => {
if (p.finishTime > 0) {
const turn = p.finishTime - p.arrivalTime;
html += `<tr><td>${p.name}</td><td>${p.arrivalTime}</td><td>${p.startTime}</td><td>${p.finishTime}</td><td>${turn}</td></tr>`;
} else {
html += `<tr><td>${p.name}</td><td>${p.arrivalTime}</td><td>${p.startTime>=0?p.startTime:'-'}</td><td>未完成</td><td>-</td></tr>`;
}
});
html += '</tbody></table>';
html += `<div class="avg">平均周转时间: ${data.avgTurnaround.toFixed(2)}</div>`;
el.innerHTML = html;
}
function playPause(key) {
const s = state[key];
if (s.timer) {
clearInterval(s.timer);
s.timer = null;
event.target.textContent = '▶ 播放';
} else {
const speed = parseInt(document.getElementById('speed-'+key).value);
const interval = Math.max(50, 1100 - speed*100);
s.timer = setInterval(() => {
if (s.step < s.data.snapshots.length-1) {
s.step++;
renderStep(key);
} else {
clearInterval(s.timer);
s.timer = null;
}
}, interval);
event.target.textContent = '⏸ 暂停';
}
}
function stepFwd(key) {
const s = state[key];
if (s.data && s.step < s.data.snapshots.length-1) {
s.step++;
renderStep(key);
}
}
function stepBack(key) {
const s = state[key];
if (s.data && s.step > 0) {
s.step--;
resetGanttToStep(key, s.step);
renderStep(key);
}
}
function resetAnim(key) {
const s = state[key];
if (s.timer) { clearInterval(s.timer); s.timer = null; }
s.step = 0;
if (s.data) {
state[key].ganttCells = {};
buildGantt(key, s.data);
renderStep(key);
}
}
function resetGanttToStep(key, step) {
const s = state[key];
state[key].ganttCells = {};
buildGantt(key, s.data);
updateGantt(key, step);
}
function buildCompare() {
const el = document.getElementById('compare-content');
const d1 = state.prr.data, d2 = state.mlfq.data;
if (!d1 || !d2) { el.innerHTML='请先运行两种算法'; return; }
const p1 = d1.snapshots[d1.snapshots.length-1].processes;
const p2 = d2.snapshots[d2.snapshots.length-1].processes;
let html = '<div class="compare-card"><div>';
html += '<h3 style="color:var(--accent);margin-bottom:8px">基于优先级的时间片轮转</h3>';
html += '<table><thead><tr><th>进程</th><th>周转时间</th></tr></thead><tbody>';
p1.forEach(p => {
const t = p.finishTime>0 ? p.finishTime-p.arrivalTime : '-';
html += `<tr><td>${p.name}</td><td>${t}</td></tr>`;
});
html += `</tbody></table><div class="avg">平均周转时间: ${d1.avgTurnaround.toFixed(2)}</div>`;
html += '</div><div>';
html += '<h3 style="color:var(--purple);margin-bottom:8px">多级反馈队列轮转</h3>';
html += '<table><thead><tr><th>进程</th><th>周转时间</th></tr></thead><tbody>';
p2.forEach(p => {
const t = p.finishTime>0 ? p.finishTime-p.arrivalTime : '-';
html += `<tr><td>${p.name}</td><td>${t}</td></tr>`;
});
html += `</tbody></table><div class="avg">平均周转时间: ${d2.avgTurnaround.toFixed(2)}</div>`;
html += '</div></div>';
html += '<div class="card" style="margin-top:12px"><h3>性能分析</h3>';
if (d1.avgTurnaround < d2.avgTurnaround) {
html += `<p>基于优先级的时间片轮转算法平均周转时间更短 (${d1.avgTurnaround.toFixed(2)} vs ${d2.avgTurnaround.toFixed(2)}),在本组数据中性能更优。</p>`;
} else if (d2.avgTurnaround < d1.avgTurnaround) {
html += `<p>多级反馈队列轮转算法平均周转时间更短 (${d2.avgTurnaround.toFixed(2)} vs ${d1.avgTurnaround.toFixed(2)}),在本组数据中性能更优。</p>`;
} else {
html += `<p>两种算法平均周转时间相同 (${d1.avgTurnaround.toFixed(2)})。</p>`;
}
html += '<p style="margin-top:8px;color:var(--muted);font-size:.85rem">优先级时间片轮转每次执行后优先级降低,可能导致长进程饥饿;多级反馈队列根据队列级别分配不同时间片,短进程在高级队列即可完成,长进程逐步降级获得更长的时间片,兼顾了响应时间和吞吐量。</p>';
html += '</div>';
el.innerHTML = html;
}
genTable();
</script>
</body>
</html>