How GNURadio Core Works

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 ofgr_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,或者其他。

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章