GPU虚拟化笔记

First Post:

Last Update:

一、样例demo透析:

旨在透过简单的demo样例刨析,FlexGV系统的工作原理。
demo地址:https://github.com/J-StrawHat/cuda-hook-demo/blob/master/build.sh

首先什么是解释一下什么是CMake项目:

CMake 是一个 跨平台的构建系统,用于生成编译配置文件(如 Makefile、Visual Studio 解决方案等)。CMake 本身不会编译代码,但它会为不同的平台和编译器生成适合的构建配置文件,从而让开发者可以使用 make 或 ninja 等工具完成编译。

一个 CMake 项目,通常指的是使用 CMake 构建的 C/C++ 项目,其中包含:

CMakeLists.txt(CMake 配置文件)
源代码(.cpp、.h 等)
CMake 生成的构建文件(Makefile、Visual Studio 解决方案 等)

CMake 不是编译器,它用于生成编译配置文件(如 Makefile)。
CMake 项目 是指使用 CMakeLists.txt 组织的 C/C++ 项目。
使用 CMake 的好处:

跨平台:支持 Linux、Windows、MacOS。
自动查找依赖(如 find_package(OpenCV))。
支持构建动态/静态库。

你可以把 CMake 理解为 “项目构建的指挥官”,它负责告诉编译器怎么编译你的项目。

1.build.sh (主要用于构建编译系统,提前创建好如何编译和处理文件。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/bin/bash
set -o errexit
set -o pipefail
set -o nounset

echo_cmd() {
echo $1
$1
}

WORK_PATH=$(cd $(dirname $0) && pwd) && cd $WORK_PATH

compile() {
isSample=$1

if [[ $isSample == 1 ]]; then
cmakeFlag="-DBUILD_SAMPLE=ON"
else
cmakeFlag="-DBUILD_HOOK=ON"
fi

echo_cmd "rm -rf build"
echo_cmd "mkdir build"
echo_cmd "cd build"
echo_cmd "cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$WORK_PATH/out $cmakeFlag -DCMAKE_SKIP_RPATH=ON .."
echo_cmd "make -j2"
echo_cmd "make install"
echo_cmd "cd $WORK_PATH"
}

echo_cmd "rm -rf out"

# build for hook
compile 0

# build for sample
compile 1

这个脚本的主要流程是:

确保所有命令在当前脚本所在目录下执行。
清除 out 目录,确保干净环境。
定义 compile 函数,接收 0 或 1 选择不同的 cmake 选项。
先编译 hook 版本(-DBUILD_HOOK=ON)。
再编译 sample 版本(-DBUILD_SAMPLE=ON)。
这样可以确保 hook 和 sample 版本都被正确编译和安装。

2.cuda_hook.cpp //这里相当于拦截层

这个文件主要用于创建cuda_hook,来截取用户的请求也就是CUDA_API。逻辑流程就是在执行用户的需求时也就是执行main.cu文件的时候,首先在终端通过LD_PRELOAD 将main.cu加载到对应的cuda_hook所在的目录里,在main.cu执行的时候,LD_PRELOAD=./libhook.so,系统在 动态链接库解析阶段 发现 libhook.so 里提供了 cudaMalloc,所以会优先调用 libhook.so 里的 cudaMalloc。

LD_PRELOAD原理:预加载一个共享库,在动态链接时优先使用自己的 cudaMalloc。不影响 原始 CUDA 运行时库(只是拦截)

在 LD_PRELOAD 机制下:

1.程序启动时,LD_PRELOAD 里的 libhook.so 先被加载。
2.cudaMalloc 被调用时,动态链接器优先使用 libhook.so 里的 cudaMalloc。
3.如果 libhook.so 里的 cudaMalloc 需要调用原始 cudaMalloc,它会用 dlsym(RTLD_NEXT, "cudaMalloc") 找到 CUDA 运行时库里的真正 cudaMalloc 并调用它。这里可以实现拦截和真实函数的衔接。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>
#include <cuda_runtime.h>
#include <dlfcn.h>

// 声明原始的 cudaMalloc 函数类型
typedef cudaError_t (*cudaMalloc_t)(void **devPtr, size_t size);

// 获取原始 cudaMalloc 函数指针
static cudaMalloc_t real_cudaMalloc = nullptr;

// 自定义的 cudaMalloc 拦截函数
cudaError_t cudaMalloc(void **devPtr, size_t size) {
if (!real_cudaMalloc) {
// 动态加载原始的 cudaMalloc 函数
real_cudaMalloc = (cudaMalloc_t) dlsym(RTLD_NEXT, "cudaMalloc");
//dlsym() 是 dlfcn.h 提供的一个函数,用于在共享库中查找符号(函数地址)。RTLD_NEXT 表示 查找下一个同名符号,即查找 CUDA 运行时库中的 原始 cudaMalloc。如果 dlsym() 失败(找不到原始 cudaMalloc),则输出错误信息,并返回 cudaErrorUnknown。
if (!real_cudaMalloc) {
fprintf(stderr, "[hook] Error: unable to find real cudaMalloc\n");
return cudaErrorUnknown;
}
}

// 输出日志:记录每次 cudaMalloc 调用的大小
printf("[hook] Intercepted cudaMalloc call: size = %zu bytes\n", size);

// 调用原始的 cudaMalloc
return real_cudaMalloc(devPtr, size);
}

3.main.cu //这里相当于用户层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <cstdio>
#include <cassert>
#include <cuda_runtime.h>

int main() {
void *devPtr;
cudaError_t err = cudaMalloc(&devPtr, 1024);
if (err != cudaSuccess) {
printf("[main] cudaMalloc failed: %s\n", cudaGetErrorString(err));
}
else {
printf("[main] cudaMalloc succeeded\n");
assert(cudaFree(devPtr) == cudaSuccess);
}
return 0;
}

4.CMakeLists.txt

CMakeLists.txt 是 CMake 的配置文件,它定义了 如何构建(编译、链接、安装)一个 C/C++ 项目。

CMake 是一个跨平台的 构建工具,可以生成适用于不同编译器(GCC、Clang、MSVC)和构建系统(Makefile、Ninja、Visual Studio)的项目文件。
CMakeLists.txt 主要用于 描述源码结构、指定编译选项、链接库 等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
cmake_minimum_required(VERSION 3.12)
project(CudaHook)


set (CMAKE_POSITION_INDEPENDENT_CODE ON)

set (CMAKE_C_FLAGS "-std=c17")
set (CMAKE_C_FLAGS_DEBUG "$ENV{CFLAGS} -O0 -g2 -ggdb -DHOOK_BUILD_DEBUG")
set (CMAKE_C_FLAGS_RELEASE "$ENV{CFLAGS} -O3")

set (CMAKE_CXX_FLAGS "-std=c++17")
set (CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -g2 -ggdb -DHOOK_BUILD_DEBUG")
set (CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3")

set (CMAKE_SHARED_LINKER_FLAGS "-s -Wl,--exclude-libs,ALL")
set (CMAKE_EXE_LINKER_FLAGS "-Wl,--as-needed")

# 添加一个选项来决定是否编译拦截库或者 main 程序
option(BUILD_SAMPLE "Build the sample main program" OFF)
option(BUILD_HOOK "Build the CUDA hook library" OFF)

# 如果选择编译拦截库
if(BUILD_HOOK)
find_package(CUDA REQUIRED)
include_directories(${CUDA_INCLUDE_DIRS})
add_library(cuda_hook SHARED cuda_hook.cpp)
set_target_properties(cuda_hook PROPERTIES POSITION_INDEPENDENT_CODE ON)
target_link_libraries(cuda_hook ${CUDA_LIBRARIES} dl)
install(TARGETS cuda_hook LIBRARY DESTINATION lib64)
endif()

# 如果选择编译 main 程序
if(BUILD_SAMPLE)
find_package(CUDA REQUIRED)
unset (CUDA_USE_STATIC_CUDA_RUNTIME CACHE)
option (CUDA_USE_STATIC_CUDA_RUNTIME OFF)

include_directories(${CUDA_INCLUDE_DIRS})
set (CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -std=c++17")

cuda_add_executable(main main.cu)
target_link_libraries(main ${CUDA_LIBRARIES} dl)
install(TARGETS main RUNTIME DESTINATION .)
endif()