檢視原始碼 driver_entry

Erlang 驅動程式使用的 driver-entry 結構。

描述

警告

請極度小心地使用此功能。

驅動程式回呼函式會作為 VM 原生程式碼的直接擴展執行。執行並非在安全環境中進行。VM *無法* 提供與執行 Erlang 程式碼時相同的服務,例如搶佔式排程或記憶體保護。如果驅動程式回呼函式的行為不佳,整個 VM 都會出現問題。

  • 發生崩潰的驅動程式回呼函式會導致整個 VM 崩潰。
  • 錯誤實作的驅動程式回呼函式可能會導致 VM 內部狀態不一致,這可能會導致 VM 崩潰,或在呼叫驅動程式回呼函式後的任何時間點,導致 VM 出現各種錯誤行為。
  • 在返回之前執行長時間工作的驅動程式回呼函式會降低 VM 的回應速度,並可能導致各種奇怪的行為。這些奇怪的行為包括但不限於極端的記憶體使用量,以及排程器之間不良的負載平衡。由於長時間工作而可能發生的奇怪行為,在 Erlang/OTP 版本之間也可能有所不同。

從 ERTS 5.9 (Erlang/OTP R15B) 開始,驅動程式介面已變更,回呼函式的類型較大,包括 outputcontrolcall。請參閱 erl_driver 中的驅動程式版本管理

注意

舊驅動程式(使用早於 5.9 的 ERTS 版本的 erl_driver.h 編譯)必須更新,並使用擴充介面(使用 版本管理)。

driver_entry 結構是一個 C 結構,所有 Erlang 驅動程式都會定義此結構。它包含 Erlang 驅動程式的進入點,當 Erlang 程式碼存取驅動程式時,Erlang 模擬器會呼叫這些進入點。

erl_driver 驅動程式 API 函式需要一個埠控制代碼,用於識別驅動程式實例(和模擬器中的埠)。這只會傳遞給 start 函式,而不會傳遞給其他函式。start 函式會傳回一個驅動程式定義的控制代碼,該控制代碼會傳遞給其他函式。常見的做法是讓 start 函式配置一些應用程式定義的結構,並將 port 控制代碼存放在其中,以便稍後與驅動程式 API 函式一起使用。

驅動程式回呼函式會從 Erlang 模擬器同步呼叫。如果它們在完成之前花費太長時間,可能會導致模擬器逾時。如有必要,請使用佇列或非同步呼叫,因為模擬器必須具有回應能力。

驅動程式結構包含驅動程式名稱和一些 15 個函式指標,模擬器會在不同的時間呼叫這些指標。

驅動程式中唯一匯出的函式是 driver_init。此函式會傳回指向驅動程式中其他函式的 driver_entry 結構。driver_init 函式使用巨集 DRIVER_INIT(drivername) 宣告。(這是因為不同的作業系統具有不同的名稱。)

當在 C++ 中撰寫驅動程式時,驅動程式條目必須為 "C" 連結。一種做法是在驅動程式條目之前的某處放置以下程式碼行

extern "C" DRIVER_INIT(drivername);

當驅動程式將 driver_entry 傳遞給模擬器後,驅動程式*不*允許修改 driver_entry

如果透過 --enable-static-drivers 編譯驅動程式以進行靜態包含,則必須在 DRIVER_INIT 宣告之前定義 STATIC_ERLANG_DRIVER

注意

請*勿*將 driver_entry 宣告為 const。這是因為模擬器必須修改 handlehandle2 欄位。靜態配置且宣告為 constdriver_entry 可能位於唯讀記憶體中,這會導致模擬器崩潰。

資料類型

ErlDrvEntry

typedef struct erl_drv_entry {
    int (*init)(void);          /* Called at system startup for statically
                                   linked drivers, and after loading for
                                   dynamically loaded drivers */
#ifndef ERL_SYS_DRV
    ErlDrvData (*start)(ErlDrvPort port, char *command);
                                /* Called when open_port/2 is invoked,
                                   return value -1 means failure */
#else
    ErlDrvData (*start)(ErlDrvPort port, char *command, SysDriverOpts* opts);
                                /* Special options, only for system driver */
#endif
    void (*stop)(ErlDrvData drv_data);
                                /* Called when port is closed, and when the
                                   emulator is halted */
    void (*output)(ErlDrvData drv_data, char *buf, ErlDrvSizeT len);
                                /* Called when we have output from Erlang to
                                   the port */
    void (*ready_input)(ErlDrvData drv_data, ErlDrvEvent event);
                                /* Called when we have input from one of
                                   the driver's handles */
    void (*ready_output)(ErlDrvData drv_data, ErlDrvEvent event);
                                /* Called when output is possible to one of
                                   the driver's handles */
    char *driver_name;          /* Name supplied as command in
                                   erlang:open_port/2 */
    void (*finish)(void);       /* Called before unloading the driver -
                                   dynamic drivers only */
    void *handle;               /* Reserved, used by emulator internally */
    ErlDrvSSizeT (*control)(ErlDrvData drv_data, unsigned int command,
                            char *buf, ErlDrvSizeT len,
			    char **rbuf, ErlDrvSizeT rlen);
                                /* "ioctl" for drivers - invoked by
                                   port_control/3 */
    void (*timeout)(ErlDrvData drv_data);
                                /* Handling of time-out in driver */
    void (*outputv)(ErlDrvData drv_data, ErlIOVec *ev);
                                /* Called when we have output from Erlang
                                   to the port */
    void (*ready_async)(ErlDrvData drv_data, ErlDrvThreadData thread_data);
    void (*flush)(ErlDrvData drv_data);
                                /* Called when the port is about to be
                                   closed, and there is data in the
                                   driver queue that must be flushed
                                   before 'stop' can be called */
    ErlDrvSSizeT (*call)(ErlDrvData drv_data, unsigned int command,
                         char *buf, ErlDrvSizeT len,
			 char **rbuf, ErlDrvSizeT rlen, unsigned int *flags);
                                /* Works mostly like 'control', a synchronous
                                   call into the driver */
    void* unused_event_callback;
    int extended_marker;        /* ERL_DRV_EXTENDED_MARKER */
    int major_version;          /* ERL_DRV_EXTENDED_MAJOR_VERSION */
    int minor_version;          /* ERL_DRV_EXTENDED_MINOR_VERSION */
    int driver_flags;           /* ERL_DRV_FLAGs */
    void *handle2;              /* Reserved, used by emulator internally */
    void (*process_exit)(ErlDrvData drv_data, ErlDrvMonitor *monitor);
                                /* Called when a process monitor fires */
    void (*stop_select)(ErlDrvEvent event, void* reserved);
                                /* Called to close an event object */
 } ErlDrvEntry;
  • int (*init)(void) - 在驅動程式被 erl_ddll:load_driver/2 載入後直接呼叫(實際上是在驅動程式新增至驅動程式清單時)。驅動程式應傳回 0,或者,如果驅動程式無法初始化,則傳回 -1

  • ErlDrvData (*start)(ErlDrvPort port, char* command) - 當驅動程式被實例化時呼叫,當 erlang:open_port/2 被呼叫時。驅動程式應傳回一個數字 >= 0 或一個指標,或者,如果驅動程式無法啟動,則傳回以下三個錯誤代碼之一

    • ERL_DRV_ERROR_GENERAL - 一般錯誤,無錯誤代碼

    • ERL_DRV_ERROR_ERRNO - 錯誤,在 errno 中有錯誤代碼

    • ERL_DRV_ERROR_BADARG - 錯誤,badarg

    如果傳回錯誤代碼,則不會啟動埠。

  • void (*stop)(ErlDrvData drv_data) - 當埠關閉時呼叫,使用 erlang:port_close/1Port ! {self(), close}。請注意,終止埠擁有者程序也會關閉埠。如果 drv_data 是指向 start 中配置的記憶體的指標,則 stop 是釋放該記憶體的位置。

  • void (*output)(ErlDrvData drv_data, char *buf, ErlDrvSizeT len) - 當 Erlang 程序已將資料傳送到埠時呼叫。資料由 buf 指向,且為 len 位元組。資料會透過 Port ! {self(), {command, Data}} 或使用 erlang:port_command/2 傳送到埠。根據埠的開啟方式,它應該是整數 0...255 的列表或二進位檔。請參閱 erlang:open_port/2erlang:port_command/2

  • void (*ready_input)(ErlDrvData drv_data, ErlDrvEvent event)

  • void (*ready_output)(ErlDrvData drv_data, ErlDrvEvent event) - 當驅動程式事件(在參數 event 中指定)發出訊號時呼叫。這用於協助非同步驅動程式在發生某些情況時「喚醒」。

    在 Unix 上,event 是管道或通訊端控制代碼(或 select 系統呼叫可理解的內容)。

    在 Windows 上,event 是一個 EventSemaphore(或 WaitForMultipleObjects API 函式可理解的內容)。(模擬器中的一些技巧允許使用超過內建 64 個 Events 的限制。)

    若要將此與執行緒和非同步常式搭配使用,請在 Unix 上建立管道,並在 Windows 上建立 Event。當常式完成時,寫入管道(在 Windows 上使用 SetEvent),這會使模擬器呼叫 ready_inputready_output

    可能會發生虛假事件。也就是說,即使沒有發出真正的事件訊號,也會呼叫 ready_inputready_output。實際上,這種情況很少見(並且取決於作業系統),但穩健的驅動程式仍然必須能夠處理這種情況。

  • char *driver_name - 驅動程式名稱。它必須對應於 erlang:open_port/2 中使用的原子,以及驅動程式程式庫檔案的名稱(不含副檔名)。

  • void (*finish)(void) - 當驅動程式卸載時由 erl_ddll 驅動程式呼叫。(它只會在動態驅動程式中呼叫。)

    驅動程式只會在呼叫 erl_ddll:unload_driver/1 或模擬器停止時卸載。

  • void *handle - 此欄位保留供模擬器內部使用。模擬器將修改此欄位,因此 driver_entry 未宣告為 const 非常重要。

  • ErlDrvSSizeT (*control)(ErlDrvData drv_data, unsigned int command, char *buf, ErlDrvSizeT len, char **rbuf, ErlDrvSizeT rlen) - 使用 erlang:port_control/3 呼叫的特殊常式。它的作用有點像 Erlang 驅動程式的「ioctl」。指定給 port_control/3 的資料會到達 buflen。驅動程式可以使用 *rbufrlen 將資料傳送回去。

    這是呼叫驅動程式並取得回應的最快方法。它在 Erlang 模擬器中不會進行內容切換,也不需要訊息傳遞。當 Erlang 太慢時,它適用於呼叫 C 函式以取得更快的執行速度。

    如果驅動程式想要傳回資料,它應該將資料傳回至 rbuf。當呼叫 control 時,*rbuf 會指向預設緩衝區 rlen 位元組,該緩衝區可用於傳回資料。資料的傳回方式會因埠控制旗標(使用 erl_driver:set_port_control_flags 設定的旗標)而有所不同。

    如果 flag 設定為 PORT_CONTROL_FLAG_BINARY,則會回傳一個二進制檔案。小型二進制檔案可以透過將原始資料寫入預設緩衝區來回傳。二進制檔案也可以透過設定 *rbuf 指向以 erl_driver:driver_alloc_binary 分配的二進制檔案來回傳。這個二進制檔案會在 control 回傳後自動釋放。驅動程式可以使用 erl_driver:driver_binary_inc_refc 保留此二進制檔案以進行唯讀存取,並在稍後使用 erl_driver:driver_free_binary 釋放。在 control 回傳後,絕對不允許變更此二進制檔案。如果 *rbuf 設定為 NULL,則會回傳一個空列表。

    如果 flag 設定為 0,則資料會以整數列表形式回傳。可以使用預設緩衝區,或者設定 *rbuf 指向以 erl_driver:driver_alloc 分配的較大緩衝區。此緩衝區會在 control 回傳後自動釋放。

    如果回傳的位元組數超過幾個,使用二進制檔案會比較快。

    回傳值是 *rbuf 中回傳的位元組數。

  • void (*timeout)(ErlDrvData drv_data) - 在驅動程式的計時器達到 0 之後的任何時間呼叫。計時器使用 erl_driver:driver_set_timer 啟用。驅動程式之間沒有優先順序或順序,因此如果多個驅動程式同時逾時,則會先呼叫其中一個。

  • void (*outputv)(ErlDrvData drv_data, ErlIOVec *ev) - 每當寫入埠時呼叫。如果它是 NULL,則改為呼叫 output 函數。此函數比 output 快,因為它直接採用 ErlIOVec,不需要複製資料。埠必須處於二進制模式,請參閱 erlang:open_port/2

    ErlIOVec 包含一個適用於 writevSysIOVec,以及一個或多個二進制檔案。如果這些二進制檔案在驅動程式從 outputv 回傳時要保留,則可以將它們排隊(例如使用 erl_driver:driver_enq_bin),或者如果它們保存在靜態或全域變數中,則可以增加參考計數器。

  • void (*ready_async)(ErlDrvData drv_data, ErlDrvThreadData thread_data) - 在非同步呼叫完成後呼叫。非同步呼叫使用 erl_driver:driver_async 啟動。此函數從 Erlang 模擬器執行緒呼叫,與非同步函數不同,後者在某些執行緒中呼叫(如果啟用了多執行緒)。

  • void (*flush)(ErlDrvData drv_data) - 當埠即將關閉,且驅動程式佇列中有必須在呼叫 'stop' 之前刷新時呼叫。

  • ErlDrvSSizeT (*call)(ErlDrvData drv_data, unsigned int command, char *buf, ErlDrvSizeT len, char **rbuf, ErlDrvSizeT rlen, unsigned int *flags) - 從 erlang:port_call/3 呼叫。它的運作方式與 control 回呼很像,但使用外部詞彙格式進行輸入和輸出。

    command 是一個整數,從 Erlang 的呼叫中取得(erlang:port_call/3 的第二個引數)。

    buflen 提供呼叫的引數(erlang:port_call/3 的第三個引數)。可以使用 ei 函數解碼它們。

    rbuf 指向一個回傳緩衝區,長度為 rlen 位元組。回傳資料必須是以外部(二進制)格式表示的有效 Erlang 詞彙。這會轉換為 Erlang 詞彙並由 erlang:port_call/3 回傳給呼叫者。如果需要比 rlen 位元組更多的空間來回傳資料,則可以將 *rbuf 設定為以 erl_driver:driver_alloc 分配的記憶體。此記憶體會在 call 回傳後自動釋放。

    回傳值是 *rbuf 中回傳的位元組數。如果回傳 ERL_DRV_ERROR_GENERAL(或實際上任何小於 0 的值),則 erlang:port_call/3 會拋出 BAD_ARG

  • void (*event)(ErlDrvData drv_data, ErlDrvEvent event, ErlDrvEventData event_data) - 故意保留未記載。

  • int extended_marker - 此欄位必須等於 ERL_DRV_EXTENDED_MARKER0。舊版驅動程式(不知道擴充驅動程式介面)應將此欄位設定為 0。如果此欄位為 0,則以下所有欄位也必須0,如果是指標欄位,則必須為 NULL

  • int major_version - 如果欄位 extended_marker 等於 ERL_DRV_EXTENDED_MARKER,則此欄位必須等於 ERL_DRV_EXTENDED_MAJOR_VERSION

  • int minor_version - 如果欄位 extended_marker 等於 ERL_DRV_EXTENDED_MARKER,則此欄位必須等於 ERL_DRV_EXTENDED_MINOR_VERSION

  • int driver_flags - 此欄位用於將驅動程式功能和其他資訊傳遞到執行階段系統。如果欄位 extended_marker 等於 ERL_DRV_EXTENDED_MARKER,則它必須包含 0 或以位元方式 OR 運算的驅動程式旗標 (ERL_DRV_FLAG_*)。存在以下驅動程式旗標

    • ERL_DRV_FLAG_USE_PORT_LOCKING - 執行此驅動程式的所有埠,執行階段系統會使用埠層級鎖定,而不是驅動程式層級鎖定。如需更多資訊,請參閱 erl_driver

    • ERL_DRV_FLAG_SOFT_BUSY - 標示驅動程式實例即使在驅動程式實例將自己標記為忙碌時(請參閱 erl_driver:set_busy_port),也可以處理在 output 和/或 outputv 回呼中呼叫的情況。從 ERTS 5.7.4 開始,Erlang 分發使用的驅動程式需要此旗標(此行為一直是分發使用的驅動程式所要求的)。

    • ERL_DRV_FLAG_NO_BUSY_MSGQ - 停用忙碌埠訊息佇列功能。如需更多資訊,請參閱 erl_driver:erl_drv_busy_msgq_limits

    • ERL_DRV_FLAG_USE_INIT_ACK - 指定此旗標時,連結的驅動程式必須手動確認已使用 erl_driver:erl_drv_init_ack() 成功啟動埠。這讓實作者可以在完成一些初始非同步初始化後,讓 erlang:open_portbadarg 退出。

  • void *handle2 - 此欄位保留給模擬器的內部使用。模擬器會修改此欄位,因此宣告 driver_entry 不是 const 很重要。

  • void (*process_exit)(ErlDrvData drv_data, ErlDrvMonitor *monitor) - 當受監視的處理序退出時呼叫。drv_data 是與監視處理序的埠相關聯的資料(使用 erl_driver:driver_monitor_process),並且 monitor 對應於建立監視器時填入的 ErlDrvMonitor 結構。驅動程式介面函數 erl_driver:driver_get_monitored_process 可用於以 ErlDrvTermData 形式擷取退出處理序的處理序 ID。

  • void (*stop_select)(ErlDrvEvent event, void* reserved) - 在可以安全關閉事件物件時,代表 erl_driver:driver_select 呼叫。

    在 Unix 上,典型的實作方式是執行 close((int)event)

    引數 reserved 供未來使用,應忽略。

    與大多數其他回呼函數不同,stop_select 的呼叫與任何埠無關。沒有 ErlDrvData 引數傳遞給函數。不保證持有驅動程式鎖定或埠鎖定。呼叫 driver_select 的埠甚至可以在呼叫 stop_select 時關閉。但也可能 stop_select 是由 erl_driver:driver_select 直接呼叫。

    不允許從 stop_select 呼叫 驅動程式 API 中的任何函數。這種嚴格限制的原因是 stop_select 可能被呼叫的不穩定環境。

另請參閱

erl_driver(3), erlang, erl_ddll