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)
// 将上述申请到的堆区地址进行初始化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__);
// 系统调用就是用系统打印进行输出