【Linux】常用内核函数

1、poll_wait函数

  poll_wait函数是Linux内核中的一个函数,用于将当前进程添加到等待队列中,以便在指定等待事件发生时唤醒。

poll_wait函数的原型如下:

void poll_wait(struct file *filp, wait_queue_head_t *queue, poll_table *p);

参数解释:

  • filp:指向要等待事件的文件描述符对应的struct file结构体。
  • queue:指向等待队列头的指针,该等待队列用于管理等待事件的进程。
  • p:指向用于轮询等待事件的poll_table实例。

  在应用程序中,通常会使用select、poll或epoll等函数对多个文件描述符进行监听。而poll_wait函数通常用于驱动程序中,当某个异步事件准备好时,会调用poll_wakeup函数唤醒对应的等待进程。这样,应用程序就可以通过轮询检查文件描述符状态来实现异步事件的处理。

  需要注意的是,poll_wait函数并不能直接被应用程序调用,它是内核中的一个函数。应用程序只能通过调用select、poll或epoll等函数来注册关注的文件描述符,并由内核来调用poll_wait函数以及其他相关函数来实现等待事件和唤醒进程等功能。

2、atomic_long_read函数

  atomic_long_read函数是一个原子读取长整型变量的函数,用于确保读取操作的原子性,防止在读取过程中被并发线程修改。

  原子操作是指在执行期间不会受到中断或其他线程的干扰,要么执行完整个操作,要么完全不执行。在并发编程中,原子操作是一种可以安全地进行并发访问的操作,可以保证在并发环境下数据的一致性和正确性。

atomic_long_read函数的原型如下:

long atomic_long_read(const atomic_long_t *v);

参数解释:

  • v:指向要读取值的原子长整型变量的指针。

返回值为原子变量的当前值。

头文件及命名空间:
atomic_long_read函数是在Linux内核中定义的,所以其对应的头文件是<linux/atomic.h>。在使用atomic_long_read函数之前,需要包含这个头文件,即在代码中添加以下语句:

#include <linux/atomic.h>

注意:atomic_long_read函数是在内核空间使用的,无法直接在用户空间应用程序中调用。在内核中使用时,需要将代码编写为内核模块或内核驱动程序。

3、dma_async_issue_pending 函数

  dma_async_issue_pending 函数是用于触发异步处理挂起的DMA传输的操作。在涉及到DMA(Direct Memory Access,直接内存访问)传输时,它的作用是启动已经排队等待执行的DMA传输。

以下是关于 dma_async_issue_pending 函数的简要说明:

void dma_async_issue_pending(struct dma_chan *chan);
  • chan:指向你希望发出挂起传输的DMA通道的指针。

  通常情况下,你会在准备并排队了一个或多个DMA传输描述符之后(使用诸如 dmaengine_prep_slave_sg 或 dmaengine_prep_dma_cyclic 这样的函数),但尚未启动实际传输时使用 dma_async_issue_pending 函数。调用 dma_async_issue_pending 会确保任何排队的传输会尽快开始执行。

以下是一个简单的示例,展示了如何在代码中使用 dma_async_issue_pending:

#include <linux/dmaengine.h>

struct dma_chan *dma_channel; // 获取对DMA通道的引用

// 准备并排队一个或多个DMA传输描述符

// 发出挂起的传输以开始执行
dma_async_issue_pending(dma_channel);

  这个函数在Linux内核中非常重要,特别是在处理异步操作和多个设备共享DMA资源的情况下,有助于高效管理DMA传输。它确保了已经排队的传输会在适当的时机被启动。

4、wake_up_interruptible 函数

  wake_up_interruptible 函数是Linux内核中用于唤醒等待中的进程的函数。它通常用于多线程或多进程编程中,其中一个线程或进程等待某个条件变成真,而另一个线程或进程负责满足这个条件。

以下是关于 wake_up_interruptible 函数的简要说明:

void wake_up_interruptible(struct wait_queue_head_t *q);
  • q:一个等待队列头的指针,它用于管理等待中的进程。

  该函数的作用是唤醒处于等待状态的进程,这些进程通过调用类似于 wait_event_interruptible 或 wait_event_interruptible_timeout 等函数等待某个条件的发生。一旦条件满足或者需要唤醒等待的进程时,你可以调用 wake_up_interruptible 函数来唤醒它们。

  这个函数主要用于实现睡眠和唤醒机制,以便在多任务环境中进行线程/进程之间的同步和通信。例如,一个线程可能在等待某个设备完成数据传输,而另一个线程在数据传输完成后会调用 wake_up_interruptible 来唤醒等待的线程,以便它们可以继续执行。

  需要注意的是,wake_up_interruptible 函数只会唤醒使用可中断等待方式等待的进程,如果你希望唤醒不可中断等待的进程,可以使用 wake_up 函数。此外,Linux内核中还有其他用于管理等待队列和进程等待的函数,以便更精确地控制进程的唤醒。

5、memset 函数

  memset 函数是C/C++标准库中的一个函数,用于将一块内存区域的内容设置为特定的值。通常情况下,它用于初始化数组或分配的内存块,以确保这些内存块中的每个字节都具有相同的值。

以下是 memset 函数的原型:

void *memset(void *s, int c, size_t n);
  • s:指向要设置值的内存区域的指针。
  • c:要设置的值,通常是一个整数。该值会被转换为 unsigned char 类型并复制到每个字节中。
  • n:要设置的字节数,即内存区域的大小。

  memset 函数会将内存区域 s 中的前 n 个字节都设置为值 c。这可以用来初始化内存,清除内存或设置内存中的特定区域。

以下是一个示例,演示如何使用 memset 函数:

#include <stdio.h>
#include <string.h>

int main() {
    char buffer[10];
    memset(buffer, 'A', sizeof(buffer)); // 将buffer中的所有字节设置为字符 'A'

    for (int i = 0; i < sizeof(buffer); i++) {
        printf("%c ", buffer[i]);
    }

    return 0;
}

  在这个示例中,memset 被用来将 buffer 数组中的所有字节设置为字符 ‘A’。这将输出 “A A A A A A A A A A”。请注意,memset 函数返回的是 void* 类型的指针,通常不需要使用它的返回值。

6、xilinx_vdma_channel_set_config 函数

  xilinx_vdma_channel_set_config 函数是与 Xilinx FPGA 上的 Video DMA(Direct Memory Access,直接内存访问)控制器相关的函数之一。这个函数通常用于配置特定通道(channel)的参数,以控制DMA传输的行为,特别是在视频和图像处理应用中。

  尽管 xilinx_vdma_channel_set_config 函数不是标准C库函数,而是特定于 Xilinx FPGA 平台的函数,但通常它的参数和功能类似于以下描述:

int xilinx_vdma_channel_set_config(struct vdma_channel *channel, struct vdma_config *config);
  • channel:指向要配置的VDMA通道的指针。
  • config:指向包含配置信息的结构体或数据结构的指针。这个结构体通常包括有关DMA传输参数的信息,如传输方向、分辨率、帧缓冲等。

  VDMA通道通常用于高带宽的数据传输,特别是在视频和图像处理中,它可以有效地将数据从内存传输到视频显示设备或从视频源捕获数据到内存。xilinx_vdma_channel_set_config 函数的目的是配置通道以满足特定应用的需求,例如设置传输的方向(输入或输出)、分辨率、颜色格式等参数。

  请注意,具体的函数参数和功能可能会因Xilinx FPGA平台的不同而异,因此在使用此函数时,应参考相关的Xilinx文档和API文档以获取详细信息和示例用法。此外,确保你的代码和硬件配置与目标FPGA设备相匹配,以避免配置错误或不兼容性。

7、dmaengine_prep_interleaved_dma 函数

  dmaengine_prep_interleaved_dma 函数是Linux内核中DMA引擎子系统的一部分,用于准备和配置DMA传输请求以进行交错式(interleaved)DMA传输。交错DMA传输通常用于同时传输多个源和目标之间的数据,以提高数据传输的效率。

以下是 dmaengine_prep_interleaved_dma 函数的原型:

struct dma_async_tx_descriptor *dmaengine_prep_interleaved_dma(struct dma_chan *chan,
                                    struct dma_interleaved_template *xt,
                                    unsigned int numf, unsigned int flags);
  • chan:指向DMA通道的指针,用于配置和启动DMA传输。
  • xt:指向 dma_interleaved_template
    结构的指针,它描述了交错DMA传输的模板,包括源和目标的地址、传输长度等信息。
  • numf:描述了交错传输的帧数(frames),每个帧表示一次DMA传输操作。
  • flags:传输标志,可以用于配置DMA传输的行为,例如设置DMA传输方向、使用中断等。

  函数返回一个 dma_async_tx_descriptor 结构的指针,它表示已经准备好的DMA传输描述符。这个描述符可以稍后提交给DMA引擎以开始实际的DMA传输。

  dmaengine_prep_interleaved_dma 函数用于准备一个或多个交错DMA传输的帧,每个帧包含了一组源和目标地址以及传输的长度。这些帧描述了一次交错DMA传输的操作。你可以使用多个帧来一次性配置多个交错DMA传输,以提高数据传输的效率。

要使用这个函数,通常的步骤如下:

  1. 创建一个 dma_interleaved_template 结构,描述交错DMA传输的模板,包括源和目标地址、传输长度等信息。
  2. 调用 dmaengine_prep_interleaved_dma 函数,将 DMA通道、模板、帧数和标志传递给它,以准备DMA传输描述符。
  3. 提交准备好的 DMA 传输描述符给 DMA 引擎,以开始实际的 DMA 传输。

  请注意,具体的使用方式和参数可能会根据你的应用和硬件平台而有所不同。你需要参考相关的文档和示例代码,以确保正确地配置和使用 dmaengine_prep_interleaved_dma 函数。

8、copy_to_user 函数

  copy_to_user 函数是Linux内核中的一个函数,用于将内核空间中的数据复制到用户空间。这个函数通常用于在内核模块中向用户空间返回数据,以便用户空间应用程序可以访问这些数据。

以下是 copy_to_user 函数的原型:

unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
  • to:指向用户空间目标缓冲区的指针。这是要将数据复制到的地方。
  • from:指向内核空间源缓冲区的指针。这是要复制的数据的来源。
  • n:要复制的字节数。

  copy_to_user 函数的目的是将 from 指针指向的内核空间缓冲区中的数据复制到用户空间中的 to 指针指向的缓冲区中。它会返回一个无符号长整数,表示成功复制的字节数。如果复制过程中发生错误,它可能返回一个负数,表示错误代码。

以下是一个示例,演示如何在Linux内核模块中使用 copy_to_user 函数向用户空间返回数据:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/uaccess.h> // 包含 copy_to_user 函数的头文件

static char kernel_data[] = "Hello from kernel space!";

static int __init my_module_init(void) {
    char user_buffer[256]; // 用户空间缓冲区

    // 使用 copy_to_user 将数据从内核空间复制到用户空间
    int bytes_copied = copy_to_user(user_buffer, kernel_data, sizeof(kernel_data));

    if (bytes_copied < 0) {
        printk(KERN_ERR "Failed to copy data to user space\n");
        return -EFAULT; // 返回错误代码
    }

    printk(KERN_INFO "Data copied to user space: %d bytes\n", bytes_copied);

    return 0;
}

static void __exit my_module_exit(void) {
    // 模块卸载时的清理操作
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("JNTM");

  在这个示例中,copy_to_user 函数被用来将 kernel_data 中的数据从内核空间复制到 user_buffer 中的用户空间缓冲区。如果复制成功,它会返回成功复制的字节数。如果复制失败,你可以根据返回的错误代码采取适当的错误处理措施。

  请注意,使用 copy_to_user 函数时需要小心,确保输入参数的合法性,以防止内存访问错误和安全问题。

9、dmaengine_terminate_all 函数

  dmaengine_terminate_all 函数是Linux内核中 DMA(Direct Memory Access,直接内存访问)引擎子系统提供的函数之一。它用于终止特定 DMA 通道上的所有挂起的 DMA 传输操作。

以下是 dmaengine_terminate_all 函数的原型:

void dmaengine_terminate_all(struct dma_chan *chan);
  • chan:指向 DMA 通道的指针,表示要终止所有挂起 DMA 传输操作的通道。

  dmaengine_terminate_all 函数的目的是立即停止指定 DMA 通道上的所有 DMA 传输,无论它们当前是否正在进行中。这在某些情况下可能是有用的,例如在需要重新配置 DMA 通道或处理错误情况时。

  在使用 dmaengine_terminate_all 函数时需要小心,因为它会强制停止 DMA 通道上的所有传输,可能导致数据丢失或不一致。因此,通常应该谨慎使用,并确保在终止传输前已经采取了适当的措施来处理已经传输的数据。

  请注意,具体的使用方式和参数可能会因你的应用和硬件平台而有所不同。在使用该函数时,应参考相关的文档和示例代码以确保正确地配置和使用 dmaengine_terminate_all 函数。此外,需要注意 DMA 引擎和通道的状态,以避免不必要的终止传输操作。

10、alloc_chrdev_region 函数

  alloc_chrdev_region 函数是Linux内核中用于动态分配字符设备驱动程序的主设备号(major number)和次设备号(minor number)的函数。它允许字符设备驱动程序在运行时请求一个或多个设备号,以便可以向用户空间提供设备文件。

以下是 alloc_chrdev_region 函数的原型:

int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, const char *name);
  • dev:一个指向 dev_t 类型的指针,用于接收分配的设备号。dev_t 是一个包含主设备号和次设备号的数据类型。
  • firstminor:请求的次设备号范围的起始值。
  • count:请求的设备号数量,通常为1。
  • name:设备驱动程序的名称,通常是一个描述性的字符串。

  alloc_chrdev_region 函数的作用是分配一个或多个字符设备的设备号,这些设备号可以用于创建设备文件。字符设备通常用于与字符流进行交互,例如终端设备、串口设备等。

以下是一个示例,演示如何在Linux内核模块中使用 alloc_chrdev_region 函数来分配字符设备的设备号:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h> // 包含 alloc_chrdev_region 函数的头文件

static dev_t first_dev; // 用于保存分配的设备号

static int __init my_module_init(void) {
    int result;

    // 使用 alloc_chrdev_region 分配设备号
    result = alloc_chrdev_region(&first_dev, 0, 1, "my_char_device");

    if (result < 0) {
        printk(KERN_ERR "Failed to allocate character device region\n");
        return result;
    }

    printk(KERN_INFO "Allocated character device region: major %d, minor %d\n", MAJOR(first_dev), MINOR(first_dev));

    // 在这里可以进行设备初始化和注册

    return 0;
}

static void __exit my_module_exit(void) {
    // 在模块卸载时进行清理操作
    unregister_chrdev_region(first_dev, 1);

    printk(KERN_INFO "Unregistered character device region\n");
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("JNTM");

  在这个示例中,alloc_chrdev_region 函数用于分配一个字符设备的设备号,并将设备号存储在 first_dev 变量中。一旦分配完成,你可以使用 MAJOR 和 MINOR 宏来获取主设备号和次设备号。最后,在模块卸载时,使用 unregister_chrdev_region 函数来释放分配的设备号。

  请注意,字符设备的主设备号通常由内核模块的开发者分配,并需要在模块代码中硬编码。主设备号用于确定哪个设备驱动程序将处理特定类型的字符设备。次设备号用于区分同一类型的多个设备。设备文件的创建和管理通常在字符设备驱动程序中进行。

11、cdev_alloc 函数

  cdev_alloc 函数是Linux内核中用于动态分配字符设备结构 struct cdev 的函数。struct cdev 是字符设备驱动程序的一部分,它用于表示字符设备的属性和操作。通过分配和初始化 struct cdev 结构,驱动程序可以注册字符设备,并定义设备文件操作。

以下是 cdev_alloc 函数的原型:

struct cdev *cdev_alloc(void);

cdev_alloc 函数没有参数,它返回一个指向新分配的 struct cdev 结构的指针。

  struct cdev 结构通常在字符设备驱动程序的初始化过程中使用。驱动程序可以使用该结构来指定字符设备的操作函数、持有设备的状态信息等。

以下是一个示例,演示如何在Linux内核模块中使用 cdev_alloc 函数来动态分配并初始化 struct cdev 结构:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h> // 包含 cdev_alloc 函数的头文件

struct cdev my_cdev; // 定义一个 cdev 结构

static int my_open(struct inode *inode, struct file *file) {
    // 在这里处理设备打开操作
    return 0;
}

static int my_release(struct inode *inode, struct file *file) {
    // 在这里处理设备关闭操作
    return 0;
}

static struct file_operations fops = {
    .open = my_open,
    .release = my_release,
    // 添加其他需要的文件操作函数
};

static int __init my_module_init(void) {
    struct cdev *my_cdev_ptr;

    // 分配并初始化 cdev 结构
    my_cdev_ptr = cdev_alloc();
    if (!my_cdev_ptr) {
        printk(KERN_ERR "Failed to allocate cdev structure\n");
        return -ENOMEM;
    }

    // 初始化 cdev 结构
    cdev_init(my_cdev_ptr, &fops);

    // 将 cdev 结构添加到字符设备子系统
    int result = cdev_add(my_cdev_ptr, MKDEV(0, 0), 1);
    if (result < 0) {
        printk(KERN_ERR "Failed to add cdev to the kernel\n");
        return result;
    }

    printk(KERN_INFO "Character device registered successfully\n");
    return 0;
}

static void __exit my_module_exit(void) {
    // 在模块卸载时进行清理操作
    cdev_del(&my_cdev);
    printk(KERN_INFO "Character device unregistered\n");
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("JNTM");

  在这个示例中,cdev_alloc 函数用于分配 struct cdev 结构,然后使用 cdev_init 函数来初始化该结构。接着,使用 cdev_add 函数将字符设备添加到字符设备子系统中,从而注册了字符设备。

  请注意,示例中还定义了 open 和 release 等文件操作函数,并将它们与 struct file_operations 结构关联,以实现字符设备的打开和关闭操作。最后,在模块卸载时,使用 cdev_del 函数来删除字符设备并释放相关资源。

12、cdev_init 函数

  cdev_init 函数是Linux内核中用于初始化字符设备结构 struct cdev 的函数。struct cdev 用于表示字符设备的属性和操作。通过调用 cdev_init 函数,可以为字符设备结构设置必要的初始值,以便后续注册字符设备。

以下是 cdev_init 函数的原型:

void cdev_init(struct cdev *cdev, const struct file_operations *fops);
  • cdev:指向要初始化的 struct cdev 结构的指针。
  • fops:指向包含字符设备操作函数的 struct file_operations
    结构的指针。这些操作函数用于处理设备文件的读取、写入、打开、关闭等操作。

cdev_init 函数的作用是将 struct cdev 结构初始化为初始状态,以便注册字符设备。通常,它在字符设备驱动程序的初始化过程中被调用。

以下是一个示例,演示如何在Linux内核模块中使用 cdev_init 函数来初始化字符设备结构:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h> // 包含 cdev_init 函数的头文件

struct cdev my_cdev; // 定义一个 cdev 结构

static int my_open(struct inode *inode, struct file *file) {
    // 在这里处理设备打开操作
    return 0;
}

static int my_release(struct inode *inode, struct file *file) {
    // 在这里处理设备关闭操作
    return 0;
}

static struct file_operations fops = {
    .open = my_open,
    .release = my_release,
    // 添加其他需要的文件操作函数
};

static int __init my_module_init(void) {
    // 初始化 cdev 结构
    cdev_init(&my_cdev, &fops);

    // 在这里可以进一步设置 cdev 结构的属性

    printk(KERN_INFO "Character device initialized successfully\n");
    return 0;
}

static void __exit my_module_exit(void) {
    // 在模块卸载时进行清理操作
    printk(KERN_INFO "Character device cleanup\n");
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("JNTM");

  在这个示例中,cdev_init 函数用于初始化 my_cdev 结构,该结构表示字符设备的属性和操作。然后,将字符设备操作函数关联到 struct file_operations 结构中,并在 cdev_init 后进行其他初始化步骤,例如设置设备属性。最后,在模块卸载时,可以进行必要的清理操作。

  通过调用 cdev_init 函数,可以确保字符设备结构的正确初始化,以便后续可以将其添加到字符设备子系统并注册字符设备。

13、cdev_add 函数

  cdev_add 函数是Linux内核中用于向字符设备子系统注册字符设备的函数。一旦字符设备已经用 cdev_init 初始化,可以使用 cdev_add 将其添加到字符设备子系统中,使其可以被用户空间程序访问。

以下是 cdev_add 函数的原型:

int cdev_add(struct cdev *p, dev_t dev, unsigned count);
  • p:指向已初始化的 struct cdev 结构的指针,表示要添加的字符设备。
  • dev:设备号(dev_t),包括主设备号和次设备号。这个设备号将与字符设备关联。
  • count:要添加的字符设备的数量。通常为1。

cdev_add 函数的主要作用是将字符设备注册到字符设备子系统,从而使其可用于创建设备文件并与用户空间应用程序交互。

以下是一个示例,演示如何在Linux内核模块中使用 cdev_add 函数来注册字符设备:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>

struct cdev my_cdev; // 定义一个 cdev 结构

static int my_open(struct inode *inode, struct file *file) {
    // 在这里处理设备打开操作
    return 0;
}

static int my_release(struct inode *inode, struct file *file) {
    // 在这里处理设备关闭操作
    return 0;
}

static struct file_operations fops = {
    .open = my_open,
    .release = my_release,
    // 添加其他需要的文件操作函数
};

static int __init my_module_init(void) {
    // 初始化 cdev 结构
    cdev_init(&my_cdev, &fops);

    // 添加字符设备到字符设备子系统
    int result = cdev_add(&my_cdev, MKDEV(0, 0), 1); // 使用设备号 0,0 添加一个设备
    if (result < 0) {
        printk(KERN_ERR "Failed to add cdev to the kernel\n");
        return result;
    }

    printk(KERN_INFO "Character device registered successfully\n");
    return 0;
}

static void __exit my_module_exit(void) {
    // 在模块卸载时进行清理操作
    cdev_del(&my_cdev);
    printk(KERN_INFO "Character device unregistered\n");
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("JNTM");

  在这个示例中,cdev_add 函数用于将 my_cdev 结构注册到字符设备子系统中,并关联设备号。一旦字符设备被注册,用户空间可以使用相应的设备文件来访问字符设备的功能。在模块卸载时,使用 cdev_del 函数来删除字符设备并释放相关资源。

14、class_create 函数

  class_create 函数是Linux内核中用于创建一个新的设备类(struct class)的函数。设备类是一种用于组织设备的机制,它可以帮助操作系统管理设备并提供用户空间与设备的交互接口。通常,一个设备类包含一组相关的设备。

以下是 class_create 函数的原型:

struct class *class_create(struct module *owner, const char *name);
  • owner:指向拥有这个设备类的模块的指针。通常情况下,可以将其设置为 THIS_MODULE,表示当前模块是这个设备类的所有者。
  • name:设备类的名称,用于标识这个设备类。

class_create 函数的主要作用是创建一个新的设备类,以便将相关的设备注册到该类中。创建设备类后,可以使用 device_create 函数将设备与该类相关联,并创建相应的设备文件。

以下是一个示例,演示如何在Linux内核模块中使用 class_create 函数来创建设备类:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h> // 包含 class_create 函数的头文件

static struct class *my_class; // 定义一个设备类

static int __init my_module_init(void) {
    // 创建设备类
    my_class = class_create(THIS_MODULE, "my_class");
    if (IS_ERR(my_class)) {
        printk(KERN_ERR "Failed to create device class\n");
        return PTR_ERR(my_class);
    }

    printk(KERN_INFO "Device class created successfully\n");
    return 0;
}

static void __exit my_module_exit(void) {
    // 在模块卸载时进行清理操作
    class_destroy(my_class);
    printk(KERN_INFO "Device class destroyed\n");
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("JNTM");

  在这个示例中,class_create 函数用于创建名为 “my_class” 的设备类,并将其赋值给 my_class 指针。一旦设备类创建成功,可以使用它来将设备与该类相关联。

  请注意,创建设备类是为了更好地组织设备,并将它们划分为相关的组。设备类通常在字符设备驱动程序中使用,以便将多个设备注册到同一个类中,从而简化设备管理。在模块卸载时,使用 class_destroy 函数来销毁设备类并释放相关资源。

15、IS_ERR宏

  IS_ERR 是一个宏定义,用于在Linux内核中检查一个指针是否包含错误码(error code)。它通常用于检查函数返回的指针是否表示了一个错误状态,特别是在处理内核 API 函数的返回值时非常有用。

以下是 IS_ERR 宏的定义:

#define IS_ERR(ptr)      ((unsigned long)(ptr) >= (unsigned long)-MAX_ERRNO)

  IS_ERR 宏接受一个指针作为参数,并检查该指针是否大于或等于 (unsigned long)-MAX_ERRNO。如果指针大于或等于该值,它被认为是一个错误码,并且 IS_ERR 宏返回 true(非零值),表示指针包含了一个错误状态。否则,它返回 false(0),表示指针不包含错误状态。

  这个宏通常用于检查函数的返回值,特别是在内核中,许多函数在发生错误时返回一个错误码(通常是负数),而在成功时返回有效的指针。通过使用 IS_ERR 宏,可以轻松地判断函数是否返回了错误状态,而无需深入了解函数的具体错误码。

以下是一个示例,演示如何在内核代码中使用 IS_ERR 宏来检查函数的返回值是否包含错误状态:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>

static int __init my_module_init(void) {
    struct file *filp;
    filp = filp_open("/path/to/nonexistent/file", O_RDONLY, 0);

    if (IS_ERR(filp)) {
        printk(KERN_ERR "Failed to open file: %ld\n", PTR_ERR(filp));
        return PTR_ERR(filp);
    }

    // 在这里继续处理文件操作

    return 0;
}

static void __exit my_module_exit(void) {
    // 在模块卸载时进行清理操作
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("JNTM");

  在上面的示例中,filp_open 函数用于打开一个文件,如果文件不存在或者出现其他错误,它会返回一个错误码。通过使用 IS_ERR 宏,可以轻松地检查 filp_open 的返回值是否包含错误状态,然后采取相应的错误处理措施。如果返回的指针包含错误状态,可以使用 PTR_ERR 宏来获取具体的错误码并打印出来。

16、device_create函数

  device_create 函数是Linux内核中用于创建设备文件的函数。它用于将一个字符设备或块设备的实例与设备类(struct class)相关联,并在 /dev 目录下创建一个相应的设备文件,以便用户空间应用程序可以通过设备文件与设备进行交互。

以下是 device_create 函数的原型:

struct device *device_create(struct class *class, struct device *parent, dev_t dev, const char *fmt, ...);
  • class:指向设备类(struct class)的指针,用于指定要创建设备文件的类。
  • parent:指向父设备的指针。通常情况下,可以将其设置为 NULL,表示没有父设备。
  • dev:设备号(dev_t),包括主设备号和次设备号,用于确定要创建的设备文件的名称。
  • fmt:设备文件名称的格式字符串,可以包含格式说明符(例如 %d)。
  • …:与格式字符串一起使用的可变参数,用于生成设备文件的名称。

device_create 函数的主要作用是将设备与设备类相关联,并在 /dev 目录下创建相应的设备文件。这使得用户空间应用程序可以通过设备文件路径来访问设备的功能。

以下是一个示例,演示如何在Linux内核模块中使用 device_create 函数来创建设备文件:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h> // 包含 device_create 函数的头文件

static struct class *my_class; // 定义一个设备类
static struct device *my_device; // 定义一个设备实例

static int __init my_module_init(void) {
    // 创建设备类
    my_class = class_create(THIS_MODULE, "my_class");
    if (IS_ERR(my_class)) {
        printk(KERN_ERR "Failed to create device class\n");
        return PTR_ERR(my_class);
    }

    // 创建设备实例并关联到设备类
    my_device = device_create(my_class, NULL, MKDEV(0, 0), NULL, "my_device");
    if (IS_ERR(my_device)) {
        printk(KERN_ERR "Failed to create device\n");
        class_destroy(my_class);
        return PTR_ERR(my_device);
    }

    printk(KERN_INFO "Device created successfully\n");
    return 0;
}

static void __exit my_module_exit(void) {
    // 在模块卸载时进行清理操作
    device_destroy(my_class, MKDEV(0, 0));
    class_destroy(my_class);
    printk(KERN_INFO "Device destroyed\n");
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("JNTM");

  在这个示例中,首先创建了一个设备类 my_class,然后使用 device_create 函数将设备 my_device 与该类相关联,并创建了名为 “my_device” 的设备文件。用户空间应用程序可以通过 /dev/my_device 路径来访问该设备。

  在模块卸载时,使用 device_destroy 函数来销毁设备实例,并使用 class_destroy 函数来销毁设备类,以释放相关资源。

17、class_destroy 函数

  class_destroy 函数是Linux内核中用于销毁设备类(struct class)的函数。设备类是一种用于组织设备的机制,它可以帮助操作系统管理设备并提供用户空间与设备的交互接口。通常,一个设备类包含一组相关的设备。

以下是 class_destroy 函数的原型:

void class_destroy(struct class *class);
  • class:指向要销毁的设备类(struct class)的指针。

class_destroy 函数的主要作用是销毁设备类并释放与之相关的资源。当设备类不再需要时,应该调用这个函数来进行清理,以防止内存泄漏和资源泄漏。

以下是一个示例,演示如何在Linux内核模块中使用 class_destroy 函数来销毁设备类:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h> // 包含 class_destroy 函数的头文件

static struct class *my_class; // 定义一个设备类

static int __init my_module_init(void) {
    // 创建设备类
    my_class = class_create(THIS_MODULE, "my_class");
    if (IS_ERR(my_class)) {
        printk(KERN_ERR "Failed to create device class\n");
        return PTR_ERR(my_class);
    }

    printk(KERN_INFO "Device class created successfully\n");
    return 0;
}

static void __exit my_module_exit(void) {
    // 在模块卸载时进行清理操作
    class_destroy(my_class); // 销毁设备类
    printk(KERN_INFO "Device class destroyed\n");
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("JNTM");

  在这个示例中,首先创建了一个设备类 my_class,然后在模块卸载时使用 class_destroy 函数来销毁设备类,以释放相关资源。这是一种良好的实践,可以确保在不再需要设备类时进行清理,防止内存泄漏和资源泄漏。

18、cdev_del 函数

  cdev_del 函数是Linux内核中用于删除字符设备注册的函数。它允许将字符设备从内核中注销,停止设备驱动程序的使用,以及释放相关资源。

以下是 cdev_del 函数的原型:

void cdev_del(struct cdev *p);
  • p:指向要删除的字符设备结构体 struct cdev 的指针。

cdev_del 函数的主要作用是删除之前通过 cdev_add 函数添加到内核的字符设备。通过调用 cdev_del 函数,可以停止字符设备的使用,并释放设备占用的资源,包括设备文件的 inode 和字符设备结构体。

以下是一个示例,演示如何在Linux内核模块中使用 cdev_del 函数来删除字符设备的注册:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h> // 包含 cdev_del 函数的头文件

static struct cdev my_cdev; // 定义字符设备结构体

static int my_open(struct inode *inode, struct file *file) {
    // 在这里处理设备打开操作
    return 0;
}

static int __init my_module_init(void) {
    int result;

    // 初始化字符设备结构体
    cdev_init(&my_cdev, &fops);

    // 添加字符设备到内核
    result = cdev_add(&my_cdev, MKDEV(0, 0), 1);
    if (result < 0) {
        printk(KERN_ERR "Failed to add character device\n");
        return result;
    }

    printk(KERN_INFO "Character device registered successfully\n");
    return 0;
}

static void __exit my_module_exit(void) {
    // 在模块卸载时进行清理操作
    cdev_del(&my_cdev); // 删除字符设备的注册
    printk(KERN_INFO "Character device unregistered\n");
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("JNTM");

  在上面的示例中,首先通过 cdev_init 函数初始化字符设备结构体 my_cdev,然后通过 cdev_add 函数将字符设备添加到内核。在模块卸载时,使用 cdev_del 函数来删除字符设备的注册,以释放相关资源。

这种方式允许模块在加载时注册字符设备,并在卸载时将其注销,确保正确管理字符设备资源。

19、unregister_chrdev_region 函数

  unregister_chrdev_region 函数是Linux内核中用于注销字符设备号的函数。在内核中注册字符设备时,需要为设备分配一个唯一的字符设备号(包括主设备号和次设备号)。当不再需要这个字符设备号时,可以使用 unregister_chrdev_region 函数将其注销,以便将其释放回内核,以供将来的使用。

以下是 unregister_chrdev_region 函数的原型:

void unregister_chrdev_region(dev_t from, unsigned count);
  • from:表示要注销的字符设备号范围的起始字符设备号。
  • count:表示要注销的字符设备号的数量。

  unregister_chrdev_region 函数的主要作用是将之前注册的字符设备号释放回内核,以便其他模块或设备可以重新使用它们。

以下是一个示例,演示如何在Linux内核模块中使用 unregister_chrdev_region 函数来注销字符设备号:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>

static dev_t my_dev; // 字符设备号

static int my_open(struct inode *inode, struct file *file) {
    // 在这里处理设备打开操作
    return 0;
}

static int __init my_module_init(void) {
    int result;

    // 分配字符设备号,从第一个可用的字符设备号开始
    result = alloc_chrdev_region(&my_dev, 0, 1, "my_device");
    if (result < 0) {
        printk(KERN_ERR "Failed to allocate character device number\n");
        return result;
    }

    printk(KERN_INFO "Character device number allocated successfully: %d\n", my_dev);

    // 在这里可以进行字符设备的初始化和注册等操作

    return 0;
}

static void __exit my_module_exit(void) {
    // 在模块卸载时进行清理操作
    unregister_chrdev_region(my_dev, 1); // 注销字符设备号
    printk(KERN_INFO "Character device number unregistered\n");
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("JNTM");

  在上面的示例中,首先使用 alloc_chrdev_region 函数分配一个字符设备号,并在模块加载时将其注册。在模块卸载时,使用 unregister_chrdev_region 函数将字符设备号注销,以释放它并确保将来的使用。这样可以避免字符设备号资源的泄漏。

20、devm_kmalloc 函数

  devm_kmalloc 函数是Linux内核中用于在设备驱动程序中分配内存的函数。它是"managed"内存分配函数的一部分,以"managed"方式分配的内存在设备被释放时会自动被释放,从而避免了内存泄漏。

以下是 devm_kmalloc 函数的原型:

void *devm_kmalloc(struct device *dev, size_t size, gfp_t flags);
  • dev:指向设备结构体 struct device 的指针,用于将内存分配与设备相关联,以便在设备被释放时自动释放内存。
  • size:要分配的内存大小(以字节为单位)。
  • flags:内存分配标志,用于指定内存分配的行为,例如 GFP_KERNEL、GFP_ATOMIC 等。

devm_kmalloc 函数的主要作用是分配一块内存,并将其与指定的设备关联起来,以便在设备被释放时,内核会自动释放分配的内存,从而减少了内存管理的繁琐工作。

以下是一个示例,演示如何在设备驱动程序中使用 devm_kmalloc 函数分配内存:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/slab.h> // 包含 devm_kmalloc 函数的头文件

static int __init my_module_init(void) {
    struct device *dev = NULL; // 假设有一个设备结构体

    // 使用 devm_kmalloc 函数分配内存
    char *buffer = devm_kmalloc(dev, 1024, GFP_KERNEL);
    if (!buffer) {
        printk(KERN_ERR "Failed to allocate memory\n");
        return -ENOMEM;
    }

    // 在这里可以使用分配的内存进行操作

    return 0;
}

static void __exit my_module_exit(void) {
    // 在模块卸载时进行清理操作,不需要手动释放内存
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("JNTM");

  在上面的示例中,使用 devm_kmalloc 函数分配了一个大小为 1024 字节的内存块,并将其与设备相关联。当设备驱动程序被卸载时,内核会自动释放这块内存,无需手动调用 kfree 函数来释放内存。这种方式简化了内存管理,并减少了内存泄漏的风险。

21、dma_request_chan 函数

  dma_request_chan 函数是Linux内核中用于请求和获取DMA通道(Direct Memory Access Channel)的函数。DMA通道是一种硬件机制,用于在设备之间传输数据,而无需CPU的干预。通过请求和获取DMA通道,驱动程序可以有效地执行高速数据传输操作。

以下是 dma_request_chan 函数的原型:

struct dma_chan *dma_request_chan(struct device *dev, const char *name);
  • dev:指向设备结构体 struct device 的指针,用于指定请求DMA通道的设备。
  • name:DMA通道的名称。

dma_request_chan 函数的主要作用是请求并获取指定名称的DMA通道。DMA通道的名称通常与硬件平台和设备树中的配置相关联。一旦成功获取DMA通道,就可以在驱动程序中使用它来进行DMA数据传输。

以下是一个示例,演示如何在设备驱动程序中使用 dma_request_chan 函数来请求DMA通道:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/dmaengine.h> // 包含 dma_request_chan 函数的头文件

static int __init my_module_init(void) {
    struct device *dev = NULL; // 假设有一个设备结构体
    struct dma_chan *dma_channel;

    // 请求DMA通道
    dma_channel = dma_request_chan(dev, "my_dma_channel");
    if (!dma_channel) {
        printk(KERN_ERR "Failed to request DMA channel\n");
        return -ENODEV;
    }

    // 在这里可以使用获取到的DMA通道进行DMA数据传输操作

    return 0;
}

static void __exit my_module_exit(void) {
    // 在模块卸载时进行清理操作,通常不需要手动释放DMA通道
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("JNTM");

  在上面的示例中,使用 dma_request_chan 函数请求了名为 “my_dma_channel” 的DMA通道。一旦成功获取DMA通道,可以在设备驱动程序中使用它来执行DMA数据传输操作。通常情况下,不需要手动释放DMA通道,因为它们通常由DMA子系统进行管理。

22、

22、IS_ERR_OR_NULL 宏

  IS_ERR_OR_NULL 是一个宏,通常在Linux内核代码中用于检查一个指针是否为错误指针(Error Pointer)或者是空指针(NULL)。这个宏用于简化对指针的错误检查,尤其是在处理内核API返回的指针时很常见。

以下是 IS_ERR_OR_NULL 宏的定义:

#define IS_ERR_OR_NULL(ptr) (!ptr || IS_ERR(ptr))
  • ptr:要检查的指针。

IS_ERR_OR_NULL 宏的工作原理如下:

  1. 首先,它检查指针是否为 NULL,如果是 NULL,则返回真(true),因为 NULL 也被视为一种错误状态。
  2. 如果指针不为 NULL,则它使用 IS_ERR 宏来检查指针是否为错误指针。IS_ERR 宏用于检查指针的最高位是否被设置为1,以指示错误状态。
  3. 如果指针是错误指针(即,最高位被设置为 1),则返回真(true)。
  4. 如果指针既不是 NULL 也不是错误指针,则返回假(false)。

  使用 IS_ERR_OR_NULL 宏可以方便地检查指针是否处于错误状态或者是 NULL,从而简化了错误处理和安全性检查。这在内核编程中经常用于处理设备驱动程序、系统调用等场景中,以确保指针的有效性。例如:

struct device *dev = ...; // 假设有一个设备指针

if (IS_ERR_OR_NULL(dev)) {
    // 处理设备指针为错误或者 NULL 的情况
} else {
    // 处理有效的设备指针
}

这样可以避免在处理指针时发生空指针解引用或处理错误状态的指针。

23、PTR_ERR 宏

  PTR_ERR 是一个宏,通常在Linux内核代码中用于从错误指针(Error Pointer)中提取错误码(Error Code)。当内核API返回一个错误指针时,通常需要使用 PTR_ERR 宏来获取其中的错误码,以便进一步处理错误。

以下是 PTR_ERR 宏的定义:

#define PTR_ERR(ptr) ((long)ptr)
  • ptr:要提取错误码的指针。

  PTR_ERR 宏的工作原理非常简单,它将给定的指针转换为 long 类型,以提取其中的错误码。通常,错误指针中包含的错误码是一个负整数,因此使用 long 类型来保存错误码。

以下是一个示例,演示如何在Linux内核代码中使用 PTR_ERR 宏来获取错误码:

struct device *dev = ...; // 假设有一个返回错误指针的函数

if (IS_ERR(dev)) {
    long err_code = PTR_ERR(dev);
    printk(KERN_ERR "Error occurred with error code: %ld\n", err_code);
    // 进一步处理错误,例如记录日志或采取适当的措施
} else {
    // 处理正常情况下的设备指针
}

  在上面的示例中,首先使用 IS_ERR 宏检查返回的设备指针是否为错误指针。如果是错误指针,然后使用 PTR_ERR 宏提取其中的错误码,并进行相应的错误处理。

  注意:在使用 PTR_ERR 宏时,要确保传递的是一个错误指针,否则可能导致不正确的结果。通常,错误指针的最高位被设置为 1,以区分错误指针和有效指针。

24、of_property_read_u32 函数

  of_property_read_u32 函数是Linux内核中用于从设备树(Device Tree)中读取一个32位整数属性值的函数。设备树是一种描述硬件信息和配置的数据结构,在Linux内核中广泛用于描述各种硬件设备和资源。

以下是 of_property_read_u32 函数的原型:

int of_property_read_u32(const struct device_node *np, const char *propname, u32 *out_value);
  • np:指向设备节点(Device Node)的指针,该节点包含了要读取属性值的信息。
  • propname:属性的名称,即要读取的属性名字。
  • out_value:用于存储读取到的32位整数属性值的指针。

  of_property_read_u32 函数的主要作用是从设备树中查找指定设备节点中的属性,并将属性值存储在 out_value 指针所指向的位置。如果属性不存在或者无法解析成32位整数,函数将返回适当的错误代码。

以下是一个示例,演示如何在设备驱动程序中使用 of_property_read_u32 函数来读取设备树中的属性值:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/of.h> // 包含 of_property_read_u32 函数的头文件

static int __init my_module_init(void) {
    struct device_node *np = NULL; // 假设有一个设备节点指针
    u32 value;
    int ret;

    // 从设备树中读取属性值
    ret = of_property_read_u32(np, "my_property_name", &value);
    if (ret) {
        printk(KERN_ERR "Failed to read property: %d\n", ret);
        return ret;
    }

    printk(KERN_INFO "Read property value: %u\n", value);

    // 在这里可以使用读取到的属性值进行操作

    return 0;
}

static void __exit my_module_exit(void) {
    // 在模块卸载时进行清理操作
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("JNTM");

  在上面的示例中,of_property_read_u32 函数从设备树节点 np 中查找名为 “my_property_name” 的属性,并将其值存储在 value 中。如果成功读取属性值,就可以在驱动程序中使用该值进行操作。如果属性不存在或者无法解析成32位整数,函数将返回适当的错误代码。

25、pr_err 宏

  pr_err 是Linux内核中用于打印错误信息的宏。它通常用于内核驱动程序中,用于记录错误事件和状态。pr_err 宏会将错误消息记录到内核日志中,以帮助开发人员诊断和调试问题。

以下是 pr_err 宏的使用示例:

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

static int __init my_module_init(void) {
    // 模拟一个错误情况
    int error_code = -EINVAL;

    if (error_code) {
        pr_err("Error occurred with error code: %d\n", error_code);
        return error_code;
    }

    // 在正常情况下返回0
    return 0;
}

static void __exit my_module_exit(void) {
    // 在模块卸载时进行清理操作
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("JNTM");

  在上面的示例中,pr_err 宏用于打印错误消息,如果 error_code 非零,表示发生了错误。错误消息将包含错误码,以便更容易识别问题的根本原因。错误消息将记录到内核日志中,可以使用命令如 dmesg 来查看。

  pr_err 宏是内核中常用的宏之一,它具有不同的变体,如 pr_warn 用于打印警告消息,pr_info 用于打印信息性消息等,这些宏有助于开发人员了解系统状态和问题。

26、dma_alloc_coherent 函数

  dma_alloc_coherent 函数是Linux内核中用于分配一块连续的、可被DMA引擎访问的内存区域的函数。这个函数通常用于设备驱动程序中,以便设备能够执行DMA(Direct Memory Access)数据传输,将数据从设备传输到系统内存或从系统内存传输到设备,而无需CPU的干预。

以下是 dma_alloc_coherent 函数的原型:

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

参数说明:

  • dev:指向表示设备的 struct device 结构体的指针。该参数用于确定DMA内存分配的上下文,以便分配与特定设备相关的内存区域。
  • size:要分配的内存区域的大小(以字节为单位)。
  • dma_handle:用于存储分配的DMA地址的指针。DMA地址是用于DMA传输的物理地址。
  • flag:内存分配标志,通常使用 GFP_KERNEL 或 GFP_ATOMIC 等标志,以指定内存分配的上下文和行为。

  dma_alloc_coherent 函数的主要作用是在DMA引擎可以访问的物理内存中分配一块内存区域,并将分配的内存区域的物理地址存储在 dma_handle 中。这样,设备驱动程序就可以使用这个地址来执行DMA数据传输。

以下是一个示例,演示如何在设备驱动程序中使用 dma_alloc_coherent 函数来分配DMA可访问的内存:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/dma-mapping.h> // 包含 dma_alloc_coherent 函数的头文件

static int __init my_module_init(void) {
    struct device *dev = NULL; // 假设有一个设备结构体
    size_t size = 4096; // 分配的内存大小,这里假设为4KB
    dma_addr_t dma_addr;
    void *virt_addr;

    // 分配DMA可访问的内存
    virt_addr = dma_alloc_coherent(dev, size, &dma_addr, GFP_KERNEL);
    if (!virt_addr) {
        printk(KERN_ERR "Failed to allocate DMA memory\n");
        return -ENOMEM;
    }

    // 使用 virt_addr 进行DMA数据传输操作,dma_addr 包含了物理地址

    return 0;
}

static void __exit my_module_exit(void) {
    // 在模块卸载时进行内存释放操作
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("JNTM");

  在上面的示例中,dma_alloc_coherent 函数用于分配DMA可访问的内存,然后可以在设备驱动程序中使用 virt_addr 进行DMA数据传输操作。注意,在模块卸载时,通常需要释放已分配的DMA内存以防止内存泄漏。

27、PAGE_ALIGN 宏

  PAGE_ALIGN 是一个宏,通常在Linux内核代码中用于将一个数值向上对齐到页的大小。在Linux中,页的大小通常是4KB或更大,具体取决于硬件架构和配置。PAGE_ALIGN 宏的目的是确保某个数值是页大小的整数倍,以便在内核中进行内存分配和管理时能够以页为单位进行操作。

以下是 PAGE_ALIGN 宏的定义:

#define PAGE_SHIFT 12 // 4KB 页面的位移偏移量
#define PAGE_SIZE (1UL << PAGE_SHIFT) // 页大小

#define PAGE_ALIGN(addr) (((addr) + (PAGE_SIZE-1)) & PAGE_MASK)
#define PAGE_MASK (~(PAGE_SIZE-1))
  • addr:要对齐的地址或数值。

PAGE_ALIGN 宏的工作原理如下:

  1. 首先,它使用 PAGE_MASK
    宏来获取页对齐的掩码。掩码的作用是将最低的12位(对于4KB页面)设置为0,其余位设置为1。这就是为什么默认页面大小是4KB时,PAGE_MASK
    的二进制表示中有12个连续的1。
  2. 然后,它将要对齐的地址 addr 加上 PAGE_SIZE - 1,这将确保结果向上舍入到最接近的页面边界。
  3. 最后,它将上一步得到的结果与 PAGE_MASK 掩码进行按位与操作,以确保最低12位都被设置为0,从而将地址向上对齐到页的大小。

  这个宏在内核中非常常见,用于确保内存分配和管理操作按照页大小进行,以提高内存操作的效率和对齐要求。例如,在内核中分配页面时,可以使用 PAGE_ALIGN 宏来计算所需的内存大小,以确保分配的内存大小是页的整数倍。

28、init_waitqueue_head 函数

  init_waitqueue_head 函数是Linux内核中用于初始化等待队列头部的函数。等待队列(waitqueue)是一种用于实现进程或线程等待事件发生的机制,通常用于实现同步和通信。等待队列头部是等待队列的管理结构,它用于管理等待队列中等待的进程或线程。

以下是 init_waitqueue_head 函数的原型:

void init_waitqueue_head(wait_queue_head_t *q);

参数说明:

  • q:指向等待队列头部的指针,即要初始化的等待队列头部。

  init_waitqueue_head 函数的主要作用是初始化一个等待队列头部,使其为空,并可以在后续操作中用于添加、删除和唤醒等待的进程或线程。

  等待队列头部通常与条件变量一起使用,以便实现线程的等待和唤醒操作。在内核代码中,通常使用以下函数来等待事件的发生和唤醒等待的进程或线程:

  • wait_event:等待事件的发生。
  • wait_event_timeout:等待事件的发生,带有超时时间。
  • wake_up:唤醒等待队列中的一个或多个进程或线程。

以下是一个示例,演示如何在内核模块中使用 init_waitqueue_head 函数初始化等待队列头部,并使用等待队列来实现简单的同步操作:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/wait.h> // 包含等待队列相关的头文件

static wait_queue_head_t my_waitqueue;

static int my_condition = 0;

static int __init my_module_init(void) {
    printk(KERN_INFO "Initializing my module\n");

    // 初始化等待队列头部
    init_waitqueue_head(&my_waitqueue);

    return 0;
}

static void __exit my_module_exit(void) {
    printk(KERN_INFO "Exiting my module\n");
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("JNTM");

  在上面的示例中,init_waitqueue_head 函数用于初始化名为 my_waitqueue 的等待队列头部。这个等待队列头部可以在后续的代码中用于实现等待和唤醒操作,以实现进程或线程的同步。

29、dev_get_platdata 函数

  dev_get_platdata 函数是一个已弃用的函数,它曾经用于在Linux内核中获取设备的平台数据。平台数据是一种设备特定的配置信息,通常与设备在设备树或其他设备描述数据结构中定义的属性相关联。然而,自Linux内核版本4.13起,dev_get_platdata 函数已被标记为弃用,并不再建议在新的代码中使用。

  在较新版本的Linux内核中,建议使用 dev_get_drvdata 函数来获取设备的私有数据,而不再使用平台数据。私有数据是一个指针,通常用于将自定义设备特定数据关联到设备结构体中,以供设备驱动程序使用。

以下是 dev_get_platdata 函数的原型:

void *dev_get_platdata(struct device *dev);

参数说明:

  • dev:指向设备结构体的指针,表示要获取平台数据的设备。

  dev_get_platdata 函数用于获取与设备关联的平台数据指针。平台数据可以在设备初始化时与设备结构体相关联,并且在设备驱动程序中使用。

  然而,弃用 dev_get_platdata 的原因是因为它被认为是一种不够灵活和通用的方法,而且无法满足所有设备的需求。相反,使用 dev_get_drvdata 函数,将设备私有数据关联到设备结构体,并且更加通用,适用于各种类型的设备。

以下是示例代码,演示如何使用 dev_get_drvdata 函数获取设备的私有数据:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>

static int my_data = 42; // 示例的设备私有数据

static int __init my_module_init(void) {
    struct device *dev = NULL; // 假设有一个设备结构体

    // 关联设备私有数据
    dev_set_drvdata(dev, &my_data);

    // 获取设备私有数据
    int *data_ptr = dev_get_drvdata(dev);
    if (data_ptr) {
        printk(KERN_INFO "Device private data: %d\n", *data_ptr);
    } else {
        printk(KERN_ERR "Failed to get device private data\n");
    }

    return 0;
}

static void __exit my_module_exit(void) {
    // 在模块卸载时进行清理操作
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("JNTM");

  在上面的示例中,dev_set_drvdata 函数用于将设备私有数据关联到设备结构体,然后使用 dev_get_drvdata 函数获取设备的私有数据。这种方法更加灵活且通用,适用于新的内核开发。

30、devm_kzalloc 函数

  devm_kzalloc 函数是Linux内核中用于在设备驱动程序中分配内存的便捷函数之一。它通过在设备的生命周期内自动管理内存释放,可以避免内存泄漏问题。这个函数通常与设备管理(Device Management)相关的函数一起使用。

以下是 devm_kzalloc 函数的原型:

void *devm_kzalloc(struct device *dev, size_t size, gfp_t flags);

参数说明:

  • dev:指向设备结构体的指针,表示要分配内存的设备。
  • size:要分配的内存块的大小(以字节为单位)。
  • flags:内存分配标志,通常使用 GFP_KERNEL 或 GFP_ATOMIC 等标志,以指定内存分配的上下文和行为。

  devm_kzalloc 函数的作用是分配一块大小为 size 字节的内存,并将其初始化为零。与普通的内存分配函数不同,devm_kzalloc 函数会将分配的内存与设备关联起来,并在设备销毁时自动释放内存,以避免内存泄漏。

  这种自动内存管理对于设备驱动程序的开发很有用,因为它简化了内存管理的工作,确保在设备不再需要内存时释放它,而无需手动编写释放内存的代码。

以下是一个示例,演示如何在设备驱动程序中使用 devm_kzalloc 函数分配内存:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/slab.h> // 包含内存分配函数的头文件

static int __init my_module_init(void) {
    struct device *dev = NULL; // 假设有一个设备结构体
    size_t size = 128; // 要分配的内存大小,这里假设为128字节
    void *data;

    // 使用 devm_kzalloc 分配内存并与设备关联
    data = devm_kzalloc(dev, size, GFP_KERNEL);
    if (!data) {
        printk(KERN_ERR "Failed to allocate memory\n");
        return -ENOMEM;
    }

    // 在这里可以使用分配的内存

    return 0;
}

static void __exit my_module_exit(void) {
    // 在模块卸载时不需要手动释放内存,由devm_kzalloc自动管理
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("JNTM");

  在上面的示例中,devm_kzalloc 函数用于分配内存,并且无需在模块卸载时手动释放内存。内存的释放将由内核自动管理,确保在设备销毁时释放内存。这有助于减少内存泄漏的风险,提高设备驱动程序的可靠性。

31、platform_set_drvdata 函数

  platform_set_drvdata 函数是Linux内核中用于将设备特定的私有数据(platform data)关联到特定的平台设备(platform device)的函数。这个私有数据通常是与设备的特定配置或状态相关联的信息,可以在设备驱动程序中使用。

以下是 platform_set_drvdata 函数的原型:

void platform_set_drvdata(struct platform_device *pdev, void *data);

参数说明:

  • pdev:指向平台设备结构体的指针,表示要关联私有数据的平台设备。
  • data:要关联的私有数据的指针,通常是一个结构体或其他自定义数据类型。

  platform_set_drvdata 函数的作用是将特定平台设备 pdev 的私有数据字段设置为指定的 data 值。这样,设备驱动程序可以随时通过平台设备结构体访问关联的私有数据,以实现设备的特定功能和配置。

  通常,设备驱动程序会在设备初始化时使用 platform_set_drvdata 函数来关联私有数据,然后在后续的设备操作中使用这些私有数据。

以下是一个示例,演示如何在设备驱动程序中使用 platform_set_drvdata 函数关联私有数据:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>

// 示例的设备特定私有数据结构
struct my_platform_data {
    int config_value;
};

static int __init my_driver_init(void) {
    struct platform_device *pdev = NULL; // 假设有一个平台设备结构体
    struct my_platform_data *my_data = NULL; // 假设有设备特定私有数据结构

    // 分配并初始化设备特定私有数据
    my_data = kzalloc(sizeof(struct my_platform_data), GFP_KERNEL);
    if (!my_data) {
        printk(KERN_ERR "Failed to allocate memory for private data\n");
        return -ENOMEM;
    }

    // 设置设备特定私有数据
    my_data->config_value = 42; // 配置特定的值
    platform_set_drvdata(pdev, my_data);

    // 在这里可以使用设备特定私有数据

    return 0;
}

static void __exit my_driver_exit(void) {
    // 在模块卸载时释放设备特定私有数据
    struct platform_device *pdev = NULL; // 假设有一个平台设备结构体
    struct my_platform_data *my_data = platform_get_drvdata(pdev);

    if (my_data) {
        kfree(my_data);
    }
}

module_init(my_driver_init);
module_exit(my_driver_exit);

MODULE_LICENSE("JNTM");

  在上面的示例中,platform_set_drvdata 函数用于将设备特定私有数据关联到平台设备结构体中。这个私有数据在设备驱动程序的生命周期内可用,可用于配置设备或存储设备状态等设备特定操作。在模块卸载时,示例中的代码还使用 kfree 函数释放了关联的私有数据,以避免内存泄漏。

32、dma_free_coherent 函数

  dma_free_coherent 函数是Linux内核中用于释放使用DMA分配的连续内存区域的函数。DMA(Direct Memory Access)是一种用于高效地数据传输的机制,通常用于设备之间的数据传输,如网络接口卡、硬盘控制器和图形加速卡等设备。

  这个函数通常与 dma_alloc_coherent 函数一起使用,后者用于分配连续的DMA内存区域。使用 dma_alloc_coherent 分配的内存区域具有与物理设备直接通信的能力,因此需要使用 dma_free_coherent 在不再需要时将其释放,以防止内存泄漏。

以下是 dma_free_coherent 函数的原型:

void dma_free_coherent(struct device *dev, size_t size, void *vaddr, dma_addr_t dma_handle);

参数说明:

  • dev:指向设备结构体的指针,表示与DMA内存分配相关联的设备。
  • size:要释放的DMA内存区域的大小(以字节为单位)。
  • vaddr:分配的DMA内存区域的虚拟地址。
  • dma_handle:DMA句柄,用于标识DMA内存区域。

  dma_free_coherent 函数的作用是释放之前使用 dma_alloc_coherent 分配的DMA内存区域。它接受分配时使用的设备结构体、内存区域的大小、虚拟地址以及DMA句柄作为参数。这样,它可以将DMA内存区域与设备关联起来,以确保正确的内存释放。

以下是一个示例,演示了如何在设备驱动程序中使用 dma_alloc_coherent 和 dma_free_coherent 函数来分配和释放DMA内存:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>

static struct device *my_device; // 假设有一个设备结构体

static void *dma_buffer; // DMA内存区域的虚拟地址
static dma_addr_t dma_handle; // DMA内存区域的DMA句柄
static size_t dma_size = 4096; // DMA内存区域的大小,这里假设为4KB

static int __init my_module_init(void) {
    // 分配DMA内存区域
    dma_buffer = dma_alloc_coherent(my_device, dma_size, &dma_handle, GFP_KERNEL);
    if (!dma_buffer) {
        printk(KERN_ERR "Failed to allocate DMA memory\n");
        return -ENOMEM;
    }

    // 在这里可以使用DMA内存区域

    return 0;
}

static void __exit my_module_exit(void) {
    // 释放DMA内存区域
    dma_free_coherent(my_device, dma_size, dma_buffer, dma_handle);
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("JNTM");

  在上面的示例中,dma_alloc_coherent 函数用于分配DMA内存区域,并返回虚拟地址和DMA句柄。在模块卸载时,dma_free_coherent 函数用于释放DMA内存区域,以防止内存泄漏。这确保了DMA内存的正确管理和释放。

33、device_unregister 函数

  device_unregister 函数是Linux内核中用于注销设备对象的函数之一。它通常与设备驱动程序开发和管理相关,用于从设备模型中移除一个设备对象,以便在设备不再存在时释放相关的资源。

以下是 device_unregister 函数的原型:

void device_unregister(struct device *dev);

参数说明:

  • dev:指向要注销的设备对象的指针。

  device_unregister 函数的作用是将设备对象从设备模型中注销,并释放与该设备对象相关的资源。这通常在设备不再存在或不再需要时调用,以确保设备对象不会被再次使用。

以下是一个示例,演示了如何在设备驱动程序中使用 device_unregister 函数注销设备对象:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>

static struct class *my_class;
static struct device *my_device;

static int __init my_module_init(void) {
    // 创建设备类
    my_class = class_create(THIS_MODULE, "my_class");
    if (IS_ERR(my_class)) {
        printk(KERN_ERR "Failed to create device class\n");
        return PTR_ERR(my_class);
    }

    // 创建设备对象
    my_device = device_create(my_class, NULL, MKDEV(0, 0), NULL, "my_device");
    if (IS_ERR(my_device)) {
        class_destroy(my_class);
        printk(KERN_ERR "Failed to create device\n");
        return PTR_ERR(my_device);
    }

    // 在这里进行设备初始化

    return 0;
}

static void __exit my_module_exit(void) {
    // 注销设备对象并销毁设备类
    device_unregister(my_device);
    class_destroy(my_class);
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("JNTM");

  在上面的示例中,device_unregister 函数用于在模块卸载时注销设备对象 my_device,并使用 class_destroy 函数销毁设备类 my_class。这样可以确保在模块卸载时正确释放相关的资源。

  请注意,具体的设备对象和设备类的创建方式可能会因驱动程序的需求而异,但 device_unregister 函数的基本用法是在设备不再需要时调用它以注销设备对象。

34、of_match_ptr 宏

  of_match_ptr 不是一个独立的函数,而是一个宏(macro),用于在Linux内核中处理设备树(Device Tree)匹配表(match table)的实用工具。它用于创建一个包含设备树匹配表的静态数组,并将其与设备驱动程序结构体相关联。

  设备树匹配表是一种用于描述系统中设备和设备驱动程序之间关系的数据结构。这些表可以在设备驱动程序中定义,用于匹配设备树中的设备节点和设备驱动程序。当设备树被解析时,内核会根据设备树的信息尝试将设备与合适的设备驱动程序匹配。

  of_match_ptr 宏的主要目的是创建一个包含匹配表项的数组,并将这个数组与设备驱动程序结构体关联起来,以便在加载设备驱动程序时使用。

以下是 of_match_ptr 宏的示例用法:

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>

// 定义设备树匹配表项
static const struct of_device_id my_driver_match[] = {
    { .compatible = "my,device-1", },
    { .compatible = "my,device-2", },
    { /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, my_driver_match);

// 设备驱动程序结构体
static struct platform_driver my_driver = {
    .driver = {
        .name = "my_driver",
        .of_match_table = of_match_ptr(my_driver_match), // 关联匹配表
    },
};

static int __init my_driver_init(void) {
    // 在这里进行设备驱动程序初始化
    return platform_driver_register(&my_driver);
}

static void __exit my_driver_exit(void) {
    platform_driver_unregister(&my_driver);
}

module_init(my_driver_init);
module_exit(my_driver_exit);

MODULE_LICENSE("JNTM");

  在上面的示例中,我们首先定义了设备树匹配表项 my_driver_match,然后使用 MODULE_DEVICE_TABLE(of, my_driver_match) 宏将这个匹配表与模块关联起来,以便内核可以在加载模块时正确识别设备。

  接下来,我们定义了一个设备驱动程序结构体 my_driver,并在其中使用 .of_match_table 成员关联了设备树匹配表 my_driver_match。这样,内核在加载设备驱动程序时会使用这个匹配表来匹配设备。

  总之,of_match_ptr 宏有助于将设备树匹配表与设备驱动程序结构体关联起来,以实现正确的设备树匹配和设备驱动程序加载。

35、platform_driver_register 函数

  platform_driver_register 函数是Linux内核中用于注册平台设备驱动程序的函数。平台设备驱动程序通常用于管理与特定硬件平台相关的设备,这些设备可能与CPU、内存、总线等硬件相关。

以下是 platform_driver_register 函数的原型:

int platform_driver_register(struct platform_driver *driver);

参数说明:

  • driver:指向要注册的平台设备驱动程序结构体的指针。

  platform_driver_register 函数的作用是将平台设备驱动程序注册到内核中,以便在匹配到相应的硬件设备时,内核可以调用该驱动程序的初始化函数。

以下是一个示例,演示了如何在设备驱动程序中使用 platform_driver_register 函数注册平台设备驱动程序:

#include <linux/module.h>
#include <linux/platform_device.h>

// 初始化函数,当匹配到设备时调用
static int my_driver_probe(struct platform_device *pdev) {
    // 在这里进行设备初始化
    return 0;
}

// 卸载函数,当设备被移除时调用
static int my_driver_remove(struct platform_device *pdev) {
    // 在这里进行设备卸载和资源释放
    return 0;
}

// 平台设备驱动程序结构体
static struct platform_driver my_driver = {
    .driver = {
        .name = "my_driver",
        .owner = THIS_MODULE,
    },
    .probe = my_driver_probe,
    .remove = my_driver_remove,
};

static int __init my_driver_init(void) {
    return platform_driver_register(&my_driver); // 注册平台设备驱动程序
}

static void __exit my_driver_exit(void) {
    platform_driver_unregister(&my_driver); // 注销平台设备驱动程序
}

module_init(my_driver_init);
module_exit(my_driver_exit);

MODULE_LICENSE("JNTM");

  在上面的示例中,我们定义了一个平台设备驱动程序结构体 my_driver,并在其中设置了驱动程序的名称、拥有者(通常设置为 THIS_MODULE,表示由当前模块拥有)以及初始化函数和卸载函数。然后,在模块初始化函数 my_driver_init 中使用 platform_driver_register 函数来注册平台设备驱动程序,使其可用于匹配到相应的硬件设备。

  在模块卸载函数 my_driver_exit 中,我们使用 platform_driver_unregister 函数注销平台设备驱动程序,以确保在模块卸载时正确释放相关的资源。

  总之,platform_driver_register 函数用于注册平台设备驱动程序,从而允许内核在匹配到相应的硬件设备时调用相关的初始化函数。

36、platform_driver_unregister 函数

  platform_driver_unregister 函数是Linux内核中用于注销已注册的平台设备驱动程序的函数。平台设备驱动程序通常用于管理与特定硬件平台相关的设备,当这些设备不再需要时,可以使用 platform_driver_unregister 函数将其注销,以释放相关的资源。

以下是 platform_driver_unregister 函数的原型:

void platform_driver_unregister(struct platform_driver *drv);

参数说明:

  • drv:指向要注销的平台设备驱动程序结构体的指针。

  platform_driver_unregister 函数的作用是从内核中注销已注册的平台设备驱动程序,以确保在不再需要该驱动程序时,相关的资源可以被正确释放。

以下是一个示例,演示了如何在设备驱动程序中使用 platform_driver_unregister 函数注销平台设备驱动程序:

#include <linux/module.h>
#include <linux/platform_device.h>

// 初始化函数,当匹配到设备时调用
static int my_driver_probe(struct platform_device *pdev) {
    // 在这里进行设备初始化
    return 0;
}

// 卸载函数,当设备被移除时调用
static int my_driver_remove(struct platform_device *pdev) {
    // 在这里进行设备卸载和资源释放
    return 0;
}

// 平台设备驱动程序结构体
static struct platform_driver my_driver = {
    .driver = {
        .name = "my_driver",
        .owner = THIS_MODULE,
    },
    .probe = my_driver_probe,
    .remove = my_driver_remove,
};

static int __init my_driver_init(void) {
    return platform_driver_register(&my_driver); // 注册平台设备驱动程序
}

static void __exit my_driver_exit(void) {
    platform_driver_unregister(&my_driver); // 注销平台设备驱动程序
}

module_init(my_driver_init);
module_exit(my_driver_exit);

MODULE_LICENSE("JNTM");

  在上面的示例中,我们在模块卸载函数 my_driver_exit 中使用 platform_driver_unregister 函数来注销平台设备驱动程序 my_driver,以确保在模块卸载时正确释放相关的资源。

  总之,platform_driver_unregister 函数用于注销已注册的平台设备驱动程序,以确保在不再需要该驱动程序时,相关的资源可以被正确释放。

37、late_initcall函数

  late_initcall 函数是Linux内核中用于注册延迟初始化函数的宏(macro)。延迟初始化函数是在内核启动过程的一个相对晚期阶段执行的函数,用于执行一些与设备、模块或子系统初始化相关的任务。late_initcall 宏允许开发者将自定义的延迟初始化函数注册到内核,以确保它们在适当的时机被调用。

  使用 late_initcall 宏注册的函数会在内核的 “late_init” 阶段执行,这个阶段通常发生在内核初始化的后期。这可以用于执行与硬件、设备或模块初始化相关的任务,因为此时许多基本的系统初始化步骤已经完成。

以下是 late_initcall 宏的基本用法示例:

#include <linux/init.h>

static int __init my_late_init_function(void) {
    // 在这里执行延迟初始化任务
    return 0;
}

late_initcall(my_late_init_function);

  在上面的示例中,我们定义了一个名为 my_late_init_function 的初始化函数,并使用 late_initcall 宏将其注册为延迟初始化函数。在内核启动过程的 “late_init” 阶段,my_late_init_function 函数将被调用。

  通常,late_initcall 宏用于执行一些需要在内核初始化的后期执行的任务,例如初始化某些设备驱动程序、注册子系统或执行与硬件相关的初始化。

  需要注意的是,延迟初始化函数应当谨慎编写,以确保不会破坏内核的正常启动过程。在执行这些函数时,某些内核功能可能尚未完全初始化,因此开发者需要了解内核启动过程的详细信息以确保安全性和正确性。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值