¡Empieza y disfruta! coolbpf hard core mejora la eficiencia de desarrollo de BPF | Tecnología Dragon lizard

Introducción: ¡Imprescindible para productos secos! ¿Qué es el desarrollo del disfrute?

Nota del editor: la tecnología BPF todavía está en pleno apogeo. Este artículo primero presenta el conocimiento de BPF para guiarlo a comenzar con BPF, y luego presenta la compilación remota de coolbpf (anteriormente conocida como LCC, LibbpfCompilerCollection), lo que significa jugar genial BPF , cuyo objetivo es BPF complejo. El proceso de desarrollo y compilación se simplifica, y este artículo también usará un ejemplo para experimentar el desarrollo divertido de coolbpf. Este artículo está organizado a partir de la edición número 20 de Dragon Lizard Lecture Hall. La maravillosa repetición del video compartido se ha subido al sitio web oficial de Dragon Lizard (Home-Dynamic-Video), ¡bienvenido a echarle un vistazo!

640.png

I. Introducción

Para completar el desarrollo de un programa binario BPF, debe crear un entorno de desarrollo y compilación, prestar atención a la versión del kernel del sistema de destino, debe dominar la programación desde el modo kernel BPF hasta el modo usuario, y cómo cargar y enlazar con el punto HOOK correspondiente Esperando eventos Trigger, y finalmente procesar el registro de salida y los datos.

Estos procesos se sentirán bastante engorrosos para un compañero de clase que es nuevo en BPF. Este artículo primero presenta el conocimiento de BPF, lo lleva a comenzar con BPF y luego presenta la compilación remota de coolbpf (anteriormente conocida como LCC, LibbpfCompilerCollection, que significa cool BPF, el objetivo de simplificar el complejo proceso de desarrollo y compilación de BPF) , con un ejemplo para experimentar el desarrollo del disfrute coolbpf.

2. Introducción a BPF

BPF se propuso por primera vez en 1992. En ese momento, se llamaba Berkely Packet Filter (Berkely Packet Filter, comúnmente conocido como cBPF). Se afirmaba que era 20 veces más rápido que la tecnología de filtrado de paquetes más avanzada en ese momento. La aplicación principal los escenarios fueron tcpdump y seccomp.

En 2014, Alexei Starovoitov transformó completamente BPF y propuso el filtro de paquetes de Berkeley extendido (eBPF). La instrucción eBPF está más cerca del hardware ISA, lo cual es conveniente para mejorar el rendimiento y brinda una capacidad general para ejecutar códigos específicos de manera eficiente y segura en función de los eventos del sistema o del programa. Los usuarios de las funciones generales ya no se limitan a los desarrolladores del kernel. Sus escenarios de uso ya no son solo análisis de red, y se pueden desarrollar varios tipos de herramientas y plataformas, como análisis de rendimiento, seguimiento de sistemas y optimización de red, basados ​​en eBPF. Por lo general, no hacemos distinción y llamamos a cBPF y eBPF BPF.

2.1 Resumen de los puntos de conocimiento de BPF

Todo el mundo habla de BPF, y hay muchos artículos sobre BPF. Aquí, primero resumiremos los puntos de conocimiento de BPF. Finalmente, presentaremos cómo comenzar rápidamente desde la arquitectura de instrucciones BPF más básica y los tipos de mapas y programas.

  • conjunto de instrucciones risc: contiene 11 registros de 64 bits (R0 ~ R10).
  • mapas: Interacción de datos entre programas BPF o entre kernel y modo usuario.
  • tipo de programa: El tipo de programa BPF se utiliza para determinar la función del programa y dónde se adjunta el programa.
  • Funciones auxiliares: acceda a los datos del kernel a través de funciones auxiliares, como acceder a tareas, pids, etc.
  • jit: convierte el código de bytes del programa BPF en el código de máquina de la máquina de destino.
  • Fijación de objetos: proporcione un sistema de archivos BPF, prolongue el ciclo de vida del mapa y prog.
  • llamada final: un programa BPF puede llamar a otro programa BPF sin volver al programa original una vez que se completa la llamada.
  • endurecimiento: Proteja los programas BPF y sus archivos binarios para que no se corrompan (establecidos en solo lectura).

2.2 Descripción general del desarrollo de BPF

Después de décadas de desarrollo, BPF ha pasado de ser un uso funcional único a aplicaciones en varios campos, incluidos servidores empresariales nativos en la nube y sistemas de seguridad, todos los cuales utilizan esta tecnología para servir a la producción y la vida. La siguiente figura es el historial de desarrollo de la tecnología BPF (fuente:

zhuanlan.zhihu.com/p/444454862

).

640 (2).png

A medida que se incorporan más y más características a la comunidad del kernel de Linux, las funciones admitidas por BPF se han vuelto cada vez más abundantes debido a estas características.

640 (3).png

2.3 Comparación de BPF y Módulos Kernel

BPF 的优势是灵活、安全。在没有引入 BPF 之前,如果我们想要实现一些跟踪诊断的功能,可以使用 tracefs/debugfs 中导出的文件系统接口,或者编写内核模块插入到内核中运行。前者的功能较为固定,无法灵活地实现对数据的过滤;后者虽然灵活,但容易导致内核 crash,不够安全。ftrace 和 kernel module 给我们很大的自由去抓取内核的一些信息,但是却存在不少弊端。

下图是内核模块和 BPF 的对比(图源:

zhuanlan.zhihu.com/p/444454862

)。

640 (4).png

2.4 BPF 指令集

BPF(默认指 eBPF 非 cBPF) 程序指令都是 64 位,使用了 11 个 64 位寄存器,32 位称为半寄存器(subregister)和一个程序计数器(program counter),一个大小为 512 字节的 BPF 栈。所有的 BPF 指令都有着相同的编码方式。eBPF虚拟指令系统属于 RISC,拥有 11 个虚拟寄存器、r0-r10,在实际运行时,虚拟机会把这 11 个寄存器一 一对应于硬件 CPU 的物理寄存器。下图是新老指令的对比:

640 (5).png

BPF 指令的核心结构体如下,每一条 eBPF 指令都以一个 bpf_insn 来表示,在 cBPF 中是其他的一个结构体(struct sock_filter ),不过最终都会转换成统一的格式,这里我们只研究 eBPF:

640 (6).png

由结构体中的__u8 code 可以知道,一条 BPF 指令是 8 个字节长。这 8 位的 code,第 0、1、2 位表示的是该操作指令的类别,共 8 种:

640 (7).png

从最低位到最高位分别是:

  • 8 位的 opcode;有 BPF_X 类型的基于寄存器的指令,也有 BPF_K 类型的基于立即数的指令
  • 4 位的目标寄存器 (dst)
  • 4 位的原始寄存器 (src)
  • 16 位的偏移(有符号),是相对于栈、映射值(map values)、数据包(packet data)等的相对偏移量
  • 32 位的立即数 (imm)(有符号)

640 (8).png

8 bit 的 opcode 进一步拆开,下图表示的是存储和加载类指令:

640 (9).png

下图表示的是运算和跳转指令:

640 (10).png

总之,BPF 指令很简洁,我们未必会在开发过程中使用它来进行代码编写(类似于纯汇编,会让人崩溃),了解这些是有助于我们更深刻的理解 BPF 的运行原理。

2.5 BPF 的 prog type 和 map

PROG TYPE

BPF 相关的程序,首先需要设置为相对应的的程序类型,截止 Linux 内核 5.8 程序类型定义有 29 个,而且还在持续增加中,BPF 程序类型(prog_type)决定了程序可以调用的内核辅助函数的子集,也决定了程序输入上下文 -- bpf_context 结构的格式。

我们经常使用BPF程序类型主要涉及以下两类:

  • 跟踪

大部BPF程序都是这一类,主要通过kprobe、tracepoint(rawtracepoint)等追踪系统行为及获取系统硬件信息。也可以访问特定程序的内存区域,从运行进程中提取执行跟踪信息。

  • 网络

这类BPF程序用于检测和控制系统的网络流量。可以对网络接口数据包进行过滤,甚至可以完全拒绝数据包。

用户态是通过系统调用来加载BPF程序到内核的,在加载程序时,需要传递的参数中有一个字段叫prog_type,这个就是BPF的程序类型,跟踪相关的是:BPF_PROG_TYPE_KPROBE和BPF_PROG_TYPE_TRACEPOINT,网络相关是:BPF_PROG_TYPE_SK_SKB、BPF_PROG_TYPE_SOCK_OPS等。下面是描述BPF程序类型的枚举结构:

enum bpf_prog_type {
  BPF_PROG_TYPE_UNSPEC,        /* Reserve 0 as invalid program type */
  BPF_PROG_TYPE_SOCKET_FILTER,
  BPF_PROG_TYPE_KPROBE,
  BPF_PROG_TYPE_SCHED_CLS,
  BPF_PROG_TYPE_SCHED_ACT,
  BPF_PROG_TYPE_TRACEPOINT,
  BPF_PROG_TYPE_XDP,
  BPF_PROG_TYPE_PERF_EVENT,
  BPF_PROG_TYPE_CGROUP_SKB,
  BPF_PROG_TYPE_CGROUP_SOCK,
  BPF_PROG_TYPE_LWT_IN,
  BPF_PROG_TYPE_LWT_OUT,
  BPF_PROG_TYPE_LWT_XMIT,
  BPF_PROG_TYPE_SOCK_OPS,
  BPF_PROG_TYPE_SK_SKB,
  BPF_PROG_TYPE_CGROUP_DEVICE,
  BPF_PROG_TYPE_SK_MSG,
  BPF_PROG_TYPE_RAW_TRACEPOINT,
  BPF_PROG_TYPE_CGROUP_SOCK_ADDR,
  BPF_PROG_TYPE_LWT_SEG6LOCAL,
  BPF_PROG_TYPE_LIRC_MODE2,
  BPF_PROG_TYPE_SK_REUSEPORT,
  BPF_PROG_TYPE_FLOW_DISSECTOR,
  /* See /usr/include/linux/bpf.h for the full list. */
};
复制代码

BPF MAP

BPF 的 map 可用于内核 BPF 程序和用户应用程序之间实现双向的数据交换, 是重要基础数据结构,它可以通过声明 struct bpf_map_def 结构完成创建。

关于 BPF 最吸引人的一个方面,就是运行在内核上的程序可以在运行时使用消息传递相互通信,而 BPF Map 就是用户空间和内核空间之间的数据交换、信息传递的桥梁。

BPF Map 本质上是以键/值方式存储在内核中的数据结构。在内核空间的程序创建 BPF Map 并返回对应的文件描述符,在用户空间运行的程序就可以通过这个文件描述符来访问并操作 BPF Map。

根据申请内存方式的不同,BPF Map 有很多种类型,常用的类型是BPF_MAP_TYPE_HASH 和 BPF_MAP_TYPE_ARRAY,它们背后的内存管理方式跟我们熟悉的哈希表和数组基本一致。随着多 CPU 架构的成熟发展,BPF Map 也引入了per-cpu 类型,如 BPF_MAP_TYPE_PERCPU_HASH、BPF_MAP_TYPE_PERCPU_ARRAY 等,每个 CPU 都会存储并看到它自己的 Map 数据,从属于不同 CPU 之间的数据是互相隔离的。

下面是描述 BPF map 的枚举结构:

enum bpf_map_type {
  BPF_MAP_TYPE_UNSPEC,
  BPF_MAP_TYPE_HASH,
  BPF_MAP_TYPE_ARRAY,
  BPF_MAP_TYPE_PROG_ARRAY,
  BPF_MAP_TYPE_PERF_EVENT_ARRAY,
  BPF_MAP_TYPE_PERCPU_HASH,
  BPF_MAP_TYPE_PERCPU_ARRAY,
  BPF_MAP_TYPE_STACK_TRACE,
  BPF_MAP_TYPE_CGROUP_ARRAY,
  BPF_MAP_TYPE_LRU_HASH,
  BPF_MAP_TYPE_LRU_PERCPU_HASH,
  BPF_MAP_TYPE_LPM_TRIE,
  BPF_MAP_TYPE_ARRAY_OF_MAPS,
  BPF_MAP_TYPE_HASH_OF_MAPS,
  BPF_MAP_TYPE_DEVMAP,
  BPF_MAP_TYPE_SOCKMAP,
  BPF_MAP_TYPE_CPUMAP,
  BPF_MAP_TYPE_XSKMAP,
  BPF_MAP_TYPE_SOCKHASH,
  BPF_MAP_TYPE_CGROUP_STORAGE,
  BPF_MAP_TYPE_REUSEPORT_SOCKARRAY,
  BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE,
  BPF_MAP_TYPE_QUEUE,
  BPF_MAP_TYPE_STACK,
  BPF_MAP_TYPE_SK_STORAGE,
  BPF_MAP_TYPE_DEVMAP_HASH,
  BPF_MAP_TYPE_STRUCT_OPS,
  BPF_MAP_TYPE_RINGBUF,
  BPF_MAP_TYPE_INODE_STORAGE,
  BPF_MAP_TYPE_TASK_STORAGE,
  BPF_MAP_TYPE_BLOOM_FILTER,
};
复制代码

三、BPF 的开发姿势

3.1 BPF 开发框架

640 (11).png

上图就是非常经典的 BPF 开发框架图了,一般开发流程都是先将用户编写的特定程序通过 LLVM 编译为 BPF 字节码,在注入到内核中时会经过 verifier 严格的检查,确保代码不会出现死循环、宕机之类的问题,然后再通过jit将其翻译为 native code 进行执行,用户可以通过查看内核透出的数据,了解系统的运行情况。

虽然 BPF 有非常多的应用,但使用的时候也有许多的限制。比如:

  • 不能有不确定的循环操作;内核要确保执行流一定可以从BPF程序中出来。
  • 不允许睡眠;睡眠可能导致内核执行流一直出不来。
  • 不允许修改内核数据结构;一般不能修改数据报文。
  • 栈空间有限;当前只有 512 字节。
  • 不允许被直接调用内核函数;必须通过辅助函数。

3.2 开源工具和平台

BPF的编程分为两部分,一部分是运行在内核态,这是 BPF 编程的核心;另一部分是运行在用户态,这部分代码主要用来加载BPF,采集并处理数据等。

libbpf

libbpf是官方的用户态库。和编译普通的c程序会得到一个.o文件一样,LLVM编译完BPF 程序也会得到一个的.o文件(一般我们命名为xx.bpf.o)。这个 bpf.o 文件按照 elf 的格式组织 BPF 程序的字节码、map定义以及符号等信息,libbpf 库负责解析这些信息,创建 map,注入 BPF 程序到内核工作。因此,对于用户来说,需要做很多繁琐的工作:

  • 用 C 语言来编写 BPF 程序;
  • 编写 makefile,调用 LLVM 编译生成 .o 文件;
  • 调用 libbpf 的接口加载 .o 文件;
  • 使用 libbpf 的接口获取 map 中的数据。

BCC

BCC 是如今最热门也是对新手最友好的开源平台。它用 python 封装了编译、加载和读取数据的过程,提供了很多非常好用的 API。

和 libbpf 需要提前把 bpf 程序编译成 bpf.o 不同,BCC 是在运行时才调用 LLVM 进行编译的,所以要求用户环境上有 llvm 和 kernel-devel。这个就会像后面我们提到的,它会出现运行时偏移导致的瞬时资源冲高问题。

bpftrace

bpftrace 是单命令工具,让用户像使用脚本一样使用 BPF。它自定义了自己的 DSL 作为前端,底层也是调用 LLVM 的。事实上,它依赖于 BCC 提供的 libbcc.so 文件。

3.3 libbpf CORE

前面提到 libbpf 开发,成为当前最主流的开发方式,但是需要为每个内核版本都开发特定的二进制程序,不能达到同一个 BPF 程序在不同内核版本上安全运行。由于不同内核版本数据的内存布局不同,就需要支持CORE(CompileOnce、Runeverywhere),提到 CORE 我们要先来了解一下 BTF。

BTF(BPF Type Format)是一种类似于 DWARF 的格式,专用于描述程序中数据类型。其主要存在于两个地方:

一是 vmlinux 镜像内。

二是用户编写的 BPF 程序内。

二者存在着一定的差异。第一个差异是:BPF 程序内除了存在 .btf 段外,还存在.btf.ext 段,专用于记录 BPF 程序内使用的数据类型的情况。第二个差异是:vmlinux 镜像内的 BTF 程序由原存在于该镜像内的 dwarf 信息简化而来,而 BPF 程序内的 BTF 段由 CLANG 编译器生成,需要在编译时指定 --target bpf。利用 BPF 程序里的 BTF 段和存放在每个系统上的 BTF 文件,我们将这些信息进行重定位,就能确定每个数据结构的偏移,达到 CORE 的目的。

关键组件:
  • BTF: 描述内核镜像,获取内核及BPF程序类型和代码的关键信息(

    pylcc.openanolis.cn/

    )

  • Clang 释放 bpf 程序重定位信息到 .btf 段

  • Libbpf CO-RE 根据 .btf 段重定位 bpf 程序

目前在 libbpf CO-RE 中需要进行重定位的信息主要有三类:

1)结构体相关重定位,这部分和 BTF 息息相关;

Clang 通过 __builtin_preserve_access_index() 记录成员偏移量

u64 inode = task->mm->exe_file->f_inode->i_ino;
u64 inode = BPF_CORE_READ(task, mm, exe_file, f_inode, i_ino);
复制代码

2)map fd、全局变量、extern 等重定位,这部分主要依赖于 ELF 重定位机制。通过查找 ELF 重定位段收集重定位信息,更新相应指令的 imm 字段。

skel->rodata->my_cfg.feature_enabled = true;
skel->rodata->my_cfg.pid_to_filter = 123;
extern u32 LINUX_KERNEL_VERSION   __kconfig;
extern u32 CONFIG_HZ              __kconfig;
复制代码

3)子函数重定位,也是依赖于 ELF 重定位机制。但是目的不一样,子函数重定位是为了将 eBPF 程序调用的子函数同主函数放在同一块连续的内存中,便于一起加载到内核。例如将所有子程序拷贝到主程序所在区域, always_inline 函数。

libbpf CORE 开发步骤:

1)生成带所有内核类型的头文件 vmlinux.h

bpftoolbtf dump file vmlinux format c > vmlinux.h
复制代码

2)使用 Clang (版本 10 或更新版本)将BPF程序的源代码编译为 .o 对象文件;

3)从编译好的 BPF 对象文件中生成BPF skeleton 头文件 bpftool gen 命令生成;

4)在用户空间代码中包含生成的 BPF skeleton 头文件;

5)编译用户空间代码,这样会嵌入 BPF 对象代码,后续就不用发布单独的文件。

6)生成的 BPF skeleton 使用如下步骤加载、绑定、销毁:

__open() – 创建并打开 BPF 应用,之后可以设置 skel->rodata 变量。

__load() – 初始化,加载和校验BPF 应用部分。

__attach() – 附加所有可以自动附加的BPF程序 (可选,可以直接使用 libbpf API 作更多控制)。

__destroy() – 分离所有的 BPF 程序并使用其使用的所有资源。

四、 coolbpf 享受式开发

前面我们已经对 BPF 有了一个认识,到此已经入门了,同时也学习了 BPF 的高阶知识:libbpf 的 CORE,它也是未来的一个方向,但是我们也看到这里面,还需要写一堆代码:open、load、attach 等等。我们能否把这一切进行简化呢?能不能享受式的进行开发。别急,先来看看常用的开发方式:

4.1 BPF 开发常用方案对比

1)原生 libbpf,无 CO-RE (内核 samples/bpf 示例)

优势:资源占用量低

缺点:

  • 需要搭建代码工程、开发效率低;
  • 不同内核版本兼容性差;

2)BCC(BPF Compile Collection、python 代码)

优势:开发效率高,可移植性好,支持动态修改内核部分代码

缺点:

  • 部署依赖的 Clang/LLVM;
  • 每次运行都要执行 Clang/LLVM 编译,争抢内存 CPU 内存等资源;
  • 依赖目标环境头文件;

3)BPF CO-RE(libbpf-tools 下面的代码)

优势:不依赖在环境中部署 Clang/LLVM,资源占用少

缺点:

  • 仍需要搭建编译编译工程;
  • 部分代码相对固定,无法动态配置;
  • 用户态开发支持信息较少,缺乏高级语言对接;

综上所述,上述方案不能很好适配生产环境中,多内核并存、快速批量部署等需求

4.2 Coolbpf(可以酷玩的 BPF)解决的问题

通过将 BPF 的三种开发方式对比,我们发现都不能完美的在生产环境中解决如下几个问题:

  • 安装依赖库和内核头文件;
  • CPU 和内存等资源瞬时冲高;
  • BTF 需要按版本随 BPF 二进制程序发布。

为解决这几个问题,我们提出一个 coolbpf 的开发编译平台,目前包含 pylcc、rlcc、golcc 等目录,分别是高级语言 python、rust 和 go 语言支持远程和本地编译的能力。

代码链接地址:

gitee.com/anolis/cool…

github.com/aliyun/cool…

这里不对 coolbpf 过多介绍,具体内容请参考《龙蜥社区开源 coolbpf,BPF 程序开发效率提升百倍》。

4.3 pyLCC 的享受式

为了介绍什么叫享受式开发,在这里我们拿 coolbpf 的 pyLCC 进行演示:

import sys
from pylcc.lbcBase import ClbcBase, CexecCmd  //import pylcc base库
bpfProg = r"""
struct data_t {
    int cpu;
    int type;   // 0: irq, 1:sirq
    u32 stack_id;
    u64 delayed;
};
LBC_PERF_OUTPUT(e_out, struct data_t, 128); //定义perf event output array map
LBC_STACK(call_stack, 256);  //定义stack 的map
SEC("kprobe/check_timer_delay")
int j_check_timer_delay(struct pt_regs *ctx)
{
    struct data_t data = {};
    
    data.cpu = PT_REGS_PARM2(ctx);
    data.type = PT_REGS_PARM1(ctx);
    data.delayed = PT_REGS_PARM3(ctx);
    data.stack_id = bpf_get_stackid(ctx, &call_stack, KERN_STACKID_FLAGS);
    
    bpf_perf_event_output(ctx, &e_out, BPF_F_CURRENT_CPU, &data, sizeof(data));
    return 0;
}
"""
class Crunlatency(ClbcBase):
    def __init__(self, lat=10):
        self._exec = CexecCmd
        self.setupKo(lat >> 1)
    //只需要简单的init,就可以把open load attach 等动作做好,然后专注于数据处理
        super(Crunlatency, self).__init__("runlatency", bpf_str=bpfProg)
    def _cb(self, cpu, data, size):
        stacks = self.maps['call_stack'].getStacks(e.stack_id)
        print("call trace:")   //call back函数里专心处理数据
        for s in stacks:
            print(s)
复制代码

大家看到上面的示例,只需要以下三步,就可以完成一个程序的开发:

1)pip install coolbpf。

2)编写 bpf.c 代码。

3)编写 python,通过 init() 加载之后,就专注功能开发。

你可以不用关心 BPF 的汇编、字节码,也不用安装 Clang,不用安装 kernel-dev 头文件,不用自己生成 BTF 文件(它会自动到远程服务器下载),只需专注你的功能开发,比如分析网络流量、监控文件打开和关闭、跟踪系统调用。

总结来看,BPF 技术还在如火如荼的发展着,但只要掌握了这些基础的知识点,就能够触类旁通,利用好已有的工具或平台,更加能如虎添翼。通过前面的介绍,我们不仅对 BPF 有了比较深刻的理解,还能借助 coolbpf,非常酷的玩了一把享受式的开发,简洁如此,谁能不爱呢?

关于回放和课件获取

【视频回放】:视频回访已上传至龙蜥官网(可阅读原文直达):

openanolis.cn/video

查看。 【PPT课件获取】:关注微信公众号(OpenAnolis),回复“龙蜥课件” 即可获取。有任何疑问请随时咨询龙蜥助手—小龙(微信:openanolis_assis)。

—— 完 ——

加入龙蜥社群

加入微信群:添加社区助理-龙蜥社区小龙(微信:openanolis_assis),备注【龙蜥】与你同在;加入钉钉群:扫描下方钉钉群二维码。欢迎开发者/用户加入龙蜥社区(OpenAnolis)交流,共同推进龙蜥社区的发展,一起打造一个活跃的、健康的开源操作系统生态!

Cuenta oficial y grupo de comunicación Xiaolong.png

关于龙蜥社区

龙蜥社区(OpenAnolis)由企事业单位、高等院校、科研单位、非营利性组织、个人等在自愿、平等、开源、协作的基础上组成的非盈利性开源社区。龙蜥社区成立于 2020 年 9 月,旨在构建一个开源、中立、开放的Linux 上游发行版社区及创新平台。

龙蜥社区成立的短期目标是开发龙蜥操作系统(Anolis OS)作为 CentOS 停服后的应对方案,构建一个兼容国际 Linux 主流厂商的社区发行版。中长期目标是探索打造一个面向未来的操作系统,建立统一的开源操作系统生态,孵化创新开源项目,繁荣开源生态。

目前,Anolis OS 8.6 已发布,更多龙蜥自研特性,支持 X86_64 、RISC-V、Arm64、LoongArch 架构,完善适配 Intel、兆芯、鲲鹏、龙芯等芯片,并提供全栈国密支持。

欢迎下载:

openanolis.cn/download

加入我们,一起打造面向未来的开源操作系统!

openanolis.cn

原文链接:click.aliyun.com/m/100034975…

本文为阿里云原创内容,未经允许不得转载。

Supongo que te gusta

Origin juejin.im/post/7122761769612738573
Recomendado
Clasificación