字符设备驱动——讯为笔记

字符设备

字符设备和杂项设备的区别

杂项设备的主设备号是固定的,字符设备需要分配主设备号

杂项设备自动生成设备节点,字符设备需要程序生成设备节点

所以创建字符设备会比杂项设备多两步:申请设备号、创建设备节点

申请字符类设备号

#include <linux/fs.h>

静态分配设备号

即手动指定设备号

int register_chrdev_region(dev_t, unsigned, const char*);
//申请设备号
//参数:设备号的起始值;次设备号的个数;设备名称
//分配成功返回0,失败返回非0

void unregister_chrdev_region(dev_t, unsigned);
//注销设备号
//参数:设备号,次设备号个数

静态分配设备号时要确保指定的设备号没有被占用,cat /proc/devices可查看当前主设备号

动态分配设备号

int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char*);
//参数:要生成的设备号指针;请求的第一个次设备号,通常是0;连续申请的设备号个数;设备名称
//成功返回0,失败返回负数
//动态分配优先使用255-234

推荐使用动态分配的方式

dev_t设备号类型

#include <linux/kdev_t.h>
dev_t类型:设备号类型,是一个32位数
高12位保存主设备号,低20位保存次设备号

//Linux中的宏定义
#define MINORBITS 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类型的设备号

注册字符类设备

#include <linux/cdev.h>

注册步骤:

  • 定义一个cdev和file_operations结构体
  • 使用cdev_init函数初始化cdev结构体成员变量
  • 使用cdev_add函数注册到内核

使用cdev_del函数卸载

c_dev结构体

描述一个字符设备的结构体(和杂项设备的结构体类似)

struct cdev {
	struct kobject kobj;
	struct module *owner;
	const struct file_operations *ops;  //文件操作集
	struct list_head list;
	dev_t dev;   //设备号
	unsigned int count;
} __randomize_layout;

cdev相关函数

void cdev_init(struct cdev *, const struct file_operations *);
//参数:cdev结构体指针,文件操作集

int cdev_add(struct cdev *, dev_t, unsigned);
//参数:cdev结构体指针,设备号,次设备数量

void cdev_del(struct cdev *);

创建设备节点

命令mknod

mknod 名称 类型 主设备号 次设备号

#例如:给主设备号247次设备号0的设备,创建名为/dev/test的设备节点
mknod /dev/test c 247 0
#c代表字符类设备

驱动代码创建

创建步骤:

  • 使用class_create函数创建一个class类(会在/sys/class/目录下出现)
  • 使用device_create函数在创建的class类下面创建一个设备(会在/dev/目录下出现)

注销时,先创建的要后注销

相关函数

//include/linux/device.h
#define class_create(owner, name)		\
({						\
	static struct lock_class_key __key;	\
	__class_create(owner, name, &__key);	\
})
//参数:owner一般为THIS_MODULE,参数name是类名字
//返回值是指向创建的class类的指针


void class_destroy(struct class *cls);
//参数:cls是要删除的class类
struct device *device_create(struct class *cls, struct device *parent,
			     dev_t devt, void *drvdata,
			     const char *fmt, ...);
//参数:cls指在这个类下创建;parent是父设备,一般为NULL即没有父设备
// devt是设备号;drvdata是设备可能使用的一些数据,一般为NULL
// fmt是设备节点的名字,即/dev目录下文件名


void device_destroy(struct class *cls, dev_t devt);
//删除设备
//参数:设备所属的类;要删除的设备号

代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>

#define MINOR_DEVICE_NUM 1  //连续注册的次设备个数
#define DEVICE_NAME "chrdev_proc"   //设备号的名字
#define CLASS_NAME "chrdev_class"   //设备类的名字
#define DEVICE_NODE_NAME "chrdev_taxue"  //设备节点名

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("TAXUE");

static dev_t devtChr;
static struct cdev cdevChr={
    .owner = THIS_MODULE
};
static struct file_operations fileOperat = {
    .owner = THIS_MODULE
};

static struct class * classChr=NULL;
static struct device * deviceChr=NULL;

//驱动入口
static int chrdev_init(void){
    int ret;
    printk("chrdev init\n");

    ret = alloc_chrdev_region( &devtChr, 0, MINOR_DEVICE_NUM, DEVICE_NAME);  //动态分配设备号
    if (ret != 0) {
        printk("alloc chrdev failed\n");
        return -1;
    }
    printk("MAJOR=%d, ", MAJOR(devtChr));  //主设备号
    printk("MINOR=%d\n", MINOR(devtChr));  //次设备号

    cdev_init( &cdevChr, &fileOperat);  //初始化cdev结构体
    cdev_add( &cdevChr, devtChr, MINOR_DEVICE_NUM);  //向内核注册设备

    classChr = class_create(THIS_MODULE, CLASS_NAME);  //创建类
    deviceChr = device_create(classChr, NULL, devtChr, NULL, DEVICE_NODE_NAME);  //创建设备节点

    return 0;
}

static void chrdev_exit(void){

    printk("chrdev exit\n");

    device_destroy(classChr, devtChr);   //一般遵循后创建的,先注销的顺序。就像先穿的衣服要最后脱
    class_destroy(classChr);
    cdev_del(&cdevChr);
    unregister_chrdev_region( devtChr, MINOR_DEVICE_NUM);
}

module_init(chrdev_init);
module_exit(chrdev_exit);
上一篇:Linux设备驱动-内核如何管理设备号


下一篇:Linux之字符驱动认识(一)(二)