【linux字符设备驱动-01】创建一个字符设备驱动

一、创建字符设备

1、申请设备号

方法一

alloc_chrdev_region:在未指定设备号的情况下使用

dev_t devid;
alloc_chrdev_region(&devid, 0, 1, "test");
major = MAJOR(devid); /* 获取主设备号 */
minor = MINOR(devid); /* 获取次设备号 */

方法二

register_chrdev_region:在给定设备号的情况下使用

int major;
int minor;
dev_t devid;
devid = MKDEV(major, minor);
register_chrdev_region(devid, 1, "test");

通 过 alloc_chrdev_region 或者 register_chrdev_region 申请的设备号,释放函数都是:unregister_chrdev_region。

2、创建类

方法一

class_register:把类注册进内核,类需要提前准备好。与class_unregister配对使用。

//用法一:先申请内存,再初始化
struct class *my_class;

my_class= kzalloc(sizeof(*my_class), GFP_KERNEL);
my_class->name = "my_class";
my_class->owner = THIS_MODULE;

class_register(my_class);	// 会在/sys/class/下创建"my_class"目录

//用法二:定义结构体
struct class my_class= {
	.name		= "my_class",
	.owner 		= THIS_MODULE,
};

class_register(&my_class);	// 会在/sys/class/下创建"my_class"目录

方法二

class_create:创建类,并注册进内核,返回类结构体指针。与class_destroy配对使用。

struct class *my_class;
my_class= class_create(THIS_MODULE, "my_class");	// 会在/sys/class/下创建"my_class"目录

阅读class_create的源码会发现,class_create和class_register最后都调用了__class_register。两者都会在/sys/class/下创建设备类节点(是个文件夹)。

3、创建设备

device_create:创建设备,需要传入class和设备号,返回device结构体指针。与device_destroy配对使用。

struct device *device;
device = device_create(class, NULL, devid, NULL, "device_name");	// 会在/dev/下创建"device_name"设备节点

内核源码中实际上device_create用的并不多,反而是device_register与device_add更常见,其实它们之间的调用关系是:device_create->device_registe->device_add。在较新版本的内核中是:device_create->device_create_groups_vargs->device_add。

二、创建字符设备驱动

通过上面的第一部分,可以完成一个字符设备的创建,并在/dev/下面生成对应的设备节点。但是此时还不能够对设备节点进行write/read/ioctl等操作,因为并没有为设备注册相应的驱动。cdev是Linux内核中的一个结构体,定义在<linux/cdev.h>中,代表字符设备驱动的实例。cdev结构体中包含了字符设备驱动所需的各种信息,如设备号、设备文件操作函数等。通过cdev可以将设备文件操作函数与设备驱动关联起来,实现对字符设备的操作。

1、初始化:cdev_init

struct cdev my_cdev;

// 文件操作描述符
static struct file_operations my_fops = {
    .owner 			= THIS_MODULE,
    .open 			= my_open,
    .release 		= my_release,
    .read 			= my_read,
    .write 			= my_write,
    .unlocked_ioctl = my_ioctl,
};

cdev_init(&my_cdev, &my_fops);

2、添加到内核:cdev_add

cdev_add(&my_cdev, devid, 1);

cdev_init完成对cdev结构体的初始化,cdev_add将cdev注册进内核,并与设备号关联起来。当应用层对设备节点进行操作时,会根据设备节点的设备号去内核中匹配对应的文件操作描述符,之后调用其中的回调函数。
卸载驱动时,使用cdev_del函数将字符设备驱动从内核中删除。

三、一个完整的字符设备驱动程序

一个字符设备驱动除了包含必要设备号dev_t、所属类class、设备device、驱动cdev这些信息,此外还会有其它私有数据。为方便管理,将所有信息定义到一个结构体中。

1、驱动源码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/device.h>
#include <linux/uaccess.h>

#define CLASS_NAME 		"my_class"
#define DEVICE_NAME 	"my_device"
#define BUFFER_SIZE 	1024
#define MY_IOCTL_RESET 	_IO('M', 0)

struct chardev {
	char name[32];
	dev_t devid;
	struct cdev cdev;
	struct class *class;
	struct device *device;
	int major;
	int minor;
};

static char buffer[BUFFER_SIZE];
static struct chardev my_dev;

static int my_open(struct inode *inode, struct file *file)
{
    pr_info("%s\n", __func__);
    return 0;
}

static int my_release(struct inode *inode, struct file *file)
{
    pr_info("%s\n", __func__);
    return 0;
}

static ssize_t my_read(struct file *file, char __user *user_buffer, size_t count, loff_t *offset)
{
    ssize_t bytes_read = 0;
    int remaining_bytes = 0;

    remaining_bytes = BUFFER_SIZE - *offset;
    if (remaining_bytes == 0) {
        return 0;
    }

    bytes_read = min_t(size_t, count, remaining_bytes);
    if (copy_to_user(user_buffer, buffer + *offset, bytes_read)) {
        return -EFAULT;
    }

    *offset += bytes_read;
    return bytes_read;
}

static ssize_t my_write(struct file *file, const char __user *user_buffer, size_t count, loff_t *offset)
{
    ssize_t bytes_written = 0;
    int remaining_bytes = 0;

    remaining_bytes = BUFFER_SIZE - *offset;
    if (remaining_bytes == 0) {
        return -ENOSPC;
    }

    bytes_written = min_t(size_t, count, remaining_bytes);
    if (copy_from_user(buffer + *offset, user_buffer, bytes_written)) {
        return -EFAULT;
    }

    *offset += bytes_written;
    return bytes_written;
}

static long my_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    switch (cmd) {
        case MY_IOCTL_RESET:
            memset(buffer, 0, BUFFER_SIZE);
            pr_info("buffer reset\n");
            break;
        default:
            return -EINVAL;
    }

    return 0;
}

static struct file_operations my_fops = {
    .owner 			= THIS_MODULE,
    .open 			= my_open,
    .release 		= my_release,
    .read 			= my_read,
    .write 			= my_write,
    .unlocked_ioctl = my_ioctl,
};

static int __init chardev_init(void)
{
	int ret;
	
	// 1. 分配设备号
	ret = alloc_chrdev_region(&my_dev.devid, 0, 1, "my_devid");
	if (ret < 0) {
        pr_err("Failed to allocate device number: %d\n", ret);
        return ret;
    }
	my_dev.major = MAJOR(my_dev.devid);
	my_dev.minor = MINOR(my_dev.devid);
	pr_info("major = %d, minor = %d\n", my_dev.major, my_dev.minor);

	// 2. 初始化cdev结构体、添加到内核
    cdev_init(&my_dev.cdev, &my_fops);
    ret = cdev_add(&my_dev.cdev, my_dev.devid, 1);
    if (ret < 0) {
        pr_err("Failed to add cdev: %d\n", ret);
        unregister_chrdev_region(my_dev.devid, 1);	// 记得注销设备号
        return ret;
    }
	
	// 3. 创建类
	my_dev.class = class_create(THIS_MODULE, CLASS_NAME);
	if (IS_ERR(my_dev.class)) {
        ret = PTR_ERR(my_dev.class);
        pr_err("Failed to create class: %d\n", ret);
        return ret;
    }

	// 4. 创建设备
	my_dev.device = device_create(my_dev.class, NULL, my_dev.devid, NULL, DEVICE_NAME);
	if (IS_ERR(my_dev.device)) {
        ret = PTR_ERR(my_dev.device);
        pr_err("Failed to create device: %d\n", ret);
        class_destroy(my_dev.class);	// 记得注销类
        return ret;
    }
    
    pr_info("%s done\n", __func__);
	return 0;
}

static void __exit chardev_exit(void)
{
	device_destroy(my_dev.class, my_dev.devid);
    class_destroy(my_dev.class);
    cdev_del(&my_dev.cdev);
    unregister_chrdev_region(my_dev.devid, 1);
    
    pr_info("%s done\n", __func__);
}

module_init(chardev_init);
module_exit(chardev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxx");

2、测试demo

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

#define DEVICE_PATH "/dev/my_device"
#define MY_IOCTL_RESET _IO('M', 0)
static int fd;
static char buffer[256];

int file_write(void)
{
    ssize_t ret;

    // 打开设备文件
    fd = open(DEVICE_PATH, O_RDWR);
    if (fd < 0) {
        perror("Failed to open device");
        return -1;
    }

    // 写入数据到设备文件
    ret = write(fd, "Hello, device!", 14);
    if (ret < 0) {
        perror("Failed to write to device");
        close(fd);
        return -1;
    }
    printf("Write data to device: Hello, device!\n");

    // 关闭设备文件
    close(fd);

    return 0;
}

int file_read(void)
{
    ssize_t ret;

    // 打开设备文件
    fd = open(DEVICE_PATH, O_RDWR);
    if (fd < 0) {
        perror("Failed to open device");
        return -1;
    }

    // 读取设备文件中的数据
    ret = read(fd, buffer, sizeof(buffer));
    if (ret < 0) {
        perror("Failed to read from device");
        close(fd);
        return -1;
    }
    printf("Read data from device: %s\n", buffer);

    // 关闭设备文件
    close(fd);

    return 0;
}

int file_ioctl(void)
{
    ssize_t ret;

    // 打开设备文件
    fd = open(DEVICE_PATH, O_RDWR);
    if (fd < 0) {
        perror("Failed to open device");
        return -1;
    }

    // 重置设备文件中的数据
    ret = ioctl(fd, MY_IOCTL_RESET);
    if (ret < 0) {
        perror("Failed to ioctl");
        close(fd);
        return -1;
    }
    printf("Device buffer reset\n");

    // 关闭设备文件
    close(fd);

    return 0;
}

int main(void)
{
    file_write();
    file_read();
    file_ioctl();
    
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 编写 Linux 字符设备驱动程序需要熟悉 Linux 内核,并且具备 C 语言编程能力。首先,需要找到对应的设备文件,并确定对应的设备类型。然后根据设备的硬件特性,编写字符设备驱动程序,并实现相关操作函数,注册设备驱动,最后编写用户空间程序以便操作设备。 ### 回答2: 编写一个Linux字符设备驱动需要遵循以下步骤: 1. 包含必要的头文件:在驱动程序的源文件中,你需要包含一些必要的头文件。这些头文件包括“linux/module.h”、“linux/kernel.h”和“linux/fs.h”。 2. 定义设备结构体:在驱动程序中,你需要定义一个结构体来表示设备。这个结构体通常包含设备名称、设备号和其他需要的属性。 3. 实现打开和关闭设备的函数:在驱动程序中,你需要实现打开设备和关闭设备的函数。这些函数通常用于初始化设备和释放相关资源。 4. 实现读取和写入设备的函数:在驱动程序中,你需要实现读取设备和写入设备的函数。这些函数通常用于从设备中读取数据和向设备中写入数据。 5. 实现设备操作的函数:在驱动程序中,你需要实现一些设备操作的函数。这些函数通常包括设备的初始化、设备的释放和设备的控制等操作。 6. 注册驱动程序:在驱动程序的初始化函数中,你需要调用适当的函数来注册驱动程序。这个函数通常是“register_chrdev”函数。 7. 编译和加载驱动程序:将驱动程序的源文件编译成模块的形式,然后使用“insmod”命令将其加载到内核中。 8. 测试驱动程序:使用“cat”命令或其他读取文件的方式来测试驱动程序。 编写一个Linux字符设备驱动需要掌握Linux内核编程的知识和相关的驱动开发技术。同时,还需要理解设备驱动的工作原理和相关的API。要确保驱动程序的正确性和稳定性,还需要进行充分的测试。 ### 回答3: 编写一个Linux字符设备驱动需要涵盖以下几个步骤: 1. 头文件和模块初始化: 首先需要创建一个头文件,并包含必要的Linux内核头文件,定义驱动程序所需的宏、结构体和函数。在模块初始化函数中,需要完成设备的注册和申请主设备号。 2. 设备的文件变量和方法: 在驱动程序中,需要定义设备特定的结构体来保存设备的状态和数据。此外,需要定义open、release、read、write等方法来处理设备文件的打开、释放和读写操作。 3. 设备的字符设备自动创建: 在驱动程序中,可以通过cdev结构体和相应函数来自动创建一个字符设备,并将该设备与上述方法进行关联。 4. 主设备号的分配与释放: 在模块初始化函数中,需要通过调用register_chrdev_region函数来分配一个独特的主设备号,并在模块注销函数中调用unregister_chrdev_region函数来释放占用的主设备号。 5. 设备文件的创建与删除: 可以通过调用cdev_add函数将设备与相应的字符设备驱动关联起来,从而创建设备文件。在驱动程序中,也可以调用cdev_del函数来删除设备文件。 6. 内核与用户空间的数据传输: 可以通过copy_to_user和copy_from_user等函数在内核和用户空间之间传输数据。 7. 用于驱动模块的Makefile文件: 创建Makefile文件,用于编译和构建驱动模块,并链接所需的头文件和库文件。 通过以上步骤,就可以编写一个基本的Linux字符设备驱动。编写完成后,可以使用gcc编译驱动程序源代码,通过insmod命令将其插入内核并加载,然后使用mknod命令创建设备文件,在用户空间中通过open、read、write等系统调用进行设备的操作和数据传输。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值