Xenomai 源碼分析-Part I

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) // 將上述申請到的堆區地址進行初始化

  • 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", &registry_vfroot, &cobalt_vfroot); // 創建 registry 的目錄
    • ret = xnvfile_init_regular("usage", &usage_vfile, &registry_vfroot); // 創建 usage 的文件,usage實際用來記錄整個系統資源使用的情況
      root@MM5718v1:~# cat /proc/xenomai/registry/usage 
      18/2048
      
    • proc_apc = xnapc_alloc("registry_export", &registry_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(&register_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(); // 獲取當前線程自身的線程 ID
    • cobalt_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_offsetcobalt 內核的起始偏移量
        • 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 函數進行【字節、塊、頁】對齊操作:
      1. 當申請的內存大小大於一頁 XNHEAP_PAGESZ 以上時,按照頁對齊的方式分配內存空間, size = k*XNHEAP_PAGESZ
      2. 當申請的內存大小小於等於 XNHEAP_MINALIGNSZ 16Bytes時,按照字節對齊的方式分配內存空間,size = ALIGN(size, XNHEAP_MINALLOCSZ);@XNHEAP_MINALLOCSZ = 8Bytes
      3. 當申請的內存大小介於中間 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的整冪次的值 bsize
      • ilog = 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:
          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);
          
          測試結果如下 (AM5718@ARM 32Bits SMP Arch)
          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 從而計算出 pagenum
            • heap->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.
      • else // 如果已經存在 heap->buckets[ilog].freelist 則直接從中分配,並將 ilog 大小的塊 --heap->buckets[ilog].fcount; 的總數減一.
        • pagenum = ((caddr_t)block - heap->membase) / XNHEAP_PAGESZ; // 頁編號通過 blockmembase 起始地址間隔,從而計算出頁的編號
        • ++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

測試調試方法

  1. cobalt 內核代碼部分調試:

    • printk(XENO_WARNING "disabled on kernel command line\n"); // 使用 printk 來進行調試輸出打印
  2. cobalt 系統調用接口調試:

    • warning("[%s]%d\n",__FUNCTION__,__LINE__); // 系統調用就是用系統打印進行輸出
    • printf("[%s]%d\n",__FUNCTION__,__LINE__); // 系統調用就是用系統打印進行輸出
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章