欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

srsRAN 工程分析(I):srsUE 初始化

最编程 2024-04-06 20:15:03
...

0. 编程语言构成

  • srsRAN工程主要使用C++语言编写,C语言主要分布在物理层的库接口调用中;

image.png

├── build
├── cmake
├── debian
├── lib            lib/phy主要由C语言编写
├── srsenb         
├── srsepc
├── srsue
└── test

1. srsUE初始化流程

1.1 整体初始化流程

  1. radio初始化(射频硬件发现及接口注册)
  2. PHY初始化(L1层)
  3. stack初始化(L2/L3层-MAC/RLC/PDCP/RRC/NAS)
  4. gw初始化(网络数据处理)

1.2 初始化整体流程

srsue::ue ue;  /* 创建ue实例 */
ue.init(args)  /* ue初始化 */
    /* 实例化协议栈单元,内部调用各级协议栈的构造函数 */
    |- std::unique_ptr<ue_stack_lte> lte_stack(new ue_stack_lte);  /* srsue/src/stack/ue_stack_lte.cc */
    |- std::unique_ptr<gw> gw_ptr(new gw(srslog::fetch_basic_logger("GW")));  /* srsue/src/stack/upper/gw.cc */
    |- std::unique_ptr<srsue::phy> lte_phy = std::unique_ptr<srsue::phy>(new srsue::phy);  /* srsue/hdr/phy/phy.h */
    |- std::unique_ptr<srsran::radio> lte_radio = std::unique_ptr<srsran::radio>(new srsran::radio);
    
    /* 初始化硬件射频单元-硬件层UHD驱动 */
    lte_radio->init(args.rf, lte_phy.get())
    
    /* 初始化PHY层状态机及创建UE主线程 */
    lte_phy->init(args.phy, lte_stack.get(), lte_radio.get())
    lte_phy->init(phy_args_nr, lte_stack.get(), lte_radio.get())
    
    /* 初始化LTE子层协议栈及创建协议栈线程 */
    lte_stack->init(args.stack, lte_phy.get(), lte_phy.get(), gw_ptr.get())
    
    /* 初始化网关 */
    gw_ptr->init(args.gw, lte_stack.get())
    
    /* 因PHY是在独立线程中初始化的,此处根据条件变量检查初始化完成 */
    phy->wait_initialize(); 

1.3 lte_radio 初始化

1.3.1 分层结构

  • 以“发送增益控制”(set_tx_gain)举例
    • 应用层接口 bool radio::start_agc(bool tx_gain_same_rx)
    • 抽象层接口 int (*srsran_rf_set_tx_gain)(void* h, double gain)
    • 硬件层接口 SRSRAN_API int rf_uhd_set_tx_gain(void* h, double gain)
    • 硬件驱动(UHD驱动)

1.3.2 发现设备&接口注册

  • radio初始化调用的子接口均实现在 lib/ 目录下。
ue.init(args)			/* src/ue.cc */
    |-int radio::init(const rf_args_t& args, phy_interface_radio* phy_)			lib/src/radio/radio.cc
    |-bool radio::open_dev(const uint32_t& device_idx, const std::string& device_name, const std::string& devive_args)	lib/src/radio/radio.cc
        |-int srsran_rf_open_devname(srsran_rf_t* rf, const char* devname, char* args, uint32_t nof_channels)	lib/src/phy/rf/rf_imp.c
            |-available_devices[i]->srsran_rf_open_multi(args, &rf->handler, nof_channels)	按序在硬件列表中遍历检查硬件接口
                |-static rf_dev_t* available_devices[] = {&dev_uhd, ...}  软件支持的硬件列表
                |-static rf_dev_t dev_uhd = {"UHD",...}  硬件接口列表
-------------------------UHD C API----------------------------------                
                |-int rf_uhd_open_multi(char* args, void** h, uint32_t nof_channels)	硬件接口	lib/src/phy/rf/rf_uhd_imp.cc
                    |-uhd_usrp_find("", &devices_str);  //lib/src/phy/rf/rf_uhd_imp.cc
-------------------------UHD driver(uhd-3.15.0.0)-------------------
|-UHD_API uhd_error uhd_usrp_find  //host/include/uhd/usrp/usrp.h
    |-device_addrs_t device::find(const device_addr_t &hint, device_filter_t filter)  //host/lib/device.cpp
-----------------------------------------------------------------------
            //如成功打开某硬件设备,将对应的硬件接口列表注册至srsran抽象接口层
            |-rf->dev = available_devices[i];

1.3.3 射频参数初始化

  • 以tx增益控制初始化举例硬件初始化流程
|- int radio::init(const rf_args_t& args, phy_interface_radio* phy_)
    |- bool radio::open_dev(const uint32_t& device_idx, const std::string& device_name, const std::string& devive_args)	
    |- bool radio::start_agc(bool tx_gain_same_rx)
        |-int srsran_rf_start_gain_thread(srsran_rf_t* rf, bool tx_gain_same_rx)
            |- pthread_create(&rf->thread_gain, NULL, thread_gain_fcn, rf)	
            |- static void* thread_gain_fcn(void* h)
                |- srsran_rf_set_tx_gain(h, rf->cur_rx_gain + rf->tx_rx_gain_offset);
-------------------------USRP C接口-------------------------
 |- SRSRAN_API int rf_uhd_set_tx_gain(void* h, double gain);  /* lib/src/phy/rf/rf_uhd_imp.h */
        |- int rf_uhd_set_tx_gain(void* h, double gain);  /* lib/src/phy/rf/rf_uhd_imp.cc */
------------------------------------------------------------

1.4 lte_phy 初始化(概览)

  • L1-PHY初始化是最复杂的初始化过程,因其需要初始化时频结构,ZC序列,等数学运算单元和缓冲区,本部分先忽略掉具体的功能实现,主要关注整体软件流程初始化。在后续章节中,再深入分析PHY层的初始化和调度;

1.4.1 lib库创建线程的标准方法

  • srsran项目中,创建线程的pthread相关接口被统一封装,业务模块均调用start()函数创建线程;
/* lib/include/srsran/common/threads.h */
bool start(int prio = -1) { return threads_new_rt_prio(&_thread, thread_function_entry, this, prio); }

static void* thread_function_entry(void* _this)                      
{                                                                    
  pthread_setname_np(pthread_self(), ((thread*)_this)->name.c_str());
  ((thread*)_this)->run_thread();                                    
  return NULL;                                                       
}                                                                    
  • 由start()的函数实现可以得到,线程的实体就是创建线程对象自身的run_thread()方法;

1.4.2 PHY初始化(概述)

  • 本模块高度数学化,后续章节专题分析
/* srsue/src/ue.cc */
lte_phy->init(args.phy, lte_stack.get(), lte_radio.get())

-------------------- PHY初始化 -------------------------

    /* srsue/src/phy/phy.cc */
    int phy::init(const phy_args_t& args_)
    {
        ...
        start();  //创建PHY线程
    }
    
    /* srsue/src/phy/phy.cc */
    // Initializes PHY in a thread
    // 但此线程在初始化PHY完成后即终结
    void phy::run_thread()
    {
        prach_buffer.init(SRSRAN_MAX_PRB);  //初始化PRACH
        ...
        /* SYNC初始化与线程创建 */
        sfsync.init(radio, stack, &prach_buffer, &lte_workers, &nr_workers, &common, SF_RECV_THREAD_PRIO, args.sync_cpu_affinity);
    } 

1.4.3 SYNC主线程创建

  • SYNC状态机
------------------ SYNC初始化与线程创建 ------------------------
        /* srsue/src/phy/sync.cc */
        void sync::init(...)
        {
            ...
            running = true;
            
            // Start main thread
            start(prio);
        }
        
        /* srsue/src/phy/sync.cc */
        /* 主线程,以SYNC状态机驱动上层模块RRC/NAS的状态机运行 */
        void sync::run_thread()
        {
            while (running.load(std::memory_order_relaxed)) {
                switch (phy_state.run_state()) {
                    case sync_state::CELL_SEARCH:
                        run_cell_search_state();
                        break;
                    ...
                    case sync_state::IDLE:
                        run_idle_state();
                        break;
                }
                
                // Increase TTI counter
                /* TTI(transmission time interval) = 子帧subframe时长1ms,是LTE资源调度的基本单位 */
                tti = TTI_ADD(tti, 1); 
            }
        }
  • 其中SYNC状态机依赖的 phy_state.run_state() 实现如下:
/* Run_state is called by the main thread at the start of each loop. It updates the state
 * and returns the current state 
 */
/* srsue/hdr/phy/sync_state.h */
state_t run_state(){...; cur_state = next_state; ...;}	

/* srsue/hdr/phy/sync_state.h */
/* can be updated from outside (i.e. other thread) */
std::atomic<state_t>    next_state    = {IDLE}; 
  • 可以发现初始的state状态是 IDLE,表明未同步的状态;
  • 多级状态机联动
/* srsue/src/phy/sync.cc */
void sync::run_thread()   SYNC状态机,主线程
    
    /* srsue/src/stack/rrc/rrc.cc */
    void rrc::run_tti()   RRC状态机
    
        /* srsue/src/stack/upper/nas.cc */
        void nas::run_tti()   NAS状态机

1.5 lte_stack 初始化

1.5.1 LTE协议栈图解

下图引用自《docs-srsran-com-en-latest》

image.png

1.5.2 LTE 协议栈L2/L3初始化

int ue::init(const all_args_t& args_)
{
	|- int radio::init(const rf_args_t& args, phy_interface_radio* phy_)	

	/* 协议栈初始化,按层次结构逐个模块初始化 */
	/* srsue/src/stack/ue_stack_lte.cc */
	|- ue_stack_lte::init(const stack_args_t& args_)
		
		/* L2-MAC初始化	srsue/src/stack/mac/mac.cc */
		|- bool mac::init(phy_interface_mac_lte* phy, rlc_interface_mac* rlc, rrc_interface_mac* rrc)
		
		/* L2-RLC初始化	lib/src/rlc/rlc.cc */
		|- void rlc::init()
		
		/* L2-PDCP初始化	lib/src/pdcp/pdcp.cc */
		|- void pdcp::init(srsue::rlc_interface_pdcp* rlc_, srsue::rrc_interface_pdcp* rrc_, srsue::gw_interface_pdcp* gw_)
		
		/* L3-NAS初始化	srsue/src/stack/upper/nas.cc */
		|- int nas::init(usim_interface_nas* usim_, rrc_interface_nas* rrc_, gw_interface_nas* gw_, const nas_args_t& cfg_)
		
		/* L3-RRC初始化	srsue/src/stack/rrc/rrc.cc */
		|- void rrc::init()
                
                /* 运行线程,线程入口为 void ue_stack_lte::run_thread() */
                |- start(STACK_MAIN_THREAD_PRIO);
	
	/* srsue/src/stack/upper/gw.cc */
	|- int gw::init(const gw_args_t& args_, stack_interface_gw* stack_)
}
  • 各个子层初始化流程为申请必要的软件资源
    1. 初始化专用定时器 task_sched.get_unique_timer();
    2. 初始化数组空间资源 buffer,vector
    3. 初始化状态变量 cell_clean_cnt = 0;

1.5.3 L2 MAC 子层初始化

srsue/src/stack/ue_stack_lte.cc
既然是协议栈初始化,那么整个初始化流程就应该是纯软件的;
|- int ue_stack_lte::init(const stack_args_t& args_)		
    
    srsue/src/stack/mac/mac.cc
    mac初始化:主要是结构体定时器任务队列的初始化,未涉及射频层面的初始化;
    |- bool mac::init(phy_interface_mac_lte* phy, rlc_interface_mac* rlc, rrc_interface_mac* rrc) 	

        /* MAC Uplink-related Procedures */
        srsue/src/stack/mac/mac.cc
        BSR	= Buffer Status Report.
        |- bsr_procedure.init(&sr_procedure, rlc_h, &task_sched);					
            srsue/src/stack/mac/proc_bsr.cc
            主要任务为初始化BSR独立定时器
            |- void bsr_proc::init(sr_proc* sr_, rlc_interface_mac* rlc_, srsran::ext_task_sched_handle* task_sched_)		

        Power Headroom = UE Max Transmission Power - PUSCH Power = Pmax - P_pusch
        PHR = Power Headroom Report.		
        |- phr_procedure.init(phy_h, &task_sched);		
            srsue/src/stack/mac/proc_phr.cc
            |- void phr_proc::init(phy_interface_mac_lte* phy_h_, srsran::ext_task_sched_handle* task_sched_)
        
        multiplexing 多路复用
        |- mux_unit.init(rlc_h, &bsr_procedure, &phr_procedure);
            srsue/src/stack/mac/mux.cc
            |- void mux::init(rlc_interface_mac* rlc_, bsr_interface_mux* bsr_procedure_, phr_proc* phr_procedure_)
        
        demultiplexing 解多路复用
        |- demux_unit.init(phy_h, rlc_h, this, &timer_alignment);
            srsue/src/stack/mac/demux.cc
            |- void demux::init(phy_interface_mac_common*            phy_,                 
                                 rlc_interface_mac*                   rlc_,                 
                                 mac_interface_demux*                 mac_,                 
                                 srsran::timer_handler::unique_timer* time_alignment_timer_)


        RA = Random Access.	36.321
        |- ra_procedure.init(phy_h, rrc, &uernti, &timer_alignment, &mux_unit, &task_sched);		
            srsue/src/stack/mac/proc_ra.cc
            |- void ra_proc::init()

        SR = Scheduling Request.	36.321
        |- srsue/src/stack/mac/proc_sr.cc	

        // Create UL/DL unique HARQ pointers
        |- ul_harq.at(PCELL_CC_IDX)->init(&uernti, &ra_procedure, &mux_unit);
        |- dl_harq.at(PCELL_CC_IDX)->init(&uernti, &demux_unit);
  • MAC初始化的主要功能模块如下
    • BSR = Buffer Status Report
      • 一种MAC Contrl Element (MAC CE)
      • 用于上报UE待发送的上行数据量到eNB
      • 但UE不应假定eNB会足量分配RB以满足UE请求
    • PHR = Power Headroom Report
      • Power Headroom = UE Max Transmission Power - PUSCH Power = Pmax - P_pusch
      • 一种MAC CE
      • 表示功率余量,如果功率余量为正:UE“在最大传输功率下,我还能传输更多数据”,如果功率余量为负:UE“我的传输已经超过了允许的最大传输功率”
    • RA = Random Access
      • 随机接入流程
    • SR = Scheduling Request
      • 物理层消息,UE向eNB发送上行调度请求;
      • SR消息只表明UE需要上行资源,请求UL-Grant,具体UE缓冲区中的数据量由BSR上报
    • HARQ = Hybrid Automatic Repeat Request
      • MAC层重传管理机制

1.5.4 L2 RLC 子层初始化

/* srsue/src/stack/ue_stack_lte.cc */
rlc.init(&pdcp, &rrc, task_sched.get_timer_handler(), 0 /* RB_ID_SRB0 */);
/* lib/src/rlc/rlc.cc */
|- void rlc::init(srsue::pdcp_interface_rlc* pdcp_,  
           srsue::rrc_interface_rlc*  rrc_,
           srsran::timer_handler*     timers_,
           uint32_t                   lcid_)    
    // create default RLC_TM bearer for SRB0 
    |- add_bearer(default_lcid, rlc_config_t());
  • RLC初始化,创建SRB0承载的必要软件资源

1.5.5 L2 PDCP 子层初始化

/* srsue/src/stack/ue_stack_lte.cc */
pdcp.init(&rlc, &rrc, gw);
/* lib/src/pdcp/pdcp.cc */
void pdcp::init(srsue::rlc_interface_pdcp* rlc_, srsue::rrc_interface_pdcp* rrc_, srsue::gw_interface_pdcp* gw_)
{
  rlc = rlc_;
  rrc = rrc_;
  gw  = gw_;
}
  • PDCP子层初始化,只是绑定相关软件资源
  • PDCP在默认情况下,只是简单的封装PDCP头或解PDCP头;但如果开启了完整性,加密和压缩功能后,PDCP层会变得很繁忙

1.5.6 L3 NAS 子层初始化

/* srsue/src/stack/ue_stack_lte.cc */
nas.init(usim.get(), &rrc, gw, args.nas);
/* srsue/src/stack/upper/nas.cc */
|- int nas::init(usim_interface_nas* usim_, rrc_interface_nas* rrc_, gw_interface_nas* gw_, const nas_args_t& cfg_)
    
    /* Getting Home PLMN Id from USIM. */
    /* src/stack/upper/usim_base.cc */
    |- usim->get_home_plmn_id(&home_plmn)
    
    /* 选择EIA完整性保护算法 Integrity Algorithm */
    /* src/stack/upper/nas_base.cc */
    parse_security_algorithm_list(cfg_.eia, eia_caps)
    
    /* 选择EEA加密算法 Ciphering Algorithm */
    parse_security_algorithm_list(cfg_.eea, eea_caps)
    
    /* 根据USIM存储参数生成EIA/EEA密钥 */
    /* src/stack/upper/usim_base.cc */
    usim->generate_nas_keys(ctxt.k_asme, ctxt_base.k_nas_enc, ctxt_base.k_nas_int, ctxt_base.cipher_algo, ctxt_base.integ_algo);
    
    /* 初始化NAS软件定时器,省略 */
    ...
  • NAS子层初始化
    • 获取USIM中的PLMN信息;
    • 选择NAS/RRC加密算法及根据SIM卡参数生成密钥;
    • 初始化NAS层的软件定时器

1.5.7 L3 RRC 子层初始化

/* srsue/src/stack/ue_stack_lte.cc */
rrc.init(phy, &mac, &rlc, &pdcp, &nas, usim.get(), gw, &rrc_nr, args.rrc);
    
    /* srsue/src/stack/rrc/rrc.cc */
    |- void rrc::init(phy_interface_rrc_lte* phy_,   
               mac_interface_rrc*     mac_,   
               rlc_interface_rrc*     rlc_,   
               pdcp_interface_rrc*    pdcp_,  
               nas_interface_rrc*     nas_,
               usim_interface_rrc*    usim_,  
               gw_interface_rrc*      gw_,    
               rrc_nr_interface_rrc*  rrc_nr_,
               const rrc_args_t&      args_)

        /* 实例化phy_controller,用于控制小区搜索、切换等流程 */
        /* srsue/src/stack/rrc/phy_controller.cc */
        |- phy_ctrl.reset(new phy_controller{phy, task_sched, on_every_cell_selection});
        
        /* 申请RRC流程所需的专用定时器,如t300定时器为RRC Connection Request发起后的等待定时器 */
        |- t300 = task_sched.get_unique_timer();
        |- ...
        
        /* 初始化RLF = Radio Link Failure 报告,与t310,n310,n311定时器相关 */
        /* rrc_rlf_report.cc */
        |- var_rlf_report.init(task_sched);
        
        /* 初始化相关定时器参数,如 t304.set(1000, timer_expire_func); */
        |- set_rrc_default();
        
        /* 初始化测量报告 measurement report 依赖的软件资源 */
        /* srsue/src/stack/rrc/rrc_meas.cc */
        |- measurements->init(this);
        
        /* 初始化SIB数组 */
        |- ue_required_sibs.assign(&required_sibs[0], &required_sibs[NOF_REQUIRED_SIBS]);
  • RRC子层初始化
    • 实例化 phy_controller,用于控制小区搜索、选择等流程;
    • 申请及初始化 RRC 流程相关的专用定时器,如t300,t301,t302等,均为3GPP标准定义下的RRC流程定时器;
    • 初始化RLF报告,用于在UE发现与eNB不同步后,触发RLF流程,一般是触发RRC重建
    • 初始化测量依赖的软件资源 Measurement Report;
    • 初始化 SIB数组 SIB1, SIB2, SIB3 and SIB13 (eMBMS)

1.6 gw 初始化

  • 初始化MBSFN技术需要的UDP socket,MBSFN是网络电视直播的应用技术,用于同时向服务小区内的UE广播数据;
int gw::init(const gw_args_t& args_, stack_interface_gw* stack_)
{
    // MBSFN
    mbsfn_sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    ...
}
  • 在Attach流程执行成功后,gw才创建线程处理网络数据;
srsue/src/stack/upper/nas.cc
void nas::parse_attach_accept(uint32_t lcid, unique_byte_buffer_t pdu)
{
    ...
    gw->setup_if_addr();
    ...
}
    
    srsue/src/stack/upper/gw.cc
    int gw::setup_if_addr(uint32_t eps_bearer_id, uint8_t pdn_type, uint32_t ip_addr, uint8_t* ipv6_if_addr, char* err_str)
    {
        ...
        run_enable = true;
        start(GW_THREAD_PRIO);
    }
    
    srsue/src/stack/upper/gw.cc 
    void gw::run_thread()
    {
        ...
        /* 处理网络数据 */
        while (run_enable) {
            ...
        }
    }

2. srsUE初始化流程总结

2.1 功能模块

  • 如章节1所做分析,srsUE划分了四个主要的功能模块,并依次初始化。
    • radio模块:发现匹配的射频硬件,注册硬件接口到srsUE硬件抽象层;
    • phy模块:PHY层参数初始化,PRACH信道初始化,ZC序列初始化...本层的功能实现高度数学化,多处依赖FFT变换。
    • stack模块:包含L2-L3的各子模块,MAC/RLC/PDCP/NAS/RRC各子模块的初始化,主要为初始化各自模块依赖的软件buffer或timer等,值得注意的是很多timer的命名都是遵循3GPP标准,如RRC模块定义的t300定时器等。
    • gw模块:网关模块,初始化时只是创建MBSFN技术需要的UDP协议栈。在状态机开始运行后,Attach Accept流程之后,gw才会创建网络数据处理线程。

2.2 多线程

  • srsUE创建的线程如下:
    • thread("PHY")
      • 初始化PHY层参数,PRACH信道资源,创建SYNC线程,此后线程结束,并不长期存在;
    • thread("SYNC")
      • srsUE主线程,FSM有限状态机,根据SYNC同步状态(CELL_SEARCH/SFN_SYNC/CAMPING/IDLE),执行对应的流程,初始状态是IDLE;以此主线程FSM状态机为入口,调度RRC/NAS层的状态机,进而驱动整个协议栈运行;
      • FSM有限状态机,因为LTE系统以子帧subframe为资源调度的基本单位,一个子帧的时长=1个TTI,因此各级对象的状态机命名均为run_tti。如sync::run_stack_tti, ue_stack_lte::run_tti, nas.run_tti;
    • thread("STACK")
      • stack模块线程,线程自创建后,阻塞等待调度器task_scheduler队列中的task并执行。任务调度器为状态机调用的具体方法push至队列中;
    • thread("GW")
      • 在初始化GW时并不创建GW线程,在状态机开始调度,并执行完成Attach Accept流程后,创建GW线程用于处理及转发网络包数据;