srsRAN 工程分析(I):srsUE 初始化
最编程
2024-04-06 20:15:03
...
0. 编程语言构成
- srsRAN工程主要使用C++语言编写,C语言主要分布在物理层的库接口调用中;
├── build
├── cmake
├── debian
├── lib lib/phy主要由C语言编写
├── srsenb
├── srsepc
├── srsue
└── test
1. srsUE初始化流程
1.1 整体初始化流程
- radio初始化(射频硬件发现及接口注册)
- PHY初始化(L1层)
- stack初始化(L2/L3层-MAC/RLC/PDCP/RRC/NAS)
- 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, <e_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》
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_)
}
- 各个子层初始化流程为申请必要的软件资源
- 初始化专用定时器 task_sched.get_unique_timer();
- 初始化数组空间资源 buffer,vector
- 初始化状态变量 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层重传管理机制
- BSR = Buffer Status Report
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线程用于处理及转发网络包数据;
-
thread("PHY")