Kernel Misc

kref

kref_init 是一个用于初始化内核对象引用计数器(krefs)的函数。它允许你为你的对象添加引用计数,确保在多个地方使用和传递对象时,代码的正确性。以下是关于 kref_init 的一些重要信息:

  • 初始化:

在分配内存并创建对象后,你需要调用 kref_init 来初始化引用计数器。例如:

struct my_data *data;
data = kmalloc(sizeof(*data), GFP_KERNEL);
if (!data)
    return -ENOMEM;
kref_init(&data->refcount);

这将在 kref 中的 refcount 设置为 1。

  • 使用规则:

在对指针进行非临时拷贝(尤其是传递给另一个执行线程)之前,必须使用 kref_get 增加引用计数。 在完成对指针的处理后,必须调用 kref_put。如果这是对指针的最后一次引用,释放程序将被调用。

  • 示例:
void data_release(struct kref *ref) {
    struct my_data *data = container_of(ref, struct my_data, refcount);
    kfree(data);
}

void more_data_handling(void *cb_data) {
    struct my_data *data = cb_data;
    // 处理 data
    kref_put(&data->refcount, data_release);
}

int my_data_handler(void) {
    int rv = 0;
    struct my_data *data;
    data = kmalloc(sizeof(*data), GFP_KERNEL);
    if (!data)
        return -ENOMEM;
    kref_init(&data->refcount);
    kref_get(&data->refcount);
    // 创建线程处理数据
    // ...
    kref_put(&data->refcount, data_release);
    return rv;
}

在上述示例中,两个线程处理数据的顺序并不重要,kref_put 会在数据不再被引用时释放它。 请注意,遵循这些规则可以确保正确管理内核对象的引用计数,避免内存泄漏和悬挂指针。

IDR(ID Range)

IDR 是一种用于管理连续整数范围的数据结构,通常用于内核中需要为对象分配唯一标识符的场景。 idr_alloc 函数用于在 Linux 内核中分配 IDR(ID Range)对象中的未使用的 ID。

以下是 idr_alloc 函数的用法:

  1. 首先,您需要初始化一个 IDR。对于静态分配的 IDR,您可以使用 DEFINE_IDR() 宏;对于动态分配的 IDR,您可以使用 idr_init() 函数。
  2. 调用 idr_alloc() 来分配一个未使用的 ID。
  3. 使用 idr_find() 查询与该 ID 相关的指针。
  4. 使用 idr_remove() 释放该 ID。

如果需要更改与某个 ID 相关联的指针,您可以调用 idr_replace()。这通常用于保留 ID,通过将 NULL 指针传递给分配函数,然后使用保留的 ID 初始化对象,最后将初始化的对象插入 IDR。

到目前为止,所有用户都满足了 UINT_MAX 的限制,因此他们使用 idr_alloc_u32()。

如果需要按顺序分配 ID,您可以使用 idr_alloc_cyclic()。请注意,处理较大数量的 ID 时,IDR 的效率会降低,因此使用这个函数会有一些代价。

当您使用完 IDR 后,可以调用 idr_destroy() 来释放 IDR 占用的内存。这不会释放 IDR 指向的对象;如果您想这样做,请使用其中一个迭代器来执行此操作。

您可以使用 idr_is_empty() 来查看当前是否分配了任何 ID。

如果在从 IDR 分配一个新 ID 时需要带锁,您可能需要传递一组限制性的 GFP 标志,但这可能导致 IDR 无法分配内存。为了解决该问题,您可以在获取锁之前调用 idr_preload(),然后在分配之后调用 idr_preload_end()。

#include <linux/idr.h>

int main(void) {
    struct idr my_idr;
    int id1, id2;
    void *ptr1, *ptr2;

    // Initialize the IDR
    idr_init(&my_idr);

    // Allocate two unused IDs
    id1 = idr_alloc(&my_idr, "sample1", 0, 0, GFP_KERNEL);
    id2 = idr_alloc(&my_idr, "sample2", 0, 0, GFP_KERNEL);

    // Associate pointers with the IDs
    ptr1 = (void *)0xdeadbeef;
    ptr2 = (void *)0xcafebabe;
    idr_replace(&my_idr, ptr1, id1);
    idr_replace(&my_idr, ptr2, id2);

    // Look up pointers by ID
    ptr1 = idr_find(&my_idr, id1);
    ptr2 = idr_find(&my_idr, id2);

    // Free the IDs
    idr_remove(&my_idr, id1);
    idr_remove(&my_idr, id2);

    // Destroy the IDR
    idr_destroy(&my_idr);

    return 0;
}

非一致性内存 和一致性

dma_alloc_noncoherent

它是Linux内核中的一个DMA内存分配函数,用于分配一段物理内存,使其可以被DMA硬件访问12. 这个函数的作用是在非一致性内存(non-coherent memory)上分配一块区域,以便设备可以使用它作为DMA的源或目标地址。让我详细解释一下这个函数的用途和参数。

dma_alloc_noncoherent函数的原型如下:

void *dma_alloc_noncoherent(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t flag);

  • dev: 指向设备结构的指针,表示要为哪个设备分配内存。
  • size: 要分配的内存大小(以字节为单位)。
  • dma_handle: 用于返回DMA地址的指针。这个地址可以转换为与总线宽度相同的无符号整数,并传递给设备作为分配区域的DMA地址基址。
  • flag: 用于指定内存分配的GFP_标志(类似于kmalloc()中的标志)。例如,可以使用GFP_KERNEL来分配普通内核内存。 非一致性内存是一种特殊类型的内存,写入它的数据可以立即被处理器或设备读取,而无需考虑缓存效应。需要注意的是,CPU不能直接引用dma_addr_t,因为物理地址空间和DMA地址空间之间可能存在转换。

使用dma_alloc_noncoherent分配的内存区域不保证一致性,因此在使用之前,可能需要手动刷新处理器的写缓冲区,以确保设备可以正确读取该内存。 释放由dma_alloc_noncoherent分配的内存时,应使用dma_free_noncoherent函数:

void dma_free_noncoherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t dma_handle);

dev、size和dma_handle参数必须与传递给dma_alloc_noncoherent的相同。 cpu_addr是由dma_alloc_noncoherent返回的虚拟地址。 请注意,与其他内存分配函数不同,这些函数只能在启用IRQ的情况下调用。

如果你的驱动程序需要大量较小的DMA一致性内存区域,你可以使用DMA池(dma_pool)来分配和管理这些区域,而不是使用dma_alloc_coherent()。DMA池类似于kmem_cache,但它使用dma_alloc_coherent()而不是__get_free_pages()

dma_alloc_wc

这个函数允许驱动程序申请带缓存一致性的DMA内存。缓存一致性是指确保CPU和DMA设备之间的数据一致性,以避免数据不一致的问题。使用dma_alloc_wc分配的内存区域旨在在CPU和DMA设备之间保持一致,以便数据正确传输。

释放由dma_alloc_wc分配的内存时,应使用dma_free_wc函数:

void dma_free_wc(struct device *dev, size_t size, void *cpu_addr, dma_addr_t dma_handle);

  • dev、size和dma_handle参数必须与传递给dma_alloc_wc的相同。
  • cpu_addr是由dma_alloc_wc返回的虚拟地址。

adf

定时器

timer_setup

它是Linux内核中用于初始化定时器的函数。它能够方便地设置和初始化一个计时器,并通过设置参数来灵活地控制计时器的行为1. 合理使用timer_setup函数可以让我们更好地处理时间相关的任务,提高操作系统的性能和可靠性。

在Linux内核中,定时器通常使用timer_list结构体来表示。下面是timer_list结构体的一些关键字段:

  • entry: 定时器列表元素,用于将定时器挂载在内核定时器链表上。
  • expires: 定时器定时时间。
  • function: 定时器回调函数,定时器时间到时执行该函数。
  • flags: 标志位,用于设置定时器的属性。

在旧版本的内核中,我们使用init_timer函数来初始化定时器。而在新版本中,这个函数变成了timer_setup函数。下面是timer_setup函数的定义:

void timer_setup(struct timer_list *timer, void (*callback)(struct timer_list *), unsigned int flags);

使用timer_setup函数时,我们需要传入以下参数:

  • timer: 要初始化的定时器。
  • callback: 定时器的回调函数,此函数的形参是当前定时器的变量。
  • flags: 标志位,可以设置定时器的属性。
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/timer.h>

MODULE_LICENSE("GPL");

static struct timer_list my_timer;

void my_timer_callback(struct timer_list *timer) {
    printk(KERN_ALERT "This line is printed after 5 seconds.\n");
}

static int init_module_with_timer(void) {
    printk(KERN_ALERT "Initializing a module with timer.\n");

    // Setup the timer for initial use
    timer_setup(&my_timer, my_timer_callback, 0);

    // Set the timer interval to 5000 milliseconds (5 seconds)
    mod_timer(&my_timer, jiffies + msecs_to_jiffies(5000));

    return 0;
}

static void exit_module_with_timer(void) {
    printk(KERN_ALERT "Goodbye, cruel world!\n");
    del_timer(&my_timer);
}

module_init(init_module_with_timer);
module_exit(exit_module_with_timer);

高精度定时器

hrtimer_init 是 Linux 内核中与高精度定时器(HRTimer)相关的函数之一。让我为您详细介绍一下,并提供一个示例代码:

  1. HRTimer 简介
    • HRTimer 是 Linux 内核中的高精度定时器,用于提供纳秒级别的时钟精度。
    • 与传统的定时器相比,HRTimer 允许更精确地控制定时事件,适用于对时间要求较高的场景,如看门狗、USB、以太网、块设备、虚拟机等子系统。
  2. hrtimer_init 函数
    • hrtimer_init 用于初始化一个 struct hrtimer 实例。
    • 参数:
      • timer:指向要初始化的 HRTimer 实例的指针。
      • clock_id:时钟的种类,例如 CLOCK_MONOTONIC 表示自系统开机以来的单调递增时间。
      • mode:定时器的模式,可以是绝对时间(HRTIMER_MODE_ABS)或相对时间(HRTIMER_MODE_REL)。
  3. 示例代码
    • 下面是一个使用 HRTimer 的简单示例代码,用于在内核中启动一个相对时间的 HRTimer:
      #include <linux/hrtimer.h>
      #include <linux/ktime.h>
      #include <linux/module.h>
           
      MODULE_LICENSE("GPL");
           
      static struct hrtimer my_timer;
      static ktime_t interval;
           
      static enum hrtimer_restart my_timer_callback(struct hrtimer *timer) {
          // Your timer callback logic here
          // For demonstration purposes, let's print a message.
          pr_info("HRTimer callback executed!\n");
          return HRTIMER_RESTART;
      }
           
      static int __init my_module_init(void) {
          // Initialize the HRTimer
          interval = ktime_set(1, 0); // Set interval to 1 second
          hrtimer_init(&my_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
          my_timer.function = my_timer_callback;
           
          // Start the timer
          hrtimer_start(&my_timer, interval, HRTIMER_MODE_REL);
           
          pr_info("HRTimer module initialized\n");
          return 0;
      }
           
      static void __exit my_module_exit(void) {
          // Cleanup: Stop the timer
          hrtimer_cancel(&my_timer);
          pr_info("HRTimer module removed\n");
      }
           
      module_init(my_module_init);
      module_exit(my_module_exit);
      
    • 在上述示例中,我们初始化了一个相对时间的 HRTimer,设置了回调函数 my_timer_callback,并启动了定时器。

内核线程

kthread_create_worker() 函数是Linux内核中用于创建内核线程的一个函数。通过设置标志参数和格式化字符串,可以指定创建内核线程的行为和名称。它分配并初始化了一个kthread_worker结构体,并使用它来创建内核线程.

以下是kthread_create_worker函数的一些关键参数:

  • cpu: 如果大于等于0,将创建特定于某个CPU的工作线程;如果不想创建特定于CPU的工作线程,可以将CPU域赋值为-1。
  • flags: 可以设置一些标志位,根据需要来控制内核线程的行为。
  • namefmt: 一个格式化字符串,用于指定内核线程的名称。

这个函数会分配内存并初始化kthread_worker结构,然后返回指向该结构的指针。您可以根据具体需求使用这个函数来创建和管理内核线程。

如果您需要一个示例代码,以下是一个简单的例子,展示了如何在模块初始化时创建一个内核线程,以及如何在卸载模块时关闭该内核线程:

#include <linux/module.h>
#include <linux/kthread.h>
#include <linux/delay.h>

MODULE_LICENSE("GPL");

static int demo_thr(void *data) {
    while (!kthread_should_stop()) {
        msleep_interruptible(2000);
        printk(KERN_INFO "Thread is running...\n");
    }
    return 0;
}

static struct task_struct *thr = NULL;

static int kthread_demo_init(void) {
    thr = kthread_run(demo_thr, NULL, "kthread-demo");
    if (!thr) {
        printk(KERN_ERR "Failed to create kthread\n");
        return -ENOMEM;
    }
    return 0;
}

static void kthread_demo_exit(void) {
    if (thr) {
        kthread_stop(thr);
        thr = NULL;
    }
}

module_init(kthread_demo_init);
module_exit(kthread_demo_exit);

在这个示例中,我们使用kthread_run函数创建一个名为kthread-demo的内核线程,它每隔2秒打印一条信息。在卸载模块时,我们使用kthread_stop来关闭该内核线程。

工作队列

schedule_work

函数是Linux内核中的一个重要函数,用于将一个工作项(work)添加到工作队列(workqueue)中。这个函数的作用是在后台执行一些延迟较长的任务,而不会阻塞主线程的执行。

以下是关于schedule_work函数的一些要点:

  • 功能:将工作项添加到默认的工作队列(通常是system_wq)中,以便稍后执行。
  • 调用方式:schedule_work(&my_work);,其中my_work是一个已经初始化的工作项。
  • 工作队列:工作队列是一种异步执行机制,用于处理延迟的或非实时的任务。
  • 延迟执行:schedule_work会将工作项添加到工作队列中,等待系统调度执行。这样,主线程可以继续执行其他任务,而不必等待工作项完成。
  • 工作项回调函数:工作项的实际执行逻辑由回调函数定义。当工作项被调度执行时,会调用这个回调函数。

以下是一个简单的示例代码,展示了如何使用INIT_WORK和schedule_work来创建和调度一个工作项:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/workqueue.h>

MODULE_LICENSE("GPL");

static struct work_struct my_work;

// 工作项的回调函数
static void my_work_handler(struct work_struct *work) {
    printk(KERN_INFO "My work handler is running...\n");
    // 在这里执行您的工作逻辑
}

static int init_my_module(void) {
    printk(KERN_INFO "Initializing my kernel module with workqueue...\n");

    // 初始化工作项
    INIT_WORK(&my_work, my_work_handler);

    // 将工作项添加到工作队列
    schedule_work(&my_work);

    return 0;
}

static void cleanup_my_module(void) {
    printk(KERN_INFO "Cleaning up my kernel module...\n");

}

module_init(init_my_module);
module_exit(cleanup_my_module);

在这个示例中,我们首先定义了一个名为my_workqueue的工作队列结构体,以及一个名为my_work的工作项。然后,在init_my_module函数中使用create_singlethread_workqueue来创建一个名为my_workqueue的工作队列。接着,我们使用INIT_WORK来初始化工作项,并使用schedule_work来调度它。

alloc_ordered_workqueue

用于创建有序的工作队列(workqueue)。让我详细介绍一下,并提供一个示例代码:

  1. alloc_ordered_workqueue 简介
    • alloc_ordered_workqueue 函数用于分配一个有序的工作队列。
    • 有序工作队列是一种特殊类型的工作队列,它确保工作项按照提交的顺序执行。
  2. 函数签名
    struct workqueue_struct *alloc_ordered_workqueue(const char *name, unsigned int flags);
    
  3. 参数说明
    • name:工作队列的名称。
    • flags:标志位,用于配置工作队列的行为。
  4. 示例代码

    下面是一个简单的示例代码,展示如何使用 alloc_ordered_workqueue 创建一个有序的工作队列:

    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/workqueue.h>
    
    static struct workqueue_struct *my_ordered_wq;
    
    static void my_work_handler(struct work_struct *work)
    {
        pr_info("Work item executed!\n");
    }
    
    static DECLARE_WORK(my_work, my_work_handler);
    
    static int __init my_module_init(void)
    {
        my_ordered_wq = alloc_ordered_workqueue("my_ordered_wq", 0);
        if (!my_ordered_wq) {
            pr_err("Failed to create ordered workqueue\n");
            return -ENOMEM;
        }
    
        queue_work(my_ordered_wq, &my_work);
    
        return 0;
    }
    
    static void __exit my_module_exit(void)
    {
        destroy_workqueue(my_ordered_wq);
    }
    
    module_init(my_module_init);
    module_exit(my_module_exit);
    MODULE_LICENSE("GPL");
    MODULE_DESCRIPTION("Ordered Workqueue Example");
    

    在此示例中,我们创建了一个名为 “my_ordered_wq” 的有序工作队列,并将一个工作项 my_work 提交到队列中。工作项的处理函数 my_work_handler 将在有序的顺序中执行。

create_singlethread_workqueue

create_singlethread_workqueue 是 Linux 内核中的一个函数,用于创建一个只包含单个工作线程的工作队列(workqueue)。让我详细介绍一下:🙂

  1. create_singlethread_workqueue 简介
    • create_singlethread_workqueue 函数用于创建一个只包含一个工作线程的工作队列。
    • 无论系统中有多少个 CPU,这个工作队列都只会有一个工作线程
  2. 函数签名
    struct workqueue_struct *create_singlethread_workqueue(const char *name);
    
  3. 参数说明
    • name:工作队列的名称。
  4. 工作原理
    • create_singlethread_workqueue 创建的工作队列只有一个工作线程。
    • 所有提交到这个工作队列的工作项都会由这个单一的工作线程按顺序执行。
  5. 示例代码: 下面是一个简单的示例代码,展示如何使用 create_singlethread_workqueue 创建一个只包含单个工作线程的工作队列:

    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/workqueue.h>
    
    static struct workqueue_struct *my_singlethread_wq;
    
    static void my_work_handler(struct work_struct *work)
    {
        pr_info("Work item executed!\n");
    }
    
    static DECLARE_WORK(my_work, my_work_handler);
    
    static int __init my_module_init(void)
    {
        my_singlethread_wq = create_singlethread_workqueue("my_singlethread_wq");
        if (!my_singlethread_wq) {
            pr_err("Failed to create singlethread workqueue\n");
            return -ENOMEM;
        }
    
        queue_work(my_singlethread_wq, &my_work);
    
        return 0;
    }
    
    static void __exit my_module_exit(void)
    {
        destroy_workqueue(my_singlethread_wq);
    }
    
    module_init(my_module_init);
    module_exit(my_module_exit);
    MODULE_LICENSE("GPL");
    MODULE_DESCRIPTION("Singlethread Workqueue Example");
    

    在此示例中,我们创建了一个名为 “my_singlethread_wq” 的工作队列,并将一个工作项 my_work 提交到队列中。这个工作项的处理函数 my_work_handler 将在单一的工作线程中按顺序执行。

completion

init_completion() 是Linux内核中用于完成事件通知机制的一个函数,主要用于进程间或线程间的同步。这个函数初始化一个 completion 结构体,该结构体用于表示某个事件是否已经发生。在多线程或多进程编程中,有时需要一个线程或进程等待另一个线程或进程完成某个任务。

让我们来详细了解一下 init_completion() 函数的功能和用法:

  • 初始化completion结构体:
    • completion 结构体用于维护“complete”状态,表示某个任务是否已完成。
    • 结构体定义如下:
      struct completion {
         unsigned int done;
         struct swait_queue_head wait;
      };
      
    • done 字段表示完成状态,初始值为 0。
    • swait_queue_head 是一个等待队列头,用于管理等待该完成事件的线程。
  • init_completion() 函数:
    • 动态定义及初始化一个信号量:
      #define init_completion(x) __init_completion(x)
      static inline void __init_completion(struct completion *x) {
         x->done = 0;
         init_swait_queue_head(&x->wait);
      }
      
    • 这个函数实际上是初始化了 completion 结构体中的信号量。
  • 等待完成:
    • 等待信号量的释放:
      void __sched wait_for_completion(struct completion *x) {
          wait_for_common(x, MAX_SCHEDULE_TIMEOUT, TASK_UNINTERRUPTIBLE);
      }
      
  • 发信端:

    • complete() 函数用于唤醒等待该完成事件的单个线程:
      void complete(struct completion *x) {
          unsigned long flags;
          raw_spin_lock_irqsave(&x->wait.lock, flags);
          if (x->done != UINT_MAX)
             x->done++;
          swake_up_locked(&x->wait);
          raw_spin_unlock_irqrestore(&x->wait.lock, flags);
      }
      
  • 同时唤醒所有等待线程:
    • complete_all() 函数用于唤醒等待此特定完成事件的所有线程:
      void complete_all(struct completion *x) {
          unsigned long flags;
          lockdep_assert_RT_in_threaded_ctx();
          raw_spin_lock_irqsave(&x->wait.lock, flags);
          x->done = UINT_MAX;
          swake_up_all_locked(&x->wait);
          raw_spin_unlock_irqrestore(&x->wait.lock, flags);
      }
      
  • 完整示例
#include <linux/module.h>
#include <linux/init.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/kthread.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("kevin");
static struct completion my_completion;
static int my_thread(void *data){
    pr_info("My thread is waiting for completion...\n");
    wait_for_completion(&my_completion);
    pr_info("My thread woke up! Event completed.\n");
    return 0;
}
static int __init my_init(void){
    pr_info("Initializing my module...\n");
    init_completion(&my_completion);
    // Start a new kernel thread
    kthread_run(my_thread, NULL, "my_thread");
    // Simulate some work...
    msleep(2000);
    pr_info("Completing the event...\n");
    complete(&my_completion);
    return 0;
}
static void __exit my_exit(void){
    pr_info("Exiting my module...\n");
}
module_init(my_init);
module_exit(my_exit);

kobject_uevent_env

它是 Linux 内核 中的一个函数,用于在 kobject 状态发生变化时发送 uevent 到用户空间。让我详细解释一下:

  1. kobject
    • kobject 是内核中的一个抽象对象,用于表示各种内核数据结构,例如设备、驱动程序、总线等。
    • 每个 kobject 都有一个名称、引用计数和其他属性。
  2. uevent
    • uevent 是用户空间事件的缩写,用于通知用户空间程序内核中的状态变化。
    • 例如,当设备插入或移除时,内核会生成相应的 uevent
  3. kobject_uevent_env 函数
    • 这个函数用于发送 uevent 到用户空间。
    • 它接受一个指向 kobject 的指针和一个表示 uevent 的环境变量数组。
    • 用户空间程序可以监听这些事件并做出相应的处理。
  4. 使用示例
    • 在设备驱动程序中,当设备状态发生变化时,例如设备插入或移除,可以使用 kobject_uevent_env 发送相应的 uevent
    • 用户空间程序收到这些事件后,可以根据需要执行操作。

以下是一个简单的示例代码,展示了如何在内核模块中使用 kobject_uevent_env 发送 uevent

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kobject.h>

static struct kobject *my_kobj;

static int my_uevent(struct kset *kset, struct kobject *kobj, struct kobj_uevent_env *env)
{
    // Add custom environment variables to the uevent
    add_uevent_var(env, "MY_CUSTOM_VAR=hello_world");

    return 0;
}

static struct kset_uevent_ops my_uevent_ops = {
    .uevent = my_uevent,
};

static int __init my_module_init(void)
{
    my_kobj = kobject_create_and_add("my_kobject", NULL);
    if (!my_kobj)
        return -ENOMEM;

    my_kobj->kset = kset_create_and_add("my_kset", NULL, NULL);
    if (!my_kobj->kset) {
        kobject_put(my_kobj);
        return -ENOMEM;
    }

    my_kobj->kset->uevent_ops = &my_uevent_ops;

    return 0;
}

static void __exit my_module_exit(void)
{
    kset_unregister(my_kobj->kset);
    kobject_put(my_kobj);
}

module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");

在此示例中,我们创建了一个名为 my_kobjectkobject,并将其添加到一个名为 my_ksetkset 中。然后,我们设置了一个自定义的 uevent,将环境变量 MY_CUSTOM_VAR 添加到 uevent 中。

请注意,实际应用中,您需要根据您的需求自定义更多的环境变量和处理逻辑。

benchmark

results matching ""

    No results matching ""