Xenomai Edition v3.0.5
xenomai_init()
static int __init xenomai_init(void)
源碼分析
setup_init_state
// 配置Xenomai爲啓動狀態
CONFIG_SMP
// 判斷處理器CPU系統架構是否爲 SMP,是則對每一個CPU進行處理,設置標誌位
xnsched_register_classes();
// Linux Domain 調用流程結構初始化,重點關注 xnsched_class_rt
實時線程調用相關的回調函數參數
ret = xnprocfs_init_tree();
// 在 proc 文件夾下建立 Xenomai 文件夾
ret = mach_setup();
// 對系統的信息、時鐘頻率、設備的Pipeline底層進行初始化管理、底層硬件的匹配-初始化最底層中斷信號等操作的傳輸方式 pipeline,配置了 對應底層設備中斷信號的處理函數,通過取得的 CPU 時鐘參數設置 Xenomai 運行的時鐘週期
ret = ipipe_select_timers(&xnsched_realtime_cpus);
// 系統時鐘配置ipipe_get_sysinfo(&sysinfo);
// 獲取系統時鐘、頻率參數 賦值非全局變量cobalt_pipeline.timer_freq = timerfreq_arg;
// 使用更新後的全局變量對 Cobalt PipeLine 進行初始化if (cobalt_machine.init)
// 對於某些特定的機器 CPU 需要初始化的進行初始化操作virq = ipipe_alloc_virq();
// 通過系統底層pipe服務申請irq中斷,綁定到實時核 Xenomai 的資源上ret = xnclock_init(cobalt_pipeline.clock_freq);
// 初始化 Xenomai 的系統時鐘xnclock_update_freq(freq);
// 從Pipe服務底層獲取自旋鎖讀取CPU時鐘頻率nktimerlat = xnarch_timer_calibrate();
// 通過各個機器CPU自帶的標準矯正函數對時鐘頻率進行多次測算求平均得到準確的時鐘xnclock_reset_gravity(&nkclock);
// 根據機器配置設置latence補償xnclock_register(&nkclock, &xnsched_realtime_cpus);
// 生效上述時鐘配置,同時在/proc/xenomai/
文件夾下建立 CPU 相關文件
xnintr_mount();
// 中斷向量初始化
ret = xnpipe_mount();
// Xenomai 管道初始化、加載、創建pipe管道設備(device_create)、註冊字符設備(register_chrdev)
root@MM5718v1:~# ls -al /dev/rtp1
crw------- 1 root root 150, 1 Dec 26 08:14 /dev/rtp1
ret = xnselect_mount();
//
ret = sys_init();
// Xenomai 系統初始化流程
-
sysheap_size_arg = CONFIG_XENO_OPT_SYS_HEAPSZ;
// 從配置文件中獲取系統 heap 的大小 -
heapaddr = xnheap_vmalloc(sysheap_size_arg * 1024);
// 從Linux底層申請獲得指定大小的 heap 堆區,實際上獲取的內存來源於 ZONE_NORMAL 區域,我們需要的內存區域的物理地址並不要求其是連續的 -
xnheap_init(&cobalt_heap, heapaddr, sysheap_size_arg * 1024)
// 將上述申請到的堆區地址進行初始化size > XNHEAP_MAXHEAPSZ || !IS_ALIGNED(size,XNHEAP_PAGESZ)
// 分配的內存大小必須小於最大的Heap大小 2Gb = 256MB 且 必須是 PAGE_SIZE 的整數倍struct xnheap *heap
// 初始化堆區管理對象 xnheap 的成員變量heap->pagemap = kmalloc(sizeof(struct xnpagemap) * heap->npages,GFP_KERNEL);
// 爲管理 heap 頁創建對應數量的 pagemap 用於heap管理init_freelist(heap);
// 初始化配置 heap 管理結構體 pagemap,這裏還有一個重要的流程就是將heap->freelist
初始化,如下所示:init_freelist
注:/* Mark each page as free in the page map. */ for (n = 0, freepage = heap->membase; n < lastpgnum; n++, freepage += XNHEAP_PAGESZ) { *((caddr_t *)freepage) = freepage + XNHEAP_PAGESZ; heap->pagemap[n].type = XNHEAP_PFREE; heap->pagemap[n].bcount = 0; } ... heap->freelist = heap->membase;
*((caddr_t *)freepage) = freepage + XNHEAP_PAGESZ;
這裏初始化freelist
的關鍵在於通過將一級指針轉換爲二級指針,並將內存下一頁的首地址存儲到當前二級指針指向的指針內容當中,從而完成空閒內存list
的延申,這個處理非常重要,在後續 rt_heap_create 的過程中需要用來取空閒頁的首地址.
-
xnheap_set_name(&cobalt_heap, "system heap");
// 設置申請的Heap名稱爲 [system heap] -
xnsched_init(sched, cpu);
// 此處爲系統實時線程底層支持管理的初始化部分struct xnthread_init_attr attr;
// 線程狀態、名稱、子線程、所屬CPU-ID等資源管理對象定義struct xnsched_class *p;
// 線程初始化、入隊列、出隊列等其他操作回調函數鏈表封裝for_each_xnsched_class(p)
// 對每一種類的 sched 進行循環初始化操作,對於 RT-sched 的初始化操作實際上爲對創建好的 prio 優先級 map 進行初始化配置__xnthread_init
// 配置初始化線程根對象 sched->rootcb- 配置初始化線程管理模塊的定時器
timer
,主要的目的是用來記錄各個子線程相對於當前運行的總時長:xnstat_exectime_set_current(sched, &sched->rootcb.stat.account);
xnthread_init_root_tcb(&sched->rootcb);
// 初始化線程控制 TCB(Thread Control Block): 對象,\(TCB\) 僅包含了線程執行需要的 PC、SP、Condition Code、Data Register#ifdef CONFIG_XENO_OPT_WATCHDOG
// 這裏可以配置選擇是否啓用看門狗
-
xnregistry_init();
// Xenomai 內核對象管理初始化,提供內核對象存儲和快速檢索registry_obj_slots = kmalloc(...
// 系統各個模塊對象寄存器管理槽對象ret = xnvfile_init_dir("registry", ®istry_vfroot, &cobalt_vfroot);
// 創建 registry 的目錄ret = xnvfile_init_regular("usage", &usage_vfile, ®istry_vfroot);
// 創建 usage 的文件,usage實際用來記錄整個系統資源使用的情況root@MM5718v1:~# cat /proc/xenomai/registry/usage 18/2048
proc_apc = xnapc_alloc("registry_export", ®istry_proc_schedule, NULL);
// 申請一個APC slot, 用於註冊關注當前sched中的程序的加載情況,在後面其他代碼中實際上輸出到 apc 文件當中.root@MM5718v1:~# cat /proc/xenomai/apc APC CPU0 0: 0 (pipe_wakeup) 1: 0 (selector_list_destroy) 2: 0 (registry_export)
nr_object_entries = xnregistry_hash_size();
// 根據CONFIG_XENO_OPT_REGISTRY_NRSLOTS
編譯參數獲取對應的 hash 空間大小,這裏的hash結果用來方便在cobalt系統中查找對應名稱的內核對象、如:有名信號量(sem)、有名消息隊列(mq)、進程間通訊 xddp、iddp 等object_index = kmalloc(sizeof(*object_index) *nr_object_entries, GFP_KERNEL);
// 獲取對應 hash-size 大小的的 hlist_head 鏈表空間INIT_HLIST_HEAD(&object_index[n]);
// 初始化所有的鏈表空間xnsynch_init(®ister_synch, XNSYNCH_FIFO, NULL);
// 初始化底層 sched 的方式,鏈表等控制對象,實現的同步方式爲 XNSYNCH_FIFO 隊列方式,將 apc 的調用過程實現爲逐步執行的過程。提供了線程與資源同步互斥管理的功能set_realtime_core_state(COBALT_STATE_RUNNING);
// 配置當前 Xenomai 的系統狀態爲運行狀態,可以開始運行
-
ret = mach_late_setup();
// 針對特定的CPU機器,可以增加後續的初始化程序函數,用來適配該CPU的其他功能,相當於CPU功能使能的預留功能 -
ret = rtdm_init();
// 初始化實時操作系統的設備管理模塊xntree_init(&protocol_devices);
// 實際上就是使用 rb_root 紅黑樹數據結構對相關設備進行管理記錄,從而提高系統查詢設備相關參數的速度- 在設備 /dev 目錄下創建rtdm設備節點,系統中如下所示:
root@MM5718v1:~# ls -al /dev/rtdm/ drwxr-xr-x 2 root root 180 Dec 30 23:32 . drwxr-xr-x 14 root root 15780 Dec 30 23:32 .. crw-rw---- 1 root root 235, 0 Dec 30 23:32 EtherCAT0 crw-rw---- 1 root root 243, 0 Dec 30 21:48 autotune crw-rw---- 1 root root 246, 0 Dec 30 21:48 memdev-private crw-rw---- 1 root root 246, 1 Dec 30 21:48 memdev-shared crw-rw---- 1 root root 245, 0 Dec 30 21:48 memdev-sys crw-rw---- 1 root root 241, 0 Dec 30 21:48 switchtest crw-rw---- 1 root root 242, 0 Dec 30 21:48 timerbench
-
ret = cobalt_init();
// cobalt 內核初始化函數pthread_t ptid = pthread_self();
// 獲取當前線程自身的線程 IDcobalt_default_condattr_init();
// 使用系統自帶的條件變量模板進行變量屬性的控制,確保創建條件變量是的屬性與系統屬性相同:static pthread_condattr_t cobalt_default_condattr; 方便 cobalt 內核後續初始化過程中的使用需求__cobalt_init();
// 初始化low_init();
// 底層的 cobalt 的內存映射等相關內容的初始化old_sigill_handler = signal(SIGILL, sigill_handler);
// 信號異常處理函數註冊、ABI、XenomaiFeature等部分的初始化cobalt_check_features(f);
// 映射 /dev/mem 設備節點,如下所示root@MM5718v1:~# ls -al /dev/mem crw-r----- 1 root kmem 1, 1 Dec 30 21:48 /dev/mem
cobalt_init_umm(f->vdso_offset);
// 映射cobalt設備內存區域節點到 /dev/rtdm/memdev-private 上。 注: #define map_umm(__name, __size_r) __map_umm("/dev/rtdm/" __name, __size_r) 函數用來映射字符驅動設備節點到內存地址空間上,【memory heaps mapped】pthread_once(&init_bind_once, init_bind);
// 映射內存區域到 /dev/rtdm/memdev-private , 這裏注意 pthread_once 表示該線程調用的函數在系統當中只執行一次(其機理是通過 Mutex 互斥鎖改變執行標誌位從而確保線程函數僅執行一次.)init_loadup(vdso_offset);
// 初始化映射 /dev/rtdm/memdev-shared 節點, vdso_offset 爲 cobalt 內核的起始偏移量
cobalt_init_current_keys();
// 創建 線程私有key鍵值ret = pthread_key_create(&cobalt_current_key, NULL);
// 創建 cobalt_current_key 鍵值,通過 int pthread_setspecific(pthread_key_t key, const void pointer) 函數將鍵值與制定的線程內部的全局變量指針進行綁定,從而實現不同線程操作相同名稱的鍵值,實際修改了不同的全局變量的功能。
cobalt_ticks_init(f->clock_freq);
// cobalt 的基礎時鐘 ticks 初始化
sa.sa_sigaction = cobalt_sigdebug_handler;
// cobalt 核心調試啓動過程的信號處理,可能造成的失敗的原因包括了 [SIGDEBUG_NOMLOCK, SIGDEBUG_RESCNT_IMBALANCE, SIGDEBUG_MUTEX_SLEEP, SIGDEBUG_WATCHDOG]pthread_atfork(NULL, NULL, cobalt_fork_handler);
// 暫未執行相關代碼cobalt_mutex_init();
// cobalt 核心的 mutex 鎖參數屬性模板初始化,保證在後續創建的鎖的對象屬性以此爲模板,在 Xenomai 實時核心的 mutex lock 實際上是對 Linux native Mutex lock 原生鎖的封裝,增加了特定的屬性。cobalt_thread_init();
// 初始化 cobalt 核心的線程基礎屬性參數、sched 線程調度優先級等cobalt_print_init();
// 初始化打印的緩衝區大小、緩衝區內存空間等
policy = SCHED_FIFO;
// 將當前主進程切換到 實時 cobalt 核上rtdm_fd_init();
// 初始化 rtdm 文件節點對象
rt_heap_create()
int rt_heap_create(RT_HEAP *heap, const char *name, size_t heapsz, int mode)
源碼分析
if (heapsz == 0 || heapsz >= 1U << 31)
// 判斷申請的heap內存的大小必須限制在 1Byte~2Gb 之間,不在範圍內則返回輸入參數無效返回值 [-EINVAL]
if (mode & ~(H_PRIO|H_SINGLE))
// 判斷申請heap內存的模式是否正確,不正確則返回
hcb = xnmalloc(sizeof(*hcb));
// 從 cobalt 系統heap 內存中爲 hcb 變量申請內存空間
xnheap_alloc(&cobalt_heap, size)
// 從系統內存對象 cobalt_heap 中申請 size 大小的內存空間size = ALIGN(size, XNHEAP_PAGESZ);
// 根據需要申請的內存空間的大小通過 ALIGN 函數進行【字節、塊、頁】對齊操作:- 當申請的內存大小大於一頁
XNHEAP_PAGESZ
以上時,按照頁對齊的方式分配內存空間,size = k*XNHEAP_PAGESZ
; - 當申請的內存大小小於等於
XNHEAP_MINALIGNSZ
16Bytes時,按照字節對齊的方式分配內存空間,size = ALIGN(size, XNHEAP_MINALLOCSZ);
; @XNHEAP_MINALLOCSZ = 8Bytes - 當申請的內存大小介於中間
XNHEAP_MINALIGNSZ < size < XNHEAP_PAGESZ
時,按照16字節對齊的方式分配內存空間,size = ALIGN(size, XNHEAP_MINALIGNSZ);
; @XNHEAP_MINALIGNSZ = 16Bytes
- 當申請的內存大小大於一頁
if (likely(size <= XNHEAP_PAGESZ * 2))
// 如果申請的內存大小小於 <2Pages 大小時,使用 bucketed memory blocks* 進行內存分配bsize = size < XNHEAP_MINALLOCSZ ? XNHEAP_MINALLOCSZ : size;
// 將 size 的值設置爲大於XNHEAP_MINALLOCSZ
大小的值log2size = order_base_2(bsize);
// order_base_2 函數用來尋找第一個大於輸入參數 bsize 的2的冪次的次數值,存儲在 log2size 返回值當中bsize = 1 << log2size;
// 將需要申請的內存空間 size 大小轉換爲2的整冪次的值 bsizeilog = log2size - XNHEAP_MINLOG2;
// ilog 變量中存儲的是 申請空間的總字節數/8Bytes的結果,表示爲 8Bytes 的整倍數xnlock_get_irqsave(&heap->lock, s);
// 在操作全局系統的 heap 對象前,應該獲取鎖避免 heap 內存操作競爭block = heap->buckets[ilog].freelist;
// 取出 buckets 管理的 8Bytes 大小的內存池空閒列表,ilog 相當於一個標誌位,標誌這 log2size - XNHEAP_MINLOG2 大小的內存塊.if (block == NULL)
// 判斷是否還未分配產生過第一個 buckets 內存塊,如果未產生則尋找滿足要求的全新的一頁中開始分配,如下block = get_free_range(heap, bsize, log2size);
// 如果取出的 freelist 爲空地址,則調用此接口獲取大小爲 \(2^{log2size}\) 大小的連續空閒頁,調用此接口之前必須要獲取到 heap-lock 才能操作,通過不斷的向後延伸直到取得滿足申請 heap bsize 大小的條件則記錄下當前的lastpage
內存結束頁地址,內存分配起始地址headpage
,並更新當前Xenomai系統的空閒頁起始地址heap->freelist = *((caddr_t *)lastpage);
,這裏需要注意函數中使用的do{...}while(...)
語句中的操作,實際上完成了對heap->freelist
的掃描: 參考這裏:init_freelist
取強轉後的地址中的值作爲下一頁的起始地址,而地址中的值在do { lastpage = freepage; freepage = *((caddr_t *) freepage); // 這裏取地址中的值作爲下一頁的起始地址,參見測試代碼. freecont += XNHEAP_PAGESZ; } while (freepage == lastpage + XNHEAP_PAGESZ && freecont < bsize);
init_freelist
函數中已經完成了賦值操作,關於地址強轉取內容的測試如下 PointerCastTest:
測試結果如下 (AM5718@ARM 32Bits SMP Arch) :typedef void * vaddr_t; vaddr_t p; p = (vaddr_t)malloc(64); *(int *)p = 10; for(int i=0; i<64 ; i++) { *(int *)(p+i) = 10; // printf("The Data Content of P[%d]:%x\n", i, *(int *)(p+i)); } vaddr_t PTest = p; printf("PTest = %p=%p @ PTest Address:%p Content:%x\n", PTest, p, &PTest, *(int *)PTest); PTest = *((vaddr_t*)PTest); printf("PTest = %p=%p @ PTest Address:%p\n", PTest, p, &PTest);
root@MM5718v1:~# ./Burnish/RTDemoExe PTest = 0x2b0f8=0x2b0f8 @ PTest Address:0xbef10af0 Content:a0a0a0a PTest = 0xa0a0a0a=0x2b0f8 @ PTest Address:0xbef10af0
- goto splitpage; 完成空閒頁的申請操作後,跳轉到頁分塊的位置進行分塊,進入到這個位置後的 headpage 是第一個滿足大於等於需求內存大小的連續內存空間的起始頁地址.
- 當申請的內存空間大小 bsize < XNHEAP_PAGESZ 小於頁大小時,將該頁剩餘的內存空間切割爲和 bsize 大小相同的若干個 heap block 內存塊,同時使用 block 作爲塊內存的 list 記錄下來. 如果申請空間大於等於一頁的時候,將 *((caddr_t *)headpage) 設置爲
NULL
,實際即爲將 headpage 指向的下一頁地址清除,僅保留當前頁的起始地址. pagenum = (headpage - heap->membase) / XNHEAP_PAGESZ;
// 計算當前頁的相對於內存 heap->membase 起始地址的間隔總數併除以 pagesize 從而計算出 pagenumheap->pagemap[pagenum].type = log2size ? : XNHEAP_PLIST;
// 根據輸入的 log2size 參數,確定是否爲塊的開始,或者確切的子塊的大小 log2size,頁按照 size 大小分割許多子塊.heap->pagemap[pagenum].bcount = 1;
// 設置當前塊爲激活狀態for (pagecont = bsize / XNHEAP_PAGESZ; pagecont > 1; pagecont--)
// 當申請的空間大小大於 2*XNHEAP_PAGESZ 的情況下時,配置每一頁爲 XNHEAP_PCONT 類型,塊激活狀態爲 0.
- 當申請的內存空間大小 bsize < XNHEAP_PAGESZ 小於頁大小時,將該頁剩餘的內存空間切割爲和 bsize 大小相同的若干個 heap block 內存塊,同時使用 block 作爲塊內存的 list 記錄下來. 如果申請空間大於等於一頁的時候,將 *((caddr_t *)headpage) 設置爲
- goto splitpage; 完成空閒頁的申請操作後,跳轉到頁分塊的位置進行分塊,進入到這個位置後的 headpage 是第一個滿足大於等於需求內存大小的連續內存空間的起始頁地址.
else
// 如果已經存在heap->buckets[ilog].freelist
則直接從中分配,並將 ilog 大小的塊--heap->buckets[ilog].fcount;
的總數減一.pagenum = ((caddr_t)block - heap->membase) / XNHEAP_PAGESZ;
// 頁編號通過 block 與 membase 起始地址間隔,從而計算出頁的編號++heap->pagemap[pagenum].bcount;
// 根據計算的到的頁的編號,將對應激活塊的數量+1.
heap->buckets[ilog].freelist = *((caddr_t *)block);
// 完成塊的操作之後,更新buckets[ilog].freelist
的列表,向後延申heap->used += bsize;
// 記錄系統 heap 總使用量.
else {
// 針對分配內存大於 >2Pages 的處理邏輯block = get_free_range(heap, size, 0);
// 直接使用該函數分配足夠的頁即可heap->used += size;
// 分配成功則統計 heap 總使用量.
if (heapobj_init(&hcb->hobj, NULL, heapsz))
// 使用 TLSF 內存管理架構申請大片內存空間,申請失敗則返回 [-ENOMEM]
__heapobj_init_private(hobj, name, size, NULL);
// 系統調用,將通過 TLSF 算法申請到的 heap 內存空間賦值給 hobj 結構體管理,可以看到 tlsf 算法實際申請的內存空間的位置在 cobalt 管理的 private-mem-pool 區域.size += tlsf_pool_overhead;
// 這裏使用全局變量記錄了 字節數量的起始值 tlsf_pool_overhead
測試調試方法
-
cobalt 內核代碼部分調試:
printk(XENO_WARNING "disabled on kernel command line\n");
// 使用 printk 來進行調試輸出打印
-
cobalt 系統調用接口調試:
warning("[%s]%d\n",__FUNCTION__,__LINE__);
// 系統調用就是用系統打印進行輸出printf("[%s]%d\n",__FUNCTION__,__LINE__);
// 系統調用就是用系統打印進行輸出