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__); // 系统调用就是用系统打印进行输出
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章