深入探讨Linux驱动开发:字符设备驱动开发与测试

2023-05-16

文章目录

  • 一、字符设备驱动介绍
    • 1.设备驱动介绍
  • 二、设备号
    • 1.设备号介绍
    • 2.分配与释放设备编号
      • ①dev_t类型
      • ②静态分配设备号
      • ③动态分配设备号
      • ④释放主次设备号
      • ⑤手动创建设备节点
      • ⑥自动创建设备节点
      • ⑦删除设备节点
  • 三、字符设备注册
    • 1.cdev结构体
    • 2.注册cdev到内核
  • 三、字符设备驱动开发与测试
    • 1.字符设备驱动
    • 2.字符设备测试程序
    • 3.Makefile
    • 4.驱动测试
  • 总结


一、字符设备驱动介绍

1.设备驱动介绍

Linux内核将设备按照访问特性一般分为三类:字符设备、块设备、网络设备:
在这里插入图片描述
详细的学习字符设备驱动框架之前,我们先来简单的了解一下Linux下的应用程序是如何调用驱动程序的,Linux应用程序对驱动的调用如图如所示:
在这里插入图片描述
应用程序运行在用户空间,Linux驱动属于内核的一部分,运行于内核空间,要是用户想要实现对内核的操作,那么他必须使用系统调用来实现从用户空间到内核空间的操作。

二、设备号

1.设备号介绍

设备号(device number)是用于唯一标识Linux系统中的一个设备的数字,包括主设备号(major number)和次设备号(minor number)。主设备号用于标识设备驱动程序,次设备号用于标识同一驱动程序下的不同设备实例。

设备节点(device node)是用于在文件系统中表示设备的特殊文件。设备节点通常位于/dev目录下,其文件名由设备类型和设备号组成,通过设备节点,应用程序可以与设备进行交互,如打开、读取、写入等操作。

在/dev路径下可以通过ls -l查看设备节点:
在这里插入图片描述
其中可以举例:

crw-rw----  1 root        video    29,   0 416 10:30 fb0
  • c: 表示这是一个字符设备文件。
  • rw-rw----: 文件权限,代表文件所有者和所属组有读写权限,其他用户没有任何权限。
  • 1: 确定这个设备文件的硬链接数。
  • root: 文件所有者为root。
  • video: 文件所属video组。
  • 29, 0: 设备号,代表这是主设备号为 29,次设备号为 0 的设备。
  • 4月 16 10:30: 文件的最后修改时间。
  • fb0: 设备文件名,代表此文件是第一个帧缓冲设备的字符设备文件。

2.分配与释放设备编号

①dev_t类型

在内核编程中,dev_t 是一个在 Linux 内核中用来表示设备号的数据类型。它实际上是一个 32 位整数,其中高 12 位是主设备号,低 20 位是次设备号。主设备号用于指示设备类型,而次设备号用于区分同一类型下的不同设备。在编码时,我们不应该管哪些位是主设备号,哪些位是次设备号。而是应当利用在<linux/kdev_t.h>中的一套宏定义来获取一个dev_t的主、次编号:

#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1) 
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
  • 宏 MKDEV 用于将给定的主设备号和次设备号的值组合成 dev_t 类型的设备号。
  • 宏 MINORBITS 表示次设备号位数,一共是 20 位。
  • 宏 MINORMASK 表示次设备号掩码。
  • 宏 MAJOR 用于从 dev_t 中获取主设备号,将 dev_t 右移 20 位即可。
  • 宏 MINOR 用于从 dev_t 中获取次设备号,取 dev_t 的低 20 位的值即可。

②静态分配设备号

静态分配设备号是指在代码中手动指定设备号的方法。开发者在注册字符设备时,需要指定主设备号和次设备号,如果开发者已经确定了设备号,可以使用静态分配设备号的方式。当然对于设备号我们自己随机选择的话,会跟Linux内核其他的驱动冲突,这时我们可以先查看当前系统已经使用了哪些主设备号,然后我们选择一个没有使用的作为我们新的驱动使用。Linux系统中正在使用的主设备号会保存在/proc/devices文件中:
在这里插入图片描述
上面列出来的是Linux内核里所有驱动使用的主设备号,我们在编写设备时就可以选定一个未用的主设备号。

静态分配设备号需要开发者手动指定设备号,保证每个设备号都唯一。在代码中通过调用 register_chrdev_region() 函数进行注册。这个函数的第一个参数是主设备号,第二个参数是设备数量,第三个参数是设备名,在/proc/devices和sysfs中。 register_chrdev_region() 函数成功返回0,失败返回一个负数:

dev_t       devno;
int         result;
int         major = 251;
​
devno = MKDEV(major, 0);
​
result = register_chrdev_region(devno, 4, "chrdev");//静态的申请和注册设备号
if(result < 0)
{
    printk(KERN_ERR "chrdev can't use major %d\n", major);
    return -ENODEV;
}

当驱动的主、次设备号申请成功后,/proc/devices里将会出现该设备,但是/dev路径下并不会创建该设备文件,需要我们去创建设备节点。

静态分配设备号的优点是使用方便,可以确保设备号的唯一性。缺点是如果设备数量不够,就会浪费设备号资源,而如果设备数量过多,又会导致设备号的耗尽。因此,在静态分配设备号时,需要谨慎考虑设备数量,以及设备号的合理使用。

③动态分配设备号

我们通过静态分配设备号很容易带来冲突问题,如果以后我们Linux内核升级需要使能其他的设备驱动时,如果需要的驱动和我们所用的主设备号冲突,那么我们就不得不对这个主设备号进行调整,如果产品已经部署了,这种召回升级是十分致命的,所以我们写驱动的之后应该由Linux内核根据当前主设备号使用情况动态分配一个未用的给我们的设备驱动,这样就不会发生冲突的情况了。

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

参数说明:

  • dev:用于返回分配的设备号。
  • firstminor:要分配的第一个次设备号,它常常是0。
  • count:要分配的设备号数量。
  • name:设备名字。

动态分配的缺点是你无法提前创建设备节点,因为分配给你的主设备号会发生变化,对于驱动的正常使用这不是问题,但是一旦编号分配了,只能通过 查看 /proc/devices文件才能知道它的值,然后再创建设备节点。

④释放主次设备号

注销字符设备后要释放设备号,设备号释放函数如下:

void unregister_chrdev_region(dev_t from, unsigned count)

使用该函数可以将之前通过 register_chrdev_region() 函数注册的设备号从系统中取消,以便于其他设备可以使用这些设备号。通常在模块的卸载函数xxx__exit中调用该函数,释放模块使用的设备号资源。

⑤手动创建设备节点

如果我们需要创建该文件,则需要使用mknod命令创建。当然我们也可以在驱动里调用相应的函数,来通知应用程序空间自动创建该设备文件。

mknod /dev/chrdevbase c 251 0
  • “mknod”是创建节点命令
  • “/dev/chrdevbase”是要创建的节点文件
  • “c”表示这是个字符设备
  • “251”是设备的主设备号
  • “0”是设备的次设备号

例如 chrdevbaseAPP 想要读写 chrdevbase 设备,直接对/dev/chrdevbase进行读写操作即可。相当于/dev/chrdevbase这个文件是 chrdevbase 设备在用户空间中的实现。

⑥自动创建设备节点

在Linux中,可以通过udev(userspace dev)来实现自动创建设备节点,当然前提条件是用户空间移植了udev。udev是Linux系统中的一个动态设备管理机制,它负责在系统启动时检测硬件设备的插拔并创建相应的设备节点。

当udev检测到新设备插入时,它会执行一系列规则(rules)来确定设备应该如何创建,这些规则定义了设备节点的名称、权限等信息,并使用mknod系统调用来创建设备节点。这些规则通常存储在/etc/udev/rules.d/目录中的文件中,每个文件包含一组规则。

class_create():在调用device_create()前要先用class_create()创建一个类(设备依附的类)。类这个概念在Linux中被抽象成一种设备的集合。类在/sys/class目录中。class_create()这个函数使用非常简单,在内核中是一个宏定义,定义在/include/linux/device.h中:

#define class_create(owner, name)       \
({                      \
    static struct lock_class_key __key; \
    __class_create(owner, name, &__key);    \
})

此函数有两个参数:

  • owner:struct module结构体类型的指针,一般赋值为THIS_MODULE。
  • name:char类型的指针,类名。

device_create():用于创建设备节点,其定义在/include/linux/device.h中,函数原型如下:

struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)
{
    va_list vargs;
    struct device *dev;
    
    va_start(vargs, fmt);
    dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
    va_end(vargs);
return dev;
}
  • class:该设备依附的类
  • parent:父设备
  • devt:设备号(此处的设备号为主次设备号)
  • drvdata:私有数据
  • fmt:设备名

device_create能自动创建设备文件是依赖于udev这个应用程序,使用udev后,在/dev目录下就只包含系统中真正存在的设备。

⑦删除设备节点

device_destroy()函数用于销毁一个设备节点,并释放相关的资源。定义在/include/linux/device.h中:

void device_destroy(struct class *class, dev_t devt)
{
    struct device *dev;

    dev = class_find_device(class, NULL, &devt, exact_match);
    if (dev)
        device_destroy(dev);
}

参数说明:

  • class:指向struct class类型的指针,表示设备节点所属的设备类。
  • devt:表示设备号。

函数class_destroy()用于从Linux内核系统中删除设备。此函数执行的效果是删除函数class_create()在/sys/class目录下创建的节点对应的文件夹,定义在/include/linux/device.h中:

void class_destroy(struct class *cls)
{
    if ((cls == NULL) || (IS_ERR(cls)))
    return;
 
    class_unregister(cls);
}

参数说明:

  • cls:struct class结构体类型的变量,代表通过class_create创建的设备的节点。

自动创建节点示例:

static struct class *my_class;
static struct device *my_device;
static int __init my_driver_init(void)
{
    /* 创建设备类 */
    my_class = class_create(THIS_MODULE, "my_class");
    /* 创建设备节点 */
    my_device = device_create(my_class, NULL, MKDEV(MAJOR_NUM, MINOR_NUM), NULL, "my_device");
    return 0;
}
static void __exit my_driver_exit(void)
{
    /* 销毁设备节点 */
    device_destroy(my_class, MKDEV(MAJOR_NUM, MINOR_NUM));
    /* 销毁设备类 */
    class_destroy(my_class);
}
module_init(my_driver_init);
module_exit(my_driver_exit);

三、字符设备注册

1.cdev结构体

内核在内部使用类型struct cdev的结构体来代表字符设备。在内核调用你的设备操作之前,你必须分配一个这样的结构体并注册给linux内核,在这个结构体里有对于这个设备进行操作的函数,具体定义在file_operation结构体中。该结构体定义在include/linux/cdev.h文件中:

struct cdev {
    struct kobject kobj;
    struct module *owner;
    const struct file_operations *ops;
    struct list_head list;
    dev_t dev;
    unsigned int count;
};
  • kobj:kobject结构体,用于表示cdev对象在内核中的内存管理等方面的信息。
  • owner:指向内核模块的指针,表示注册这个cdev结构体的内核模块。
  • ops:指向字符设备驱动的file_operations结构体。
  • list:链表节点,用于将多个cdev结构体链接在一起。
  • dev:字符设备的设备号。
  • count:用于表示同一设备实例的数量,通常为1。

在内核编程中,我们可以使用两种方法获取结构体。
一是运行时想获取一个独立的cdev结构:

struct cdev *chrtest;
if(NULL == chrtest = cdev_alloc())
{
    printk(KERN_ERR "S3C %s driver can't alloc for the cdev.\n", DEV_NAME);
    unregister_chrdev_region_region(devno, dev_vount);
    return -ENOMEM;
}
​
chrtest->ops = &my_fops;

但是,偶尔你会想将cdev结构体嵌入一个你自己的设备特定结构。这样的情况下你需要初始化已经分配的结构体:

cdev_init(struct cdev *dev, struct file_operations *fops);

struct cdev有一个拥有者成员,应当设置为THIS_MODULE,一旦cdev结构建立,最后的步骤就是告诉内核。

2.注册cdev到内核

分配到cdev结构体后,我们将它初始化,并将对该设备驱动所支持的系统调用函数存放在file_operations结构体添加进来。然后用途cdev_add函数将它注册到内核,从而完成一个完整的Linux设备注册过程。

cdev_add函数原型如下:

int cdev_add(struct cdev *p, dev_t dev, unsigned int count);

其中,参数含义如下:

  • p:指向要添加的字符设备对象的 cdev 结构体指针。
  • dev:指定要添加的设备号。
  • count:指定添加的设备号的数量。

字符设备驱动cdev的分配和注册示例:

static struct file_operations chrtest_fods =
{
    .owner = THIS_MODULE,
    .open = chrtest_open,
    .release = chrtest_release,
    .unlocked_ioctl = chrtest_ioctl,
};struct cdev *chrtest_cdev;
if(NULL == (led_cdev = cdev_alloc))
{
    printk(KERN_ERR "S3C %s driver can't alloc for the cdev.\n", DEV_NAME);
    unregister_chdev_region(devno, dev_count);
    return -ENOMEM;   
}
​
led_cdev->owner = THIS_MODULE;
cdev_init(led_cdev, &led_fops);
​
result = cdev_add(led_cdev, devno, dev_count);if(0 != result)
{
    printk(KERN_INFO "S3C %s drive can't register cdev:result = %d\n", DEV_NAME, result);
    goto ERROR;
}

三、字符设备驱动开发与测试

1.字符设备驱动

/*********************************************************************************
 *      Copyright:  (C) 2023 Deng Yonghao<dengyonghao2001@163.com>
 *                  All rights reserved.
 *
 *       Filename:  chrdevbase.c
 *    Description:  This file is character device base
 *                 
 *        Version:  1.0.0(2023年04月10日)
 *         Author:  Deng Yonghao <dengyonghao2001@163.com>
 *      ChangeLog:  1, Release initial version on "2023年04月10日 11时30分10秒"
 *                 
 ********************************************************************************/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>

/*确定主设备号*/
//#define	DEV_MAJOR	79
#ifndef	DEV_MAJOR
#define	DEV_MAJOR 0
#endif
int dev_major = DEV_MAJOR;//主设备号					


#define	DEV_NAME	"chrdev"//设备名称

static struct cdev *chrtest_cdev;//cdev结构体

//static struct class *chrdev_class; //定义一个class用于自动创建类

static char kernel_buf[1024];


#define	MIN(a,b) (a < b ? a : b)

/*实现对应的open/read/write等函数,填入file_operations结构体 */
static ssize_t chrtest_drv_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	int err;
	printk("%s %s line %d\n", __FILE__,  __FUNCTION__, __LINE__);
	err = copy_to_user(buf, kernel_buf, MIN(1024, size));//内核空间的数据到用户空间的复制													 
	return MIN(1024, size);
}

static ssize_t chrtest_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	int err;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_from_user(kernel_buf, buf, MIN(1024, size));//将用户空间的buf复制到内核空间缓冲区kernel_buf中,因为用户空间内存不能直接访问内核空间的内存
	return MIN(1024, size);
}

static int chrtest_drv_open(struct inode *node, struct file *file)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

static int chrtest_drv_close(struct inode *node, struct file *file)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

/*定义自己的file_operations结构体*/
static struct file_operations chrtest_fops = {
	.owner		= THIS_MODULE,
	.open		= chrtest_drv_open,
	.read		= chrtest_drv_read,
	.write		= chrtest_drv_write,
	.release	= chrtest_drv_close,
};

/*把file_operations结构体告诉内核:register_chrdev*/
/*注册驱动函数:写入口函数,安装驱动程序时就会调用这个入口函数*/
static int __init chrdev_init(void)
{
	int result;
	dev_t devno;/*定义一个dev_t变量来表示设备号*/

	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	/*字符设备驱动注册流程第二步:分配主次设备号,这里即支持静态指定,也支持动态申请*/
	if(0 != dev_major)
	{
		devno = MKDEV(dev_major, 0);
		result = register_chrdev_region(devno, 1, DEV_NAME);//"/proc/devices/chrdev"
	}
	else
	{
		result = alloc_chrdev_region(&devno, 0, 1, DEV_NAME);
		dev_major = MAJOR(devno);
	}

	/*自动分配设备号失败*/
	if(result < 0)
	{
		printk(KERN_ERR " %s driver can't use major %d\n", DEV_NAME, dev_major);
		return -ENODEV;
	}
	printk(KERN_DEBUG " %s driver can't use major %d\n", DEV_NAME, dev_major);
	
	/*字符设备驱动注册流程第三步:分配cdev结构体,我们这里使用动态申请的方式*/
	if(NULL == (chrtest_cdev = cdev_alloc()))
	{
		printk(KERN_ERR " %s driver can't alloc for the cdev\n", DEV_NAME);
		unregister_chrdev_region(devno, 1);
		return -ENOMEM;
	}

	/*字符设备驱动注册流程第四步:分配cdev结构体,绑定主次设备号、fops到cdev结构体中,并注册给Linux内核*/
	chrtest_cdev->owner = THIS_MODULE;//.owner表示谁拥有你这个驱动程序
	cdev_init(chrtest_cdev, &chrtest_fops);//初始化设备
	result = cdev_add(chrtest_cdev, devno, 1);
	if(0 != result)
	{
		printk(KERN_INFO "%s driver can't register cdev:result=%d\n", DEV_NAME, result);
		goto ERROR;
	}
	printk(KERN_INFO "%s driver can register cdev:result=%d\n", DEV_NAME, result);

	/*自动创建设备类型与/dev设备节点*/
	#if 0
	chrdev_class = class_create(THIS_MODULE, DEV_NAME);//创建设备类型 sys/class/chrdevbase
	if(IS_ERR(chrdev_class))
	{
		result = PTR_ERR(chrdev_class);
		goto ERROR;
	}
	device_create(chrdev_class, NULL, MKDEV(dev_major, 0), NULL, DEV_NAME);// /dev/chrdev 注册这个设备节点
	#endif


	return 0;


ERROR:
	printk(KERN_ERR" %s driver installed failure.\n", DEV_NAME);
    cdev_del(chrtest_cdev);
    unregister_chrdev_region(devno, 1);
    return result;

}

/* 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数*/
static void __exit chrdev_exit(void)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	/*注销设备类型与/dev设备节点*/
	#if 0
	device_destroy(chrdev_class, NKDEV(dev_major, 0));//注销此设备节点
	class_destroy(chrdev_class);//删除这个设备类型
	#endif

	cdev_del(chrtest_cdev);//注销字符设备
	unregister_chrdev_region(MKDEV(dev_major, 0), 1);//释放设备号
													 

	printk(KERN_ERR"%s driver version 1.0.0 removed!\n", DEV_NAME);
	return;
}

module_init(chrdev_init);
module_exit(chrdev_exit);

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("DengYonghao <dengyonghao2001@163.com>");

2.字符设备测试程序

/*********************************************************************************
 *      Copyright:  (C) 2023 Deng Yonghao<dengyonghao2001@163.com>
 *                  All rights reserved.
 *
 *       Filename:  chrdevbaseAPP.c
 *    Description:  This file character driver test APP.
 *                 
 *        Version:  1.0.0(2023年04月12日)
 *         Author:  Deng Yonghao <dengyonghao2001@163.com>
 *      ChangeLog:  1, Release initial version on "2023年04月12日 09时58分17秒"
 *                 
 ********************************************************************************/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main (int argc, char **argv)
{
	int				fd;
	char			buf[1024];
	int				len;

	/*1.判断参数*/
	if(argc < 2)
	{
		printf("Usage: %s -w <string>\n", argv[0]);
		printf("	   %s -r\n", argv[0]);
		return -1;
	}

	/*2.打开文件(设备节点)*/
	fd = open("/dev/chrdev", O_RDWR);
	if(fd == -1)
	{
		printf("can not open file /dev/hello\n");
		return -1;
	}

	/*3.写文件或者读文件*/
	if((0 == strcmp(argv[1], "-w")) && (argc == 3))
	{
		len = strlen(argv[2]) + 1;
		len = len < 1024 ? len :1024;
		write(fd, argv[2], len);
	}
	else if((0 == strcmp(argv[1], "-r")) && (argc == 2))
	{
		len = read(fd, buf, 1024);
		buf[1023] = '\0';
		printf("APP read : %s\n", buf);
	}
	else
	{
		printf("Usage: %s -w <string>\n", argv[0]);
		printf("	   %s -r\n", argv[0]);
		return -1;
	}

	close(fd);

	return 0;
} 

3.Makefile

KERNEL_DIR := /home/dengyonghao/project/IGKdriver/imx6ull/bsp/kernel/linux-imx
COROSS_COMPILE := /opt/gcc-arm-10.3-2021.07/bin/arm-none-linux-gnueabihf-
TFTP_DIR := ../../../IGKBoard/tftpboot
PWD := $(shell pwd)
obj-m := chrdevbase.o

modules:
    $(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules
    $(COROSS_COMPILE)gcc chrdevbaseApp.c -o chrdevbaseApp
    @make clear
    cp chrdevbase.ko chrdevbaseApp $(TFTP_DIR) -f

clear:
    @rm -f *.o *.cmd *.mod *.mod.c
    @rm -rf *~ core .depend .tmp_versions Module.symvers modules.order -f
    @rm -f *ko.cmd .*.o.cmd .*.o.d
    @rm -f *.unsigned

clean:
    @rm -f *.ko

4.驱动测试

首先make编译好我们所需要的字符设备驱动程序和字符设备测试程序:
在这里插入图片描述
通过tftp下载到IGKBoard开发板中,并为测试程序赋予可执行权限:
在这里插入图片描述
通过insmod加载字符驱动模块到内核中去,并查看注册的字符设备:
在这里插入图片描述
在这里插入图片描述
然后运行测试程序可见提示信息,由于我们还没有设备节点,这里我们手动添加:
在这里插入图片描述
当然我们在代码中也写了自动创建的方式,可以通过修改代码实现自动创建设备节点。

最后再次运行测试程序:
在这里插入图片描述
在内核中,字符设备文件会被映射到设备号,而设备号会与字符设备驱动程序中的 file_operations 结构体关联,其中的write 函数用于向字符设备写入数据 read 函数用于从字符设备读取数据,也就是我们的-w和-r参数分别向字符设备写入字符串“hello”和读取字符设备中的数据,可见测试成功。

总结

字符设备驱动是 Linux 中驱动开发的一类,用于控制字符设备的行为。在字符设备驱动开发过程中,需要分配和释放设备号,使用 cdev 结构体注册字符设备到内核,提供 file_operations 结构体中的方法来对设备进行操作。

在设备号的分配和释放方面,有多种方式,包括静态分配和动态分配。使用动态分配时,需要使用 dev_t 类型来表示设备号,并调用相关函数进行分配和释放。同时,还可以手动创建设备节点或者使用 udev 自动创建设备节点。

在字符设备注册方面,需要使用 cdev 结构体,将设备号和 file_operations 结构体中的方法与其绑定,再通过调用 cdev_add() 函数将其注册到内核中。

最后,为了测试字符设备驱动,我编写了测试程序。在测试程序中,使用 open() 函数打开设备节点,使用 read() 和 write() 函数进行读写操作。通过测试程序,我们可以验证字符设备驱动的功能是否正常。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

深入探讨Linux驱动开发:字符设备驱动开发与测试 的相关文章

  • 【STM32CubeMX】使用STM32F103C8T6输出PWM波形实现呼吸灯

    STM32CubeMX 使用STM32F103C8T6输出PWM波形实现呼吸灯 一 关于PWM二 Cube MX创建工程三 修改代码四 效果展示五 总结六 参考与代码下载 一 关于PWM 1 关于PWM 含义 PWM xff08 Pulse
  • 【STM32】基于HAL库使用最小系统板移植uCOS

    STM32 基于HAL库使用最小系统板移植uCOS 一 CubeMX建立工程模板二 下载uC OS III源码三 移植准备四 开始移植1 将uCOS文件添加到项目2 为bsp c和bsp h添加代码3 修改main c文件代码4 修改其余文
  • Ubuntu20.04打不开终端

    Ubuntu20 04打不开终端 下载xterm输入gnome terminal显示 xff1a Error constructing proxy for org gnome Terminal org gnome Terminal Fact
  • 串口DMA发送接收

    目录 一 DMA的基本介绍 1 DMA定义 2 原理 1 请求 2 响应 3 传输 4 结束 3 传送方式 1 停止CPU访问内存 2 周期挪用 3 DMA与CPU交替访问内存 4 DMA中断 二 新建cubemx项目 1 选择STM32F
  • Time Limit Exceeded的原因

    Time Limit Exceeded的原因及避免方法 荷叶田田 CSDN博客
  • GStreamer学习三(延迟)

    1 延迟 延迟 xff08 即latency xff09 是在时间戳0处捕获的样本到达接收器所花费的时间 此时间是根据流水线的时钟测量的 对于只有包含与时钟同步的 接收器 元素的流水线 xff0c latency 始终为0 xff0c 因为
  • 第一届ACC全国高校联赛

    y 竞赛 AcWing 面向全国高校同学的高校联赛 https www acwing com activity content 1173 一 1 暴力 include lt bits stdc 43 43 h gt using namesp
  • JDBC连接数据库

    个人简介 x1f496 作者简介 xff1a 大家好 xff01 我是yukki 个人主页 xff1a yukki x1f4c2 喜欢 xff1a x1f308 点赞 x1f308 收藏 xff01 更新Java x1f308 python
  • idea 文件夹右键新建没有Class

    个人简介 作者简介 xff1a 大家好 xff01 我是yukki 个人主页 xff1a yukki 喜欢 xff1a x1f308 点赞 x1f308 收藏 x1f308 一键三连 xff01 共勉 一 问题发现 xff1a 没法创建ja
  • 《关于我找了好久的bug,却没找出来的,又不小心解决了的事》

    个人简介 作者简介 xff1a 大家好 xff01 我是yukki 个人主页 xff1a yukki 喜欢 xff1a x1f308 点赞 x1f308 收藏 x1f308 一键三连 xff01 共勉 问题 xff1a 这是一个Spring
  • 某某科技实习日志

    个人简介 作者简介 xff1a 大家好 xff01 我是yukki 个人主页 xff1a yukki 喜欢 xff1a x1f308 点赞 x1f308 收藏 x1f308 一键三连 xff01 共勉 时间 2023年 4月 11日 今日任
  • XXXX实习日志

    个人简介 作者简介 xff1a 大家好 xff01 我是yukki 个人主页 xff1a yukki 喜欢 xff1a x1f308 点赞 x1f308 收藏 x1f308 一键三连 xff01 共勉 时间 2023年 4月 12日 今日任
  • STM32——中断优先级分组

    一 SCB AIRCR寄存器 首先 xff0c 对STM32中断进行分组 xff0c 0 4 同时 xff0c 每个中断设置一个抢占优先级和一个响应优先级 1 高抢占可以打断正在执行的低抢占 2 抢占相等 xff0c 高响应不能打断低响应
  • Keil报错总结(1)

    一 newline expected extra characters found c323 头文件定义有问题 ifndef define endif 他们后面的文件名与文件名不一致 xff0c 或者大小写不一致 xff0c 文件名尽量避免
  • GPIO实验

    一 GPIO简介 GPIO xff08 General purpose input output xff09 即通用型输入输出 xff0c GPIO可以控制连接在其之上的引脚实现信号的输入和输出 芯片的引脚与外部设备相连 xff0c 从而实
  • Exynos_4412——中断处理(中断学习结尾篇)

    目录 一 ARM的异常处理机制 1 1异常概念 1 2异常处理机制 1 3ARM异常源 1 4异常模式 1 5ARM异常响应 1 6异常向量表 1 7异常返回 1 8IRQ异常举例 二 工程模板代码结构 三 中断处理框架搭建 四 中断处理程
  • ROS中控制小乌龟移动(2种方法)

    操作系统 xff1a Ubuntu16 04 ROS版本 xff1a Kinetic 目录 方法一 xff1a 用键盘控制小乌龟移动1 启动ROS Master2 打开小乌龟3 键盘控制小乌龟 方法二 xff1a 通过命令发布话题控制小乌龟
  • QT/C++——对话框

    一 标准对话框 include 34 widget h 34 include lt QVBoxLayout gt include lt QHBoxLayout gt Widget Widget QWidget parent QWidget
  • QT/C++——主窗口和事件处理

    一 主窗口 上面就是一个主窗口 xff0c 主窗口中的每一个都是Action 这次新建工程要选择mainwindow ifndef MAINWINDOW H define MAINWINDOW H include lt QMainWindo
  • QT/C++——网络编程

    目录 一 基础知识复习 二 UDP 客户端 xff1a 服务器 xff1a 三 TCP 服务器 xff1a 客户端 xff1a 四 小项目 客户端 xff1a 服务器 xff1a 一 基础知识复习 这部分内容前面讲的比较详细 xff0c 现

随机推荐

  • Linux驱动开发——高级I/O操作(一)

    一个设备除了能通过读写操作来收发数据或返回 保存数据 xff0c 还应该有很多其他的操作 比如一个串口设备还应该具备波特率获取和设置 帧格式获取和设置的操作 一个LED设备甚至不应该有读写操作 xff0c 而应该具备点灯和灭灯的操作 硬件设
  • ubuntu22.04安装与配置

    目录 一 环境及下载 iso下载 VM配置 二 虚拟机与环境配置 虚拟机开始后的配置 一些工具配置 参考 xff1a VMware Workstation Pro 文档 一 环境及下载 iso下载 Download Ubuntu Deskt
  • Linux——互斥与同步

    目录 一种典型的竞态 内核中的并发 中断屏蔽 原子变量 自旋锁 读写锁 顺序锁 一种典型的竞态 假设整型变量i是驱动代码中的一个个全局变量 xff0c 在驱动的某个例程中执行了i 43 43 操作 xff0c 而在中断服务程序中也执行了i
  • 基于max30102的物联网病房监测系统(传感驱动和数据处理)

    目录 一 实物展示 二 主体介绍 三 MAX30102的驱动 四 MAX30102的数据处理 奋斗一个星期 xff0c 每个引脚都是扒皮焊接然后再把皮包回去的 这几天吸的垃圾气体感觉要少活两年 一 实物展示 这次吸取上次教训 xff0c 把
  • 基于max30102的物联网病房监测系统(中断处理和主题逻辑)

    目录 五 中断处理 六 主体框架 对采集数据的初始化 核心功能的实现 烟雾 通信帧格式 wifi接收数据的处理 OLED显示 五 中断处理 void SysTick Handler void TimingDelay Decrement vo
  • 无人机4G数传方案(合宙cat1模块)

    一 合宙Cat1简介 YED C724 核心板是由银尔达 xff08 yinerda xff09 基于合宙 Air724 模组推出的低功耗 xff0c 超小体积 xff0c 高性能嵌入式 4G Cat1 核心版 xff0c 标准的 2 54
  • C++学习ros2话题机制(发布与订阅)

    C 43 43 学习ros2 一 创建文件和文件夹1 结构2 创建工作空间和工作包3 直接创建node cpp文件 二 编写节点文件 xff08 发布订阅 xff09 1 node1 cpp xff08 发布 xff09 2 CMakeLi
  • AttributeError: module ‘keras.backend’ has no attribute ‘set_image_dim_ordering’

    问题 原始代码如下 xff1a keras span class token punctuation span backend span class token punctuation span set image dim ordering
  • openmv识别红色物体并返回坐标给stm32单片机,通过pid控制舵机云台

    本人搜索了有关于舵机云台pid控制的代码 xff0c 但是都没有搜到想要的结果 xff0c 现在自己写出来了代码 xff0c 所以就将自己写的代码分享出来 xff0c 和大家一起学习进步 1 openmv识别红色物体 43 返回中心坐标的的
  • vs解决报错:C++ qualified name is not allowed(E0283)

    我们看 把在GCC下编译过关的c 43 43 程序放在vs下却不能过 仅给出部分代码 其他以此类推 先不要慌着改 看下详细信息 看上去都是语法错误 但这真的没任何语法错误啊 百度上查找下 报错信息都不一样 别人是类里面多加限定符 我这是正常
  • Linux下的man命令

    目录 一 man是什么 xff1f 二 man命令的使用1 通过man man查看man手册2 通过man来进行查询 四 总结 一 man是什么 xff1f man所代表的的是英文单词manual xff0c 也就是帮助手册的意思 xff0
  • IO多路复用之select

    目录 一 IO多路复用二 select函数三 select实现socket服务器 xff08 1 xff09 流程图 xff1a xff08 2 xff09 代码讲解 xff1a 四 总结代码示例 xff1a 一 IO多路复用 IO多路复用
  • curl 和 wget 命令下载

    curl 和 wget 命令下载 一 wget下载1 wget介绍2 wget下载方法 二 curl下载1 curl介绍2 curl下载方法 三 wget下载sqlite实例总结 一 wget下载 1 wget介绍 wget 是一个从网络上
  • Linux下载安装和使用SQLite

    Linux安装SQLite 一 SQLite下载二 SQLite安装三 SQLite的使用1 解决无法直接用sqlit3命令2 解决无法编译的问题 总结 一 SQLite下载 首先 xff0c 前往SQLite官网下载页面找到包含confi
  • 解决:‘config.status: error: Something went wrong bootstrapping makefile fragments......’问题

    解决 xff1a config status error Something went wrong bootstrapping makefile fragments 问题 一 问题二 解决方法 一 问题 首先我们来看安装sqlite时报的这
  • TFTP服务器搭建与使用

    文章目录 一 TFTP协议二 TFTP服务器搭建1 安装TFTP服务器2 创建TFTP服务文件夹3 配置tftp文件4 配置tftpd hpa文件 三 TFTP服务器使用 一 TFTP协议 TFTP xff08 Trivial File T
  • 深入探讨Linux驱动开发:Linux设备树

    文章目录 一 设备树介绍二 设备树框架1 设备树框架2 节点基本格式3 节点部分属性简介 总结 一 设备树介绍 设备树 xff08 Device Tree xff0c 简称 DT xff09 是一种在嵌入式系统中描述硬件设备的一种数据结构和
  • 深入探讨Linux驱动开发:驱动介绍与hello驱动实例

    文章目录 前言一 Linux驱动介绍1 用户态和内核态2 内核功能介绍3 驱动程序介绍 二 驱动程序分类与注意事项1 驱动程序分类2 内核驱动开发注意事项 三 hello驱动开发1 驱动模块2 模块加载和卸载函数3 编写hello模块4 M
  • ROS中使用乐视 奥比中光(Astra Pro)深度相机显示彩色和深度图像

    环境 UbuntuROS Kinect or Melodic 奥比中光ROS驱动包安装地址 xff1a https github com orbbec ros astra camera 1 安装ROS 2 安装依赖 span class t
  • 深入探讨Linux驱动开发:字符设备驱动开发与测试

    文章目录 一 字符设备驱动介绍1 设备驱动介绍 二 设备号1 设备号介绍2 分配与释放设备编号 dev t类型 静态分配设备号 动态分配设备号 释放主次设备号 手动创建设备节点 自动创建设备节点 删除设备节点 三 字符设备注册1 cdev结