目录

前言

一、内核模块代码解析

1.1 模块初始化与退出

1.2 文件操作接口实现

二、设备文件创建

2.1 手动创建(不推荐)

2.2 自动创建(推荐)

三、驱动与设备关联方式

3.1 传统字符设备注册

3.2 设备树 + Platform驱动(嵌入式首选)

3.3 Platform设备 + 驱动(非设备树系统)

四、编译与测试

4.1 Makefile示例

4.2 操作步骤

五、验证与调试

5.1 关键验证命令

5.2 常见问题排查

六、完整示例(Misc Dev)


前言

在Linux内核开发中,设备驱动是连接硬件与操作系统的关键桥梁。本文将介绍一个简单的字符设备驱动示例。


一、内核模块代码解析

1.1 模块初始化与退出

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

static int __init demo_init(void)
{
    printk("demo init\\n");
    register_chrdev(0, "chardev_demo", &demo_fops); // 动态分配主设备号
    return 0;
}

static void __exit demo_exit(void)
{
    printk("demo exit\\n");
    unregister_chrdev(0, "chardev_demo");
}

module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
  • module_init/exit:定义模块加载/卸载时的入口函数
  • register_chrdev:注册字符设备,参数0表示动态分配主设备号
  • printk:内核日志输出函数

1.2 文件操作接口实现

static int demo_open(struct inode *inode, struct file *file)
{
    printk("demo open\\n");
    return 0;
}

static int demo_release(struct inode *inode, struct file *file)
{
    printk("demo release\\n");
    return 0;
}

static struct file_operations demo_fops = {
    .open = demo_open,
    .release = demo_release,
    .owner = THIS_MODULE,
};
  • file_operations:定义设备文件支持的操作
  • 当前实现基本的打开/关闭功能,可扩展read/write/ioctl等方法

二、设备文件创建

2.1 手动创建(不推荐)

# 假设主设备号为250,次设备号为0
sudo mknod /dev/my_device c 250 0

2.2 自动创建(推荐)

在驱动初始化代码中添加:

static struct class *my_class;
static dev_t dev_id;

// 在demo_init中
my_class = class_create(THIS_MODULE, "my_class");
device_create(my_class, NULL, dev_id, NULL, "my_device");
  • class_create:创建设备类
  • device_create:自动在/dev下生成设备节点
  • 通过udev机制动态管理设备文件

三、驱动与设备关联方式

3.1 传统字符设备注册

major = register_chrdev(0, "my_device", &fops);
dev_id = MKDEV(major, 0); // 组合设备号
  • 适用于简单设备,需手动或自动分配主设备号

3.2 设备树 + Platform驱动(嵌入式首选)

设备树定义:

my_device@1234 {
    compatible = "my-vendor,my-device";
    reg = <0x1234 0x100>;
};

驱动匹配:

static const struct of_device_id my_driver_of_match[] = {
    { .compatible = "my-vendor,my-device" },
    {}
};

3.3 Platform设备 + 驱动(非设备树系统)

// Platform设备注册
static struct platform_device my_device = {
    .name = "my-device-name",
};
platform_device_register(&my_device);

// Platform驱动注册
static struct platform_driver my_driver = {
    .probe = my_probe,
    .driver = { .name = "my-device-name" },
};
platform_driver_register(&my_driver);

四、编译与测试

4.1 Makefile示例

obj-m += char_demo.o
KDIR := /path/to/linux-source
PWD := $(shell pwd)

all:
    make -C $(KDIR) M=$(PWD) modules
clean:
    make -C $(KDIR) M=$(PWD) clean

4.2 操作步骤

make                # 编译模块
sudo insmod demo.ko # 加载模块
dmesg | tail        # 查看内核日志
ls /dev/my_device   # 验证设备文件
sudo rmmod demo     # 卸载模块

五、验证与调试

5.1 关键验证命令

5.2 常见问题排查

  1. 权限问题:使用sudo加载模块,检查/dev节点权限
  2. 版本兼容:确保内核头文件与运行内核版本一致
  3. 日志追踪:通过dmesg -w实时监控内核消息

六、完整示例(Misc Dev)

GitHub - soha1991/linux_learn: My linux learning project, including driver, userspace app, etc.

driver code:

#include "linux/device.h"
#include "linux/device/class.h"
#include "linux/export.h"
#include "linux/gfp_types.h"
#include "linux/miscdevice.h"
#include "linux/mutex.h"
#include "linux/pagemap.h"
#include "linux/printk.h"
#include "linux/slab.h"
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/mm.h>
#include <linux/dma-mapping.h>

#define DEMO_DEV_NAME "demo_device"
#define STATUS_READY 0
#define STATUS_BUSY  1
#define IOCTL_MAGIC  'V'
#define IOCTL_SET_MODE _IOW(IOCTL_MAGIC, 1, int)
#define IOCTL_GET_STATUS _IOR(IOCTL_MAGIC, 1, int)

struct demo_device {
    char *dma_buffer;           // DMA模拟缓冲区
    size_t buffer_size;         // 缓冲区大小
    int mode;                   // 工作模式
    atomic_t status;            // 设备状态
    struct mutex lock;          // 互斥锁
};
static struct demo_device *vdev;

static int demo_open (struct inode *, struct file *);
static int demo_release (struct inode *, struct file *);
// static int demo_mmap (struct file *, struct vm_area_struct *);
// static long demo_ioctl (struct file *, unsigned int, unsigned long);

static int demo_open (struct inode * inode, struct file * filp)
{
    printk("demo open\n");
    if (!try_module_get (THIS_MODULE))
    {
        return -EBUSY;
    }

    atomic_set(&vdev->status, STATUS_BUSY);

    filp->private_data = vdev;
    return 0;
}

static int demo_release (struct inode * inode, struct file * filp)
{
    printk("demo release\n");
    module_put (THIS_MODULE);
    atomic_set(&vdev->status, STATUS_READY);
    return 0;
}

static ssize_t demo_read (struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
    struct demo_device *dev = (struct demo_device *)filp->private_data;
    int ret = 0;
    printk("demo_read pos %lld\n",*pos);
    mutex_lock (&dev->lock);

    if (*pos >= dev->buffer_size)
    {
    ret = 0;
    goto out;
    }

    if (*pos + count > dev->buffer_size)
    {
        count = dev->buffer_size - *pos;
    }

    if (copy_to_user(buf, dev->dma_buffer + *pos, count))
    {
        ret = -EFAULT;
        goto out;
    }

    *pos += count;
    ret = count;

out:
    mutex_unlock (&dev->lock);
    return ret;
}

static ssize_t demo_write (struct file *filp, const char __user * buf, size_t count, loff_t *pos)
{
    struct demo_device *dev = (struct demo_device *)filp->private_data;
    int ret = 0;

    printk("demo_write pos %lld\n",*pos);

    mutex_lock (&dev->lock);

    if (*pos >= dev->buffer_size)
    {
        ret = -ENOSPC;
        goto out;
    }

    if (*pos + count > dev->buffer_size)
    {
        count = dev->buffer_size - *pos;
    }

    if (copy_from_user(dev->dma_buffer + *pos, buf, count))
    {
        ret = -EFAULT;
        goto out;
    }
    *pos += count;
    ret = count;
out:
    mutex_unlock(&dev->lock);
    return ret;
}

static long demo_ioctl (struct file *filp, unsigned int cmd, unsigned long arg)
{
    struct demo_device *dev = filp->private_data;
    int tmp;
    int ret = 0;

    switch (cmd)
    {
        case IOCTL_SET_MODE:
            if (copy_from_user(&tmp, (int __user *)arg, sizeof(int)))
            {
                return -EFAULT;
            }
            dev->mode = tmp;
            break;
        case IOCTL_GET_STATUS:
            tmp = atomic_read(&dev->status);
            if (copy_to_user((int __user *)arg, &tmp, sizeof(int)))
            {
                return -EFAULT;
            }
            break;
        default:
            printk("Not support cmd 0x%x\n", cmd);
            ret = -ENOTTY;
    }
    return ret;

}

static struct file_operations demo_fops = {
    .open = demo_open,
    .release = demo_release,
    .read = demo_read,
    .write = demo_write,
    .unlocked_ioctl = demo_ioctl,
    .owner = THIS_MODULE,
};

static struct miscdevice demo_device = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = DEMO_DEV_NAME,
    .fops = &demo_fops,
    .mode = 0666,
};


static int __init demo_init(void)
{
    int ret = 0;
    printk("demo init\n");

    vdev = (struct demo_device *)kmalloc (sizeof(struct demo_device), GFP_KERNEL);
    if (!vdev)
        return -ENOMEM;

    vdev->buffer_size = 8*1024;

    vdev->dma_buffer = (char *)kmalloc (vdev->buffer_size, GFP_KERNEL);
    if (!vdev->dma_buffer)
    {
        ret = -ENOMEM;
        goto free_dev;
    }

    mutex_init(&vdev->lock);
    atomic_set(&vdev->status, STATUS_READY);
    vdev->mode = 0;

    ret = misc_register (&demo_device);
    if (ret < 0)
    {
        printk("misc_register err, %d\n", ret);
        ret = -EIO;
        goto free_buf;
    }


free_buf:
    kfree(vdev->dma_buffer);

free_dev:
    kfree(vdev);

    return ret;
}

static void __exit demo_exit(void)
{
    misc_deregister (&demo_device);
    kfree (vdev->dma_buffer);
    kfree (vdev);
    printk("demo exit\n");
}

module_init(demo_init)
module_exit(demo_exit)
MODULE_LICENSE("GPL");

 App code

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>

#define DEVICE_PATH "/dev/demo_device"

// 如果驱动支持 ioctl,定义命令(需与内核驱动一致)
#define IOCTL_MAGIC  'V'
#define IOCTL_SET_MODE _IOW(IOCTL_MAGIC, 1, int)
#define IOCTL_GET_STATUS _IOR(IOCTL_MAGIC, 1, int)

int main() {
    int fd;
    char write_buf[100] = "Hello from userspace!";
    char read_buf[100] = {0};

    // 1. 打开设备文件
    fd = open(DEVICE_PATH, O_RDWR);
    if (fd < 0) {
        perror("Failed to open device");
        exit(EXIT_FAILURE);
    }
    printf("Device opened successfully.\n");

    // 2. 向设备写入数据
    ssize_t ret = write(fd, write_buf, strlen(write_buf));
    if (ret < 0) {
        perror("Write failed");
        close(fd);
        exit(EXIT_FAILURE);
    }
    close(fd);
    printf("Wrote %zd bytes: %s\n", ret, write_buf);

    fd = open(DEVICE_PATH, O_RDWR);
    if (fd < 0) {
        perror("Failed to open device");
        exit(EXIT_FAILURE);
    }

    // 3. 从设备读取数据
    ret = read(fd, read_buf, sizeof(read_buf));
    if (ret < 0) {
        perror("Read failed");
        close(fd);
        exit(EXIT_FAILURE);
    }

    printf("Read %zd bytes: %s\n", ret, read_buf);

    // 4. 可选:测试 ioctl(如果驱动支持)
    int mode  = 1;
    if (ioctl(fd, IOCTL_SET_MODE, &mode) < 0) {
        perror("IOCTL failed");
    } else {
        printf("IOCTL SET MODE succeeded. Value: %d\n", mode);
    }

    int status;
    if (ioctl(fd, IOCTL_GET_STATUS, &status) < 0) {
        perror("IOCTL failed");
    } else {
        printf("IOCTL GET STATUS succeeded. Value: %d\n", status);
    }

    // 5. 关闭设备
    close(fd);
    return 0;
}

 Makefile

obj-m += misc_drv.o

KDIR := $(shell pwd)/../../
PWD := $(shell pwd)

all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean

 build cmd

cd demo_path
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-


cd app_path
aarch64-linux-gnu-gcc -static demo_app.c -o demo_app

更多推荐