完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
Linux设备驱动:
Linux设备驱动分为以下三类: (1)字符设备:键盘,打印机 (2)块设备:硬盘,NAND (3)网络设备:网卡 对于字符设备是最基本,最常见的设备: 对字符设备的驱动主要完成以下动作: 1、定义一个结构体static struct file_operations变量,其内定义一些设备的open,read,write,close等控制函数 2、在结构体外分别实现结构体中定义的这些函数 3、向内核中注册或删除驱动模块 块设备与字符设备的驱动结构是不同的,但是对于用户来说没有什么区别,块设备比字符设备要复杂,在I/O操作上极为不同表现在缓冲,I/O调度,请求队列等。 1、操作的硬件接口实现不一样; 2、数据块的数据有一定的格式 网络设备不同于字符和块设备,但是有字符和快设备的部分功能 回顾: 1.unix/linux,一切皆文件 应用程序访问设备,设备在用户空间都是以文件的形式 2.设备文件(设备节点) 就是代表着一个要操作访问的设备 /dev/下 字符设备文件 c 204 64 /dev/s3c2410_serial0 c 204 65 /dev/s3c2410_serial1 块设备文件 b 8 1 /dev/sda1 mount -t vfat /dev/sda1 /mnt 问:设备文件如何得到? 3.设备号 主设备号 次设备号 问:驱动程序如何和设备号进行关联和绑定呢? 问:应用程序如何获取设备文件里的设备号的? 问:应用程序如何通过设备号就找到了自己使用的设备驱动呢? 问:应用程序找到驱动程序以后,如何利用驱动实现对设备的读写访问呢? 答1:由于设备号对于内核来说是一种有限的资源,所以 在驱动和设备号进行绑定之前,驱动应该向内核去申请设备号, 申请设备号的方法有静态分配和动态分配: 1.静态分配 步骤: 1.首先通过cat /proc/devices命令查看当前运行中的内核中哪个主设备号是空闲的,如果是空闲状态,驱动就可以向内核申请分配这个主设备号 Character devices: 主设备号 设备名称 1 mem 2 pty 3 ttyp 4 /dev/vc/0 204 s3c2410_serial 2.主设备号通过步骤1可以找到,次设备号根据驱动管理的设备个数来指定,然后通过MKDEV宏构造一个设备号出来 3.调用register_chrdev_region(dev_t dev, int count, char *name); 优点:可以在申请设备号之前创建好设备文件 缺点:如果驱动程序放在别的平台上运行时,有可能主设备号被别的驱动程序所占有,最后引起申请设备号失败!不便于驱动的推广! 2.动态分配:就是让内核帮咱去分配主次设备号信息 步骤: 1.调用alloc_chrdev_region(...)向内核去申请 优点:便于驱动的推广 缺点:不能提前创建好设备节点 案例:要求驱动能够支持动态分配和静态分配两种方法! 提示:采用内核模块参数的知识点! 设备节点创建: 1.手工创建 mknod /dev/mytest c 250 0 < < 应用程序访问字符设备驱动程序的流程: 1.安装字符设备驱动 1.0 分配底层驱动的操作集合 struct file_operations led_fops = { .open = led_open, //底层驱动自己来实现 .read = led_read //底层驱动自己来实现 } 1.1 分配struct cdev 1.2 调用cdev_init初始化cdev cdev->ops = &led_fops 1.3 调用cdev_add将分配好的cdev注册到内核中的一个cdev数组中 以设备号为索引来放! 2.当应用程序调用open时 2.1 应用程序调用open 2.2 保存open对应的系统调用号到寄存器中 2.3 调用swi触发一个软中断异常 2.4 CPU就会调转到内核实现定义好的一个异常向量表的入口地址vector_swi 2.5 根据系统调用号在内核实现准备好的一个系统调用表中找到对应的系统 调用函数sys_open 2.6 在sys_open中首先通过inode->i_rdev获取设备号信息 2.7 根据设备号在内核的cdev数组中找到自己对应的驱动cdev 2.8 找到cdev以后将这个cdev赋值给inode->i_cdev用于缓存 2.9 创建struct file对象,然后将2.7步骤中从已知的cdev中取出底层驱动的 操作集合ops,将这个ops赋值给file->f_op 2.10 如果底层驱动ops中有open函数,那么最终调用底层驱动的open file->f_op->open 2.11 执行完底层驱动的open以后,原路返回给应用程序的open 应用程序要读取设备信息: 1.前提是驱动安装完毕 2.前提是设备open成功,表明inode,file,cdev,file_operations四个结构体已经建立好了关系! 3.应用程序调用read->sys_read: 3.1 获取file 3.2 直接file->f_op->read(); 总结:对于实现一个驱动程序只需围绕着struct cdev和struct file_operations结构体即可 案例:当应用程序调用open时,打开所有的灯 当应用程序调用close时,关闭所有的灯 1.申请设备号 2.分配cdev 3.初始化cdev 指定驱动操作集合struct file_operations (首先分配初始化驱动操作集合) 4.注册cdev 实验步骤: 0.arm-linux-gcc -o led_test led_test.c //交叉编译测试程序 1.insmod led_drv.ko 2.cat /proc/devices 查看分配的设备号 3.mknod /dev/myled c 250 0 //创建设备节点,注意主设备号别弄错 4../led_test 总结: 问:应用程序如何访问设备? 答:设备在linux系统中,以设备文件的形式存在,应用程序访问设备文件就是在访问设备 问:如何创建设备文件? 答:有两种方法: 手工创建:mknod mknod /dev/myled c 250 0 问:主,次设备号,做什么用? 答:主设备号用于应用程序找到驱动程序,次设备号用于驱动区分具体的设备个体 问:设备文件或者普通的文件在linux系统如何去描述呢? 答:struct inode,一个文件有唯一的inode,如果这个文件是设备文件 struct inode { dev_t i_rdev; //如果是设备文件,存放设备文件的设备号 struct cdev *i_cdev; //如果是字符设备文件,存放的是字符设备驱动的 cdev对象的地址 }; 问:如果设备文件在应用程序调用open打开成功以后,linux如何去描述呢? 答:struct file,用来描述文件被打开以后的属性,有内核在内核空间创建 struct file { struct file_operations *f_op; //当应用程序调用open时,最终调用 系统调用的sys_open,sys_open根据inode->i_rdev设备号,根据这个 设备号,在内核全局数组cdev中,找到自己的字符设备驱动cdev, 在从这个cdev的ops(led_fops),最终将驱动的操作集合赋值给f_op } 问:在linux内核中,如何描述一个字符设备驱动呢? 答:struct cdev,用于描述一个字符设备驱动 struct cdev { struct file_operations *ops;//表示这个字符设备驱动具体有哪些底层操 作的方法,初始化通过cdev_init函数来完成 }; 问:由于file,inode的创建和销毁都是内核来实现,但是cdev的创建,销毁必须由驱动开发者本身来实现,那么如何操作cdev呢? 答:如果要描述一个设备驱动是一个字符设备驱动,通过一下几个步骤来实现: 1.struct cdev led_cdev; //分配一个led灯的字符设备驱动对象 2.在驱动的入口函数调用cdev_init完成cdev的初始化 3.调用cdev_add向内核注册1,2分配初始化好的cdev(led_cdev),一旦完成 这种注册,表明内核就存在一个真是的LED灯的字符设备驱动,并且本身也有了一些属性(底层操作的方法ops, 设备号dev,设备的个数count)。内核注册的过程起始就是将分配初始化好的cdev放在一个散列表(数组)中,以设备号为索引! 问:应用程序如何找到自己的cdev,本质上其实要找的是cdev附加的底层操作集合ops 答:应用程序通过inode->i_rdev(设备号),以设备号为索引在数组cdev中找到 自己的cdev(led_cdev),然后将led_cdev的ops赋值给file->f_op 问:如果一旦将底层驱动的file_operations硬件操作集合赋值给file->f_op,将来会有什么作用呢? 答: open->sys_open->file->f_op->open = led_open close->sys_close->file->f_op->release = led_close read->sys_read->file->f_op->read = led_read write->sys_write->file->f_op->write = led_write ... 问:UC的read,write如何使用? 答: char buf[1024] = {0}; int fd; fd = open(...) read(fd, buf, 1024); //从设备读数据 write(fd, "hello..", size); //写数据到设备中 案例:当应用程序调用write向驱动写入1时,打开所有的灯 当应用程序调用write向驱动写入0时,关闭所有的灯 当应用程序调用read时,能够获取灯的开关状态 int cmd; cmd = 1; write(fd, &cmd, sizeof(cmd));->led_write cmd = 0; write(fd, &cmd, sizeof(cmd)); 案例:要求能够指定某一个灯的开光状态 struct led_cmd { int cmd; //开关命令 int index; //哪个灯 }; ./led_test on 1 ./led_test on 2 ./led_test off 1 ./led_test off 2 创建设备节点: 1.cat /proc/devices //查看内核帮你分配的主设备号 2.mknod /dev/myled c 250 0 //设备文件名myled给应用程序使用 案例:自己编写驱动和测试代码通过write和read实现操作蜂鸣器 beep_drv.c beep_test.c ./beep_test on ./beep_test off 使用的GPIO:GPD0_1 案例:编写驱动和测试能够读取按键的状态信息,如果按键松开,驱动上报0x51 如果按键按下,驱动上报0x50 btn_drv.c btn_test.c 使用管脚:GPH0_0 总结:字符设备驱动开发涉及的四个重要的结构体 struct inode struct file struct cdev struct file_operations 面试题:应用程序如何访问字符设备驱动 1.应用到系统调用 open->sys_open 2.系统调用到驱动 sys_open->led_open 原文转自来自http://www.arm8.net/thread-440-1-1.html |
|
|
|
【社区之星】李静:不断学习,去追随新威廉希尔官方网站 的脚步,将知识通过实践转换为能力
1942 浏览 0 评论
3222 浏览 0 评论
快速部署!米尔全志T527开发板的OpenCV行人检测方案指南
11772 浏览 0 评论
3298 浏览 0 评论
边缘设备的奇妙之旅:在小凌派-RK2206上部署AI模型来实现视觉巡线
1714 浏览 0 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2025-7-18 19:17 , Processed in 0.576395 second(s), Total 66, Slave 49 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191