MPI中的網絡通信的原理,需要解決以下幾個問題:
1. MPI使用什么網絡協議進行通信?
2.中央數據庫是存儲在哪一台機器上?
3.集群中如果有一台機器掛掉了是否會影響其他機器?
參考: https://aosabook.org/en/openmpi.html
根據MCA, 每個框架下的模塊是可變的,例如, btl (字節傳輸層)框架下有N多個網絡協議模塊:

既然是可變的,但是我們運行的時候都沒有傳入對應的選擇參數,也就是說明有默認值。 官方文檔也說了,工程師和科學家盡可能幫我們選擇一個合理的默認值,但是對於不同的機器集群會有不同的效果,所以建議我們自己測試最好的參數。
當每個通信域(包括MPI_COMM_WORLD和MPI_COMM_SELF)被創建時,每個可用模塊被詢問是否需要在新通信域中使用。模塊可以拒絕被使用,例如,一個基於共享內存的模塊只有當通信域中的所有進程都在相同的物理節點上時,才允許被使用。通信域將會選擇最高優先級的模塊使用。
當然,這個也是可以讓用戶更改的
根據 https://aosabook.org/en/openmpi.html 中介紹的,用戶可以通過傳入MCA命令行參數去改變運行時的模塊
再看到位於 c/send.c 文件中的Send函數定義:
#if OMPI_BUILD_MPI_PROFILING #if OPAL_HAVE_WEAK_SYMBOLS #pragma weak MPI_Send = PMPI_Send #endif #define MPI_Send PMPI_Send #endif static const char FUNC_NAME[] = "MPI_Send"; int MPI_Send(const void *buf, int count, MPI_Datatype type, int dest, int tag, MPI_Comm comm) { int rc = MPI_SUCCESS; MEMCHECKER( memchecker_datatype(type); memchecker_call(&opal_memchecker_base_isdefined, buf, count, type); memchecker_comm(comm); ); if ( MPI_PARAM_CHECK ) { OMPI_ERR_INIT_FINALIZE(FUNC_NAME); if (ompi_comm_invalid(comm)) { return OMPI_ERRHANDLER_INVOKE(MPI_COMM_WORLD, MPI_ERR_COMM, FUNC_NAME); } else if (count < 0) { rc = MPI_ERR_COUNT; } else if (tag < 0 || tag > mca_pml.pml_max_tag) { rc = MPI_ERR_TAG; } else if (ompi_comm_peer_invalid(comm, dest) && (MPI_PROC_NULL != dest)) { rc = MPI_ERR_RANK; } else { OMPI_CHECK_DATATYPE_FOR_SEND(rc, type, count); OMPI_CHECK_USER_BUFFER(rc, buf, type, count); } OMPI_ERRHANDLER_CHECK(rc, comm, rc, FUNC_NAME); } if (MPI_PROC_NULL == dest) { return MPI_SUCCESS; } OPAL_CR_ENTER_LIBRARY(); rc = MCA_PML_CALL(send(buf, count, type, dest, tag, MCA_PML_BASE_SEND_STANDARD, comm)); OMPI_ERRHANDLER_RETURN(rc, comm, rc, FUNC_NAME); }
前面一堆都是錯誤檢查,會讓不合法的操作不會真正的進行 send 這個操作。
最后看到關鍵的發送代碼:
rc = MCA_PML_CALL(send(buf, count, type, dest, tag, MCA_PML_BASE_SEND_STANDARD, comm));
MCA_PML_CALL 是一個宏,我們在 pml.h 中可以找到它:
#if MCA_ompi_pml_DIRECT_CALL
#include MCA_ompi_pml_DIRECT_CALL_HEADER
#define MCA_PML_CALL_STAMP(a, b) mca_pml_ ## a ## _ ## b
#define MCA_PML_CALL_EXPANDER(a, b) MCA_PML_CALL_STAMP(a,b)
#define MCA_PML_CALL(a) MCA_PML_CALL_EXPANDER(MCA_ompi_pml_DIRECT_CALL_COMPONENT, a)
#else
#define MCA_PML_CALL(a) mca_pml.pml_ ## a
#endif
由於 if 下代碼塊搜索不到,所以我們直接看 else 中的 mca_pml.pml_ send(buf, count, type, dest, tag, MCA_PML_BASE_SEND_STANDARD, comm)
其實 mca_pm 是一個導出的 mca_pml_base_module_t 變量:
OMPI_DECLSPEC extern mca_pml_base_module_t mca_pml;
mca_pml_base_module_t 的定義如下:
struct mca_pml_base_module_1_0_1_t {
/* downcalls from MCA to PML */
mca_pml_base_module_add_procs_fn_t pml_add_procs;
mca_pml_base_module_del_procs_fn_t pml_del_procs;
mca_pml_base_module_enable_fn_t pml_enable;
mca_pml_base_module_progress_fn_t pml_progress;
/* downcalls from MPI to PML */
mca_pml_base_module_add_comm_fn_t pml_add_comm;
mca_pml_base_module_del_comm_fn_t pml_del_comm;
mca_pml_base_module_irecv_init_fn_t pml_irecv_init;
mca_pml_base_module_irecv_fn_t pml_irecv;
mca_pml_base_module_recv_fn_t pml_recv;
mca_pml_base_module_isend_init_fn_t pml_isend_init;
mca_pml_base_module_isend_fn_t pml_isend;
mca_pml_base_module_send_fn_t pml_send;
mca_pml_base_module_iprobe_fn_t pml_iprobe;
mca_pml_base_module_probe_fn_t pml_probe;
mca_pml_base_module_start_fn_t pml_start;
mca_pml_base_module_improbe_fn_t pml_improbe;
mca_pml_base_module_mprobe_fn_t pml_mprobe;
mca_pml_base_module_imrecv_fn_t pml_imrecv;
mca_pml_base_module_mrecv_fn_t pml_mrecv;
/* diagnostics */
mca_pml_base_module_dump_fn_t pml_dump;
/* FT Event */
mca_pml_base_module_ft_event_fn_t pml_ft_event;
/* maximum constant sizes */
uint32_t pml_max_contextid;
int pml_max_tag;
int pml_flags;
};
typedef struct mca_pml_base_module_1_0_1_t mca_pml_base_module_1_0_1_t;
typedef mca_pml_base_module_1_0_1_t mca_pml_base_module_t;
哦!!!這下明白了吧!!!我們所有的進行的 send , recv 等點對點 (PML) 的通信函數都封裝在了這個結構體 的 函數指針成員里。
為什么要這么做呢?
——之前我們說過它支持不同的通信協議,在用戶沒有特定輸入的時候,默認選擇最高優先級的通信協議。如果每個通信協議都對應一套函數,那不是很麻煩???
為了讓這個設計簡單,可維護,用一個 base 封裝起常見的操作,改變函數指針即可以改變使用的協議啦!!
那么下一節,我們就得看看,這個導出的 mca_pml_base_module_t 變量 mca_pm 的 函數指針在哪里初始化?——也就是,我們要看看它如何選擇通信協議的!
