驱动模块概述
用户空间
linux 一切皆文件
设备文件: 鼠标、键盘、LED、屏幕、Flash、内存、网卡
对于文件的API的控制有:open、read、write
这些API能打开不同硬件设备,并控制,是因为不同设备有不同的设备驱动
编辑设备驱动就只有两个功能:
1、添加驱动
- 设备名
- 设备号
- 设备驱动函数
- 操作寄存器来驱动IO口
2、调用驱动
驱动实现路径:
用户层:open(”/dev/pin4”,O_RDWR)——>
open去调用sys_call
从用户空间到内核空间会发生一次软中断,中断号:0x80
为了快速响应用汇编去实现了sys_call
内核层:sys_call——>
sys_call去调用VFS里的sys_open
VFS:sys_open——>
sys_open根据设备名,去找到对应的主设备号,次设备号,再去找到pin4引脚的设备驱动里的open
驱动pin4:open——>
open就的对寄存器的操作,驱动IO口
APP应用层
APP: CP指令,FTP项目,实现各种功能的应用层的软件
APP需要基础: C语言,C库
C库功能: 文件,进程,进程间通信,线程,网络,界面(GTK)
打开设备:
fd=open(”/dev/pin4”,O_RDWR);
1、需要有设备文件名,pin4
2、硬件设备号1
- 主设备号
- 次设备号
Linux的设备管理是和文件系统紧密结合的,各种设备都以文件的形式存放在/dev目录下,称为设备文件。应用程序可以打开、关闭和读写这些设备文件,完成对设备的操作,就像操作普通的数据文件一样。为了管理这些设备,系统为设备编了号,每个设备号又分为主设备号和次设备号。主设备号用来区分不同种类的设备,而次设备号用来区分同一类型的多个设备
C library & C库
提供了APP支配内核工作的接口,由此处封装实现各种功能
open、read、write、fork、pthread、socket
- C 库是所有系统基本都会有,但wiringPI库,不同的平台就不一定会提供,那么开发者就必须要学会自己开发,树莓派的就是可以用wiringPI库
内核空间
应用层open调用:
sys_call
调用VFS虚拟文件系统:
sys_open、sys_read、sys_write
进程、内存、线程、网络、设备驱动
设备驱动有驱动链表管理所有设备的驱动
有两个功能:
1、添加
- 编写完驱动程序,加载到内核
2、查找 - 调用驱动程序,用户空间用open,通过设备号进行检索
驱动程序是在内核中呈现链表的形式存在
驱动插入链表的顺序,也是通过设备号进行检索
磁盘驱动——>pin4驱动——>pin5驱动——>pin6驱动
对应的驱动控制对应的设备
硬件
磁盘.txt、pin4、pin5、pin6
驱动框架应用
基础的驱动框架:
#include <linux/fs.h> //file_operations声明
#include <linux/module.h> //module_init module_exit声明
#include <linux/init.h> //__init __exit 宏定义声明
#include <linux/device.h> //class devise声明
#include <linux/uaccess.h> //copy_from_user 的头文件
#include <linux/types.h> //设备号 dev_t 类型声明
#include <asm/io.h> //ioremap iounmap的头文件
static struct class *pin4_class;
static struct device *pin4_class_dev;
static dev_t devno; //设备号
static int major =231; //主设备号
static int minor =0; //次设备号
static char *module_name="pin4"; //模块名
//led_open函数
static int pin4_open(struct inode *inode,struct file *file)
{
printk("pin4_open\n"); //内核的打印函数和printf类似
return 0;
}
//led_write函数
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
return 0;
}
static struct file_operations pin4_fops = {
.owner = THIS_MODULE,
.open = pin4_open,
.write = pin4_write,
};
int __init pin4_drv_init(void) //真实的驱动运行入口
{
int ret;
devno = MKDEV(major,minor); //创建设备号
ret = register_chrdev(major, module_name,&pin4_fops); //注册驱动 告诉内核,把这个驱动加入到内核驱动的链表中
pin4_class=class_create(THIS_MODULE,"myfirstdemo"); //由代码在dev下自动生成设备
pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name); //创建设备文件
return 0;
}
void __exit pin4_drv_exit(void)
{
device_destroy(pin4_class,devno);
class_destroy(pin4_class);
unregister_chrdev(major, module_name); //卸载驱动
}
module_init(pin4_drv_init); //入口, 内核加载该驱动的时候,这时,这个宏会被调用
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");
static:限定作用域,限定变量只能当前文件使用,防止其它文件内也有同样的变量名之后造成冲突
可以用命令来生成设备
sudo mknod yang c 8 1
超级用户 生成一个yang的字符设备,主设备号为8,次设备号为1
pin4测试驱动框架:
#include <linux/fs.h> //file_operations声明
#include <linux/module.h> //module_init module_exit声明
#include <linux/init.h> //__init __exit 宏定义声明
#include <linux/device.h> //class devise声明
#include <linux/uaccess.h> //copy_from_user 的头文件
#include <linux/types.h> //设备号 dev_t 类型声明
#include <asm/io.h> //ioremap iounmap的头文件
static struct class *pin4_class; //主设备
static struct device *pin4_class_dev; //次设备
static dev_t devno; //设备号
static int major =231; //主设备号
static int minor =0; //次设备号
static char *module_name="pin4"; //模块名
//led_open函数
static int pin4_open(struct inode *inode,struct file *file)
{
printk("pin4_open\n"); //内核的打印函数和printf类似
return 0;
}
//led_read函数
static ssize_t pin4_read(struct file *file,char __user *buf,size_t count, loff_t *ppos)
{
printk("pin4_read\n");
return 0;
}
//led_write函数
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
printk("pin4_write\n");
return 0;
}
static struct file_operations pin4_fops = {
.owner = THIS_MODULE,
.open = pin4_open,
.read = pin4_read,
.write = pin4_write,
};
int __init pin4_drv_init(void)
{
int ret;
devno = MKDEV(major,minor); //创建设备号
ret = register_chrdev(major, module_name,&pin4_fops); //注册驱动 告诉内核,把这个驱动加入到内核驱动的链表中
pin4_class=class_create(THIS_MODULE,"myfirstdemo");
pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name); //创建设备文件
return 0;
}
void __exit pin4_drv_exit(void)
{
device_destroy(pin4_class,devno);
class_destroy(pin4_class);
unregister_chrdev(major, module_name); //卸载驱动
}
module_init(pin4_drv_init); //入口
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");
上层驱动程序:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main()
{
int fd;
fd = open("/dev/pin4",O_RDWR);
if(fd < 0){
printf("open failed\n");
perror("reson");
}else{
printf("open success\n");
}
fd = write(fd,'1',1);
return 0;
}
printk(“pin4_write\n”); 相当于printf函数效果,只不过是需要用dmesg查看内核打印信息
启用模块编译
因为是字符设备,所以需要把编写的pin4driver.c 代码C文件拷贝到内核文件里的char文件夹下
cp pin4driver.c /home/yangyingchun/SYSTEM/linux-rpi-4.14.y/drivers/char/.
模块编译:
1、因为是把驱动文件拷贝到了 /home/yangyingchun/SYSTEM/linux-rpi-4.14.y/drivers/char/
这个文件夹下,所以要修改当前文件夹 /home/yangyingchun/SYSTEM/linux-rpi-4.14.y/drivers/char/
下的Makefile文件
告诉编译器,要编译该驱动文件
vi Makefile
新添加一行,添加驱动编译模块
obj-m += pin4driver.o
2、编译内核驱动
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules
生成pin4driver.ko文件
3、拷贝驱动模块pin4driver.ko文件到树莓派
scp drivers/char/pin4driver.ko pi@192.168.1.105:/home/pi
拷贝测试程序:
scp pin4test pi@192.168.1.105:/home/pi
转到树莓派操作
4、加载内核驱动
sudo insmod pin4driver.ko
5、查看当前内核模块,看驱动设备模块是否加载成功
lsmod
6、给驱动模块pin4driver.ko文件权限
sudo chmod 666 /dev/pin4
7、执行测试程序
./pin4test
8、查看内核打印信息
dmesg
9、卸载驱动
sudo rmmod pin4driver