ECALL Swtichless调用及tRTS端Swtichless初始化
目錄
?
以SampleCode/Switchless為例講解ECALL?Switchless的代碼流程
第一個Switchless ECALL,需要在tRTS端初始化一下Switchless模式
接下來構建ECALL任務
用Switchless的方法來使用Swtichless ECALL
工人線程做了什么?
tRTS端初始化ECALL管理器
工人線程執行ECALL【process_switchless_call】
Switchless ECALL的內容就這么多了
以SampleCode/Switchless為例講解ECALL?Switchless的代碼流程
void ecall_empty_switchless(void) {}【ecall_empty_switchless】是這個例子中用到的典型的Swtichless ECALL,函數里面啥也不執行。
那編譯的時候怎么能夠知道這個函數是使用ECALL Swtichless模式呢?總不能函數符號名里面加一個Swtichless就說明它是Switchless模式的ECALL吧。這可能是個方法,但是無形地會限制函數名的寫法(比如Ordinary ECALL名字里就不能再寫Switchless字眼了)。因此如果要把這個ECALL標記為Switchless模式,那么我們就需要在EDL文件(Enclave Definition Language)中進行標記聲明。
SampleCode/Switchless的EDL文件如下
//位于SampleCode/Switchless/Enclave/Enclave.edl enclave {from "sgx_tstdc.edl" import *;from "sgx_tswitchless.edl" import *;trusted {public void ecall_repeat_ocalls(unsigned long nrepeats, int use_switchless);public void ecall_empty(void);public void ecall_empty_switchless(void) transition_using_threads;};untrusted {void ocall_empty(void);void ocall_empty_switchless(void) transition_using_threads;}; };我們可以看到【transition_using_threads】這個標記就是說明該函數需要改成Switchless的ECALL。
那么是由誰來識別這個標記并把這個函數轉換成Switchless的ECALL呢?是由sgx_edger8r來做的。sgx_edger8r會對EDL文件里的內容進行識別,并對SGX的橋函數(E/OCALL)進行編排。EDL文件里面還有對橋函數參數屬性的設置,請見《SGX軟硬件棧(四)——橋函數》
sgx_edger8r會將橋函數(Switchless或Ordinary)編排成型,變成所謂的stub(Enclave_{u,t}.{c,h})。使得你原來可能是直接在Enclave寫了個ECALL函數,而實際ECALL調用過程變成了APP->stub(uRTS)->EENTER->stub(tRTS)->Enclave->ECALL。
因此sgx_edger8r對【ecall_empty_switchless】調整以后。在uRTS的stub端,變成了如下樣子:
sgx_status_t ecall_empty_switchless(sgx_enclave_id_t eid) {sgx_status_t status;status = sgx_ecall_switchless(eid, 2, &ocall_table_Enclave, NULL);return status; }可以看到【ecall_empty_switchless】通過【sgx_ecall_switchless】來嘗試執行ECALL。
第一個Switchless ECALL,需要在tRTS端初始化一下Switchless模式
extern "C" sgx_status_t sgx_ecall_switchless(const sgx_enclave_id_t enclave_id, const int proc, const void *ocall_table, void *ms) {return _sgx_ecall(enclave_id, proc, ocall_table, ms, true); } static sgx_status_t _sgx_ecall(const sgx_enclave_id_t enclave_id, const int proc, const void *ocall_table, void *ms, const bool is_switchless) {...result = enclave->ecall(proc, ocall_table, ms, is_switchless);... }可以看到【sgx_ecall_switchless】第一個參數是Enclave ID,第二個參數是ECALL的索引值,第三個參數是Enclave內部可能會用到的OCALL表,第四個參數是傳參的Marshalling。
【_sgx_ecall】額外增加了一個參數說明是不是Switchless的ECALL。我們這里是true。如果是Ordinary的ECALL,那么就是false了。
【_sgx_ecall】是【CEnclave::ecall】的套殼。
雖然和ECALL Switchless/Ordinary模式一樣調用的【CEnclave::ecall】,但是參數【is_switchless】指明是不是用Switchless模式,現在這里是true。
sgx_status_t CEnclave::ecall(const int proc, const void *ocall_table, void *ms, const bool is_switchless) {...if (m_switchless){// we need to pass ocall_table pointer to the enclave when initializing switchless on trusted side.if (m_first_ecall && ocall_table){// can create race condition here if we have several threads initiating "first" ecall// so it is possible the first switchless ecall will fallbackm_first_ecall = false;// we are setting the flag here, cause otherwise it will create deadlock in sl_on_first_ecall_func_ptr()g_sl_funcs.sl_on_first_ecall_func_ptr(m_switchless, m_enclave_id, ocall_table);}//Do switchless ECall in a switchless wayif (is_switchless){int need_fallback = 0;sgx_status_t ret = SGX_ERROR_UNEXPECTED;ret = g_sl_funcs.sl_ecall_func_ptr(m_switchless, proc, ms, &need_fallback);if (likely(!need_fallback)){se_rdunlock(&m_rwlock);return ret;}}}... }針對(Enclave ID所對應的)Enclave的第一個Swtichless ECALL,我們需要將【ocall_table】事先傳入Enclave中。此處調用的【g_sl_funcs.sl_on_first_ecall_func_ptr】函數指針指向的是【sl_uswitchless_on_first_ecall】函數,這函數最終目的是讓tRTS也保管一份uSwitchless管理器,這個uSwitchless管理器原來只在uRTS保留過一份。tRTS中的uSwitchless管理器保存到tRTS的全局變量【g_uswitchless_handle】。
// holds the pointer to untrusted structure describing switchless configuration // pointer is assigned only after all necessary checks struct sl_uswitchless* g_uswitchless_handle = NULL;具體說來,這個【sl_uswitchless_on_first_ecall】函數準備一下參數,以【sl_call_once】的方式(只調用一次的方式)調用【init_tswitchless】->【sl_init_switchless(uRTS, stub)】->切換上下文進入到tRTS(EENTER)->【sl_init_switchless(tRTS, stub)】
(這里提到的stub是由sgx_edger8r生成的放在Enclave{u,t}.{c,h}里橋函數接口,在開發者眼里,我們可以直接調用ECALL_A,但是實際上完成的是這樣的過程:【ECALL_A(uRTS)】->【ECALL_A stub(uRTS)】->切換上下文進入到tRTS(EENTER)->【ECALL_A stub(tRTS)】->【ECALL_A(tRTS)】)
/* Override the weak symbol defined in tRTS */ sgx_status_t sl_init_switchless(void* _handle_u) {...if (lock_cmpxchg_ptr((void*)&g_uswitchless_handle, NULL, (void*)handle_u) != NULL)... }【sl_init_switchless(tRTS, stub)】中會讓uSwitchless管理器存放到tRTS的【g_uswitchless_handle】。
之后在uRTS中,【init_tswitchless】函數將OCALL表掛到uSwitchless管理器上以及uSwitchless管理器所包含的OCALL管理器上。此時,uSwitchless管理器可以標記uRTS、tRTS中的Switchless初始化都完成了。
tRTS的Switchless既然初始化完了,那么就一次性的喚醒所有{t,u}Worker線程們。之前uRTS端Switchless初始化時,工人線程們激活后都在睡覺。
接下來構建ECALL任務
所使用的【g_sl_funcs.sl_ecall_func_ptr】函數指針指向【sl_uswitchless_do_switchless_ecall】(此時我們在uRTS)
/* sgx_ecall_switchless() from uRTS calls this function to do the real job */ sgx_status_t sl_uswitchless_do_switchless_ecall(void* _switchless,const unsigned int ecall_id,void* ecall_ms,int* need_fallback)首先確保一下tRTS端的Switchless初始化完畢了,并且tRTS端有tWorker工人線程,數量不能為0。如果有睡覺的tWorker工人線程,那么全部給叫醒。
構造ECALL任務【struct sl_call_task call_task】
| 【struct sl_call_task call_task】成員變量 | 賦值 | 說明 |
| status | SL_INIT,代表ECALL任務剛初始 | ECALL任務狀態 |
| func_id | ecall_id | ECALL索引值 |
| func_data | ecall_ms | ECALL的Marshalling參數 |
| ret_code | SGX_ERROR_UNEXPECTED | ECALL的返回值 |
調用【sl_call_mngr_call】,通過信號線讓tWorker工人線程執行Switchless ECALL。信號線的初始化在《SGX初始化中,uRTS端的Switchless模式的初始化》
如果Switchless ECALL時,沒有可用的tWorker工人線程,那么通過回退Fallback成Ordinary ECALL,切換上下文執行ECALL。
用Switchless的方法來使用Swtichless ECALL
static inline int sl_call_mngr_call(struct sl_call_mngr* mngr, struct sl_call_task* call_task, uint32_t max_tries) {/*Used to make actual switchless call by both enclave & untrusted codemngr: points to ECALL or OCALL manager. For enclave, OCALL mngr and all its content are checkeds in sl_mngr_clone() functionsee init_tswitchless_ocall_mngr()call_task: contains all the information of function to be called,when called by enclave to make OCALL, call_task resides on enclaves stack*/.../* Allocate a free signal line to send signal */...// copy task data to internal array accessable by both sides (trusted & untrusted).../* Send a signal so that workers will access the buffer for switchless call* requests. Here, a memory barrier is used to make sure the buffer is* visible when the signal is received on other CPUs. */...// wait till the other side has picked the task for processing.../* The request must has been accepted. Now wait for its completion */...// copy the return code... }這個函數中,首先申請一個空閑的信號線【struct sl_siglines.free_lines】(一個Bit位代表一個信號)。將ECALL任務的狀態從【SL_INIT】改成【SL_SUBMITTED】。
將ECALL任務復制到uSwitchless管理器保存的ECALL管理器的任務池中。前面說到過uRTS和tRTS都能訪問這個存儲在uRTS的uSwitchless管理器的地址。
【sgx_mfence】確保工人線程能夠看到任務池中的ECALL任務。然后發送(本質是變量設置)信號線【struct sl_siglines.event_lines】的信號一個Bit位代表一個信號),tWorker會收到信號(本質是對信號線變量循環檢測)并開始執行ECALL任務。
ECALL調用者線程然后循環等到ECALL任務被接受【SL_ACCEPTED】、被完成【SL_DONE】。
等到ECALL被完成之后,調用者線程就返回了。
那么ECALL任務的另一端,接收ECALL任務并執行的tWorker線程作了什么呢?
工人線程做了什么?
之前說到工人線程,以tWorker為例,uRTS初始化Switchless時候處于睡眠狀態,當tRTS初始化Switchless時,{t,u}Worker兩種工人線程會被喚醒。此時工人線程都還處于uRTS,對于tWorker來說,他需要進入到tRTS中去。
tWorker被喚醒之后,會進入【tworker_process_calls(uRTS)】->【sl_run_switchless_tworker(uRTS)】->切換上下文進入Enclave->【sl_run_switchless_tworker(tRTS)】。
/*=========================================================================* Process Fast ECalls*========================================================================*//* The builtin ECMD_RUN_SWITCHLESS_TWORKER ECall calls this function eventually */ // This is a worker's thread function, which polls the event_lines bitmap for incoming // switchless ECALLs requests sgx_status_t sl_run_switchless_tworker()以【sl_once_t】只調用一次的方式調用在tRTS內部初始化ECALL管理器【init_tswitchless_ecall_mngr】,uRTS端初始化Switchless時只初始化了uRTS端的E/OCALL管理器,tRTS也需要一份E/OCALL管理器。
tRTS端初始化ECALL管理器
// initialize enclave's ecall manager static uint64_t init_tswitchless_ecall_mngr(void* param) {...// g_uswitchless_handle is checked in sl_init_switchless()...// clone ecall manager structure allocated outside enclave, performing all the relevant checks...// abort in case of bogus initialization...// allocate switchless ecalls table inside the enclave... }將uRTS的ECALL管理器連同它的信號線管理器克隆一份存到tRTS的【g_ecall_mngr】,主要是地址層面(仍屬于uRTS)。
拷貝的信息不包括ECALL信號線的【free_lines】和tWorker處理ECALL的函數句柄【process_switchless_call】,uRTS的ECALL管理器的信號線管理器的處理ECALL的函數句柄是NULL(空),而不是【process_switchless_call】。
另外一個沒有拷貝的是ECALL表【fcall_table】,這個ECALL表需要tRTS在Enclave內部重新創建一個出來,并注冊到tRTS的【g_ecall_mngr】上。【fcall_table】的來源就是sgx_edger8r生成的存在stub【Enclave_t.c】中的【g_ecall_table】。
tRTS端初始化ECALL管理器的內容就這么多。
之后就是試圖讓工人線程執行ECALL任務【sl_call_mngr_process】->【sl_siglines_process_signals】->查看所有信號線每個Bit確定是否有信號來了->【process_switchless_call】。并且工人線程執行ECALL的失敗重試次數是傳參指定的或者默認(20000)的。
工人線程執行ECALL【process_switchless_call】
static inline void process_switchless_call(struct sl_siglines* siglns, uint32_t line) {/*Main processing function which is called by a worker thread(trusted or untrusted)from sl_siglines_process_signals() function when there is a pending request for a switchless ECALL/OCALL.siglns points to the untrusted data structure with pending requests bitmap,siglns is checked by sl_siglines_clone() function before use by the enclaveline indicates which bit is set*/... }【process_switchless_call】首先將CALL管理器和CALL任務取出,然后將狀態設置為【SL_ACCEPTED】。
然后用CALL任務中的索引值從【sl_call_table_t.func】(來源于stub【Enclave_t.c】中的【g_ecall_table】)獲取ECALL函數的虛擬地址。
然后執行ECALL ID對應的ECALL。
Switchless ECALL的內容就這么多了
工人線程執行完ECALL后,將ECALL任務的狀態設置為【SL_DONE】。
工人線程再次回到自己的Main Loop。
uRTS端等待ECALL執行完畢的線程,當看到任務狀態變成了【SL_DONE】就返回了。
總結
以上是生活随笔為你收集整理的ECALL Swtichless调用及tRTS端Swtichless初始化的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 区块链常见的几大共识机制
- 下一篇: 20分钟 Awk 入门