初学驱动-字符设备驱动第一天(1)

刚开始学习驱动,首先学习的是字符设备驱动。这边文章先介绍的是刚学需要了解的粗略的知识(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,有大佬乐意指导的或者有推荐的欢迎指教,有同道小白欢迎讨论分享。心得:今天学习字符驱动设备,刚开始学就要将里面的不懂得东西,去细纠理解深刻一点,先了解各个细小的知识,然后去整体的看代码来一遍就知道大概的来龙去脉了。

后面有什么要添加的更改的,持续更新.记得关注小白

你可能感兴趣的:(字符设备驱动学习,linux,内核)