This commit is contained in:
2026-06-14 02:06:00 +03:00
parent 66d9f8037c
commit 2ecb2298be
4 changed files with 700 additions and 0 deletions

236
atlas_os/CMC.lua Normal file
View File

@@ -0,0 +1,236 @@
-- 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[4] = mem_tx → MMC.in[1] (FS request)
local state = 0
local ST_IDLE = 0
local ST_WAIT_FS = 1
local uid, gid, cwd, curUser = 0, 0, "/", "root"
local pendingCmd = nil
local lastRespSeq = 0
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
end
local function fsReq(op, arg)
local msg = op
if arg then msg = msg .. "|" .. arg end
out[4] = msg
end
local cmds = {}
local function reg(name, fn, desc)
cmds[name] = { fn = fn, desc = desc }
end
reg("help", function(a)
local t = "AtlasOS v2.0 commands:\n"
for n, e in pairs(cmds) do
t = t .. " " .. n .. " " .. e.desc .. "\n"
end
return t
end, "Show help")
reg("echo", function(a)
if #a == 0 then error("echo: missing text") end
return table.concat(a, " ")
end, "Echo text")
reg("clear", function(a)
doClear()
end, "Clear screen")
reg("color", function(a)
if #a < 3 then error("color: need R G B") end
local r, g, b = tonumber(a[1]), tonumber(a[2]), tonumber(a[3])
if not (r and g and b) then error("color: invalid values") end
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)
end, "Set color R G B")
reg("status", function(a)
return "AtlasOS v2.0 | User: " .. curUser .. "(" .. uid .. ") | CWD: " .. cwd
end, "System status")
reg("ls", function(a)
local path = cwd
local opts = {}
for i = 1, #a do
if a[i]:sub(1, 1) == "-" then
opts[a[i]] = true
else
path = a[i]
end
end
pendingCmd = { name = "ls", path = path, opts = opts }
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] }
state = ST_WAIT_FS
fsReq("READ", a[1] .. "|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 }
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] }
state = ST_WAIT_FS
fsReq("STAT", a[1])
end, "Show file metadata")
local function execCmd(cmdStr)
local parts = {}
for t in cmdStr:gmatch("%S+") do
parts[#parts + 1] = t
end
if #parts == 0 then return end
local name = parts[1]
local args = {}
for i = 2, #parts do
args[#args + 1] = parts[i]
end
local entry = cmds[name]
if not entry then
printErr("Unknown: " .. name .. ". Try 'help'.")
return
end
local ok, result = pcall(entry.fn, args)
if not ok then
printErr(tostring(result))
elseif result ~= nil then
printOut(result)
end
end
local function handleFSResponse(op, data)
if not pendingCmd then return end
local cmd = pendingCmd
if op == "ERR" then
local code = data or "EIO"
if code == "ENOENT" then
printErr(cmd.path .. ": No such file or directory")
elseif code == "EACCES" then
printErr(cmd.path .. ": Permission denied")
elseif code == "ENOTDIR" then
printErr(cmd.path .. ": Not a directory")
elseif code == "EISDIR" then
printErr(cmd.path .. ": Is a directory")
else
printErr(cmd.path .. ": " .. code)
end
pendingCmd = nil
state = ST_IDLE
return
end
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
end
end
if #outLines == 0 then outLines[1] = "(empty)" end
printOut(table.concat(outLines, "\n"))
elseif cmd.name == "cat" then
printOut(data)
elseif cmd.name == "cd" then
cwd = data
printOut(cwd)
elseif cmd.name == "stat" then
local parts = {}
for s in data:gmatch("([^|]+)") do
parts[#parts + 1] = s
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)
else
printOut(data)
end
end
pendingCmd = nil
state = ST_IDLE
end
inp = {}
function upd()
if state == ST_IDLE then
if inp[1] ~= nil then
local op, cmd = parseMsg(tostring(inp[1]))
if op == "IN" and cmd and #cmd > 0 then
execCmd(cmd)
end
end
end
if state == ST_WAIT_FS and inp[2] ~= nil then
local resp = tostring(inp[2])
if #resp > 0 then
local op, rest = parseMsg(resp)
if op and rest then
local seqStr, data = parseMsg(rest)
local seq = tonumber(seqStr) or 0
if seq > lastRespSeq then
lastRespSeq = seq
handleFSResponse(op, data)
end
end
end
end
table.clear(inp)
end

39
atlas_os/IOC.lua Normal file
View File

@@ -0,0 +1,39 @@
-- IOC.lua — I/O Controller (AtlasOS Terminal v2.0)
-- out[1] = display_text → Terminal
-- out[2] = display_clear → Terminal
-- 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)
local txActive = false
inp = {}
function upd()
if inp[1] ~= nil then
out[4] = "IN|" .. tostring(inp[1])
txActive = true
elseif txActive then
out[4] = ""
txActive = false
end
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])
end
table.clear(inp)
end

273
atlas_os/MMC.lua Normal file
View File

@@ -0,0 +1,273 @@
-- MMC.lua — Memory/FS Controller (AtlasOS v2.0)
-- in[1] = mem_rx ← CMC.out[4] (FS request)
-- in[3] = mem_read ← Memory.out[1]
-- out[1] = mem_tx → CMC.in[2] (FS response)
-- out[5] = mem_data → Memory.in[1]
-- out[6] = mem_write → Memory.in[2]
local SEG_SIZE = 8192
local MAX_INODES = 64
local ROOT_INO = 1
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 split(str, sep)
local t = {}
if not str then return t end
for s in str:gmatch("[^" .. sep .. "]+") do
t[#t + 1] = s
end
return t
end
local inodes = {}
local dirs = {}
local files = {}
local users = {}
local sb = { seg_size = SEG_SIZE, max_inodes = MAX_INODES, root_ino = ROOT_INO }
local function fsSerialize()
local lines = {}
lines[#lines + 1] = "V|1"
lines[#lines + 1] = "S|" .. sb.seg_size .. "|" .. sb.max_inodes .. "|" .. sb.root_ino
for _, u in pairs(users) do
lines[#lines + 1] = "U|" .. u.uid .. "|" .. u.name .. "|" .. u.gid .. "|" .. u.home .. "|" .. (u.hash or "-")
end
for i = 1, MAX_INODES do
local ino = inodes[i]
if ino then
lines[#lines + 1] = "I|" .. ino.ino .. "|" .. ino.typ .. "|" .. ino.mode .. "|" .. ino.uid .. "|" .. ino.gid .. "|" .. ino.size .. "|" .. ino.mtime .. "|"
if ino.typ == "d" and dirs[ino.ino] then
local entries = {}
for _, e in ipairs(dirs[ino.ino]) do
entries[#entries + 1] = e.name .. ":" .. e.ino
end
lines[#lines + 1] = "D|" .. ino.ino .. "|" .. table.concat(entries, "|")
elseif ino.typ == "f" and files[ino.ino] then
lines[#lines + 1] = "F|" .. ino.ino .. "|" .. files[ino.ino]
end
end
end
return table.concat(lines, "\n")
end
local function fsDeserialize(str)
inodes = {}
dirs = {}
files = {}
users = {}
sb = { seg_size = SEG_SIZE, max_inodes = MAX_INODES, root_ino = ROOT_INO }
if not str or #str == 0 then return false end
for line in str:gmatch("[^\n]+") do
local typ = line:sub(1, 1)
local rest = line:sub(3)
if typ == "V" then
elseif typ == "S" then
local parts = split(rest, "|")
if #parts >= 3 then
sb.seg_size = tonumber(parts[1]) or SEG_SIZE
sb.max_inodes = tonumber(parts[2]) or MAX_INODES
sb.root_ino = tonumber(parts[3]) or ROOT_INO
end
elseif typ == "U" then
local parts = split(rest, "|")
if #parts >= 4 then
users[tonumber(parts[1])] = { uid = tonumber(parts[1]), name = parts[2], gid = tonumber(parts[3]), home = parts[4], hash = parts[5] or "-" }
end
elseif typ == "I" then
local parts = split(rest, "|")
if #parts >= 7 then
local ino = tonumber(parts[1])
inodes[ino] = { ino = ino, typ = parts[2], mode = parts[3], uid = tonumber(parts[4]), gid = tonumber(parts[5]), size = tonumber(parts[6]), mtime = tonumber(parts[7]) or 0 }
end
elseif typ == "D" then
local parts = split(rest, "|")
if #parts >= 2 then
local ino = tonumber(parts[1])
local entries = {}
for i = 2, #parts do
local eparts = split(parts[i], ":")
if #eparts >= 2 then
entries[#entries + 1] = { name = eparts[1], ino = tonumber(eparts[2]) }
end
end
dirs[ino] = entries
end
elseif typ == "F" then
local parts = split(rest, "|")
if #parts >= 2 then
local ino = tonumber(parts[1])
local content = parts[2] or ""
for i = 3, #parts do
content = content .. "|" .. parts[i]
end
files[ino] = content
end
end
end
return next(inodes) ~= nil
end
local function findInodeByPath(path)
if not path or #path == 0 then path = "/" end
local parts = split(path, "/")
local cur = inodes[sb.root_ino]
if not cur then return nil end
if path == "/" then return cur end
local curIno = sb.root_ino
for i = 1, #parts do
local name = parts[i]
if #name > 0 then
local found = false
local dentries = dirs[curIno]
if dentries then
for _, e in ipairs(dentries) do
if e.name == name then
curIno = e.ino
cur = inodes[curIno]
found = true
break
end
end
end
if not found then return nil end
end
end
return cur and inodes[curIno] or nil
end
local respSeq = 0
local function processReq(req)
local op, rest = parseMsg(req)
if not op then return "ERR|EINVAL" end
respSeq = respSeq + 1
local seqStr = tostring(respSeq) .. "|"
if op == "STAT" then
local path = rest or "/"
local ino = findInodeByPath(path)
if not ino then return "ERR|" .. seqStr .. "ENOENT" end
return "OK|" .. seqStr .. ino.ino .. "|" .. ino.typ .. "|" .. ino.mode .. "|" .. ino.uid .. "|" .. ino.gid .. "|" .. ino.size .. "|" .. ino.mtime
elseif op == "READ" then
local parts = split(rest or "", "|")
local path = parts[1] or ""
local offset = tonumber(parts[2]) or 0
local len = tonumber(parts[3]) or 8192
local ino = findInodeByPath(path)
if not ino then return "ERR|" .. seqStr .. "ENOENT" end
if ino.typ ~= "f" then return "ERR|" .. seqStr .. "EISDIR" end
local content = files[ino.ino] or ""
local data = content:sub(offset + 1, offset + len)
return "OK|" .. seqStr .. data
elseif op == "LS" then
local path = rest or "/"
local ino = findInodeByPath(path)
if not ino then return "ERR|" .. seqStr .. "ENOENT" end
if ino.typ ~= "d" then return "ERR|" .. seqStr .. "ENOTDIR" end
local dentries = dirs[ino.ino]
if not dentries then return "OK|" .. seqStr end
local names = {}
for _, e in ipairs(dentries) do
names[#names + 1] = e.name
end
return "OK|" .. seqStr .. table.concat(names, "|")
elseif op == "CHDIR" then
local path = rest or "/"
local ino = findInodeByPath(path)
if not ino then return "ERR|" .. seqStr .. "ENOENT" end
if ino.typ ~= "d" then return "ERR|" .. seqStr .. "ENOTDIR" end
return "OK|" .. seqStr .. path
elseif op == "WRITE" then
return "ERR|" .. seqStr .. "EROFS"
end
return "ERR|" .. seqStr .. "EINVAL"
end
local function defaultFS()
inodes = {}
dirs = {}
files = {}
users = {}
users[0] = { uid = 0, name = "root", gid = 0, home = "/", hash = "-" }
users[100] = { uid = 100, name = "captain", gid = 10, home = "/home/captain", hash = "-" }
local function mkIno(num, typ, mode, uid, gid, size)
inodes[num] = { ino = num, typ = typ, mode = mode, uid = uid, gid = gid, size = size or 0, mtime = 0 }
end
local function mkDir(num, entries)
dirs[num] = entries
end
local function mkFile(num, content)
files[num] = content or ""
local sz = #(content or "")
if inodes[num] then inodes[num].size = sz end
end
mkIno(1, "d", "755", 0, 0, 0)
mkDir(1, { { name = ".", ino = 1 }, { name = "..", ino = 1 }, { name = "home", ino = 2 }, { name = "etc", ino = 4 } })
mkIno(2, "d", "755", 0, 0, 0)
mkDir(2, { { name = ".", ino = 2 }, { name = "..", ino = 1 }, { name = "captain", ino = 5 } })
mkIno(5, "d", "700", 100, 10, 0)
mkDir(5, { { name = ".", ino = 5 }, { name = "..", ino = 2 }, { name = "readme.txt", ino = 3 } })
mkIno(3, "f", "644", 100, 10, 0)
mkFile(3, "Welcome to AtlasOS v2.0!\n")
mkIno(4, "d", "755", 0, 0, 0)
mkDir(4, { { name = ".", ino = 4 }, { name = "..", ino = 1 } })
local str = fsSerialize()
out[5] = str
out[6] = 1
end
local fsLoaded = false
local prevReq = ""
local writePending = false
inp = {}
function upd()
if not fsLoaded then
local memStr = inp[3] or ""
if #memStr > 0 and fsDeserialize(memStr) then
fsLoaded = true
else
fsLoaded = true
defaultFS()
writePending = true
end
end
if writePending then
local str = fsSerialize()
out[5] = str
out[6] = 1
writePending = false
end
if inp[1] ~= nil then
local req = tostring(inp[1])
if #req > 0 and req ~= prevReq then
prevReq = req
local resp = processReq(req)
out[1] = resp
end
end
table.clear(inp)
end

152
atlas_os/docs/wiring.md Normal file
View File

@@ -0,0 +1,152 @@
# Схема соединений AtlasOS v2.0
## Компоненты
| Компонент | Кол-во | Назначение |
|---|---|---|
| MicroLua (IOC) | 1 | I/O Controller — терминал |
| MicroLua (CMC) | 1 | Command Controller — shell |
| MicroLua (MMC) | 1 | Memory/FS Controller — ФС |
| Terminal | 1 | текстовый дисплей |
| Memory | 1 | хранилище 8192 символа |
## Полная схема
```
Терминал (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 │ │
│ └───┬────┘ │
│ │ │
└──────────────────┴────────────┘
```
## Соединения проводом
### IOC ↔ Терминал
| Пин 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) | ввод пользователя |
### IOC ↔ CMC
| Пин 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` |
### CMC ↔ MMC
| Пин CMC | → / ← | Пин MMC | Назначение |
|---|---|---|---|
| CMC.out[4] | → | MMC.in[1] | запрос `OP|args` |
| MMC.out[1] | → | CMC.in[2] | ответ `OK|seq|...` |
### MMC ↔ Memory
| Пин MMC | → / ← | Пин Memory | Назначение |
|---|---|---|---|
| MMC.out[5] | → | Memory.in[1] | данные для записи |
| MMC.out[6] | → | Memory.in[2] | сигнал записи (1) |
| Memory.out[1] | → | MMC.in[3] | чтение данных |
## Протокол шины
### IOC → CMC (CMD)
```
IN|команда
```
### CMC → IOC (CMD)
```
OUT|текст — вывести текст
1 на out[2] — очистить экран
R,G,B на out[3] — установить цвет
```
### CMC → MMC (MEM)
```
STAT|путь — метаданные
READ|путь|смещение|длина — чтение файла
LS|путь — список каталога
CHDIR|путь — смена директории (проверка)
WRITE|путь|смещение|данные — запись (пока EROFS)
```
### MMC → CMC (MEM)
```
OK|seq|данные
ERR|seq|код_ошибки
```
Коды ошибок: `ENOENT`, `EACCES`, `ENOTDIR`, `EISDIR`, `EROFS`, `EINVAL`
## Последовательность запуска
1. Загрузить код MMC.lua в контроллер MMC
2. Загрузить код CMC.lua в контроллер CMC
3. Загрузить код IOC.lua в контроллер IOC
4. Соединить провода по схеме выше
5. При первом запуске MMC инициализирует пустую ФС и запишет в Memory
6. Подать сигнал на Terminal — появится приглашение
## Пример сессии
```
> 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
echo Echo text
clear Clear screen
color Set color R G B
status System status
ls List directory [path]
cat Show file content
cd Change directory [path]
stat Show file metadata
```