ECALL Swtichless调用及tRTS端Swtichless初始化
目錄
?
以SampleCode/Switchless為例講解ECALL?Switchless的代碼流程
第一個(gè)Switchless ECALL,需要在tRTS端初始化一下Switchless模式
接下來(lái)構(gòu)建ECALL任務(wù)
用Switchless的方法來(lái)使用Swtichless ECALL
工人線程做了什么?
tRTS端初始化ECALL管理器
工人線程執(zhí)行ECALL【process_switchless_call】
Switchless ECALL的內(nèi)容就這么多了
以SampleCode/Switchless為例講解ECALL?Switchless的代碼流程
void ecall_empty_switchless(void) {}【ecall_empty_switchless】是這個(gè)例子中用到的典型的Swtichless ECALL,函數(shù)里面啥也不執(zhí)行。
那編譯的時(shí)候怎么能夠知道這個(gè)函數(shù)是使用ECALL Swtichless模式呢?總不能函數(shù)符號(hào)名里面加一個(gè)Swtichless就說(shuō)明它是Switchless模式的ECALL吧。這可能是個(gè)方法,但是無(wú)形地會(huì)限制函數(shù)名的寫(xiě)法(比如Ordinary ECALL名字里就不能再寫(xiě)Switchless字眼了)。因此如果要把這個(gè)ECALL標(biāo)記為Switchless模式,那么我們就需要在EDL文件(Enclave Definition Language)中進(jìn)行標(biāo)記聲明。
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】這個(gè)標(biāo)記就是說(shuō)明該函數(shù)需要改成Switchless的ECALL。
那么是由誰(shuí)來(lái)識(shí)別這個(gè)標(biāo)記并把這個(gè)函數(shù)轉(zhuǎn)換成Switchless的ECALL呢?是由sgx_edger8r來(lái)做的。sgx_edger8r會(huì)對(duì)EDL文件里的內(nèi)容進(jìn)行識(shí)別,并對(duì)SGX的橋函數(shù)(E/OCALL)進(jìn)行編排。EDL文件里面還有對(duì)橋函數(shù)參數(shù)屬性的設(shè)置,請(qǐng)見(jiàn)《SGX軟硬件棧(四)——橋函數(shù)》
sgx_edger8r會(huì)將橋函數(shù)(Switchless或Ordinary)編排成型,變成所謂的stub(Enclave_{u,t}.{c,h})。使得你原來(lái)可能是直接在Enclave寫(xiě)了個(gè)ECALL函數(shù),而實(shí)際ECALL調(diào)用過(guò)程變成了APP->stub(uRTS)->EENTER->stub(tRTS)->Enclave->ECALL。
因此sgx_edger8r對(duì)【ecall_empty_switchless】調(diào)整以后。在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】通過(guò)【sgx_ecall_switchless】來(lái)嘗試執(zhí)行ECALL。
第一個(gè)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】第一個(gè)參數(shù)是Enclave ID,第二個(gè)參數(shù)是ECALL的索引值,第三個(gè)參數(shù)是Enclave內(nèi)部可能會(huì)用到的OCALL表,第四個(gè)參數(shù)是傳參的Marshalling。
【_sgx_ecall】額外增加了一個(gè)參數(shù)說(shuō)明是不是Switchless的ECALL。我們這里是true。如果是Ordinary的ECALL,那么就是false了。
【_sgx_ecall】是【CEnclave::ecall】的套殼。
雖然和ECALL Switchless/Ordinary模式一樣調(diào)用的【CEnclave::ecall】,但是參數(shù)【is_switchless】指明是不是用Switchless模式,現(xiàn)在這里是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;}}}... }針對(duì)(Enclave ID所對(duì)應(yīng)的)Enclave的第一個(gè)Swtichless ECALL,我們需要將【ocall_table】事先傳入Enclave中。此處調(diào)用的【g_sl_funcs.sl_on_first_ecall_func_ptr】函數(shù)指針指向的是【sl_uswitchless_on_first_ecall】函數(shù),這函數(shù)最終目的是讓tRTS也保管一份uSwitchless管理器,這個(gè)uSwitchless管理器原來(lái)只在uRTS保留過(guò)一份。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;具體說(shuō)來(lái),這個(gè)【sl_uswitchless_on_first_ecall】函數(shù)準(zhǔn)備一下參數(shù),以【sl_call_once】的方式(只調(diào)用一次的方式)調(diào)用【init_tswitchless】->【sl_init_switchless(uRTS, stub)】->切換上下文進(jìn)入到tRTS(EENTER)->【sl_init_switchless(tRTS, stub)】
(這里提到的stub是由sgx_edger8r生成的放在Enclave{u,t}.{c,h}里橋函數(shù)接口,在開(kāi)發(fā)者眼里,我們可以直接調(diào)用ECALL_A,但是實(shí)際上完成的是這樣的過(guò)程:【ECALL_A(uRTS)】->【ECALL_A stub(uRTS)】->切換上下文進(jìn)入到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)】中會(huì)讓uSwitchless管理器存放到tRTS的【g_uswitchless_handle】。
之后在uRTS中,【init_tswitchless】函數(shù)將OCALL表掛到uSwitchless管理器上以及uSwitchless管理器所包含的OCALL管理器上。此時(shí),uSwitchless管理器可以標(biāo)記uRTS、tRTS中的Switchless初始化都完成了。
tRTS的Switchless既然初始化完了,那么就一次性的喚醒所有{t,u}Worker線程們。之前uRTS端Switchless初始化時(shí),工人線程們激活后都在睡覺(jué)。
接下來(lái)構(gòu)建ECALL任務(wù)
所使用的【g_sl_funcs.sl_ecall_func_ptr】函數(shù)指針指向【sl_uswitchless_do_switchless_ecall】(此時(shí)我們?cè)趗RTS)
/* 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工人線程,數(shù)量不能為0。如果有睡覺(jué)的tWorker工人線程,那么全部給叫醒。
構(gòu)造ECALL任務(wù)【struct sl_call_task call_task】
| 【struct sl_call_task call_task】成員變量 | 賦值 | 說(shuō)明 |
| status | SL_INIT,代表ECALL任務(wù)剛初始 | ECALL任務(wù)狀態(tài) |
| func_id | ecall_id | ECALL索引值 |
| func_data | ecall_ms | ECALL的Marshalling參數(shù) |
| ret_code | SGX_ERROR_UNEXPECTED | ECALL的返回值 |
調(diào)用【sl_call_mngr_call】,通過(guò)信號(hào)線讓tWorker工人線程執(zhí)行Switchless ECALL。信號(hào)線的初始化在《SGX初始化中,uRTS端的Switchless模式的初始化》
如果Switchless ECALL時(shí),沒(méi)有可用的tWorker工人線程,那么通過(guò)回退Fallback成Ordinary ECALL,切換上下文執(zhí)行ECALL。
用Switchless的方法來(lái)使用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... }這個(gè)函數(shù)中,首先申請(qǐng)一個(gè)空閑的信號(hào)線【struct sl_siglines.free_lines】(一個(gè)Bit位代表一個(gè)信號(hào))。將ECALL任務(wù)的狀態(tài)從【SL_INIT】改成【SL_SUBMITTED】。
將ECALL任務(wù)復(fù)制到uSwitchless管理器保存的ECALL管理器的任務(wù)池中。前面說(shuō)到過(guò)uRTS和tRTS都能訪問(wèn)這個(gè)存儲(chǔ)在uRTS的uSwitchless管理器的地址。
【sgx_mfence】確保工人線程能夠看到任務(wù)池中的ECALL任務(wù)。然后發(fā)送(本質(zhì)是變量設(shè)置)信號(hào)線【struct sl_siglines.event_lines】的信號(hào)一個(gè)Bit位代表一個(gè)信號(hào)),tWorker會(huì)收到信號(hào)(本質(zhì)是對(duì)信號(hào)線變量循環(huán)檢測(cè))并開(kāi)始執(zhí)行ECALL任務(wù)。
ECALL調(diào)用者線程然后循環(huán)等到ECALL任務(wù)被接受【SL_ACCEPTED】、被完成【SL_DONE】。
等到ECALL被完成之后,調(diào)用者線程就返回了。
那么ECALL任務(wù)的另一端,接收ECALL任務(wù)并執(zhí)行的tWorker線程作了什么呢?
工人線程做了什么?
之前說(shuō)到工人線程,以tWorker為例,uRTS初始化Switchless時(shí)候處于睡眠狀態(tài),當(dāng)tRTS初始化Switchless時(shí),{t,u}Worker兩種工人線程會(huì)被喚醒。此時(shí)工人線程都還處于uRTS,對(duì)于tWorker來(lái)說(shuō),他需要進(jìn)入到tRTS中去。
tWorker被喚醒之后,會(huì)進(jìn)入【tworker_process_calls(uRTS)】->【sl_run_switchless_tworker(uRTS)】->切換上下文進(jìn)入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】只調(diào)用一次的方式調(diào)用在tRTS內(nèi)部初始化ECALL管理器【init_tswitchless_ecall_mngr】,uRTS端初始化Switchless時(shí)只初始化了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管理器連同它的信號(hào)線管理器克隆一份存到tRTS的【g_ecall_mngr】,主要是地址層面(仍屬于uRTS)。
拷貝的信息不包括ECALL信號(hào)線的【free_lines】和tWorker處理ECALL的函數(shù)句柄【process_switchless_call】,uRTS的ECALL管理器的信號(hào)線管理器的處理ECALL的函數(shù)句柄是NULL(空),而不是【process_switchless_call】。
另外一個(gè)沒(méi)有拷貝的是ECALL表【fcall_table】,這個(gè)ECALL表需要tRTS在Enclave內(nèi)部重新創(chuàng)建一個(gè)出來(lái),并注冊(cè)到tRTS的【g_ecall_mngr】上。【fcall_table】的來(lái)源就是sgx_edger8r生成的存在stub【Enclave_t.c】中的【g_ecall_table】。
tRTS端初始化ECALL管理器的內(nèi)容就這么多。
之后就是試圖讓工人線程執(zhí)行ECALL任務(wù)【sl_call_mngr_process】->【sl_siglines_process_signals】->查看所有信號(hào)線每個(gè)Bit確定是否有信號(hào)來(lái)了->【process_switchless_call】。并且工人線程執(zhí)行ECALL的失敗重試次數(shù)是傳參指定的或者默認(rèn)(20000)的。
工人線程執(zhí)行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任務(wù)取出,然后將狀態(tài)設(shè)置為【SL_ACCEPTED】。
然后用CALL任務(wù)中的索引值從【sl_call_table_t.func】(來(lái)源于stub【Enclave_t.c】中的【g_ecall_table】)獲取ECALL函數(shù)的虛擬地址。
然后執(zhí)行ECALL ID對(duì)應(yīng)的ECALL。
Switchless ECALL的內(nèi)容就這么多了
工人線程執(zhí)行完ECALL后,將ECALL任務(wù)的狀態(tài)設(shè)置為【SL_DONE】。
工人線程再次回到自己的Main Loop。
uRTS端等待ECALL執(zhí)行完畢的線程,當(dāng)看到任務(wù)狀態(tài)變成了【SL_DONE】就返回了。
總結(jié)
以上是生活随笔為你收集整理的ECALL Swtichless调用及tRTS端Swtichless初始化的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 区块链常见的几大共识机制
- 下一篇: 20分钟 Awk 入门