iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 12
0
IoT

拿到錘子的我想在微控制器上面執行 Ruby系列 第 12

Day 12 - 實作方法(一)

在前面的實作中,我們是直接對 OP_SEND 放入 printf 的處理將結果印出來,但這樣就表示我們在 Ruby 不論呼叫任何方法都只會將他印出來,這並不是我們預期的效果。

現在我們已經可以取出方法名稱,也就表示我們可以利用這個字串去查詢是否有登記在記憶體裡面,並且找到對應的 C Function 來執行,基本上它是一個很簡單的 Key-Value 結構,不過在這之前我們會需要有一個地方去存放這個字串跟方法的關係表。

mrb_state

在前面的實作中我們直接跳過了 mrb_state 的處理,如果是在正常的 mruby 呼叫中我們會這樣使用

mrb_state* mrb = mrb_open();
mrb_load_string(mrb, "puts 1");
mrb_close(mrb);

基本上所有的 mruby 呼叫都會需要將 mrb_state 傳遞進去,這是因為 mrb_state 基本上紀錄了整個 Ruby VM 運行的狀態,也包括了我們所需要紀錄方法的關係表。

首先,我們先增加一個 src/iron.h 來定義對應的行為。

#ifndef _IRON_H_
#define _IRON_H_

#define IRON_API extern

typedef struct mrb_state {
  int exc; /* exception */
} mrb_state;

IRON_API mrb_state* mrb_open(void);
IRON_API void mrb_close(mrb_state* mrb);


#endif

我們需要使用 mrb_open 來產生一個新的 mrb_state 供我們使用,以及 mrb_close 來將使用完畢的 mrb_state 情除掉,除此之外我們還需要將這些方法標記為 extern 才能夠被其他專案呼叫到,像是 Arduino 是 C++ 為基底的,如果沒有 extern 的話很有可能是無法讀取到的。

在 mruby 中我們可以利用使否有定義 MRB_API 來區分是否為可以呼叫的方法,還是 mruby 內部自己使用的方法。

這邊會叫做 IRON 的原因單純是取名困難,也可以用 mruby 的方式叫做 MRB_API 之類的,另外 extern 的部分還不太清楚實際的使用狀況,後續實作利用 PlatformIO 時即使缺少了也沒有發生錯誤,應該是有其他原因造成的。

接下來加入 src/iron.c 實作對應的行為

#include <stdlib.h>

#include "iron.h"

IRON_API mrb_state*
mrb_open(void) {
  static const mrb_state mrb_state_zero = { 0 };
  mrb_state* mrb = (mrb_state*)malloc(sizeof(mrb_state));

  *mrb = mrb_state_zero;

  return mrb;
}

IRON_API void
mrb_close(mrb_state* mrb) {
  if(!mrb) return;

  free(mrb);
}

這邊我們使用了 malloc 分配了記憶體,也代表不會被自動的回收,因此我們需要利用 mrb_close 來將分配的記憶體回收,除此之外因為 malloc 分配給我們的記憶體可能有殘留其他程式使用過的部分,因此我們製作了一個透過 0 (NULL) 填滿的資料將他複製到我們申請的記憶體區段中確實的清除掉資料殘留。

跟 Ruby 不同的是 C 語言是不會有 GC 等處理的,正常狀況下大部分變數都會在方法被呼叫完畢後回收。如果不想要被回收,就會利用像是 malloc 這類方式項要求一段記憶體來使用,同時也就需要負起將這段記憶體清除的責任。

最後我們在針對 mrb_exec 等方法加入將 mrb_state* 傳入的修改。

// src/main.c

int main(int argc, char** argv) {

  mrb_state* mrb = mrb_open();
  mrb_run(mrb, app);
  mrb_close(mrb);

  return 0;
}
// src/vm.h

#define mrb_run(mrb, irep) mrb_exec(mrb, irep_load(irep))

int mrb_exec(mrb_state* mrb, const uint8_t* irep);
// src/vm.c

int mrb_exec(mrb_state* mrb, const uint8_t* data) {
  // ...
}

如此一來我們的 Ruby VM 就開始能儲存一些狀態,接下來就是將一些狀態移動到 mrb_state 裡面。

// src/vm.c

// ...
    default:
        DEBUG_LOG("Unsupport OP Code: %d", insn);	       
        mrb->exc = 1;

像是原本是用 error = 1 的變數來紀錄是否出錯,現在我們就可以將他存到 mrb_state 裡面。

在 mruby 中 exc 是 Exception 資訊的紀錄,他實際會指向一個 Exception 物件,在發生錯誤的時候讓我們可以從 exc 裡面取出錯誤的類型跟資訊並且呈現給使用者。

有了 mrb_state 後我們就可以將對應資料儲存進去,下一篇我們會開始來處理紀錄 Ruby 方法跟 C 語言中方法的對應。

實際上 reg 也會被放到 mrb_state 中,不過現階段並不是必要的,在我實驗的實作階段中 feature/6-mrb_state 中有做這樣的修改,但是我們可以等到後面處理變數的時候再一起修改。


上一篇
Day 11 - 重構 VM 處理程序
下一篇
Day 13 - 實作方法(二)
系列文
拿到錘子的我想在微控制器上面執行 Ruby30

尚未有邦友留言

立即登入留言