5GC开源5G核心网(Open5GS)架构详解
Posted 从善若水
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了5GC开源5G核心网(Open5GS)架构详解相关的知识,希望对你有一定的参考价值。
博主未授权任何人或组织机构转载博主任何原创文章,感谢各位对原创的支持!
博主链接
本人就职于国际知名终端厂商,负责modem芯片研发。
在5G早期负责终端数据业务层、核心网相关的开发工作,目前牵头6G算力网络技术标准研究。
博客内容主要围绕:
5G/6G协议讲解
算力网络讲解(云计算,边缘计算,端计算)
高级C语言讲解
Rust语言讲解
文章目录
Open5GS 架构详解
官方网站:https://open5gs.org/
项目code地址:https://github.com/open5gs/open5gs
Open5GS安装步骤:https://open5gs.org/open5gs/docs/guide/01-quickstart/
Open5GS 项目介绍
下图是来自官方网站的软件架构图,图中展示了一些软件容器和一些NFs,Open5GS实现了 4G/5G NSA 和 5G SA 核心网功能。
4G/ 5G NSA Core
Open5GS 4G/ 5G NSA 核心网包括以下组件:
- MME - Mobility Management Entity
- HSS - Home Subscriber Server
- PCRF - Policy and Charging Rules Function
- SGWC - Serving Gateway Control Plane
- SGWU - Serving Gateway User Plane
- PGWC/SMF - Packet Gateway Control Plane / (component contained in Open5GS SMF)
- PGWU/UPF - Packet Gateway User Plane / (component contained in Open5GS UPF)
在4G/ 5G NSA Core实现了控制面和用户面的分离(CUPS)。
MME是主要的控制平面,负责管理会话、移动性、Paging和承载(bearers)。MME与HSS相连接,HSS会生成SIM卡的鉴权矢量以及签约用户的Profile。MME也会与网关服务器的控制平面 SGWC 和 PGWC/SMF相连接。在4G中的所有eNodeBs都会与MME相连接。控制平面的最后一个节点是PCRF,它位于PGWC/SMF和HSS之间,处理收费和执行订阅用户策略。
用户平面用于承载eNB/ NSA gNB (5G NSA基站)与外部广域网之间的用户数据报文。两个用户平面核心组件是SGWU和PGWU/UPF,每一个都与它们的控制平面连接。eNB / NSA gNG连接到SGWU, SGWU连接到PGWU/UPF,再连接到WAN。通过在物理上将控制面和用户面分开,这样就可以在现场部署多个用户面服务器(例如,有高速互联网连接的地方),同时还能保持集中的控制面功能。这有利于支持MEC的应用场景。
上面的控制面和数据面分离,以及控制面集中,数据面可以分布式部署的思想其实就是SDN的思想,关于SDN的介绍可以参考我的博客《【SDN vs. NFV】纠缠不清的SDN和NFV》
所有这些Open5GS组件都有配置文件。每个配置文件包含组件的IP绑定地址/本地接口名称,以及需要连接的其它组件的IP地址/ DNS名称。
5G SA Core
Open5GS 5G SA Core包括以下功能:
- AMF - Access and Mobility Management Function
- SMF - Session Management Function
- UPF - User Plane Function
- AUSF - Authentication Server Function
- NRF - NF Repository Function
- UDM - Unified Data Management
- UDR - Unified Data Repository
- PCF - Policy and Charging Function
- NSSF - Network Slice Selection Function
- BSF - Binding Support Function
5G SA核心网的工作方式与4G核心不同——它使用了基于服务的体系结构(SBI)。将控制面功能配置为向NRF注册,然后由NRF帮助控制面发现其需要的核心网服务。AMF处理连接和移动性管理,是4G MME任务的一个子集。gnb (5G基站)连接到AMF。UDM、AUSF和UDR执行与4G HSS类似的操作,生成SIM认证向量并保存用户配置文件。会话管理全部由SMF处理(以前是由4G MME/ SGWC/ PGWC负责)。NSSF提供了一种选择网络片的方法。最后是PCF,用于收费和执行订阅者策略。
5G SA核心网用户平面简单得多,只包含单一功能。UPF用于承载gNB与外网之间的用户数据报文,它也连接回SMF。除SMF和UPF外,所有5G SA核心网功能的配置文件中只包含该功能的IP绑定地址/本地接口名和NRF的IP地址/ DNS名。
上面5G核心网基于服务的架构,有一部分NFV的思想,关于NFV的介绍可以参考我的博客《【SDN vs. NFV】纠缠不清的SDN和NFV》
下面是项目的目录以及目录中的主要内容,
Open5GS 软件架构
Open5GS 主体由两部分组成,分别是容器环境和NF实体。在容器环境中包含了NF运行需要的最基本的服务,例如消息通知机制、内存管理和定时器等。NF实体就是具体运行的网络功能,架构中提供了统一的接口,每个NF只需要实现这个接口就能放入容器环境中运行。
在NF实体中,每个NF在初始化时都会创建一些池,这些池限定了实例(instance)的数量,例如socket句柄、激活的session、注册的ue等。同时还会初始化一个sbi
实例,用于在不同NFs之间的通信功能。每个NF有一个有限状态机(FSM),状态机是消息驱动的。一般一个NF会提供多个services,NF首先从service池中获取一个service实例,然后初始化这个service,并存入hash表中方便查找。针对不同的service可能还有与这个service相关的FSM。
内存管理这里可以选择开源的内存池库talloc
或者使用自开发的ogs_pool
内存池,总之内存都会被提前开辟,尽量减少内存分配、释放甚至发生缺页异常时对性能的影响。
消息通知机制使用I/O多路复用技术
实现消息接收、发送,并结合queue
实现向FSM传递消息。
定时器机制也是借用I/O多路复用技术
实现的,使用红黑树
存储定时器,位于树根最左边的节点是最快超时的定时器。
Open5GS 配置文件
Open5GS 的配置文件格式为.yaml
格式,每个网络功能(NF)都有一个配置文件,每个NF配置文件中必须要有绑定IP的地址,也就是这个NF自己的SBI地址和端口,一般还会有一个NRF的SBI地址和端口,用于NF向NRF注册自己以及服务发现,下面是一个AMF的配置文件:
amf:
sbi:
- addr: 127.0.0.5
port: 7777
ngap:
- addr: 127.0.0.5
guami:
- plmn_id:
mcc: 901
mnc: 70
amf_id:
region: 2
set: 1
tai:
- plmn_id:
mcc: 901
mnc: 70
tac: 1
plmn_support:
- plmn_id:
mcc: 901
mnc: 70
s_nssai:
- sst: 1
security:
integrity_order : [ NIA2, NIA1, NIA0 ]
ciphering_order : [ NEA0, NEA1, NEA2 ]
network_name:
full: Open5GS
amf_name: open5gs-amf0
nrf:
sbi:
- addr:
- 127.0.0.10
- ::1
port: 7777
在每个NF启动之前会进行初始化操作,在初始化过程中会解析配置文件参数,并保存起来。Open5GS使用开源的LibYAML
库(官方网站)对.yaml
文件进行解析,相关代码如下:
// ~/open5gs-main/lib/app/ogs-init.c
int ogs_app_initialize(
const char *version, const char *default_config,
const char *const argv[])
......
/**************************************************************************
* Stage 2 : Load Configuration File
*/
if (optarg.config_file)
ogs_app()->file = optarg.config_file; // 用户自定义的配置文件路径
else
ogs_app()->file = default_config; //默认配置文件路径
rv = ogs_app_config_read(); // 解析NF配置文件
if (rv != OGS_OK) return rv;
rv = ogs_app_context_parse_config(); // 解析NF配置文件
if (rv != OGS_OK) return rv;
......
return rv;
Open5GS所有默认的配置文件都放置在 ~/open5gs-main/configs
中,如下图:
如果没有设置用户自定义的配置文件,则每个NF启动的时候会自动读取默认的配置文件,默认的配置文件路径在编译Open5GS的时候由meson配置文件自动生成,下图是AMF的meson配置文件:
# ~/open5gs-main/src/amf/meson.build
......
executable('open5gs-amfd',
sources : amf_sources,
c_args : '-DDEFAULT_CONFIG_FILENAME="@0@/amf.yaml"'.format(open5gs_sysconfdir),
include_directories : srcinc,
dependencies : libamf_dep,
install_rpath : libdir,
install : true)
可以看到这里c_args : '-DDEFAULT_CONFIG_FILENAME="@0@/amf.yaml"'.format(open5gs_sysconfdir)
定义配置文件的默认路径。
如何传递用户自定义的配置文件?
通过添加运行时参数-c custom_confg_file_path
即可
YAML的github项目链接
一些简单的yaml语法
YAML 是 “YAML Ain’t a Markup Language”(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)。YAML 的语法和其他高级语言类似,并且可以简单表达清单、散列表,标量等数据形态。
YAML 的配置文件后缀为
.yml
,如:runoob.yml 。
基本语法
- 大小写敏感
- 使用缩进表示层级关系
- 缩进不允许使用tab,只允许空格
- 缩进的空格数不重要,只要相同层级的元素左对齐即可
- '#'表示注释
数据类型
YAML 支持以下几种数据类型:
- 对象:键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary)
- 数组:一组按次序排列的值,又称为序列(sequence) / 列表(list)
- 纯量(scalars):单个的、不可再分的值
YAML 对象
对象键值对使用冒号结构表示 key: value,冒号后面要加一个空格。也可以使用 key:key1: value1, key2: value2, ...
。还可以使用缩进表示层级关系,如下
key:
child-key: value
child-key2: value2
...
较为复杂的对象格式,可以使用问号加一个空格代表一个复杂的 key,配合一个冒号加一个空格代表一个 value:
?
- complexkey1
- complexkey2
:
- complexvalue1
- complexvalue2
意思即对象的属性是一个数组[complexkey1,complexkey2]
,对应的值也是一个数[complexvalue1,complexvalue2]
YAML 数组
以 -
开头的行表示构成一个数组:
- A
- B
- C
YAML 支持多维数组,可以使用行内表示:
key: [value1, value2, ...]
数据结构的子成员是一个数组,则可以在该项下面缩进一个空格。
-
- A
- B
- C
一个相对复杂的例子:
companies:
-
id: 1
name: company1
price: 200W
-
id: 2
name: company2
price: 500W
意思是 companies 属性是一个数组,每一个数组元素又是由 id、name、price 三个属性构成。
数组也可以使用流式(flow)的方式表示:
companies: [id: 1,name: company1,price: 200W,id: 2,name: company2,price: 500W]
复合结构
数组和对象可以构成复合结构,例:
languages:
- Ruby
- Perl
- Python
websites:
YAML: yaml.org
Ruby: ruby-lang.org
Python: python.org
Perl: use.perl.org
转换为 json 为:
languages: [ 'Ruby', 'Perl', 'Python'],
websites:
YAML: 'yaml.org',
Ruby: 'ruby-lang.org',
Python: 'python.org',
Perl: 'use.perl.org'
纯量
纯量是最基本的,不可再分的值,包括:
- 字符串
- 布尔值
- 整数
- 浮点数
- Null
- 时间
- 日期
使用一个例子来快速了解纯量的基本使用:
boolean:
- TRUE #true,True都可以
- FALSE #false,False都可以
float:
- 3.14
- 6.8523015e+5 #可以使用科学计数法
int:
- 123
- 0b1010_0111_0100_1010_1110 #二进制表示
null:
nodeName: 'node'
parent: ~ #使用~表示null
string:
- 哈哈
- 'Hello world' #可以使用双引号或者单引号包裹特殊字符
- newline
newline2 #字符串可以拆成多行,每一行会被转化成一个空格
date:
- 2018-02-17 #日期必须使用ISO 8601格式,即yyyy-MM-dd
datetime:
- 2018-02-17T15:02:31+08:00 #时间使用ISO 8601格式,时间和日期之间使用T连接,最后使用+代表时区
引用
&
锚点和 *
别名,可以用来引用:
defaults: &defaults
adapter: postgres
host: localhost
development:
database: myapp_development
<<: *defaults
test:
database: myapp_test
<<: *defaults
相当于:
defaults:
adapter: postgres
host: localhost
development:
database: myapp_development
adapter: postgres
host: localhost
test:
database: myapp_test
adapter: postgres
host: localhost
&
用来建立锚点(defaults),<<
表示合并到当前数据,*
用来引用锚点。
下面是另一个例子:
- &showell Steve
- Clark
- Brian
- Oren
- *showell
转为 javascript 代码如下:
[ 'Steve', 'Clark', 'Brian', 'Oren', 'Steve' ]
Open5GS 初始化过程
在Open5GS中,每个NF都是一个守护进程,所以每个NF Instance都可以单独启动/关闭,下面是官方教程中给出的参考命令:
$ sudo systemctl restart open5gs-mmed
$ sudo systemctl restart open5gs-sgwcd
$ sudo systemctl restart open5gs-smfd
$ sudo systemctl restart open5gs-amfd
$ sudo systemctl restart open5gs-sgwud
$ sudo systemctl restart open5gs-upfd
$ sudo systemctl restart open5gs-hssd
$ sudo systemctl restart open5gs-pcrfd
$ sudo systemctl restart open5gs-nrfd
$ sudo systemctl restart open5gs-ausfd
$ sudo systemctl restart open5gs-udmd
$ sudo systemctl restart open5gs-pcfd
$ sudo systemctl restart open5gs-nssfd
$ sudo systemctl restart open5gs-bsfd
$ sudo systemctl restart open5gs-udrd
$ sudo systemctl restart open5gs-webui
Open5GS有一个通用的main.c
文件,用于初始化容器环境,然后调用通用的接口app_initialize()
来启动一个NF。下面是这个main.c
的部分关键代码:
# ~/open5gs-main/open5gs-main/src/main.c
int main(int argc, const char *const argv[])
//程序入参解析
......
ogs_signal_init();
ogs_setup_signal_thread();
/*
* ogs_app_initialize() 初始化容器环境
* OPEN5GS_VERSION Open5GS版本号,在编译Open5GS时动态生成
* DEFAULT_CONFIG_FILENAME NF的默认配置文件路径
*/
rv = ogs_app_initialize(OPEN5GS_VERSION, DEFAULT_CONFIG_FILENAME, argv_out);
if (rv != OGS_OK)
if (rv == OGS_RETRY)
return EXIT_SUCCESS;
ogs_fatal("Open5GS initialization failed. Aborted");
return OGS_ERROR;
/*
* app_initialize()创建NF守护进程实例
*/
rv = app_initialize(argv_out);
if (rv != OGS_OK)
if (rv == OGS_RETRY)
return EXIT_SUCCESS;
ogs_fatal("Open5GS initialization failed. Aborted");
return OGS_ERROR;
atexit(terminate);
ogs_signal_thread(check_signal);
ogs_info("Open5GS daemon terminating...");
return OGS_OK;
OPEN5GS_VERSION 是由位于
~/open5gs-main/src/meson.build
在编译时生成的,具体代码如下:...... version_conf.set_quoted('OPEN5GS_VERSION', package_version) configure_file(output : 'version.h', configuration : version_conf) ......
上面代码中提到的app_initialize()
其实就是一个抽象的接口,每个NF必须实现这个接口,以完成NF的创建。每个NF都会有一个app.c
文件,在这个文件中实现了接口函数app_initialize()
,下面是AMF的例子:
/* ~/open5gs-main/src/amf/app.c */
int app_initialize(const char *const argv[])
int rv;
ogs_sctp_init(ogs_app()->usrsctp.udp_port);
/*
* amf_initialize() AMF具体创建函数
*/
rv = amf_initialize();
if (rv != OGS_OK)
ogs_error("Failed to intialize AMF");
return rv;
ogs_info("AMF initialize...done");
return OGS_OK;
下面是不同NF的app.c
文件路径:
NF 启动过程(AMF为例)
我们上面介绍了容器环境的初始化以及NF创建接口函数的实现,下面以AMF实体为例子,介绍一下NF守护进程的创建过程。每个NF会有一个init.c
文件,在这个文件中定义NF的初始化流程,代码如下:
/* ~/open5gs-main/src/amf/init.c */
/*
* 初始化AMF实体
*/
int amf_initialize()
int rv;
amf_context_init();
amf_event_init();
ogs_sbi_context_init();
// 创建AMF和NRF之间的SBI接口
rv = ogs_sbi_context_parse_config("amf", "nrf");
if (rv != OGS_OK) return rv;
// 初始化AMF上下文
rv = amf_context_parse_config();
if (rv != OGS_OK) return rv;
rv = amf_m_tmsi_pool_generate();
if (rv != OGS_OK) return rv;
rv = ogs_log_config_domain(
ogs_app()->logger.domain, ogs_app()->logger.level);
if (rv != OGS_OK) return rv;
rv = amf_sbi_open();
if (rv != OGS_OK) return rv;
rv = ngap_open();
if (rv != OGS_OK) return rv;
//初始化完成,启动AMF程序(创建了一个守护进程)
thread = ogs_thread_create(amf_main, NULL);
if (!thread) return OGS_ERROR;
initialized = 1;
return OGS_OK;
/*
* AMF的守护进程,这部分代码每个NF基本都是相似的,唯一不同的是 FSM 的初始化参数
*/
static void amf_main(void *data)
ogs_fsm_t amf_sm;
int rv;
// AMF 有限状态机初始化
ogs_fsm_create(&amf_sm, amf_state_initial, amf_state_final);
ogs_fsm_init(&amf_sm, 0);
// polling 等待消息
for ( ;; )
// 等待消息
ogs_pollset_poll(ogs_app()->pollset,
ogs_timer_mgr_next(ogs_app()->timer_mgr));
/*
* After ogs_pollset_poll(), ogs_timer_mgr_expire() must be called.
*
* The reason is why ogs_timer_mgr_next() can get the corrent value
* when ogs_timer_stop() is called internally in ogs_timer_mgr_expire().
*
* You should not use event-queue before ogs_timer_mgr_expire().
* In this case, ogs_timer_mgr_expire() does not work
* because 'if rv == OGS_DONE' statement is exiting and
* not calling ogs_timer_mgr_expire().
*/
// 处理超时定时器
ogs_timer_mgr_expire(ogs_app()->timer_mgr);
for ( ;; )
amf_event_t *e = NULL;
//获取消息
rv = ogs_queue_trypop(ogs_app()->queue, (void**)&e);
ogs_assert(rv != OGS_ERROR);
if (rv == OGS_DONE)
goto done;
if (rv == OGS_RETRY)
break;
ogs_assert(e);
//将消息送入FSM进行处理
ogs_fsm_dispatch(&amf_sm, e);
amf_event_free(e);
done:
ogs_fsm_fini(&amf_sm, 0);
ogs_fsm_delete(&amf_sm);
下面是每个NF的init.c
文件路径:
Open5GS 定时器机制
Open5GS使用红黑树
来管理定时器,因为红黑树
的性质,即使在最差的情况下定时器的查找、插入、删除操作也能有一个较好的性能。
Open5GS中红黑树的实现code位于
~/open5gs-main/lib/core/ogs-rbtree.c
几个关键的定时器函数介绍
定时器函数名 | 功能 |
---|---|
ogs_timer_add() | 创建一个定时器 |
ogs_timer_delete() | 删除一个定时器 |
ogs_timer_start() | 启动一个定时器 |
ogs_timer_stop() | 停止定时器 |
ogs_timer_mgr_next() | 获取下一个即将超时的定时器的剩余时间,如果没有定时器在运行,则返回 INFINITE |
ogs_timer_mgr_expire() | 处理超时的定时器,执行超时定时器的回调函数 |
定时器的时间从哪里来?
这里使用的是系统时间,是由I\\O多路复用技术
来帮我们记录定时器运行时间的,还记得前面介绍的AMF实体的amf_main()
函数吗?代码片段如下:
static void amf_main(void *data)
ogs_fsm_t amf_sm;
int rv;
// AMF 有限状态机初始化
ogs_fsm_create(&amf_sm, amf_state_initial, amf_state_final);
ogs_fsm_init(&amf_sm, 0);
// polling 等待消息
for ( ;; )
// 等待消息
ogs_pollset_poll(ogs_app()->pollset,
ogs_timer_mgr_next(ogs_app()->timer_mgr));
......
done:
ogs_fsm_fini(&amf_sm, 0);
ogs_fsm_delete(&amf_sm);
ogs_timer_mgr_next()
函数获取即将超时的定时器的剩余超时时间,并作为参数传入ogs_pollset_poll()
函数,这个函数就是我们上面提及的I\\O多路复用技术
的实现,在Open5GS中可以选择使用select
、epoll
、kqueue
这三种I\\O多路复用技术
,但是我们一般都是以epoll
技术,因为epoll
相比其它的I\\O多路复用技术
性能更好。下面是epoll
函数针对ogs_pollset_poll()
的具体实现过程(详细内容会在 消息通知机制 中介绍):
static int epoll_process(ogs_pollset_t *pollset, ogs_time_t timeout)
struct epoll_context_s *context = NULL;
int num_of_poll;
int i;
5G核心网技术基础自学系列 | 5GC架构概述
[5GC]《5G核心网-赋能数字化时代》| 6.1 PDU会话的概念