一、驱动认知
首先理解Linux内核框图 文件系统认知,Linux内核框图
1、什么是驱动
- linux内核驱动。软件层面上的驱动 广义上是指:这一段代码操作了硬件去动,所以这一段代码就叫硬件的驱动程序。
- 狭义上驱动程序就是专指操作系统中用来操控硬件的逻辑方法的部分代码。而我们这里讲的驱动就指的是这个狭义上的驱动。
2、Linux驱动的体系架构
- 分离、分层思想
- 驱动的上面是系统调用API
- 驱动的下面是硬件
- 驱动本身的实现也是基于分离、分层的思想(比如分成不同类型的驱动)
3、Linux驱动的分类
- 分3类:字符设备驱动、块设备驱动、网络设备驱动
- 分类原则:设备本身读写操作的特征差异
- 时刻要注意一点,我们的驱动本质上是不分类的,因为我们的硬件是不同的,有不同的读写特性,所以的硬件是分类的,所以我们的驱动也就要跟着分类了
字符设备: 准确的说应该叫“字节设备”,软件操作设备时是以字节为单位进行的。典型的如LCD、串口、GPIO、蜂鸣器、触摸屏······ 块设备: 块设备是相对于字符设备定义的,块设备被软件操作时是以块(多个字节构成的一个单位)为单位的。设备的块大小是设备本身设计时定义好的,软件是不能去更改的,不同设备的块大小可以不一样。常见的块设备都是存储类设备,如:硬盘、NandFlash、iNand、SD····操作块设备时,我们必须以块单位进行操作,将一块的数据读取到内存中去,然后在内存中找到相应的那个字节数据修改之后再以一块的数据写入到我们的块设备中去 网络设备: 网络设备是专为网卡设计的驱动模型,linux中网络设备驱动主要目的是为了支持API中socket相关的那些函数工作
4.为什么要学习写驱动
- 树莓派开发简单是因为有厂家提供的wiringPi库,实现超声波,实现继电器操作,做灯的点亮…都非常简单
- 但未来做开发时,不一定都是用树莓派,则可能没有wiringPi库可以用。但只要能运行Linux系统,linux的标准C库一定有
- 所以我们可以基于C库、Linux内核和拿到芯片手册,电路图…就能做开发,写一套属于自己的驱动库
二、开始树莓派Linux内核驱动开发
- 设备驱动文件路径:
/home/pi/lessonPI/linux-rpi-4.14.y/drivers ,在Linux环境编写 - 设备文件(文件显示黄色)路径:根目录下的dev文件
/dev ,在树莓派环境使用 - 在Linux环境通过写好设备驱动文件然后进行内核编译生成驱动模块xxx.ko,接着把驱动模块发送给树莓派,树莓派把驱动模块加载到设备文件。从而用户可以通过/dev目录下的这些设备文件访问外部硬件设备,比如通过open(/dev/mouse,O_RDONLY)来访问鼠标的输入 从而可以进行各种骚操作
- 内核底层其实是用链表去管理这些驱动,学过FreeRTOS系统源码的可以从FreeRTOS源码的底层链表去理解
- 设备驱动文件需要有设备号、主设备号、次设备号、设备文件名。这些可以理解为驱动任务节点所需要的内容,可以从FreeRTOS源码的任务节点去结合这个理解
- 整个过程大致:
C库open——>sys_call——>sys_open(属于VFS层虚拟文件系统)——>根据open里的那个设备文件里的设备名去内核找到驱动链表——>接着根据其主设备号与次设备号找到相关驱动函数 ,所以我们只需要让内核执行我们自己写的设备名(即执行我们最后得到的设备文件),就可以执行我们写的驱动函数了
- 拷贝设备驱动文件代码
gpio属于字符设备,所以把该设备驱动文件拷贝到/home/pi/lessonPI/linux-rpi-4.14.y/drivers/char
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <asm/io.h>
static struct class *pin4_class;
static struct device *pin4_class_dev;
static dev_t devno;
static int major = 232;
static int minor = 0;
static char *module_name = "pin4";
static int pin4_open(struct inode *inode, struct file *file)
{
printk("pin4_open\n");
return 0;
}
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,
.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>
#include <unistd.h>
#include <string.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");
}
write(fd,"a",strlen("a"));
close(fd);
return 0;
}
- 在/drivers/char目录下修改Makefile配置文件,打开后加入
obj-m += pin4driver.o ,为了进行内核编译时能把设备驱动文件生成为驱动模块xxx.ko - 在Linux里回到树莓派内核源码根目录linux-rpi-4.14.y,执行指令进行编译内核:
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules 补充:modules:我们只需生成驱动模块,所以只需modules参数即可 编译成功,生成pin4driver.ko 驱动模块 编译完成后执行指令发送给树莓派scp drivers/char/pin4driver.ko pi@192.168.1.1:/home/pi - 执行指令编译应用代码:
arm-linux-gnueabihf-gcc pin4text.c -o pin4text 编译完成后执行指令发送给树莓派scp pin4text pi@192.168.1.1:/home/pi - Linux操作完了,接下来到树莓派操作了。
执行指令加载到设备文件(即把pin4driver.ko驱动模块加载到/dev设备文件里)sudo insmod pin4driver.ko - 查看加入后的设备号是否和代码写的一样
ls pin4 -l ,可见主设备号和次设备号和代码里的一样 执行lsmod 查看是否有pin4driver驱动模块 若一样和有说明以上操作成功了 - 还需要给pin4这个设备文件一个权限,执行指令:
sudo chmod 666 /dev/pin4 补充:666代表所有人都可以读写pin4 - 运行用户代码
./pin4text ,运行完成后输入指令查看运行完./pin4text后内核打印了什么dmesg | grep pin4 可见,确实是按照我们写好的设备驱动文件打印。 到此,你已经实现了调用自己的设备文件了,然后就可以进行各种骚操作啦
回顾:第二大点灰色款里的第3小点,用户可以通过/dev目录下的这些设备文件访问外部硬件设备,所以我们需要学会这套技能
转载请标明出处,谢谢 作者:星辰~念
|