找回密码
 立即注册
Qt开源社区 门户 查看内容

Linux驱动学习(1):万丈高楼平地起,Linux驱动从我做起!我的第一个字符设备驱动程序

2019-10-25 09:51| 发布者: admin| 查看: 458| 评论: 0

摘要: Frist:学习资料参考原子哥教程。不仅白嫖了STM32、UCos/GUI系列教程,现在Linux也不放过~,感谢原子哥所有免费资料!And 这个标题有点长~基础知识:字符设备是Linux驱动中最基本的一种设备类型驱动,它是按照一个个 ...
Frist:学习资料参考原子哥教程。不仅白嫖了STM32、UCos/GUI系列教程,现在Linux也不放过~,感谢原子哥所有免费资料!

And 这个标题有点长~

基础知识:

字符设备是Linux驱动中最基本的一种设备类型驱动,它是按照一个个字节按照顺序进行读写操作的设备。比如常见的LED、按键、LCD、IIC等都是字节设备。

应用层调用f_Open、f_read、f_write等库函数去操作设备。

驱动程序就是由我们去实现这些库函数映射到的对应设备驱动。

如下图的关系式。我们只需要去实现驱动程序这一层即可。

在Linux中,一切皆为文件。

比如点灯,当我们写完驱动程序并加载后,会在/dev目录生成相应的文件,应用层只要调用f_write函数参数时文件路径/Dev/xxx,内核即可找到对应的硬件设备,因此应用层的程序猿不需要去关心底层硬件如何实现。

所有的文件操作首先是f_open,其实就是对应裸机中的LED_init();调用逻辑如下:

开发流程:

在裸机中开发,操作一个led首先要配置GPIO,还要有写GPIO的函数、读GPIO的函数。

Linux当然也少不了,只是要按照一定格式去完成,要不怎么说是系统!

所以原子哥说了学驱动就是学框架,看到这里我们就可以先跳过uboot Makefile 内核等等。

1. 驱动模块的加载和卸载。

加载Linux驱动用insmod命令。例如:insmod led.ko (led.ko是加载完驱动后生成的ko文件);

卸载驱动用remod命令。

驱动的加载和卸载主要由两个函数:

Module_init(xxx_init)  ; //加载

Module_exit(xxx_exit)  ; //卸载

xxx_init是一个函数,当使用insmod加载驱动时,系统就会调用xxx_init();因此我们需要实现这两个函数,如下:

Static int_init xxx_init()

{

   //具体内容  

   Return 0;

}

Static void _init xxx_exit()

{

   //具体内容  

}

2. 字符设备的注册与注销。

这里区分与上面的驱动加载,总之驱动加载与字符设备注册时两个操作。

字符设备的注册:

Static inline int register_chrdev(unsigned int major, const char *name,const struct file_opration  *fops);

字符设备的注销:

Static inline int unregister_chrdev(unsigned int major, const char *name);

Major:设备号,目前理解为设备编码。

Name:设备名称。这个名字不是必须记住的,用过设备号也可以找到设备。

*fops:所有驱动函数的集合的结构体指针,里面包括了fopen、fread、fwrite等。

首先声明*fops结构体为全局变量(要用到很多次);

Static struct file_opration  *fileops;

接下来我们来调用这两个函数。那么有谁来调用他两呢?当然是驱动的加载和卸载函数了!因此驱动的加载和卸载函数包含了字符设备的注册和注销。

把上面写好的代码拷贝下来:

Static int_init xxx_init()

{

   Int wRes;

   wRes = register_chrdev(200,”leddrv”,fileops);

   If(wRes < 0)

{

  //失败则。。

}

   Return 0;

}

这里的设备号200要求是唯一的,也就是不能重复,这里需要查看目前系统已经使用的设备号,然后填一个没有被占用的设备号,因此称作静态分配。

静态分配的缺点就是当设备号多的时候很难去找哪个是空闲的设备号。

动态分配则通过系统自动分配,避免了冲突的风险。后面再介绍。

输入cat/prot/device查看已经使用的设备号。

Static void _init xxx_exit()

{

   unregister_chrdev(200,”leddrv”);

}

3. 实现设备的具体驱动操作函数。

从上面的两步。我们已经配置好系统层相关的函数。现在来实现硬件设备具体的操作函数。也就是之前说的fopen/fread/fwrite等等。

还记得前面定义了一个结构体吗?

Fileops里面就包含了这些库函数的指针入口,因此我们只要将我们自己的操作函数指针赋值给他即可。

Led_open / led_read / led_write / led_release这是我们自己写的函数,名字不重要,最终我们要把他赋值给fopen/fread/fwrite等等。

因此这些函数的形参返回值一定要跟c库文件函数一致!下面举例子

Static int Led_open(struct inode *inode, struct file *file)//形参照抄库函数的形参就好

{

  //具体功能

  Return 0;

}

这里的具体功能当然就是裸机中的led_init里面的GPIO配置啦!很熟悉有没有!

剩下的函数就不写了,把裸机的操作内容填进去就ok。完事了就赋值,怎么赋值呢?回忆前面的结构体,传入指针!

Static struct file_opration  *fileops=

{

.open = Led_open;

.read = led_read;

.write = led_write;

...

}

至此,所有函数、形参均已实现。

4. 添加作者信息和licesnse

MODULE_LICENSE(“GPL”);  //GPL协议

MODULE_AUTHOR(“yuanzige”);  //致敬原子哥

至此,一个Linux基本框架完成,整理一下即可,看着有点简单有没有!。


----------------------------------------------------------------------------------------------------------------------
我们尊重原创,也注重分享,文章来源于微信公众号:会发光的眼睛,建议关注公众号查看原文。如若侵权请联系qter@qter.org。
----------------------------------------------------------------------------------------------------------------------

鲜花

握手

雷人

路过

鸡蛋

公告
可以关注我们的微信公众号yafeilinux_friends获取最新动态,或者加入QQ会员群进行交流:190741849、186601429(已满) 我知道了