我是靠谱客的博主 务实果汁,最近开发中收集的这篇文章主要介绍srsLTE 源码分析 – UE_01,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

笔者近些年在做了一些LTE相关的项目,很多知识都是从LTE理论开始一点点看起,也在网络上找了很多的资料,目前在网络上对LTE协议,5G相关的协议介绍资料比较多,但是对于协议栈实现的部分内容较少,这对于开发人员,很多内容理解的不是太直观。为了能对自己的学习的知识作一个总结,同时也希望对相关的开发人员提供一些帮助,不要再走一些弯路。因此,笔者选择了srsLTE的开源代码作为LTE学习的切入点,来分析LTE协议栈的详细实现过程。具体的srsLTE代码读者可以自行到github上下载,由于能力和理解有限,文中可能会有一些错误,还请读者们见谅。

     srsLTE的代码是基于C++实现的,文中用了很多C++11/14的特性,后续的讲解中有相关的内容需要特殊说明的,我们再作详细的介绍。整个系列文章,将先介绍srsUE的代码,后续再介绍srsENB的代码。

     作为整个UE系列的第一篇文章,前面的几篇,我们先从宏观的角度去理解代码,比如:有哪些线程?线程之间是怎么协调共工作的?协议栈是如何工作起来的?数据流程是怎么走的?文档将近尽可能的先将这些整体的框架先做一些介绍。后续,再结合协议内容,分析一些具体的协议和模块:调度、PHR、BSR、功控、HARQ、MUX、逻辑信道、SR、随机接入、attach流程等;文章也会按照协议,如NAS、RRC、PDCP、RLC、MAC等协议代码详细讲解。

     首先,要搞清楚的一点就是,代码中有哪些主要的线程?这些线程之间的是如何协调的?这点对于理解协议栈的实现是非常重要的,不理解这些线程就无法清楚的了解整个协议栈是如何工作的。

UE协议栈关键线程

     先介绍gw线程,该线程是整个UE的数据出口,srslte的实现是基于Linux系统实现的, Linux系统中应用程序的业务数据 (http、ping)的数据包,是要经过Linux的TCP/IP协议栈,经过系统路由之后,最终数据会发给某个网卡设备,再由网卡设备的驱动程序进行转发。

     细心的读者可能发现一个问题,由于整个协议栈是运行在应用层,而Linux系统的应用程序的数据会默认发给内核设备,srsLTE采用了Linux系统的tun设备来实现的,tun设备的使用主要有几个步骤,在attach成功之后:

  1. 在gw中open /dev/net/tun创建设备,
  2. 设置IP,并设置为默认路由;
  3. 使用read系统函数,就可以捕获到应用程序的业务数据包;
  4. 使用write系统函数,就可以将PDCP的IP数据包发给内核,再经过Linux的TCP/IP协议栈将数据包转给应用程序。

        gw线程在读到应用数据包之后,会通过 ue_stack_lte的write_sdu接口,将数据进一步送给协议栈进行处理,最后通过LTE的uu接口发送给LTE基站。关于tun的编程方法,有不理解的读者可以自行查一些资料,这里就不详细赘述,后续有需要的话,可以专门的来介绍下该部分的知识点。

  // 打开内核的tun设备

  tun_fd = open("/dev/net/tun", O_RDWR);

  log.info("TUN file descriptor = %dn", tun_fd);

  if (0 > tun_fd) {

    err_str = strerror(errno);

    log.error("Failed to open TUN device: %sn", err_str);

    return SRSLTE_ERROR_CANT_START;

  }

  // 设置tun的设备名

  memset(&ifr, 0, sizeof(ifr));

  ifr.ifr_flags = IFF_TUN | IFF_NO_PI;

  strncpy(

      ifr.ifr_ifrn.ifrn_name, args.tun_dev_name.c_str(), std::min(args.tun_dev_name.length(), (size_t)(IFNAMSIZ - 1)));

  ifr.ifr_ifrn.ifrn_name[IFNAMSIZ - 1] = 0;

  if (0 > ioctl(tun_fd, TUNSETIFF, &ifr)) {

    err_str = strerror(errno);

    log.error("Failed to set TUN device name: %sn", err_str);

    close(tun_fd);

    return SRSLTE_ERROR_CANT_START;

  }

  // Bring up the interface

  // 通过ioctl接口激活tun设备,类似ifconfig tun0 up命令

  sock = socket(AF_INET, SOCK_DGRAM, 0);

  if (0 > ioctl(sock, SIOCGIFFLAGS, &ifr)) {

    err_str = strerror(errno);

    log.error("Failed to bring up socket: %sn", err_str);

    close(tun_fd);

    return SRSLTE_ERROR_CANT_START;

  }

  ifr.ifr_flags |= IFF_UP | IFF_RUNNING;

  if (0 > ioctl(sock, SIOCSIFFLAGS, &ifr)) {

    err_str = strerror(errno);

    log.error("Failed to set socket flags: %sn", err_str);

    close(tun_fd);

    return SRSLTE_ERROR_CANT_START;

  }

// 设置tun的IP地址,Linux系统的路由和协议栈会用到

    sock                                                  = socket(AF_INET, SOCK_DGRAM, 0);

    ifr.ifr_addr.sa_family                                = AF_INET;

    ((struct sockaddr_in*)&ifr.ifr_addr)->sin_addr.s_addr = htonl(ip_addr);

    if (0 > ioctl(sock, SIOCSIFADDR, &ifr)) {

      err_str = strerror(errno);

      log.debug("Failed to set socket address: %sn", err_str);

      close(tun_fd);

      return SRSLTE_ERROR_CANT_START;

    }

    ifr.ifr_netmask.sa_family                                = AF_INET;

    ((struct sockaddr_in*)&ifr.ifr_netmask)->sin_addr.s_addr = inet_addr(args.tun_dev_netmask.c_str());

    if (0 > ioctl(sock, SIOCSIFNETMASK, &ifr)) {

      err_str = strerror(errno);

      log.debug("Failed to set socket netmask: %sn", err_str);

      close(tun_fd);

      return SRSLTE_ERROR_CANT_START;

    }

        ue_stack_lte线程是高层的调度的入口,他是由sync线程进入触发唤醒的,sync线程每个TTI(1ms)都会触发Ue_stack_lte线程运行run_tti,在run_tti线程里面,会调度mac、rrc、nas以及timer的运行。这里是一个比较难理解点,下面我们还是通过代码来分析下具体的过程。

Sync在IDLE状态直接调用radio_recv_fnc,radio_recv_fnc函数会调用stack->run_tti;

case sync_state::IDLE:

        if (radio_h->is_init()) {

          uint32_t nsamples = 1920;

          if (current_srate > 0) {

            nsamples = current_srate / 1000;

          }

          Debug("Discarding %d samplesn", nsamples);

          srslte_timestamp_t rx_time = {};

         // sync IDLE状态会在radio_recv_fnc函数里面调用stack->run_tti接口

          if (!radio_recv_fnc(dummy_buffer, nsamples, &rx_time)) {

            log_h->console("SYNC:  Receiving from radio while in IDLE_RXn");

          }

        Sync在CAMPING状态直接调用srslte_ue_sync_zerocopy,srslte_ue_sync_zerocopy函数调用recv_samples;recv_samples函数会通过回调函数的方式调用radio_recv_fnc,最终也会调用stack->run_tti。

case sync_state::CAMPING:

        worker = (sf_worker*)workers_pool->wait_worker(tti);

        if (worker) {

          // Map carrier/antenna buffers to worker buffers

          for (uint32_t c = 0; c < worker_com->args->nof_carriers; c++) {

            for (uint32_t i = 0; i < worker_com->args->nof_rx_ant; i++) {

              sync_buffer.set(c, i, worker_com->args->nof_rx_ant, worker->get_buffer(c, i));

            }

          }

         // Primary Cell (PCell) Synchronization

         // srslte_ue_sync_zerocopy函数 -> recv_samples -> q->recv_callback -> stack->run_tti

          switch (srslte_ue_sync_zerocopy(&ue_sync, sync_buffer.to_cf_t(),

        stack->run_tti接口函数做的事情比较简单,它的主要任务就是往线程的任务队列里面push一个run_tti_impl任务,ue_stack_lte线程主要就是等待任务执行,当push任务之后,Ue_stack_lte线程就会被唤醒,然后执行对应的run_tti_impl函数。Ue_stack_lte线程每个TTI被唤醒一次,执行一次run_tti_impl。

void ue_stack_lte::run_tti(uint32_t tti, uint32_t tti_jump)

{

  pending_tasks.push(sync_queue_id, [this, tti, tti_jump]() { run_tti_impl(tti, tti_jump); });

}

        Sync线程,这个线程是整个协议栈(包括PHY层)的驱动引擎,整个协议栈都是通过他来trigger的,物理层的上下行调度都是靠它来触发执行的,换句话讲,是sync线程唤醒了其他的线程执行。这里先不展开介绍这个线程的代码。后续在介绍具体流程的时候再详细介绍。

---------------------------------------------------------------------------------------------------------------------------------

        好了,作为sysLTE的第一篇文章,先写到这里,后续会继续进行更新,有问题的读者可以在评论区留言。

最后

以上就是务实果汁为你收集整理的srsLTE 源码分析 – UE_01的全部内容,希望文章能够帮你解决srsLTE 源码分析 – UE_01所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(79)

评论列表共有 0 条评论

立即
投稿
返回
顶部