嵌入式linux-字符设备驱动开发流程
·
字符设备驱动开发流程
在嵌入式linux开发中,主要包括字符设备开发、块设备开发以及网络设备开发三种。字符设备开发是最常见的驱动开发,包括我们常见的gpio、led、SPI、I2C等,都是字符设备开发的一种。
一、常用概念
1、Linux设备号
为了方便系统管理驱动以及设备,linux中每个设备都有设备号,分为主设备号、次设备号两部分。其中,主设备号代表某个具体的驱动,次设备号表示使用这个驱动的某个具体的设备。
linux提供了一个名为dev_t的数据结构表示设备号,其是一个32位的数据类型,其中高12位表示主设备号、低20位代表次设备号。
- 设备号的分配:
设备号可以采用静态分配或者动态分配两种,其中前者是我们手动进行分配,后者则是系统内核自动给我们分配,一般推荐动态分配。
用于动态设备设备号的函数如下:
int alloc_chrdev_region(dev_t* dev,unsigned baseminor,unsigned count, const char* name)
//dev:用于保存我们申请的设备号
//baseminor:次设备号的起始地址,该函数可以申请很多主设备号一样、次设备号不同的设备号
//count:设备号申请的数量
//name:设备名称
//如果已经有设备号了,使用该函数来注册设备号即可
int register_chrdev_region(dev_t from,unsigned count,const char* name);
//销毁字符设备后要释放设备号,释放函数如下:
void unregister_chrdev_region(dev_t from,unsigned count);
二、字符设备驱动开发框架
linux驱动开发并不像裸机开发那样,驱动程序可以按照我们的意愿各种变换,其拥有一套规定的框架,我们只需要按照这套框架来编写我们的驱动程序即可。
2.1 字符驱动的加载与卸载
驱动的加载有两种形式:
- 一种为在我们内核编译的时候,就直接将驱动编译进入内核,这样驱动就会一直存在于内核中,比如我们想要加载V4L2驱动,一般在内核图形化配置的时候,就会直接选中加载入内核中;
- 另一种为驱动模块化(linux下扩展名为.ko),这样我们使用“modprobe”或者“insmod”命令,就可以将驱动模块加载进内核。在调试过程中,一般我们采用模块化驱动,这样就可以只编译驱动即可,而不需要编译整个内核了。
驱动的加载与卸载操作需要对用两种操作函数,模块加载和卸载注册函数,如下:
module_init(xxx_init); //加载
module_exit(xxx_exit);//卸载
2.2 字符设备结构cdev
在linux下使用cdev这个结构体来表示一个字符设备,定义如下:
struct cdev {
struct kobject kobj;
struct module *owner; //指向该设备使用的字符驱动模块
const struct file_operations *ops; //操作函数
dev_t dev; //设备号
unsigned int count;
};
struct cdev test_cdev;//在编写驱动前,先定义一个字符设备结构体
定义好后,对其进行初始化,如下:
void cdev_init(struct cdev* cdev,const struct file_operation *fops);
然后向linux系统中添加字符设备,使用函数如下:
int cdev_add(struct cdev *p,dev_t dev,unsigned count);
卸载驱动的时候要删除字符设备
cdev_del(struct cdev *p);
2.3 自动创建设备节点
自动创建设备节点的工作是在驱动程序初始化中完成的,一般在cdev_add函数后面添加自动创建设备节点的代码。
首先要创建一个类,每一个设备都应该属于一个类下,然后在这个类下创建具体的设备,比如创建一个类LED,这个类下面包括led0、led1等具体的设备。
struct class *class_create(struct module *owner,const char *name);
//owner表示驱动程序
//name:类名称
类创建好之后,就是创建我们的设备节点,函数如下:
struct device *device_create(struct class *class,
struct device *parent,
dev_t devt,
void *drvdata,
const char *fmt,...)
//class表示设备要创建在那个类下
//parent:设备的父设备,一般为NULL
//devt:设备号
//drvdata:设备可能使用的一些数据,一般为NULL
//fmt:设备名称,如果fmt=xxx,则会在/dev/class/xxx
//最后返回的就是这个具体的设备
2.4 完整流程:
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
static int major;
static struct cdev my_cdev;
static struct class *my_class;
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 ssize_t my_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) {
// 从设备读取数据到用户空间
return count;
}
static ssize_t my_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) {
// 将用户空间数据写入设备
return count;
}
static const struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = my_open,
.release = my_release,
.read = my_read,
.write = my_write,
};
static int __init my_driver_init(void) {
dev_t dev;
int ret;
// 动态分配主设备号
ret = alloc_chrdev_region(&dev, 0, 1, "my_device");
if (ret < 0) {
printk(KERN_ERR "Failed to allocate chrdev region\n");
return ret;
}
major = MAJOR(dev);
// 初始化字符设备结构体
cdev_init(&my_cdev, &my_fops);
my_cdev.owner = THIS_MODULE;
// 添加字符设备到内核
ret = cdev_add(&my_cdev, dev, 1);
if (ret < 0) {
printk(KERN_ERR "Failed to add cdev\n");
unregister_chrdev_region(dev, 1);
return ret;
}
// 创建设备类
my_class = class_create(THIS_MODULE, "my_class");
if (IS_ERR(my_class)) {
printk(KERN_ERR "Failed to create class\n");
cdev_del(&my_cdev);
unregister_chrdev_region(dev, 1);
return PTR_ERR(my_class);
}
// 创建设备节点(/dev/my_device)
device_create(my_class, NULL, MKDEV(major, 0), NULL, "my_device");
printk(KERN_INFO "Character device registered with major %d\n", major);
return 0;
}
static void __exit my_driver_exit(void) {
dev_t dev = MKDEV(major, 0);
device_destroy(my_class, dev);
class_unregister(my_class);
class_destroy(my_class);
cdev_del(&my_cdev);
unregister_chrdev_region(dev, 1);
printk(KERN_INFO "Character device unregistered\n");
}
module_init(my_driver_init);
module_exit(my_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple character device driver");
总之,编写字符驱动的过程如下:
- 注册一个cdev结构体
- 编写fop函数
- 申请设备号dev_t
- cdev初始化
- 向内核添加cdev
- 创建设备类
- 创建字符设备节点
更多推荐

所有评论(0)