系列文章目录
第一章 Linux 中内核与驱动程序
第二章 Linux 设备驱动编写 (misc)
第三章 Linux 设备驱动编写及设备节点自动生成 (cdev)
第四章 Linux 平台总线platform与设备树
第五章 Linux 设备树中pinctrl与gpio(lichee nano pi)
文章目录
前言
前两节提到misc_device是一种特殊的字符设备,主设备号都是10。字符设备的设备号就需要我们自己去分配设备号,而且字符设备不能自动生成设备文件(节点),这个也是需要我们去生成的。这两两点可以说是misc设备和字符设备的区别。
一、分配设备号
设备号是由主设备号+次设备号组成,两个一块构成dev_t类型的设备号。
dev_t类型在<linux/tppe.h>头文件中有定义,是一个32位数。高12位是用来保存主设备号,低20位是用来保存次设备的号
分配设备号有两种方式:
1.1 静态分配一个设备号
我们使用的是:
register_chrdev_region(dev_t, unsigned, const char *);
需要明确知道我们的系统里面那些设备号没有用。
参数:
第一个:设备号的起始值。类型是dev_t类型
第二个:次设备号的个数。
第三个:设备的名称
返回值:成功返回0,失败返回负数
cat /proc/device //查看设备中那个设备号被占用
Linux在<linux/kdev_t.h>中提供了几个宏定义来操作设备号
#define MINORBITS 20
次设备号的位数,一共是20位
#define MINORMASK((1U <<MINORBITS) - 1)
次设备号的掩码
#define MAJOR(dev) ((unsigned int) ((dev) > > MINORBITS))
在dev_t里面获取我们的主设备号
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
在dev_t里面获取我们的次设备号
#define MKDEV(ma,mi)(.(ma)<< MINORBITS) | (mi))
将我们的主设备号和次设备号组成一个dev_t类型。第一个参数是主设备号,第二个参数是次设备号
1.2 动态分配
alloc_chrdev_region(dev_t *. unsigned, unsigned, const char *);
参数:
第一个:保存生成的设备号
第二个:我们请求的第一个次设备号,通常是0
第三个:连续申请的设备号的个数。
第四个:设备名称
返回值:成功返回0,失败返回负数
1.3 注销设备号
我们使用的是
unregister_chrdev_region(dev_t, unsigned);
参数:
第一个:分配设备号的起始地址
第二个:申请的连续设备号的个数
1.4 例程结果
本文编译了一个x86的在自己电脑上跑了一下,用dmesg查看内核日志;
设备名字也对应的上。
二、字符设备驱动注册
2.1 一:定义一个cdev结构体
cdev结构体定义如下
2.2 使用cdev_init函数初始化cdev结构体成员变量
void cdev_init(struct cdev *, const struct file_operations *);
参数:
第一个:要初始化的cdev
第二个:文件操作集
cdev->ops = fops; //实际就是把文件操作集写给ops
2.3 使用cdev_add函数注册到内核
int cdev_add(struct cdev *, dev_t, unsigned);
参数:
第一个:cdev的结构体指针
第二个:设备号
第三个:次设备号的数量
2.4 cdev注销
void cdev_del(struct cdev *);
2.5 例程结果
三、生成设备节点
字符设备注册完以后自动生成设备节点。
3.1 手动生成设备节点
我们需要在insmod模块之后,使用mknod命令创建一个设备节点,这样才能在应用层对设备进行操作。(要不没设备文件你操作个啥)
格式:mknod 名称 类型 主设备号 次设备号
举例:
mknod /dev/test c 247 0
3.2 自动生成设备节点
1.什么是mdev?
mdev是udev的简化版本,是busybox中所带的程序,最适合用在嵌入式系统。
2.什么是udev?
udev是一种工具,它能够根据系统中的硬件设备的状态动态更新设备文件,包括设备文件的创建,删除等。设备文件通常放在/dev目录下。使用udev后,在/dev目录下就只包含系统中真正存在的设备。udev一般用在PC 上的 linux中,相对mdev来说要复杂些。
3.怎么自动创建设备节点?
自动创建设备节点分为俩个步骤:
步骤一:使用class_create函数创建一个class的类。
步骤二:使用device_create函数在我们创建的类下面创建一个设备。
4.创建和删除类函数
在Linux驱动程序中一般通过两个函数来完成设备节点的创建和删除。首先要创建一个class类结构体,
class结构体定义在include/linux/device.h里面。class_create是类创建函数,class_create是个宏定义,内容
如下:
#define class_create(owner, name)
( {
static struct lock_class_key_key;
_class_create(owner, name, &_key);
})
struct class *_class_create(struct module *owner, const char *name,
struct lock_class_key *key)
class_create一共有两个参数,参数owner一般为THIS_MODULE,参数name是类名字。返回值是个指向结构体class的指针,也就是创建的类。
下面命令可以查看类
ls /sys/class/
卸载驱动程序的时候需要删除掉类,类删除函数为class_destroy,函数原型如下:
void class_destroy(struct class *cls);
参数cls就是要删除的类。
5.创建设备函数
当使用上节的函数创建完成一个类后,使用device_create函数在这个类下创建一个设备。
device_create函数原型如下:
struct device *device_create(struct class *class,struct device *parent,
dev_t devt,
void *drvdata,
const char*fmt,...)
device create是个可变参数函数,
参数class就是设备要创建哪个类下面;
参数parent是父设备,一般为NULL,也就是没有父设备;
参数devt是设备号;
参数drvdata是设备可能会使用的一些数据,一般为NULL;
参数 fmt是设备名字,如果设置fmt=xxx的话,就会生成/dev/xxx这个设备文件。返回值就是创建好的设备。
同样的,卸载驱动的时候需要删除掉创建的设备,设备删除函数为device_destroy,函数原型如下:
void device_destroy(struct class *class, dev_t devt)
参数 class是要删除的设备所处的类,参数devt是要删除的设备号。
3.3 例程结果
结果生成了my_cdev_nod。
附一张 法师 的思维导图
例程代码
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/kernel.h> /* printk() */
#include <linux/cdev.h> /* cdev... */
#include <linux/fs.h> /* everything... */
#include <linux/types.h> /* size_t */
#include <linux/uaccess.h>
#include <linux/kdev_t.h>
#define DEVICE_NUMBER 1 //需要注册设备的数量
#define DEVICE_SNAME "s_my_cdev" //动态分配的设备名称
#define DEVICE_ANAME "a_my_cdev" //静态分配的设备名称
#define DEVICE_MINOR_NUMBER 0
#define DEVICE_CLASS_NAME "my_cdev_class"//类的名称
#define DEVICE_NODE_NAME "my_cdev_nod" //要生成的设备节点的名称
static int major_num,minor_num;
module_param(major_num,int,S_IRUSR);//驱动传参,不了解的搜一下,比较简单
module_param(minor_num,int,S_IRUSR);
int cedv_open(struct inode *inode,struct file *file)
{
printk( "open cedv\n");
return 0;
}
struct file_operations cedv_fops={
.owner = THIS_MODULE,
.open = cedv_open,
};
dev_t dev_num;//设备号
struct cdev cdev ; //字符设备结构体
struct class *class ; //类相关的结构体
struct device *device ; //设备节点相关的结构体
static int cdevice_init(void)
{
int ret;
if(major_num)//输入参数了就静态分配,没有就动态分配
{
printk( "major_num = %d \n",major_num);
printk( "minor_num = %d \n",minor_num);
dev_num = MKDEV(major_num,minor_num);
ret=register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME);
if(ret < 0)
{
printk( "register_chrdev_region is failed\n");
return -1;
}
printk( "register_chrdev_region is succeed\n");
}
else
{
ret =alloc_chrdev_region(&dev_num,DEVICE_MINOR_NUMBER, DEVICE_NUMBER, DEVICE_ANAME);
if(ret < 0)
{
printk( "alloc_chrdev_region is failed\n");
return -1;
}
printk( "alloc_chrdev_region is succeed\n");
major_num = MAJOR(dev_num);
minor_num = MINOR(dev_num);
printk( "major_num = %d \n",major_num);
printk( "minor_num = %d \n",minor_num);
}
/******************上边的是分配设备号****************************/
cdev.owner = THIS_MODULE;
cdev_init(&cdev ,&cedv_fops);
cdev_add(&cdev, dev_num, DEVICE_NUMBER);
/******************上边的是注册字符设备**************************/
class = class_create(THIS_MODULE,DEVICE_CLASS_NAME);
device = device_create(class,NULL,dev_num,NULL,DEVICE_NODE_NAME);
/******************上边的是自动生成设备节点**********************/
return 0;
}
static void cedvice_exit(void)
{
unregister_chrdev_region(MKDEV(major_num,minor_num), DEVICE_NUMBER);
cdev_del(&cdev);
device_destroy(class, dev_num);
class_destroy(class);
printk(KERN_ALERT "Goodbye,cdev\n");
}
/* register the init and exit routine of the module */
module_init( cdevice_init );
module_exit( cedvice_exit );
MODULE_LICENSE("GPL");