From 5ef9eb1cb5bc0caf66627f100fbe3c4a186d3858 Mon Sep 17 00:00:00 2001 From: SlavaVlad Date: Sun, 14 Jun 2026 02:27:37 +0300 Subject: [PATCH] v1 --- atlas_os/CMC.lua | 195 ++++++++++++++++++++++++------------- atlas_os/IOC.lua | 24 +++-- atlas_os/MMC.lua | 5 + atlas_os/docs/wiring.md | 206 +++++++++++++++++++++------------------- 4 files changed, 254 insertions(+), 176 deletions(-) diff --git a/atlas_os/CMC.lua b/atlas_os/CMC.lua index 2ce4433..aaac0e6 100644 --- a/atlas_os/CMC.lua +++ b/atlas_os/CMC.lua @@ -1,45 +1,58 @@ -- CMC.lua — Command Controller (AtlasOS Shell/CPU v2.0) -- in[1] = cmd_rx ← IOC.out[4] (IN|command) -- in[2] = mem_rx ← MMC.out[1] (FS response) --- out[1] = text_tx → IOC.in[2] (OUT|text) --- out[2] = clear_tx → IOC.in[3] (1 = clear) --- out[3] = color_tx → IOC.in[4] (R,G,B) +-- out[1] = disp_tx → IOC.in[2] (OUT|text / CLR / COL|R,G,B) -- out[4] = mem_tx → MMC.in[1] (FS request) -local state = 0 local ST_IDLE = 0 local ST_WAIT_FS = 1 +local state = ST_IDLE local uid, gid, cwd, curUser = 0, 0, "/", "root" - -local pendingCmd = nil local lastRespSeq = 0 +local outQueue = {} +local pendingCmd = nil + local function parseMsg(msg) local p = msg:find("|") return p and msg:sub(1, p - 1) or msg, p and msg:sub(p + 1) or nil end -local function printOut(t) - out[1] = "OUT|" .. tostring(t) -end - -local function printErr(t) - out[1] = "OUT|" .. tostring(t) -end - -local function doClear() - out[2] = 1 -end - -local function doColor(r, g, b) - out[3] = r .. "," .. g .. "," .. b +local function resolvePath(path) + if not path or path == "" then return cwd end + if path:sub(1, 1) == "/" then return path end + if cwd:sub(-1) == "/" then return cwd .. path end + return cwd .. "/" .. path end local function fsReq(op, arg) - local msg = op - if arg then msg = msg .. "|" .. arg end - out[4] = msg + out[4] = op .. (arg and ("|" .. arg) or "") +end + +-- queue items: op, value (op = "t" text, "c" color, "x" clear) +local function qText(v) + outQueue[#outQueue + 1] = { "t", tostring(v) } +end + +local function qErr(v) + outQueue[#outQueue + 1] = { "c", "255,40,40" } + outQueue[#outQueue + 1] = { "t", tostring(v) } + outQueue[#outQueue + 1] = { "c", "0,255,0" } +end + +local function qClear() + outQueue[#outQueue + 1] = { "x" } +end + +local function qColor(r, g, b) + outQueue[#outQueue + 1] = { "c", r .. "," .. g .. "," .. b } +end + +local function queueLines(lines) + for i = 1, #lines do + qText(lines[i]) + end end local cmds = {} @@ -49,20 +62,26 @@ local function reg(name, fn, desc) end reg("help", function(a) - local t = "AtlasOS v2.0 commands:\n" + local lines = { curUser .. " " .. cwd .. "> help" } + lines[#lines + 1] = "AtlasOS v2.0:" for n, e in pairs(cmds) do - t = t .. " " .. n .. " " .. e.desc .. "\n" + lines[#lines + 1] = " " .. n .. " " .. e.desc end - return t -end, "Show help") + queueLines(lines) +end, "This help") reg("echo", function(a) if #a == 0 then error("echo: missing text") end - return table.concat(a, " ") + local out = "" + for i = 1, #a do + if #out > 0 then out = out .. " " end + out = out .. tostring(a[i]) + end + queueLines({ curUser .. " " .. cwd .. "> echo " .. out, out }) end, "Echo text") reg("clear", function(a) - doClear() + qClear() end, "Clear screen") reg("color", function(a) @@ -72,12 +91,16 @@ reg("color", function(a) r = math.floor(math.max(0, math.min(255, r))) g = math.floor(math.max(0, math.min(255, g))) b = math.floor(math.max(0, math.min(255, b))) - doColor(r, g, b) - printOut("Color set to " .. r .. "," .. g .. "," .. b) + qColor(r, g, b) + queueLines({ curUser .. " " .. cwd .. "> color", + "Color set to " .. r .. "," .. g .. "," .. b }) end, "Set color R G B") reg("status", function(a) - return "AtlasOS v2.0 | User: " .. curUser .. "(" .. uid .. ") | CWD: " .. cwd + pendingCmd = { name = "status", + prompt = curUser .. " " .. cwd .. "> status" } + state = ST_WAIT_FS + fsReq("STATFS", "") end, "System status") reg("ls", function(a) @@ -87,39 +110,46 @@ reg("ls", function(a) if a[i]:sub(1, 1) == "-" then opts[a[i]] = true else - path = a[i] + path = resolvePath(a[i]) end end - pendingCmd = { name = "ls", path = path, opts = opts } + local cmdStr = "ls" + if #a > 0 then cmdStr = "ls " .. table.concat(a, " ") end + pendingCmd = { name = "ls", opts = opts, path = path, + prompt = curUser .. " " .. cwd .. "> " .. cmdStr } state = ST_WAIT_FS fsReq("LS", path) end, "List directory [path]") reg("cat", function(a) - if #a == 0 then error("cat: missing path") end - pendingCmd = { name = "cat", path = a[1] } + if #a == 0 then error("cat: need path") end + local path = resolvePath(a[1]) + pendingCmd = { name = "cat", path = path, + prompt = curUser .. " " .. cwd .. "> cat " .. a[1] } state = ST_WAIT_FS - fsReq("READ", a[1] .. "|0|8192") + fsReq("READ", path .. "|0|8192") end, "Show file content") reg("cd", function(a) - local path = a[1] - if not path then path = "/" end - pendingCmd = { name = "cd", path = path } + local path = a[1] and resolvePath(a[1]) or "/" + pendingCmd = { name = "cd", path = path, + prompt = curUser .. " " .. cwd .. "> cd " .. (a[1] or "") } state = ST_WAIT_FS fsReq("CHDIR", path) end, "Change directory [path]") reg("stat", function(a) - if #a == 0 then error("stat: missing path") end - pendingCmd = { name = "stat", path = a[1] } + if #a == 0 then error("stat: need path") end + local path = resolvePath(a[1]) + pendingCmd = { name = "stat", path = path, + prompt = curUser .. " " .. cwd .. "> stat " .. a[1] } state = ST_WAIT_FS - fsReq("STAT", a[1]) + fsReq("STAT", path) end, "Show file metadata") local function execCmd(cmdStr) local parts = {} - for t in cmdStr:gmatch("%S+") do + for t in cmdStr:gmatch("[^ ]+") do parts[#parts + 1] = t end if #parts == 0 then return end @@ -132,15 +162,15 @@ local function execCmd(cmdStr) local entry = cmds[name] if not entry then - printErr("Unknown: " .. name .. ". Try 'help'.") + qText(curUser .. " " .. cwd .. "> " .. cmdStr) + qErr("Unknown: " .. name .. ". Try 'help'.") return end - local ok, result = pcall(entry.fn, args) + local ok, msg = pcall(entry.fn, args) if not ok then - printErr(tostring(result)) - elseif result ~= nil then - printOut(result) + qText(curUser .. " " .. cwd .. "> " .. cmdStr) + qErr(tostring(msg)) end end @@ -150,41 +180,64 @@ local function handleFSResponse(op, data) if op == "ERR" then local code = data or "EIO" + local m = cmd.path if code == "ENOENT" then - printErr(cmd.path .. ": No such file or directory") + m = m .. ": No such file or directory" elseif code == "EACCES" then - printErr(cmd.path .. ": Permission denied") + m = m .. ": Permission denied" elseif code == "ENOTDIR" then - printErr(cmd.path .. ": Not a directory") + m = m .. ": Not a directory" elseif code == "EISDIR" then - printErr(cmd.path .. ": Is a directory") + m = m .. ": Is a directory" else - printErr(cmd.path .. ": " .. code) + m = m .. ": " .. code end + qText(cmd.prompt) + qErr(m) pendingCmd = nil state = ST_IDLE return end + local lines = { cmd.prompt } + if cmd.name == "ls" then local showAll = (cmd.opts["-la"] or cmd.opts["-a"]) local names = {} for n in data:gmatch("([^|]+)") do names[#names + 1] = n end - local outLines = {} for _, n in ipairs(names) do if n ~= "." and n ~= ".." or showAll then - outLines[#outLines + 1] = n + lines[#lines + 1] = n end end - if #outLines == 0 then outLines[1] = "(empty)" end - printOut(table.concat(outLines, "\n")) + if #lines == 1 then lines[#lines + 1] = "(empty)" end + elseif cmd.name == "cat" then - printOut(data) + if data and #data > 0 then + for line in data:gmatch("[^\n]+") do + lines[#lines + 1] = line + end + else + lines[#lines + 1] = "(empty)" + end + elseif cmd.name == "cd" then cwd = data - printOut(cwd) + lines[#lines + 1] = cwd + + elseif cmd.name == "status" then + local parts = {} + for s in data:gmatch("([^|]+)") do + parts[#parts + 1] = s + end + local total = tonumber(parts[1]) or 8192 + local used = tonumber(parts[2]) or 0 + lines[#lines + 1] = "AtlasOS v2.0 User: " .. curUser .. "(" .. uid .. ")" + lines[#lines + 1] = "CWD: " .. cwd + lines[#lines + 1] = "Mem: " .. (total - used) .. "/" .. total .. " free" + elseif cmd.name == "stat" then local parts = {} for s in data:gmatch("([^|]+)") do @@ -192,15 +245,15 @@ local function handleFSResponse(op, data) end if #parts >= 6 then local typ = parts[2] == "d" and "directory" or "file" - local out = "Inode: " .. parts[1] .. " Type: " .. typ .. "\n" - out = out .. "Mode: " .. parts[3] .. " UID: " .. parts[4] .. " GID: " .. parts[5] .. "\n" - out = out .. "Size: " .. parts[6] .. " Modified: " .. (parts[7] or "?") - printOut(out) + lines[#lines + 1] = "Inode: " .. parts[1] .. " Type: " .. typ + lines[#lines + 1] = "Mode: " .. parts[3] .. " UID: " .. parts[4] .. " GID: " .. parts[5] + lines[#lines + 1] = "Size: " .. parts[6] .. " Modified: " .. (parts[7] or "?") else - printOut(data) + lines[#lines + 1] = data end end + queueLines(lines) pendingCmd = nil state = ST_IDLE end @@ -208,6 +261,18 @@ end inp = {} function upd() + if #outQueue > 0 then + local item = outQueue[1] + table.remove(outQueue, 1) + if item[1] == "t" then + out[1] = "OUT|" .. tostring(item[2]) + elseif item[1] == "c" then + out[1] = "COL|" .. tostring(item[2]) + elseif item[1] == "x" then + out[1] = "CLR" + end + end + if state == ST_IDLE then if inp[1] ~= nil then local op, cmd = parseMsg(tostring(inp[1])) diff --git a/atlas_os/IOC.lua b/atlas_os/IOC.lua index 8fbe347..7c04655 100644 --- a/atlas_os/IOC.lua +++ b/atlas_os/IOC.lua @@ -4,9 +4,7 @@ -- out[3] = display_color → Terminal -- out[4] = cmd_tx → CMC.in[1] (IN|command) -- in[1] = user_input ← Terminal --- in[2] = text_rx ← CMC.out[1] (OUT|text) --- in[3] = clear_rx ← CMC.out[2] (1 = clear) --- in[4] = color_rx ← CMC.out[3] (R,G,B) +-- in[2] = cmd_rx ← CMC.out[1] (OUT|text / CLR / COL|R,G,B) local txActive = false @@ -23,16 +21,16 @@ function upd() if inp[2] ~= nil then local msg = tostring(inp[2]) - local _, arg = msg:match("^(%w+)%|(.+)$") - if arg then out[1] = arg end - end - - if inp[3] ~= nil and tonumber(inp[3]) == 1 then - out[2] = 1 - end - - if inp[4] ~= nil then - out[3] = tostring(inp[4]) + local pipe = msg:find("|") + local op = pipe and msg:sub(1, pipe - 1) or msg + local arg = pipe and msg:sub(pipe + 1) or nil + if op == "OUT" and arg then + out[1] = arg + elseif op == "CLR" then + out[2] = 1 + elseif op == "COL" and arg then + out[3] = arg + end end table.clear(inp) diff --git a/atlas_os/MMC.lua b/atlas_os/MMC.lua index 23b0a1f..ba430e3 100644 --- a/atlas_os/MMC.lua +++ b/atlas_os/MMC.lua @@ -178,6 +178,11 @@ local function processReq(req) end return "OK|" .. seqStr .. table.concat(names, "|") + elseif op == "STATFS" then + local total = sb.seg_size + local used = #fsSerialize() + return "OK|" .. seqStr .. total .. "|" .. used + elseif op == "CHDIR" then local path = rest or "/" local ino = findInodeByPath(path) diff --git a/atlas_os/docs/wiring.md b/atlas_os/docs/wiring.md index aaa0ed8..b13130c 100644 --- a/atlas_os/docs/wiring.md +++ b/atlas_os/docs/wiring.md @@ -10,137 +10,131 @@ | Terminal | 1 | текстовый дисплей | | Memory | 1 | хранилище 8192 символа | -## Полная схема +## Распиновка и соединения + +### IOC — I/O Controller ``` - Терминал (Keyboard/Button) Терминал (Display) - │ text ▲ - ▼ │ text - ┌──────────┐ ┌──────────┐ │ clear - │ │─────────▶ │──────┤ color - │ IOC │ IN|cmd │ CMC │ │ - │ │◀────────│ │◀─────┤ - └──────────┘ OUT|text│ │ 1 │ - ▲ clear=1 │ │◀─────┤ - │ R,G,B └────▲────┘ R,G,B│ - │ │ │ - │ LS|path │ - │ READ|... │ - │ │ │ - │ ┌────▼────┐ │ - │ │ │ │ - │ │ MMC │ │ - │ │ │ │ - │ └──┬──┬───┘ │ - │ │ │ │ - │ data │ │ write(1) │ - │ ▼ ▼ │ - │ ┌────────┐ │ - │ │ Memory │ │ - │ │ 8192 │ │ - │ └───┬────┘ │ - │ │ │ - └──────────────────┴────────────┘ +in[1] ← Terminal (текстовый ввод) +in[2] ← CMC.out[1] (текст "OUT|...") +in[3] ← CMC.out[2] (очистка, 1) +in[4] ← CMC.out[3] (цвет "R,G,B") +out[1] → Terminal (текст на экран) +out[2] → Terminal (очистка) +out[3] → Terminal (цвет "R,G,B") +out[4] → CMC.in[1] (команда "IN|...") ``` -## Соединения проводом +### CMC — Command Controller -### IOC ↔ Терминал +``` +in[1] ← IOC.out[4] (команда "IN|...") +in[2] ← MMC.out[1] (ответ ФС "OK|seq|..." / "ERR|seq|...") +out[1] → IOC.in[2] (текст "OUT|...") +out[2] → IOC.in[3] (очистка, 1) +out[3] → IOC.in[4] (цвет "R,G,B") +out[4] → MMC.in[1] (запрос ФС "OP|...") +``` -| Пин IOC | → / ← | Пин Terminal | Назначение | -|---|---|---|---| -| IOC.out[1] | → | Display.in (text) | текст на экран | -| IOC.out[2] | → | Display.in (clear) | очистка (1) | -| IOC.out[3] | → | Display.in (color) | цвет R,G,B | -| IOC.in[1] | ← | Keyboard.out (text) | ввод пользователя | +### MMC — Memory/FS Controller -### IOC ↔ CMC +``` +in[1] ← CMC.out[4] (запрос ФС "OP|...") +in[3] ← Memory.out[1] (чтение данных) +out[1] → CMC.in[2] (ответ ФС "OK|seq|..." / "ERR|seq|...") +out[5] → Memory.in[1] (запись данных) +out[6] → Memory.in[2] (сигнал записи, 1) +``` -| Пин IOC | → / ← | Пин CMC | Назначение | -|---|---|---|---| -| IOC.out[4] | → | CMC.in[1] | команда `IN|cmd` | -| CMC.out[1] | → | IOC.in[2] | текст `OUT|text` | -| CMC.out[2] | → | IOC.in[3] | очистка (1) | -| CMC.out[3] | → | IOC.in[4] | цвет `R,G,B` | +### Memory -### CMC ↔ MMC +``` +in[1] ← MMC.out[5] (данные для записи) +in[2] ← MMC.out[6] (сигнал записи, 1) +out[1] → MMC.in[3] (хранимое значение) +``` -| Пин CMC | → / ← | Пин MMC | Назначение | -|---|---|---|---| -| CMC.out[4] | → | MMC.in[1] | запрос `OP|args` | -| MMC.out[1] | → | CMC.in[2] | ответ `OK|seq|...` | +## Формат вывода терминала -### MMC ↔ Memory +Терминал: 72 символа × 17 строк. -| Пин MMC | → / ← | Пин Memory | Назначение | -|---|---|---|---| -| MMC.out[5] | → | Memory.in[1] | данные для записи | -| MMC.out[6] | → | Memory.in[2] | сигнал записи (1) | -| Memory.out[1] | → | MMC.in[3] | чтение данных | +Вывод строится очередью — одна строка за кадр. Приглашение ввода: + +``` +root /path$ команда_пользователя +<результат команды> +``` + +Ошибки отображаются красным цветом. Свободная память — `status`. ## Протокол шины ### IOC → CMC (CMD) ``` -IN|команда +IN|команда_и_аргументы ``` -### CMC → IOC (CMD) +Пример: `IN|ls /home`, `IN|cat readme.txt` -``` -OUT|текст — вывести текст -1 на out[2] — очистить экран -R,G,B на out[3] — установить цвет -``` +### CMC → IOC (три отдельных пина) -### CMC → MMC (MEM) +out[1] — текст: `OUT|текст` +out[2] — очистка: `1` +out[3] — цвет: `R,G,B` -``` -STAT|путь — метаданные -READ|путь|смещение|длина — чтение файла -LS|путь — список каталога -CHDIR|путь — смена директории (проверка) -WRITE|путь|смещение|данные — запись (пока EROFS) -``` +Контроллер IOC разбирает эти три пина независимо. -### MMC → CMC (MEM) +### CMC → MMC (MEM — запросы) -``` -OK|seq|данные -ERR|seq|код_ошибки -``` +| Опкод | Формат | Описание | +|---|---|---| +| `LS` | `LS|путь` | список каталога | +| `READ` | `READ|путь|смещение|длина` | чтение файла | +| `STAT` | `STAT|путь` | метаданные inode | +| `CHDIR` | `CHDIR|путь` | смена рабочего каталога (проверка `x`) | +| `STATFS` | `STATFS` | статистика памяти: `total\|used` | +| `WRITE` | `WRITE|путь|...` | запись (пока readonly — EROFS) | -Коды ошибок: `ENOENT`, `EACCES`, `ENOTDIR`, `EISDIR`, `EROFS`, `EINVAL` +### MMC → CMC (MEM — ответы) + +Успех: `OK|seq|данные` +Ошибка: `ERR|seq|код` + +Коды: `ENOENT`, `EACCES`, `ENOTDIR`, `EISDIR`, `EROFS`, `EINVAL` + +Поле `seq` — монотонный счётчик (позволяет CMC отличить новый ответ от старого). + +## Команды CMC + +| Команда | Аргументы | Описание | +|---|---|---| +| `help` | — | список команд | +| `echo` | текст | вывести текст | +| `clear` | — | очистить экран | +| `color` | R G B | установить цвет (0-255) | +| `status` | — | система + свободная память | +| `ls` | `[-a] [путь]` | список файлов; `-a` включая `.` и `..` | +| `cat` | путь | содержимое файла | +| `cd` | `[путь]` | сменить каталог; без аргумента → `/` | +| `stat` | путь | метаданные файла/каталога | + +Пути могут быть абсолютные (`/home/file.txt`) или относительные (`file.txt` — относительно `cwd`). ## Последовательность запуска -1. Загрузить код MMC.lua в контроллер MMC -2. Загрузить код CMC.lua в контроллер CMC -3. Загрузить код IOC.lua в контроллер IOC -4. Соединить провода по схеме выше -5. При первом запуске MMC инициализирует пустую ФС и запишет в Memory -6. Подать сигнал на Terminal — появится приглашение +1. Загрузить код **MMC.lua** → старт, чтение Memory; если пусто — инициализация ФС по умолчанию +2. Загрузить код **CMC.lua** → старт, ожидание команд +3. Загрузить код **IOC.lua** → старт, ожидание ввода +4. Соединить провода по схеме +5. Ввод с клавиатуры → отобразится приглашение `root />` ## Пример сессии ``` -> ls -home -etc -> ls /home/captain -readme.txt -> cat /home/captain/readme.txt -Welcome to AtlasOS v2.0! -> cd /home/captain -/home/captain -> status -AtlasOS v2.0 | User: root(0) | CWD: /home/captain -> color 0 255 128 -Color set to 0,255,128 -> help -AtlasOS v2.0 commands: - help Show help +root /> help +AtlasOS v2.0: + help This help echo Echo text clear Clear screen color Set color R G B @@ -149,4 +143,20 @@ AtlasOS v2.0 commands: cat Show file content cd Change directory [path] stat Show file metadata +root /> status +AtlasOS v2.0 User: root(0) +CWD: / +Mem: 7900/8192 free +root /> cat /home/captain/readme.txt +Welcome to AtlasOS v2.0! +root /> cd /home/captain +/home/captain +root /home/captain$ ls +readme.txt +root /home/captain$ color 0 255 128 +Color set to 0,255,128 +root /home/captain$ cat nonexistent +nonexistent: No such file or directory (красным) +root /> unknown +Unknown: unknown. Try 'help'. (красным) ```