GNU Radio Scheduler
1、 Scheduler 任务简述
GNU Radio中每个block的调度工作如下图所示,主要包含三个方面:

图1 block的调度工作
(1) 维护block对应的buffer的读写指针;
blcok对应的buffer读写指针在buffer章节介绍中有描述,这里不再赘述。
(2) 处理各个block的约束要求
Block的约束需求主要是四部分设置:Forecast函数的功能是告诉调度器对于每次输出的item需要多少个输入的items;Output Multiple则是通过函数set_output_multiple(int multiple)设置每次输出items的大小,如果没有足够的数据则不输出。Alignment表明数据对齐,通过函数set_alignment(int multiple)进行设置,保证输入的数据为multiple倍数大小;History主要用来设置buffer读指针的history items,一方面保证我们有足够有效的数据history items超过输出的output_items大小,另一方面它经常用于有因果关系的信号间进行工作。具体使用方式如下图:

图2 history使用
(3) 完成信息的传输
GNU Radio采用消息传递(Message)和流标签(Stream tag)两种机制在block 之间传输信息。其中Stream tag是同步标签,只能单向传输,Message是异步消息,可以向任何方向传输。在GNU Radio流图中,Stream tag实线表示,Message虚线表示。
【注意】Stream tag与数据流是并行传输模式,不是插入到原始数据流,也不会改变原始数据流,而是绑定到数据流的某一个样点,只能在block之间传递消息,不能通过天线发送出去。
2、 流图调度
2.1 模块调度初始化
流图建立后,运行流图,创建调度器。对流图的模块进行拓扑排列,获取各个block的vector信息,设置流图中所有模块的done标志,然后循环为各个block创建线程并启动线程body进行信息处理。

图3 流图的block初始化流程
2.2 模块线程整体处理
各block线程主体处理的流程如下图所示。先前的GNU Radio框架介绍提及到,模块的默认调度机制是TPB模式,即多线程调度。每个block的线程处理主体函数的实现均包含在tpb_thread_body.cc文件中,进入线程body,首先获取block的detail()信息以及当前线程ID,接着绑定线程、设置线程优先级,然后while循环进行消息、数据流的run_one_iteration操作以及返回状态处理。具体介绍代码分析部分再描述。

图4 各block线程的主体处理流程
2.3 模块线程核心处理
run_one_iteration()函数是线程主体运行的核心,包含了模块的主要功能实现。

图4 block线程核心处理流程
总的来说,run_one_iteration()函数按照模块类型(信源、信宿以及中间模块三种)进行逐一处理。针对block的每个输出端口,检查各自对应的buffer的剩余空间,从而获得block可以输出的items数目。如果没有用于输出的足够的空间,则返回BLKD_OUT(输出阻塞);同时,根据前面计算出的输出items数目,计算出所需要的输入items数目(通过forecast函数),综合检查输入的items数目是否满足要求,不满足则返回BLKD_IN;如果输入的items足够,也有足够的空间存放输出的items,则执行block::general_work()完成模块的功能。
2.3.1 try again流程
该流程主要从以下几个方面进行逻辑的判断和处理:
1、如果是固定速率的block,有一些专门的限制条件,单独分析;
2、未设置Output Multiple,针对Alignment是否设置进行判断;
3、调用forecast计算输入items,判断数据是否满足要求,回调work函数进行处理;
4、其他的异常情况判断,返回相应状态处理。
具体的实现流程如下图所示:

图5 try again处理流程
2.3.2 work流程
work流程通过获取buffer指针,调用各个block的general_work()函数进行数据处理,如果启用了work处理函数的性能计数器,则统计性能,接着根据返回值进行相应处理并返回结果状态值。

图6 work处理流程
2.3.3 DONE流程
该流程代表当前数据信息处理完成,然后设置block的done标志,通知上下游模块可以进行数据信息的输入和输出。

图7 done处理流程
3、 代码走读
3.1 调度器初始化
流图运行的时候,会根据系统选择的调度器模式(TPB)进行调度器的创建
和初始化(对流图的模块进行拓扑排列,获取各个block的vector信息(模块的上下游关系),设置流图中所有模块的done标志,循环为各个block创建线程)。

3.2 线程处理主体函数


线程主体函数处理部分,首先是获取线程ID,绑定线程,然后设置各个block的线程优先级,清除block的finish标志。接下就是信息的处理部分了,先遍历消息队列,如果有当前消息注册的句柄,则从队列中POP,然后调用该block的handler分发消息;否则将该消息塞进队列,判断队列里的最大消息数,超过容量则将最开始进队列的消息删除掉。如果不是消息而是流模块,则判断输入输出数据信息调用run_one_iteration()处理,返回处理的状态结果值。然后就针对各返回状态值进行处理:
◆ READY:通知上下游模块有足够的buffer空间和数据输出;
◆ READY_NO_OUTPUT:通知上游模块有buffer空间可以使用;
◆ DONE:告知上下游模块信息处理完成;
◆ BLKD_IN:等待数据的输入或者新的消息处理;
◆ BLKD_OUT:等待数据的缓存空间释放或者新的消息处理。
3.3 流模块数据处理
代码在block_executor.cc文件中,主要根据block类型(信源、信宿以及中间模块)进行分别处理。
3.3.1信源模块
该模块只有下游模块,综合计算输出buffer空间,如果足够则调用work处理,否则等待buffer的释放。

3.3.2信宿模块
该模块只有上游模块,如果输入数据完成且数据不满足block的output multiple,直接返回DONE;如果没有输出则表明没有输入数据,返回BLKD_IN,否则直接进入try again流程处理。

3.3.3中间模块
中间模块从上游模块接收数据,然后输出到下游模块,在处理时先确认输入的数据,然后计算输出buffer的空间,如果noutput_items值为0,则阻塞中,等待buffer,值为-1则表明已经完成,否则进入try again进行进一步的判断和分析。

3.3.4 try again处理

该部分针对block的限制需求进行相关判断分析和处理,主要包含fixed_rate、output_multiple_set、alignment、forecast以及input的check,并返回对应的处理值。如果一切都正常,则获取buffer的r_ptr指针读取输入数据,调用work进行数据的处理。
3.3.5 work处理
获取输出buffer的写指针,将输入数据调用general_work处理输出写入buffer。

3.3.6其他处理
包括调整剩余待处理的未对齐数据的unaligned属性;如果是标签流,按照新的速率进行数据传递;更新buffer的指针位置;设置新的传输速率等等。

4、 LOG日志
在block的调度时,消息模块主要是由框架进行队列的管理及分发,这部分日志没什么好查看,如果想看消息具体的实现细节,则需在各个block的实现文件中增加相应日志信息;流模块则是由调度器循环不断的处理,主要的细节处理在block_executor.cc文件中,将#define ENABLE_LOGGING 0修改为#define ENABLE_LOGGING 1,即打开日志的使能开关,就可以看到各个block在运行中的各步骤打印信息,同样如果要看模块的具体功能实现细节,则需在blcok的work实现中增加相应的日志记录。
全部回复 (0)
cll
社区创作者
