Quá Trình Khởi Động Tự Thân Của Hệ Thống Tập Tin Ảo - nói dối e blog

Quá Trình Khởi Động Tự Thân Của Hệ Thống Tập Tin Ảo

Trong thiết kế động cơ game của chúng tôi, một hệ thống tập tin ảo (Virtual File System - VFS) đã được xây dựng với khả năng tích hợp nhiều loại hệ thống tập tin thực thể khác nhau như mô-đun hệ thống tập tin địa phương, mô-đun RAM, hay thậm chí là mô-đun truy cập qua mạng. Mô hình này cũng áp dụng cho kho tài nguyên (resource repository) mà chúng tôi thảo luận trước đó - một dạng mô-đun hệ thống tập tin đặc biệt.

Điểm đặc biệt nằm ở việc toàn bộ hệ thống này được viết bằng Lua, tạo ra vòng lặp “mẹ đẻ trứng hay trứng nở mẹ” (chicken and egg problem). Mã nguồn Lua nằm trong chính hệ thống tập tin này, trong khi mô-đun mạng - phương thức cập nhật chính cho hệ thống - cũng được viết bằng Lua và lưu trữ bên trong hệ thống đó. Làm thế nào để khởi động được hệ thống khi phần mềm phụ thuộc lẫn nhau như vậy?

Giải pháp tổng thể

Chúng tôi thiết kế một cơ chế khởi động tự thân (self-bootstrapping) với các nguyên tắc sau:

  1. Đơn giản hóa cấu trúc: Thay vì cho phép gắn kết mô-đun hệ thống tập tin bất kỳ vào bất kỳ thư mục nào, chúng tôi thiết lập một thư mục cố định .firmware để chứa mã khởi động tối thiểu. Mã này có thể được cập nhật sau khi mô-đun mạng hoạt động.

  2. Tối ưu hóa bằng C: Dù ưu tiên sử dụng Lua, nhưng giai đoạn khởi động đầu tiên vẫn cần một lõi C tối giản để tạo môi trường ảo Lua đầu tiên. Lõi này chỉ chứa các hàm thiết yếu như truy cập tập tin qua API hệ điều hành.

  3. Cơ chế hai lớp khởi động:

    • Giai đoạn 1: Khởi tạo VM Lua tối giản từ mã nguồn vật lý trong .firmware
    • Giai đoạn 2: Sử dụng VM này để tải phiên bản mới từ kho tài nguyên (repo), sau đó thay thế hoàn toàn VM ban đầu bằng phiên bản cập nhật

Kiến trúc hệ thống

1
2
3
struct vfs * vfs_init(const char *firmware, const char *repo);
const char * vfs_load(struct vfs *V, const char *path);
void vfs_exit(struct vfs *V);

Trong đó:

  • firmware: Thư mục chứa mã nguồn khởi động vật lý (bootstrap)
  • repo: Kho tài nguyên chứa phiên bản cập nhật

Chi tiết triển khai C

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
struct vfs {
  struct luavm *L;
  int handle;
};

static int linitvfs(lua_State *L) {
  luaL_checktype(L,1, LUA_TLIGHTUSERDATA);
  struct vfs ** V = (struct vfs **)lua_touserdata(L, 1);
  *V = lua_newuserdata(L, sizeof(struct vfs));
  return 1;
}

extern int luaopen_winfile(lua_State *L);

static int lfs(lua_State *L) {
  return luaopen_winfile(L);
}

static int cfuncs(lua_State *L) {
  luaL_checkversion(L);
  luaL_Reg l[] = {
    { "initvfs", linitvfs },    
    { "lfs", lfs },
    { NULL, NULL },
  };
  luaL_newlib(L, l);
  return 1;
}

static const char * init_source = "local _, firmware = ... ; loadfile(firmware .. '/bootstrap.lua')(...)";

struct vfs * vfs_init(const char *firmware, const char *dir) {
  struct luavm *L = luavm_new();
  if (L == NULL) return NULL;
  
  struct vfs *V = NULL;
  const char * err = luavm_init(L, init_source, "ssfp", firmware, dir, cfuncs, &V);
  
  if (err || V == NULL) {
    luavm_close(L);
    return NULL;
  }
  
  V->L = L;
  err = luavm_register(L, "return _LOAD", "=vfs.load", &V->handle);
  
  if (err) {
    luavm_close(L);
    return NULL;
  }
  
  return V;
}

void vfs_exit(struct vfs *V) {
  if (V) luavm_close(V->L);
}

const char * vfs_load(struct vfs *V, const char *path) {
  const char * ret = NULL;
  const char * err = luavm_call(V->L, V->handle, "sS", path, &ret);
  
  if (err) {
    fprintf(stderr, "Lỗi nạp tập tin: %s\n", err);
    return NULL;
  }
  
  return ret;
}

Chi tiết triển khai Lua (bootstrap.lua)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
local errlog, firmware, dir, cfuncs, V = ...
cfuncs = cfuncs()
package.preload.lfs = cfuncs.lfs  -- Khởi tạo thư viện lfs

local vfs = assert(loadfile(firmware .. "/vfs.lua"))()
local repo = vfs.new(firmware, dir)

-- Thử tải phiên bản vfs mới từ kho
local f = repo:open(".firmware/vfs.lua")  
if f then
  local vfs_source = f:read "a"
  f:close()
  vfs = assert(load(vfs_source, "@.firmware/vfs.lua"))()
  repo = vfs.new(firmware, dir)
end

local function readfile(f)
  if f then
    local content = f:read "a"
    f:close()
    return content
  end
end

-- Kiểm tra cập nhật bootstrap
local bootstrap = readfile(repo:open(".firmware/bootstrap.lua"))
if bootstrap then
  local newboot = load(bootstrap, "@.firmware/bootstrap.lua")
  local selfchunk = string.dump(debug.getinfo(1, "f").func, true)
  
  if string.dump(newboot, true) ~= selfchunk then
    newboot(...)  -- Nạp lại phiên bản mới
    return
  end
end

-- Hàm tải tập tin cuối cùng
function _LOAD(path)
  local f = repo:open(path)
  if f then
    local content = f:read "a"
    f:close()
    return content
  end
end

-- Khởi tạo VFS chính thức
_VFS = cfuncs.initvfs(V)  

Quy trình khởi động chi tiết

  1. Khởi tạo VM Lua cơ sở: Sử dụng C code để tải file bootstrap.lua vật lý từ thư mục firmware
0%