下載libibverbs最新代碼,https://downloads.openfabrics.org/verbs/README.html 為1.2.0版本。后面開始逐步分析libibverbs源碼。
1、ibv_get_device_list:
該函數具體的實現在libibverbs-1.2.0/src/devices.c文件中。
struct ibv_device **__ibv_get_device_list(int *num) { …… pthread_once(&device_list_once, count_devices); //count_devices這個函數在本進程中僅執行一次。具體解析見1.1 …… l = calloc(num_devices + 1, sizeof (struct ibv_device *)); //分配n個長度為size的連續空間,並將連續空間清零 …… for (i = 0; i < num_devices; ++i) l[i] = device_list[i]; if (num) *num = num_devices; return l; }
1.1 pthread_once:
在多線程環境中,有些事僅需要執行一次。通常當初始化應用程序時,可以比較容易地將其放在main函數中。但當你寫一個庫時,就不能在main里面初始化了,你可以用靜態初始化,但使用一次初始化(pthread_once)會比較容易些。
int pthread_once(pthread_once_t *once_control, void (*init_routine) (void));
功能:本函數使用初值為PTHREAD_ONCE_INIT的once_control變量保證init_routine()函數在本進程執行序列中僅執行一次。
Linux Threads使用互斥鎖和條件變量保證由pthread_once()指定的函數執行且僅執行一次,而once_control表示是否執行過。如果once_control的初值不是PTHREAD_ONCE_INIT(Linux Threads定義為0),pthread_once() 的行為就會不正常。在LinuxThreads中,實際"一次性函數"的執行狀態有三種:NEVER(0)、IN_PROGRESS(1)、DONE (2),如果once初值設為1,則由於所有pthread_once()都必須等待其中一個激發"已執行一次"信號,因此所有pthread_once ()都會陷入永久的等待中;如果設為2,則表示該函數已執行過一次,從而所有pthread_once()都會立即返回0。
接着繼續分析,重點函數count_devices,count_devices->ibverbs_init(init.c)。在ibverbs_init函數中,會先獲取系統路徑一般為/sys,然后檢查內核ABI版本,對於低於或高於某個ABI版本,ibverbs就不支持了。關於ABI可以參考https://blog.csdn.net/juS3Ve/article/details/82782987,具體說ABI與cpu架構和OS有關。
接下來會檢查資源限制,如下:
static void check_memlock_limit(void) { struct rlimit rlim; if (!geteuid()) return; if (getrlimit(RLIMIT_MEMLOCK, &rlim)) { //進程可鎖定在內存中的最大數據量,字節為單位。 fprintf(stderr, PFX "Warning: getrlimit(RLIMIT_MEMLOCK) failed."); return; } if (rlim.rlim_cur <= 32768) fprintf(stderr, PFX "Warning: RLIMIT_MEMLOCK is %lu bytes.\n" " This will severely limit memory registrations.\n", rlim.rlim_cur); }
這里要求進程中可鎖定在內存中的最大數據量,軟限制要大於32K。否則會影響到內存注冊。在使用時,可以在系統上設置ulimit取消限制。
接下來讀取配置文件調用函數read_config(),該函數會從/sysocnfdir/libibverbs.d目錄下讀取配置文件,在我的系統中配置文件路徑為/etc/libibverbs.d,在這個目錄下內容如下:
linux-MgXfWk:/etc/libibverbs.d # ls bnxt_re.driver cxgb4.driver hns.driver ipathverbs.driver mlx5.driver nes.driver qedr.driver vmw_pvrdma.driver cxgb3.driver hfi1verbs.driver i40iw.driver mlx4.driver mthca.driver ocrdma.driver rxe.driver linux-MgXfWk:/etc/libibverbs.d # cat cxgb4.driver driver cxgb4 linux-MgXfWk:/etc/libibverbs.d # cat mlx5.driver driver mlx5
從這些文件中讀取driver名字,然后將他們加入到一個鏈表中,鏈表名稱為driver_name_list。
然后是調用find_sysfs_devs函數,獲取/sys/class/infiniband_verbs目錄下的設備文件,查看內核代碼,在uverbs_main.c中,會創建對應的設備。
那么現在的問題是:
在find_sysfs_devs函數中,會去查找sys/class/infiniband_verbs目錄下的設備文件,那么這些文件是來自於哪里,是如何創建的?接下來我們來看一下,infiniband的內核代碼,在內核代碼中的路徑為./drivers/infinniband/core/uverbs_main.c,從這個文件看起。
這個文件里有一個module_init函數,這個是模塊被加載后第一個調用的模塊,對應的初始化函數為ib_uverbs_init。查找編譯文件,可以查看到,這個模塊的名字為ib_uverbs.ko。當我們執行insmod ib_uverbs.ko時,就會調用ib_uverbs_init函數。
static int __init ib_uverbs_init(void)
我們看到這個函數,有__init標志,這個標志的意思是:
__init宏告知編譯器,將變量或函數放在一個特殊的區域,這個區域定義在vmlinux.lds中。__init將函數放在".init.text"這個代碼區中,__initdata將數據放在".init.data"這個數據區中。標記為初始化的函數,表明該函數供在初始化期間使用。在模塊裝載之后,模塊裝載就會將初始化函數扔掉。這樣可以將該函數占用的內存釋放出來。
也就是,這里的初始化函數,會在模塊裝載后,它使用的內存會被釋放。
首先調用register_chrdev_region,該函數與alloc_chrdev_region函數一樣都是向系統申請設備號,區別在於,前一個函數用於已知起始設備的設備號的情況,而alloc_chrdev_region用於設備號未知,向系統動態申請未被占用的設備號的情況。
接下來的內容,我們在下一個節展開。