刚开始学习驱动,首先学习的是字符设备驱动。这边文章先介绍的是刚学需要了解的粗略的知识(cdev结构体、设备号),然后是整合最简单的用例看整体的思路。
简述
Linux设备驱动有三种:字符设备驱动、块设备驱动、网络设备驱动。其中字符设备驱动最为基础。
其中最为关键的就是cdev和file_operations这两个结构体。个人理解:cdev就是用来描述的字符设备——字符设备,file_operations就是操作字符设备的函数的集合的结构体。
一、cdev
struct cdev{
struct kobject kobj; //内嵌的内核对象
struct module *owner; //该字符设备所在的内核模块的对象指针,一般为THIS_MODULE主要模块计数
const struct file_operations *ops; //字符设备所能实现的操作集(打开、关闭、读写)
struct list_head list; //将已经向内核注册的所有的字符设备形成链表
dev_t dev; //字符设备号,一共32位,前12位主设备号,
//后20位次设备号(主设备号标识设别类型,次设备号标识同一设备类型中的不同设备)
unsigned int count; //隶属同一主设备号的此设备号的个数
}
这是cdev结构体的包含的内容,每一个成员后面都标注清楚了。然后就是操作cdev相关的操作函数了
void cdev *cdev_init(struct cdev*,struct file_operations*)//初始化cdev成员,并建立与file_operations之间的联系。
{
/*
参数:
stuct cdev *p---被初始话的cdev对象
const struct file_operations *fops---字符设备操作方法集
*/
}
struct cdev *cdev_alloc(void)//动态申请cdev内存
{
/*返回值:
成功:cdev对象的地址
失败:null
*/
}
int cdev_add(struct cdev *p,dev_t dev,unsigned count)//注册cdev设备对象(添加到字符设备链表中)
{
/*
参数:
struct cdev *p---被注册cdev对象
dev_t dev ---设备的第一个设备号
count---这个设备连续的此设备号数
返回值:
成功:0
失败:负数(绝对值为错误码)
*/
}
void cdev_del(struct cdev *p)//将cdev对象从系统中移除
{
/*
参数:
struct cdev *p---要移除的cdev对象
*/
}
void cdev_put(struct cdev*p)//释放cdev的内存
{
/*
参数:struct cdev *p---要移除的cdev的对象
*/
}
函数的大致说明也是在里面标注出来了的,其实看单词的大致意思就能懂大致的意思。
二、设备号
设备号(dev_t dev),是在结构体cdev里面定义的,一共32位,前12位主设备号,后20位次设备号(主设备号标识设别类型,次设备号标识同一设备类型中的不同设备)
在Linux内核中提供了几个方便的操作的宏实现了对于设备号dev_t的操作
MAJOR(dev_t dev)---------------根据设备号分解出主设备号
MINOR(dev_t dev)---------------根据设备号分解处次设备号
MKDEV(int major,int minor)-----合成设备号
获取设备号后,需要调用函数向内核申请设备号,申请分为动态申请和静态申请
//静态申请设备号
int register_chrdev_regin(dev_t from,unsigned count,const char *name)
{
/*
参数:
from:这组连续的设备号的起始设备号
count:这组设备号的大小(此设备号的个数)
name:编号相关联的设备名称
返回值:
成功:0
失败:负数
*/
}
//动态申请设备号
int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name)
{
/*
参数:
*dev:存放返回的设备号
baseminor:通常为0
*/
}
三、例子
该案例只是为了说明字符设备的添加方法,没有实现file_operations的任何函数。
下面是比较完整的例子,是看的别人的结合自己的理解将注释加了上去,需要的小伙伴可以看一下,跟着思路去理解。
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*
***************该案例只是为了说明字符设备的添加方法,***************
****************没有实现file_operations的任何函数*********************
*/
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Shijia Yin");
#define DEV_NAME "test"
#define DEV_MIN_NUM 2
#define DEV_MAJOR 0
#define DEV_MINOR 0
int dev_major = DEV_MAJOR;
int dev_minor = DEV_MINOR;
//Linux内核编码习惯,通常会为设备定义一个相关的结构体,该结构体包含设备所设计的cdev
//私有数据以及锁等信息,这里test_dev_t就是设备结构体
/*
struct cdev{
struct kobject kobj; //内嵌的内核对象
struct module *owner; //该字符设备所在的内核模块的对象指针,一般为THIS_MODULE主要用于模块计数
const struct file_operations *ops; //字符设备所能实现的操作集(打开、关闭、读写)
struct list_head list; //将已经向内核注册的所有的字符设备形成链表
dev_t dev; //字符设备号,一共32位,前12位主设备号,
//后20位次设备号(主设备号标识设别类型,次设备号标识同一设备类型中的不同设备)
unsigned int count; //隶属同一主设备号的此设备号的个数
}
*/
struct test_dev_t {
struct cdev cdev;
} test_dev;
struct file_operations test_fops = {
.owner = THIS_MODULE,
};
static int test_init(void)
{
int err;
int ret = 0;
dev_t dev_num;
if(dev_major) {
dev_num = MKDEV(dev_major, dev_minor);//合成设备号
ret = register_chrdev_region(dev_num, DEV_MIN_NUM, DEV_NAME);//静态申请设备号
} else {
ret = alloc_chrdev_region(&dev_num, dev_minor, DEV_MIN_NUM, DEV_NAME);//动态申请设备号
dev_major = MAJOR(dev_num);//分离出主设备号
printk(KERN_EMERG "major number is %d !\n", dev_major);//printk将信息记录到log中(printk问题https://blog.csdn.net/wwwlyj123321/article/details/88422640)
}
if(ret < 0) {
printk(KERN_EMERG "register_chrdev_region req %d is failed!\n", dev_major);
}
cdev_init(&test_dev.cdev, &test_fops); //初始化cdev,并建立fiel_operations联系
test_dev.cdev.ops = &test_fops; //将字符设备的操作集给devc这个字符设备
test_dev.cdev.owner = THIS_MODULE; //字符设备所在的内核模块的对象指针,用于模块计数
err = cdev_add(&test_dev.cdev, dev_num, 1);//注册cdev设备对象(添加到系统字符设备的链表中)
//err绝对值是错误码
if(err) {
printk(KERN_EMERG "cdev_add %d is fail! %d\n", dev_minor, err);//注册失败将主设备号中的的错误码打印到log中
} else {
printk(KERN_EMERG "cdev_add %d is success! \n", dev_minor);//成功也将信息记录到log中
}
printk(KERN_ALERT "Hello, world\n");
return 0;
}
static void test_exit(void)
{
printk(KERN_EMERG "test exit!\n");
cdev_del(&test_dev.cdev);//cdev对象从系统中移除
unregister_chrdev_region(MKDEV(dev_major, dev_minor), DEV_MIN_NUM);//释放设备号
printk(KERN_ALERT "Goodbye, cruel world\n");
}
module_init(test_init);//加载驱动https://blog.csdn.net/qq_37600027/article/details/90739977
module_exit(test_exit);//卸载驱动
这里面有一些刚开始接触我也不懂的,后面附有别人的链接可以去看一下,比如printk,这个函数跟C语言的printf是由去别的,这里不做详细的说明,复制注释里面的链接查看还是比较清楚的。
四、心得
我是一个驱动小小小白,刚开始自己学习驱动,自己啥都不懂,慢慢探索,每天白天学习,晚上总结更新CSDN,有大佬乐意指导的或者有推荐的欢迎指教,有同道小白欢迎讨论分享。心得:今天学习字符驱动设备,刚开始学就要将里面的不懂得东西,去细纠理解深刻一点,先了解各个细小的知识,然后去整体的看代码来一遍就知道大概的来龙去脉了。
后面有什么要添加的更改的,持续更新.记得关注小白