仙桃网站网站建设,网站建设是什么时间段申请域名,网站开发教程 模板,品牌策划公司简介1 设备的概念
在学习驱动和其开发之前#xff0c;首先要知道所谓驱动#xff0c;其对象就是设备。
1.1 主设备号次设备号#xff1a;
在Linux中#xff0c;各种设备都以文件的形式存在/dev目录下#xff0c;称为设备文件。最上层的应用程序可以打开#xff0c;关…1 设备的概念
在学习驱动和其开发之前首先要知道所谓驱动其对象就是设备。
1.1 主设备号次设备号
在Linux中各种设备都以文件的形式存在/dev目录下称为设备文件。最上层的应用程序可以打开关闭读写这些设备文件从而完成对设备的操作。
为了管理这些设备系统为设备编了号每个设备都拥有主设备号和次设备号。主设备号用于区分不同种类的设备而次设备号用于区分同一类型的多个设备。对于常用的设备如硬盘Linux赋予的主设备号一般是3
在/dev目录下输入“ls -l”就可以看到设备文件对应的主次设备号 1.2 设备号的用处
在了解了什么是主次设备号之后就要了解设备号的用处
在用户态中当用户调用了如open, read, write等函数想要操作设备文件时需要两个参数第一个是文件名第二个就是设备号在内核态中存在着一个驱动链表用于管理所有设备的驱动而驱动在链表中的位置就由设备号来检索 2 驱动的概念 参考最全Linux驱动开发全流程详细解析持续更新-CSDN博客 Q1什么是驱动 A驱动与底层硬件直接打交道充当了硬件设备与应用软件中间的桥梁 Q2驱动的功能 对设备初始化和释放把数据从内核传送到硬件 从硬件读取数据读取应用程序传送给设备文件的数据 回送应用程序请求的数据检测和处理设备出现的错误 Q3驱动的分类 Linux驱动分为三个基础大类字符设备驱动块设备驱动网络设备驱动。 字符设备(Char Device) 字符设备是个能够像字节流类似文件一样被访问的设备对字符设备发出读/写请求时实际的硬件I/O操作一般紧接着发生字符设备驱动程序通常至少要实现open、close、read和write系统调用比如我们常见的lcd、触摸屏、键盘、led、串口等等他们一般对应具体的硬件都是进行出具的采集、处理、传输 块设备(Block Device) 一个块设备驱动程序主要通过传输固定大小的数据一般为512或1k来访问设备块设备通过buffer cache(内存缓冲区)访问可以随机存取即任何块都可以读写不必考虑它在设备的什么地方块设备可以通过它们的设备特殊文件访问但是更常见的是通过文件系统进行访问只有一个块设备可以支持一个安装的文件系统比如我们常见的电脑硬盘、SD卡、U盘、光盘等 网络设备(Net Device) 任何网络事务都经过一个网络接口形成即一个能够和其他主机交换数据的设备访问网络接口的方法仍然是给它们分配一个唯一的名字比如eth0但这个名字在文件系统中不存在对应的节点内核和网络设备驱动程序间的通信完全不同于内核和字符以及块驱动程序之间的通信内核调用一套和数据包传输相关的函socket函数而不是read、write等比如我们常见的网卡设备、蓝牙设备 回顾上节Linux系统的构造 可见驱动位于内核态上面是系统调用下面是硬件设备。 2.1 上层用户操控设备的流程
每一个系统调用在驱动中都有与之对应的一个驱动函数
以open为例其系统调用为sys_open那么驱动文件中就会有一个与其对应的“实现open的驱动函数xxx_open()”其大致步骤就是用户层C库的open→系统调用层的open→驱动函数的open 以上是一个我作为初学者总结的大概流程下图是一个更详细的流程 2.2 Linux驱动的运行方式
将驱动编译进 Linux 内核中当 Linux 内核启动的时就会自动运行驱动程序将驱动编译成模块Linux 下模块扩展名为.ko并在Linux 内核启动以后使用相应命令加载驱动模块 3 驱动开发实战
尝试使用“将驱动编译成模块的方式”来编写“字符设备的驱动”
驱动开发不是一件容易的事情对于初学者可以先根据一个固定的框架来学习驱动的开发
3.1 基本的字符设备驱动框架
注释很重要认真看
#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_namepin4; //模块名//_open函数
static int pin4_open(struct inode *inode,struct file *file)
{printk(pin4_open\n); //内核的打印函数和printf类似return 0;
}//_write函数
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{printk(pin4_write\n); //内核的打印函数和printf类似return 0;
}static struct file_operations pin4_fops { //结构体的类型是“file_operations”名字可以自定义
//该结构体的成员就包含实现open和write的驱动函数
//当上层用户想要open或者write这个设备时就会最终跳转到这个驱动代码中实现的open和write操作函数
//此处只赋值了该结构体中的三个成员变量在keil中是不能这样写的linux中可以这个结构体其实有很多成员如果想要实现更多的驱动函数可以把更多的该结构体成员赋值并在这段代码中重写.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); //注册驱动告诉内核把这个驱动加入到内核驱动的链表中//以下两句代码目的是“生成设备文件”也可以通过“mknod”命令手动生成但是一般不会这样做pin4_classclass_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); //linux内核遵循GPL协议 在代码中添加大量“static”的原因是内核的代码太多了为了防止出现重名导致歧义加入static可以保证变量的作用域只在当前代码中从而不会影响到其他的代码。 file_operation 结构体是 Linux 内核驱动操作函数集合 比如我在写某设备的驱动文件并想为其实现一个read的驱动函数那么我就需要 定义一个类型为file_operation的结构体XXX其中XXX是自定义的结构体名在XXX结构体中使用“.read XXX_read,”其中XXX_read是自定义的函数名在驱动代码里重写XXX_read函数函数的参数格式和file_operation类型结构体中给出的write成员的参数格式保持一致函数体就是想要实现的具体内容 3.2 驱动的编译
打开虚拟机进入Linux源码的路径 然后进入“drivers/char/”子目录driver驱动char字符型设备 在这个路径下创建一个新的C文件mydriver_pin4.c内容为刚刚的字符驱动框架 修改当前路径下的Makefile确保这个新的驱动会被编译到 回到linux内核源码的路径运行以下指令尝试编译
ARCHarm CROSS_COMPILE/home/mjm/ras_CrossCompile/gcc-linaro-5.1-2015.08-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf- KERNELkernel7 make -j4 modules 回顾在前几节学习内核编译时的代码 ARCHarm CROSS_COMPILE/home/mjm/ras_CrossCompile/gcc-linaro-5.1-2015.08-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf- KERNELkernel7 make -j4 zImage modules dtbs 区别就是代码的最后少了“zImage”和“dtbs”。原因也很简单现在只需要编译驱动不需要再次生成镜像文件等其他文件。 将编译好的“mydriver_pin4.ko”通过以下的scp命令发送到树莓派
scp drivers/char/mydriver_pin4.ko pi192.168.2.26:/home/pi/mjm_code 此时编译好的驱动文件就出现在树莓派上了 3.3 驱动的加载(卸载)
由于现在刚刚把驱动编译成了.ko的模块所以需要运行以下指令来加载驱动模块
sudo insmod mydriver_pin4.ko 补充 驱动的卸载 sudo rmmod mydriver_pin4.ko //此时驱动名字后不用加.ko 查看内核模块 lsmod 运行成功后就可以在/dev下看到生成的设备文件“pin4”了 如果用“ls -l” 就可以看到这个设备文件的主次设备号和框架代码中的设置相同 3.4 驱动的测试
在树莓派下写一个测试驱动的C代码
pin4_test.c:
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include stdio.hint main()
{int driver_fd;driver_fd open(/dev/pin4,O_RDWR); //以可读可写打开的方式打开驱动if(driver_fd 0){perror(fail to open driver file:);}else{printf(open driver file success!\n);}driver_fd write(driver_fd,a,1); //向驱动文件写一个字节if(driver_fd 0){perror(fail to write to driver file:);}else{printf(write success!\n);}return 0;
}
编译然后运行
1. gcc pin4_test.c -o pin4_test
2. sudo ./pin4_test如果加了sudo还是没法运行可以根据错误提示修改可能需要给驱动文件一个执行权限 sudo chmod 666 /dev/pin4
//666代表让所有用户都有所有权限 可见运行成功没有报错此时还可以另开一个窗口输入“dmesg”查看内核打印的信息 可见内核也按照框架代码中的printk成功打印了信息驱动测试成功
同时结果也再次印证了当用户在最上层对 驱动文件 调用C库的open函数后最后的结果还是调用最底层 驱动文件里实现的open驱动函数