Lambert’s Blog

记录学习和进步

使用GDB调试程序

好记性不如烂笔头

February 15, 2024 · 2 min · Lambert Xiao

什么是NUMA

NUMA 今天在学习DPDK的过程中接触到了NUMA,特此记录 什么是NUMA NUMA全称为 Non-uniform memory access, 即非一致性内存访问,它是一种计算机内存设计方案,主要用于多处理器的计算机中。但在理解NUMA之前, 我们需要先了解SMP(Symmetric multiprocessing),即对称性多处理;在SMP架构中,CPU和内存的关系如下图所示: 所有的CPU核心处理器都通过系统总线与内存进行数据交互(中间有Cache层会对数据进行访问加速),这架构很简单对吧,一目了然,但是这个架构存在一个问题, 就是当CPU的核心处理器越来越多时,由于总线在同一时刻只能有一个设备在访问,因此CPU的核心处理器之间会相互争夺总线的使用权,核心越多, 争夺得越激烈,运行效率就越低。 在这种情况下,NUMA诞生了,NUMA的架构其实也很简单,如下 NUMA 尝试通过为每个处理器提供单独的内存来解决此问题,从而避免多个处理器尝试寻址同一内存时对性能造成的影响。 NUMA定义了一个叫Node的概念,每一个Node会包含一组CPU的处理器,内存控制器(Memory Controler)和一组内存。CPU的处理器通过内存控制器访问内存。 Node与Node之间是物理上相互连接着的。 Node内的处理器访问Node里的内存,称为local access,Node内的处理器访问其他Node的内存,称为remote access。 怎么使用NUMA 经过前面的介绍,我们知道,local access是在一个Node内部发生的,争夺内存控制器的使用权的处理器数量是有限的,因此它必然是很高效的; 我们在编程过程中也要尽量让程序在一个Node内运行,而不要在各个Node间相互调度。在Linux上,提供了一个 numactl 的工具可以帮助我们对处理器的numa策略进行配置。 显示node的配置 numactl -H available: 2 nodes (0-1) node 0 cpus: 0 1 2 3 4 5 6 7 8 9 20 21 22 23 24 25 26 27 28 29 node 0 size: 63822 MB node 0 free: 20142 MB node 1 cpus: 10 11 12 13 14 15 16 17 18 19 30 31 32 33 34 35 36 37 38 39 node 1 size: 64507 MB node 1 free: 19918 MB node distances: node 0 1 0: 10 21 1: 21 10 可以看到,我的机器上被划分了两个Node,并且每个node分配的内存在60GB左右,其中 Node0包含处理器(0 1 2 3 4 5 6 7 8 9 20 21 22 23 24 25 26 27 28 29), Node1包含(10 11 12 13 14 15 16 17 18 19 30 31 32 33 34 35 36 37 38 39)。 Node Distances是NUMA架构中的节点距离,指的是从一个节点访问另一个节点上的内存所需要付出的代价或延迟。

February 12, 2024 · 1 min · Lambert Xiao

什么是UIO

UIO 又是学DPDK的时候接触到的知识,DPDK里有特别多的用户态的驱动,实现用户态驱动需要会用到的技术之一就是UIO 什么是UIO UIO全称为UserSpace I/O,是Linux内核提供的一个用户态驱动框架;它由两部分组成,一个非常小巧的内核模块和一个用户态驱动; 为什么需要用UIO 对于许多类型的设备,创建Linux内核驱动程序是多余的。真正需要的是能处理硬件的中断和访问设备的内存空间。控制设备的逻辑不一定必须在内核内,因为设备不需要利用内核提供的任何其他资源。一种常见的设备类型是工业I/O卡。为了解决这种情况,UIO诞生了。对于典型的工业I/O卡,只需要一个非常小的内核模块。驱动程序的主要部分将在用户空间中运行。这简化了开发并降低了内核模块中出现严重错误的风险。 什么硬件适合用UIO UIO不是一个通用的驱动程序接口。已经被其他内核子系统处理得很好的设备(如网络、串行或USB)不适合UIO驱动程序。非常适合UIO驱动程序的硬件可以满足以下所有要求: 设备具有可映射的内存。通过写入该存储器,可以完全控制该设备。 设备通常会产生中断。 该设备不适合标准内核子系统之一。 使用UIO的好处 只需编写和维护一个小的内核模块。 在用户空间中开发驱动程序的主要部分,使用您习惯的所有工具和库。 驱动程序中的错误不会使内核崩溃。 你的驱动程序的更新可以在不重新编译内核的情况下进行。 UIO的困局 UIO的设计初衷是为了提供一种简单、轻量级的用户空间I/O框架,使得用户空间程序能够直接访问设备I/O内存和中断,而不需要内核空间的参与。因此, 不支持 DMA(不受 IOMMU 的保护),也就意味着没有DMA为它做虚拟地址到物理地址的转化,只能直接访问物理地址,危险性极高 由于不支持DMA,所以UIO只能在用户空间操作硬件设备的寄存器空间,而无法支持通过DMA把内核空间的数据传送到用户空间。对于需要通过DMA进行大量数据传输的I/O设备,如网卡、显卡等,UIO就无法满足需求,需要使用其他机制,如VFIO(Virtual Function I/O)等。 UIO需要root权限。

February 12, 2024 · 1 min · Lambert Xiao

什么是VFIO

VFIO 什么是VFIO VFIO可以简单理解为一个增强版的UIO,上一个文章里提到了UIO的几个不足,不支持DMA,仅支持有限的中断,需要用root访问等,而VFIO就是为了弥补这些不足诞生的。 VFIO也是linux提供的一个用户态驱动框架,使用VFIO能开发用户态驱动。VFIO 允许用户将物理设备直接分配给虚拟机,从而让虚拟机获得接近物理机的性能。(VFIO 可以绕过虚拟化管理程序,直接将虚拟机的 I/O 请求发送到物理设备) 为什么要用VFIO 一些应用程序,特别是在高性能计算领域,由于需要很低的延时开销,需要从用户空间直接访问设备。在没有VFIO的时候,有两个选择, 可以选择直接开发内核驱动(复杂又繁琐,稳定性差) 使用UIO框架,但该框架没有IOMMU保护的概念,有限的中断支持,并且需要root权限才能访问PCI配置空间等内容。 VFIO驱动程序框架旨在解决这些问题,取代KVM PCI特定的设备分配代码,并提供比UIO更安全,功能更丰富的用户空间驱动程序环境。 VFIO被用在哪里 KVM QEMU VMware ESXi Hyper-V 怎么使用 VFIO 使用用户态驱动来实现设备直通。用户态驱动运行在虚拟机的用户空间中,可以直接访问物理设备。 VFIO 的工作流程如下: 用户在虚拟机中安装 VFIO 驱动。 用户将物理设备分配给虚拟机。 VFIO 驱动在虚拟机的用户空间中启动。 VFIO 驱动访问物理设备。 VFIO 提供了一系列 API 来管理设备直通。这些 API 允许用户: 列出可用的物理设备 将物理设备分配给虚拟机 从虚拟机中释放物理设备 配置 VFIO 驱动 VFIO的组成 Device(设备) 设备是指要操作的硬件设备,这些设备可以是网卡、显卡、存储控制器等。在VFIO中,设备是通过IOMMU(Input/Output Memory Management Unit)进行管理的。IOMMU是一个硬件单元,它可以把设备的IO地址映射成虚拟地址,为设备提供页表映射,使得设备可以直接通过DMA(Direct Memory Access)方式访问内存。 设备在VFIO中是被隔离和暴露给虚拟机或用户空间程序的关键资源。通过VFIO,设备可以被分配给特定的虚拟机或用户空间程序,以实现设备直通。 Group(组) Group是IOMMU能进行DMA隔离的最小硬件单元。一个group可以包含一个或多个device,具体取决于物理平台上硬件的IOMMU拓扑结构,Group是硬件上的划分。这意味着,如果一个设备在硬件拓扑上是独立的,那么它本身就构成一个IOMMU group。而如果多个设备在硬件上是互联的,需要相互访问数据,那么这些设备需要被放到同一个IOMMU group中。在VFIO中,group是设备直通的最小单位。也就是说,当设备直通给一个虚拟机时,group内的所有设备都必须同时直通给该虚拟机。 Container(容器) Container是由多个group组成的集合,Container是逻辑上的划分,为了让内部的group能共享某些资源。 虽然group是VFIO的最小隔离单元,但在某些情况下,将多个group组合到一个container中可以提高系统的性能。例如,当多个group需要共享一个页表时,将它们组合到一个container中是有益的。此外,将多个group放入一个container中也方便用户进行管理和控制。...

February 12, 2024 · 1 min · Lambert Xiao

什么是Virtio

Virtio 什么是Virtio Virtio是一种半虚拟化 I/O 设备虚拟化技术,用于在虚拟机管理程序(hypervisor)中实现虚拟设备。 它提供了一种通用的、高性能的设备模型和应用编程接口 (API),可以减少虚拟化开销并提高虚拟机的性能。 半虚拟化是一种虚拟化技术,它介于完全虚拟化和裸机运行之间。与完全虚拟化不同,半虚拟化需要对操作系统内核进行修改,使其意识到自己正在运行在虚拟化环境中。 为什么使用Virtio Virtio的出现是为了解决传统虚拟化设备模拟的性能问题。传统虚拟化设备模拟通常需要在虚拟机管理程序和虚拟机之间进行多层数据复制,这会导致性能下降。Virtio通过半虚拟化技术将设备驱动程序的一部分功能移到虚拟机中,从而减少了数据复制的次数,提高了性能。 传统虚拟化 完全虚拟化 完全虚拟化是一种完全模拟物理硬件的虚拟化技术。在这种方式下,虚拟机管理程序(Hypervisor)会创建一个虚拟的硬件环境,包括虚拟 CPU、虚拟内存、虚拟磁盘、虚拟网络等。虚拟机操作系统认为自己正在运行在真实的物理硬件上。完全虚拟化的实现方式主要有两种: 软件二进制翻译:虚拟机管理程序会将虚拟机操作系统的二进制指令翻译成真实 CPU 可以执行的指令。这种方式可以支持任何操作系统,但性能会受到一定的影響。 动态二进制翻译:虚拟机管理程序会在虚拟机操作系统运行时动态地翻译指令。这种方式可以提高性能,但需要对操作系统内核进行一些修改。 硬件辅助虚拟化 硬件辅助虚拟化是一种利用 CPU 和其他硬件提供的虚拟化功能的虚拟化技术。在这种方式下,虚拟机管理程序可以利用 CPU 的虚拟化扩展指令(如 Intel VT 或 AMD SVM)来提高虚拟化的性能和效率。硬件辅助虚拟化的实现方式主要有两种: 全虚拟化:虚拟机管理程序仍然会模拟一部分硬件,但会利用 CPU 的虚拟化扩展指令来提高性能。 半虚拟化:虚拟机操作系统需要进行一些修改,以便直接使用 CPU 的虚拟化扩展指令。这种方式可以获得更高的性能,但对操作系统的兼容性要求更高。 Virtio的主要优点包括: 提高性能:Virtio通过减少数据复制的次数来提高虚拟机的性能。 提高可靠性:Virtio使用共享内存机制进行通信,这可以提高虚拟机的可靠性。 提高可移植性:Virtio提供了一种通用的设备模型,可以移植到不同的虚拟机管理程序和虚拟机操作系统中。 Virtio应用在哪里 Virtio主要用于虚拟机环境中,包括服务器虚拟化、桌面虚拟化和云计算等。 Virtio工作原理 Virtio采用前端-后端架构 前端驱动程序(FE), FE驱动程序只需要提供配置接口、传递消息、生成请求和启动后端virtio驱动程序的服务。因此,FE驱动程序易于实现,并且消除了模拟设备的性能开销 后端驱动程序(BE), BE驱动程序在主机操作系统的用户区或内核区运行,使用来自FE驱动程序的请求并将其发送到主机本地设备驱动程序。一旦主机本地设备驱动程序完成请求,BE驱动程序通知FE驱动程序请求完成 前端驱动程序和后端驱动程序通过虚拟队列(Virtqueue)进行通信。虚拟队列是一种共享内存机制,用于在虚拟机管理程序和虚拟机之间传递数据。 virtio支持PCI/PCIe总线和MMIO总线 参考文章:https://projectacrn.github.io/latest/developer-guides/hld/hld-virtio-devices.html#virtio-apis

February 12, 2024 · 1 min · Lambert Xiao

进程的内存是怎么布局的

今天来探讨一下 linux 环境下进程的内存布局

October 15, 2023 · 8 min · Lambert Xiao

openssl生成自签名证书

工作中用到了,由于需要签入指定的域名,折腾了一番,写篇文章记录一下 生成证书的配置文件 创建openssl.conf,填入下面内容 [req] distinguished_name = req_distinguished_name req_extensions = v3_req [req_distinguished_name] countryName = country stateOrProvinceName = province localityName = city organizationName = company name commonName = domain name or ip [v3_req] subjectAltName = @alt_names [alt_names] DNS.1=test.com DNS.2=www.test.com 生成私钥文件 openssl genrsa -out test.key 2048 生成证书的request文件 openssl req -new -key test.key -out test.csr -config openssl.conf -subj '/C=CN/ST=BeiJing/L=BeiJing/O=test.com/OU=test/CN=test/emailAddress=test@qq.com' 查看生成的request文件 openssl req -in test.csr -text -noout 生成证书文件 openssl x509 -req -days 3650 -sha1 -in test....

April 20, 2023 · 1 min · Lambert Xiao

Linux-dlsym

dlsym(dynamic link symbol)

April 13, 2023 · 2 min · Lambert Xiao

怎么使用pkg-config

pkg-config 是一种用于获取已安装软件包编译选项的工具

April 12, 2023 · 1 min · Lambert Xiao

如何使用mmap读写文件

读写文件新姿势

April 9, 2023 · 1 min · Lambert Xiao

gtest-使用指北

少年,你懂测试驱动开发吗?

March 29, 2023 · 2 min · Lambert Xiao

cmake-简单指北

入坑C/C++前的一座大山

March 28, 2023 · 1 min · Lambert Xiao

如何使用liburing读写磁盘

什么是liburing liburing是一个用于异步IO库,它提供了简洁易用的API来处理文件I/O、网络I/O以及事件驱动I/O等各种I/O操作。liburing库基于Linux内核中的io_uring特性实现,将I/O请求从应用层转移到内核层以提高应用程序的I/O性能。 由于liburing在内核版本5.1才引入,所以需要运行环境的linux内核版本大于等于5.1 为什么使用liburing liburing库对于一些高并发、高吞吐量的程序,特别是网络服务器、云存储等高性能系统的设计和实现有很大的帮助作用。 如何使用 建议参考源码里的步骤自行编译安装,源码:https://github.com/axboe/liburing 常用函数介绍: io_uring_queue_init:用于初始化io_uring并且返回其句柄。 io_uring_queue_exit:用于关闭并释放io_uring的句柄。 io_uring_get_sqe:用于获取一个可用的sqe,即I/O请求对应的队列元素数据结构。 io_uring_prep_readv:用于准备一个异步读请求。 io_uring_prep_writev:用于准备一个异步写请求。 io_uring_sqe_set_data:用于将用户私有数据关联到一个sqe(请求)中。 io_uring_submit:用于提交一个或一批异步IO请求到io_uring。 io_uring_peek_cqe:用于查看完成队列(cq)中的未处理项数量。 io_uring_wait_cqe:阻塞等待一个处理完成的io。 io_uring_cqe_get_data:用于获取特定的完成队列项(cqe),其中包含先前提交的IO请求的结果以及相关的私有数据。 io_uring_cqe_seen:用于标记一个完成队列项(cqe)已被处理过。 下面使用liburing封装一个DiskUtil实现对文件的基本读写 编写disk_util.h头文件 #include <functional>#include <string>#include <liburing.h>#include <thread> enum IO_OP { OP_READ = 0, OP_WRITE = 1 }; struct IORequest { IO_OP opcode; // 0: read, 1: write char* buffer; off_t offset; size_t length; std::function<void(int)> callback; }; class DiskUtil { public: DiskUtil(const std::string& file_path, int block_size); ~DiskUtil(); void submit_request(IORequest* req); void start(); void stop(); private: void io_worker_thread(); int open_file(); void close_file(); void process_io_request(IORequest* req); int submit_io_request(IORequest* req); void complete_io_request(); private: int fd_ = -1; io_uring io_ring_; const std::string file_path_; const int block_size_; std::thread io_worker_; bool is_running_ = false; }; 编写disk_tool....

March 26, 2023 · 3 min · Lambert Xiao

在centos上安装多版本gcc

前言 有些时候,我们的开发环境需要用到多个版本的gcc, g++,在centos上有方便的工具帮助我们来处理这件事 操作步骤 安装centos-release-scl sudo yum install centos-release-scl scl 的含义是 SoftwareCollections,软件集合之意. 安装devtoolset 安装gcc8使用如下命令: yum install devtoolset-8-gcc* 安装gcc7使用如下命令: yum install devtoolset-7-gcc* 安装的内容会在/opt/rh目录下 激活对应的devtoolset scl enable devtoolset-8 bash 它实际上会调用/opt/rh/devtoolset-8/enable 脚本, 完成工具切换 查看版本 g++ -v gcc -v

March 25, 2023 · 1 min · Lambert Xiao

如何使用libaio读写磁盘

什么是libaio libaio是Linux异步I/O文件操作库,它能够提供更高效的文件异步I/O读写方式。 为什么使用libaio 异步I/O操作是指将数据传输请求发送给操作系统后,操作系统会立即返回并继续执行其他任务,而不必等待数据传输完成,这种操作方式可以充分利用CPU和I/O设备的资源,提高系统的I/O性能。 相比于传统的同步I/O操作方式,异步I/O操作需要通过系统调用和事件通知机制实现,在编程实现上具有较高的难度。而libaio封装了这些细节,使得开发人员可以更方便地使用异步I/O操作,从而提高应用程序的I/O性能。因此,如果需要高效的文件I/O操作,并期望充分利用系统资源,可以考虑使用libaio。 如何使用 安装libaio apt install libaio-dev 接口介绍 io_setup:用于初始化异步IO环境并返回其句柄。 io_destroy:用于清除异步IO环境并关闭相应的文件描述符。 io_getevents:用于等待指定数量的IO事件(如读、写或错误)并将它们存储在指定的缓冲区中。 io_prep_pread:用于为读取一个文件块准备异步IO操作。 io_prep_pwrite:用于为写入一个文件块准备异步IO操作。 io_submit:用于提交一或多个异步IO请求,并将其排入异步IO环境中,等待事件处理。 下面使用libaio封装一个DiskUtil实现对文件的基本读写 编写disk_util.h头文件 #include <functional>#include <string>#include <libaio.h>#include <thread> enum IO_OP { OP_READ = 0, OP_WRITE = 1 }; struct IORequest { IO_OP opcode; // 0: read, 1: write char* buffer; off_t offset; size_t length; std::function<void(int)> callback; }; class DiskUtil { public: DiskUtil(const std::string& file_path, int block_size); ~DiskUtil(); void submit_request(IORequest* req); void start(); void stop(); private: void io_worker_thread(); int open_file(); void close_file(); void process_io_request(IORequest* req); int submit_io_request(IORequest* req); void complete_io_request(io_context_t ctx, io_event* events, int num_events); private: int fd_ = -1; const std::string file_path_; const int block_size_; // 对应一个会话的上下文 io_context_t io_ctx_ = 0; std::thread io_worker_; bool is_running_ = false; }; 编写disk_tool....

March 25, 2023 · 4 min · Lambert Xiao

怎么使用valgrind诊断内存问题

让valgrind为你的程序保驾护航

March 22, 2023 · 3 min · Lambert Xiao

聊聊内存模型

原子性, 内存屏障, 内存重排序

March 21, 2023 · 1 min · Lambert Xiao

文件系统之ext2

麻雀虽小,但五脏俱全

November 22, 2022 · 9 min · Lambert Xiao

安装新版gcc

查找当前gcc的版本 http://ftp.gnu.org/gnu/gcc/ 下载对应的版本,并解压 cd ~/gcc 使用gcc自带的脚本安装依赖 ./contrib/download_prerequisites 生成makefile mkdir build && cd build/ ../configure -enable-checking=release -enable-languages=c,c++ -disable-multilib 编译 make -j 8 如果中途出现g++: error: gengtype-lex.c: No such file or directory, 需要安装apt install flex 安装到机器上 make install 设置环境变量 export CC=/usr/local/bin/gcc export CXX=/usr/local/bin/g++ 查看默认编译选项 echo "" | gcc -v -x c++ -E - 解决GLIBCXX之类的错误 这是因为glibc++的版本太老,先查找 whereis libstdc++.so.6 // /usr/lib/x86_64-linux-gnu/libstdc++.so.6 ls -al /usr/lib/x86_64-linux-gnu/libstdc++.so.6 // /usr/lib/x86_64-linux-gnu/libstdc++.so.6 -> libstdc++.so.6.0.24 在gcc的build目录下查找新编译出来的libstdc++.so find . -name "*libstdc++*" 将找到的libstdc++.so.6.0.30放到/usr/lib/x86_64-linux-gnu/下,并通过软链替换掉原本的指向

October 31, 2022 · 1 min · Lambert Xiao

编译aws-sdk-cpp

git clone https://github.com/aws/aws-sdk-cpp cd aws-sdk-cpp mkdir build && cd build cmake ../ -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DBUILD_ONLY="s3" cmake --build aws-cpp-sdk-core cmake --build aws-cpp-sdk-s3 cmake --install aws-cpp-sdk-core --prefix ~/workspace/aws cmake --install aws-cpp-sdk-s3 --prefix ~/workspace/aws BUILD_ONLY 选项可以指定编译的模块,不同模块用;隔开

October 31, 2022 · 1 min · Lambert Xiao