How GNURadio Core Works

本文详述了GNU Radio核心调度器的工作原理,从如何调用和执行调度任务,到每个模块线程的具体运作过程,揭示了调度器如何通过多线程方式高效管理信号流。重点介绍了调度器的选择与实现,以及如何确保线程间的数据同步与冲突避免。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

I find a paper about GNURADIO COREand think it is a good reference. I will use Chinese to introduceit.
我会用中文主要介绍一下这个GNURADIO COREWORK主要讲什么东西。
Contents
1 How the GNU Radio scheduler iscalled and what it does     1
2 How a thread of each block works                       5

   内容
1.  GNU Radio 调度器怎样调用 和怎么工作
2. 每个模块的线程怎么工作
1 How the GNU Radio scheduler is called and what itdoes
As we become quite familiar with GNU Radio Python codes now,it is essential to figure out
what's going on behind the plain Python codes; that is, howGNU radio core works. A typical
example of GNU Radio 3 is as follows. (There is an olddial-tone example using flow_graph(),
which is obsolete! )
上面的话没太多意思,就是让我们从dial-tone开始去探索GNURadio怎么工作
[转载]How <wbr>GNU <wbr>Radio <wbr>Core <wbr>Works <wbr>-- <wbr>An <wbr>Analys
Besides some routine connections for blocks, the GNU Radiorunning thread starts at
my_top_block().run()
We are going to figure out what's going on after thiscode.  Since Python and C++ classes are 1-1corresponding via SWIG, We have to go deeper  andto find the run() function in C++class  gr_top_block.
这里告诉你,PYTHON和C++通过SWIG对应起来,我们要了解 python里run()这个函数,就要深入C++去了解。
Infile:  gr top block.cc
void 
gr_top_block::run()
{
  start();
  wait();
}

Then, go to start();
先找到run(),看看代码,发现start(),去看start()

In file: grtop block.cc
void 
gr_top_block::start()
{
    d_impl->start();
}

d_impl is a member thatpoints to the class gr_top_block_impl with the followingcodes:
看到start() 其实是调用了d_impl->start(),去classgr_top_block_impl 再去看
In file : grtop block impl.cc

void
gr_top_block_impl::start()
{
    gruel::scoped_lockl(d_mutex);

    if (d_state != IDLE)
      throwstd::runtime_error("top_block::start: top block already running orwait() not called after previous stop()");

    if (d_lock_count> 0)
      throwstd::runtime_error("top_block::start: can't start with flow graphlocked");

    // Create new flat flowgraph by flattening hierarchy
    d_ffg =d_owner->flatten();

    // Validate new simpleflow graph and wire it up
    d_ffg->validate();
    d_ffg->setup_connections();

    d_scheduler =make_scheduler(d_ffg);
    d_state = RUNNING;
}

The codes do some sanitycheck and then create the GNU Radio Scheduler bycalling
d_scheduler =make_scheduler(d_ffg);
Let go to function make_scheduler()as follows
上面的代码通过调用 d_scheduler = make_scheduler(d_ffg);很严谨的检查并且创建了GNURadio的调度器。让我们去make_scheduler(d_ffg)看看
In file : gr top blockimpl.cc
static gr_scheduler_sptr
make_scheduler(gr_flat_flowgraph_sptr ffg)
{
    static scheduler_maker  factory = 0;

    if (factory == 0){
      char *v= getenv("GR_SCHEDULER");
      if(!v)
        factory = scheduler_table[0].f; // usedefault
      else{
        for (size_t i = 0; i <sizeof(scheduler_table)/sizeof(scheduler_table[0]); i++){
if (strcmp(v, scheduler_table[i].name) == 0){
  factory = scheduler_table[i].f;
  break;
}
        }
        if (factory == 0){
std::cerr << "warning: InvalidGR_SCHEDULER environment variable value ""
  << v<< "".   Using ""<< scheduler_table[0].name<< ""n";
factory = scheduler_table[0].f;
        }
      }
    }
    return factory(ffg);
}

In the above program, whatis the variable static scheduler_makerfactory
we can look into the file and find,
在上面的程序中,scheduler_maker factory是什么玩意?我们在文件中找找
typedef gr_scheduler_sptr(*scheduler_maker) (gr_flat_flowgraph_sptr ffg);

Well, factory is a function pointer!Where does it points to? We can find
factory是一个函数指针,指向哪里?找找
factory =schedulertable[i].f

OK. Let us find what is inscheduler_table:
好了,让我们继续找找什么是scheduler_table
static struct scheduler_table {
    const char      *name;
    scheduler_maker f;
} scheduler_table[] = {
    { "TPB",gr_scheduler_tpb::make }, // first entry is default
    { "STS",gr_scheduler_sts::make }
};

Great! It points to a memberfunction, make, in a scheduler's class. Then theprogram 
is veryeasy to understand: it checks whether there exists a Linuxenvironment variable: 
GR_SCHEDULER. If no, use thedefault scheduler; otherwise, use user's choice. And we donot 
have somany choices on the scheduler:
好耶!它指向一个在scheduler类里边的成员函数,make。简单理解:检查是否存在Linux环境变量:GR_SCHEDULER。如果没有,就是用默认的调度器;否则,是用用户选择的。其实我们也没多少选择不是么,就下边两个:
1. TPB (default): multi-threadedscheduler.
2. STS: single-threadedscheduler.

By default, gr_scheduler_tpb::make will becalled.

In file: gr scheduler_tpb.cc
gr_scheduler_sptr
gr_scheduler_tpb::make(gr_flat_flowgraph_sptr ffg)
{
    returngr_scheduler_sptr(new gr_scheduler_tpb(ffg));
}

The constructor of gr_scheduler_tpb is called.

In file : grscheduler tpb.cc


gr_scheduler_tpb::gr_scheduler_tpb(gr_flat_flowgraph_sptrffg)
    : gr_scheduler(ffg)
{
    // Get a topologicallysorted vector of all the blocks in use.
    // Being topologicallysorted probably isn't going to matter, but
    // there's a non-zerochance it might help...

    gr_basic_block_vector_tused_blocks = ffg->calc_used_blocks();
    used_blocks =ffg->topological_sort(used_blocks);
    gr_block_vector_t blocks =gr_flat_flowgraph::make_block_vector(used_blocks);

    // Ensure that the doneflag is clear on all blocks

    for (size_t i = 0; i< blocks.size(); i++){
      blocks[i]->detail()->set_done(false);
    }

    // Fire off a thead foreach block

    for (size_t i = 0; i< blocks.size(); i++){
      std::stringstream name;
      name<< "thread-per-block["<< i <<"]: " << blocks[i];
      d_threads.create_thread(
        gruel::thread_body_wrapper<tpb_container>(tpb_container(blocks[i]),name.str()));
    }
}

Nothing strange here, theonly thing needs to mention is

 d_threads.create_thread(
        gruel::thread_body_wrapper<tpb_container>(tpb_container(blocks[i]),name.str()));
   
thread_body_wrapper wrapsthe main thread with the block name. Then, thethread 
begins from thread_body_wrapper(). Let ussee part of the program to find what happens.
thread_body_wrapper把每个block的主要线程都包住了。然后,线程从thread_body_wrapper()开始。让我们看看到底这部分程序发生了什么。
In file: thread_body_wrapper.h

  

namespacegruel 
{

  voidmask_signals();

  template<class F>
  classthread_body_wrapper
  {
   F d_f;
   std::string d_name;

  public:

   explicit thread_body_wrapper(F f, conststd::string &name="")
     : d_f(f), d_name(name){}

   void operator()()
   {
     mask_signals();

     try {
d_f();
     }
    catch(boost::thread_interrupted const&)
     {
     }
     catch(std::exception const&e)
     {
std::cerr<< "thread["<< d_name<< "]: "
 <<e.what() << std::endl;
     }
     catch(...)
     {
std::cerr<< "thread["<< d_name<< "]: "
 <<"caught unrecognized exceptionn";
     }
   }
  };
}

 
See the overloading ofoperate(), actually, d_f() is called, which is explicitly linkedto
tpb_container(). So go to the code of tpb_container:
看到重载operate(),实际上调用了d_f(),这个函数清晰的链接到tpb_container()。
In file :gr_scheduler_tpb.cc

class tpb_container
{
    gr_block_sptrd_block;
   
public:
    tpb_container(gr_block_sptrblock) : d_block(block) {}

    void operator()()
    {
      gr_tpb_thread_body body(d_block);
    }
};

Well. the overloading ofoperate() just constructs another class,gr_tpb_thread_body, 
with theblock pointer. From here, the scheduler's work isdone.
Let's briefly summarize whatis going on with the GNU Radio scheduler.
 
1. Analyzeused blocks in gr_top_block.
2. Defaultscheduler is TPB, which creates multi-threads forblocks.
3. Thescheduler creates one concurrent thread for eachblock.
4. For eachblock, the thread's entry is gr_tpb_thread_bodybody(d_block).
总结GNU RADIO调度器作用:
    1. 分析在gr_top_block中每个block
2.默认调度器是TPB,用来创造每个blocks里边的多线程
3.调度器为每个block创建线程
4.对于每个block,线程入口都在gr_tpb_thread_body 里边
2 How a thread of each blockworks

As we discussed, the TPB scheduler generatesa thread for each block whose entry starts from
theconstructor of class gr_tpb_thread_body. Let us go over theconstructor:
正如我们讨论的,TPB调度器为每个block产生一个线程,这些线程的入口来自一个gr_tpb_thread_body类的构造器。
In file :gr_tpb_thread_body.cc

gr_tpb_thread_body::gr_tpb_thread_body(gr_block_sptrblock)
    :d_exec(block)
{
    // std::cerr<< "gr_tpb_thread_body: "<< block<< std::endl;

    gr_block_detail *d =block->detail().get();
    gr_block_executor::states;
    pmt_t msg;

//Here startsthe main loop of the thread.
//开始线程的循环
    while (1){

//First, thethread processes all signals
//首先,线程处理所有信号
      boost::this_thread::interruption_point();
 
      // handle any queued up messages
      while ((msg =d->d_tpb.delete_head_nowait()))
        block->handle_msg(msg);

      d->d_tpb.clear_changed();


      s =d_exec.run_one_iteration();


      switch(s){
      case gr_block_executor::READY: // Tell neighborswe made progress.
        d->d_tpb.notify_neighbors(d);
        break;

      case gr_block_executor::READY_NO_OUTPUT: //Notify upstream only
        d->d_tpb.notify_upstream(d);
        break;

      case gr_block_executor::DONE: // Game over.
        d->d_tpb.notify_neighbors(d);
        return;

      case gr_block_executor::BLKD_IN: // Wait forinput.
        {
gruel::scoped_lockguard(d->d_tpb.mutex);
while(!d->d_tpb.input_changed){
 
  // wait for inputor message
  while(!d->d_tpb.input_changed&&d->d_tpb.empty_p())
    d->d_tpb.input_cond.wait(guard);

  // handle allpending messages
  while ((msg =d->d_tpb.delete_head_nowait_already_holding_mutex())){
    guard.unlock(); // release lock while processingmsg
    block->handle_msg(msg);
    guard.lock();
  }
}
        }
        break;

       
      case gr_block_executor::BLKD_OUT: // Wait foroutput buffer space.
        {
gruel::scoped_lockguard(d->d_tpb.mutex);
while(!d->d_tpb.output_changed){
 
  // wait for outputroom or message
  while(!d->d_tpb.output_changed&&d->d_tpb.empty_p())
    d->d_tpb.output_cond.wait(guard);

  // handle allpending messages
  while ((msg =d->d_tpb.delete_head_nowait_already_holding_mutex())){
    guard.unlock(); // release lock while processingmsg
    block->handle_msg(msg);
    guard.lock();
  }
}
        }
        break;

      default:
        assert(0);
      }
    }
}

So far so good. We can seerun_one_iteration() is the key in the whole thread,it 
includes the major functionality of theblock. Go to its source. Woo! it is a littlelong.
很好,我们看到run_one_iteration(),它是整个线程的核心,它包括block的主要功能。去看看那些源代码吧!哇!!有一点点长噢。
In file :gr_block_executor.cc

gr_block_executor::state 
gr_block_executor::run_one_iteration()
{
  ...
// Do the actual work of the block int n =m->general_work (noutput_items, d_ninput_items,d_input_items, d_output_items); LOG(*d_log<< " general_work: noutput_items = "<< noutput_items<< " result = "<< n <<std::endl);
  ...
}

Overall,the code first checks

1. whether thereexists 
sufficient 
dataspace for output. No → returnBLKD_OUT
2. whether thereare 
sufficient 
input data available,No → 
 returnBLKD_IN
总体上,这些代码首先检查:
1.是否存在足够的输出数据空间
2.是否存在足够的可靠输入数据
If thereare sufficient input data and sufficient output space, the coderuns the actual work
of the block: general_work().
So up to now, we can know how each thread inthe GNU radio core works. Let us briefly
如果这里有足够的输入数据和足够的输出空间,代码就会执行block里边的work咯:gnerral_work()
现在,我们可以知道在GNURadio里的每个线程都怎么工作了噢,所以短暂的总结一下:
summarize
1. A thread for each block has a while (1)loop.
2. The loop processes signals and run the key functionrun_one_iteration().
3. run_one_iteration() checks if there are sufficientdata for input and sufficient available 
space for output for theblock.
4. If yes, call general_work() to run the mainfunctionality of the block. Otherwise,return 
BLKD_OUT, BLKD_IN, orothers.

1. 每个block的线程都有一个while(1)循环
2. 循环处理信号并且运行主要函数 run_one_iteration()
3. run_one_iteration() 检查是否有足够的数据输入和为这个block输出足够的可靠的空间
4. 如果都可以,调用general_work() 去跑这个block主要的功能。否则,返回BLKD_OUT,BLKD_IN,或者其他。

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值