《拉依达的嵌入式\驱动面试宝典》—Linux篇(三)_Linux 驱动编程
《拉依达的嵌入式\驱动面试宝典》—Linux篇(三)_Linux 驱动编程
你好,我是拉依达。
感谢所有阅读关注我的同学支持,目前博客累计阅读 27w,关注1.5w人。其中博客《最全Linux驱动开发全流程详细解析(持续更新)-CSDN博客》已经是 Linux驱动
相关内容搜索的推荐首位,感谢大家支持。
《拉依达的嵌入式\驱动面试宝典》 最开始我个人的面试学习笔记,里面整合了所有我认为可能遇到的技术面试问题。随着我个人的学习以及参与面试,对内容不断完善。现在我已经步入工作阶段,空闲时间将之前的学习内容进行重新编排整理,并且加入了工作后更新系统的理解。是所有博客中投入最大的一个系列。包含我个人学习中所有的精华内容,希望可以最大限度的帮助到你。
所有问题及其答案均是我个人学习后查阅资料总结,每个回答都做了仔细的分析,在嵌入式及其驱动面试相关问题解析做到独一无二。
其中包含嵌入式软件开发、嵌入式驱动开发、linux驱动开发等职位遇到的所有技术方向问题。**尤其是对驱动等底层问题的解析,适合准备相关工作或者学习提升的同学 **
————————————————————————————————————————————
5.4 Linux 驱动编程
驱动设计思想
(1)机制和策略思想:简化驱动编写的难度,给上层保留尽可能多的灵活性;
(2)分离思想:移植驱动代码时,只需要改动配置参数,不需要改动驱动源码,降低驱动的移植难度;
(3)分层思想:当替换同功能不同款的硬件时,不用改动上层代码;
Linux驱动程序的功能是什么?
驱动与底层硬件直接打交道,充当了硬件与应用软件中间的桥梁。 使得操作系统通过这个驱动程序,才能控制设备硬件的工作
设备驱动的两个任务方向
- 向下,操作硬件
- 向上,将驱动程序通入内核,实现面向操作系统内核的接口内容,接口由操作系统实现
Linux驱动程序的分类有哪些?
Linux驱动分为三个基础大类:字符设备驱动,块设备驱动,网络设备驱动。
字符设备是指那些能一个字节一个字节读取数据的设备,如键盘鼠标等,常见的SPI/I2C/UART默认也是字符设备。
块设备与字符设备类似,一般是像磁盘一样的设备。网络设备主要负责主机之间的数据交换。
与字符设备和块设备完全不同,网络设备主要是面向数据包的接收和发送而设计的。
- 字符设备(Char Device)
- 字符(char)设备是个能够像字节流(类似文件)一样被访问的设备。
- 对字符设备发出读/写请求时,实际的硬件I/O操作一般紧接着发生。
- 字符设备驱动程序通常至少要实现open、close、read和write系统调用。
- 比如我们常见的lcd、触摸屏、键盘、led、串口等等,他们一般对应具体的硬件都是进行出具的采集、处理、传输。
- 块设备(Block Device)
- 一个块设备驱动程序主要通过传输固定大小的数据(一般为512或1k)来访问设备。
- 块设备通过buffer cache(内存缓冲区)访问,可以随机存取,即:任何块都可以读写,不必考虑它在设备的什么地方。
- 块设备可以通过它们的设备特殊文件访问,但是更常见的是通过文件系统进行访问。
- 只有一个块设备可以支持一个安装的文件系统。
- 比如我们常见的电脑硬盘、SD卡、U盘、光盘等。
- 网络设备(Net Device)
- 任何网络事务都经过一个网络接口形成,即一个能够和其他主机交换数据的设备。
- 访问网络接口的方法仍然是给它们分配一个唯一的名字(比如eth0),但这个名字在文件系统中不存在对应的节点。
- 内核和网络设备驱动程序间的通信,完全不同于内核和字符以及块驱动程序之间的通信,内核调用一套和数据包传输相关的函(socket函数)而不是read、write等。
- 比如我们常见的网卡设备、蓝牙设备。
Linux中引入模块机制有什么好处?
- 应用程序在退出时,可以不管资源的释放或者其他的清除工作,而把这些任务交给模块退出函数(exit)。
- 模块机制有助于缩短模块的开发周期,因为模块的安装和卸载都很方便。
- 增加驱动内容可以不用重新编译内核,单独编译模块即可
Linux 驱动有两种运行方式
- 将驱动编译进 Linux 内核中,当 Linux 内核启动的时就会自动运行驱动程序。
- 将驱动编译成模块(Linux 下模块扩展名为.ko),在Linux 内核启动以后使用相应命令加载驱动模块。
Linux内核驱动模块
Linux内核是模块化组成的 ,内核允许运行时动态的插入或删除代码。这些代码被组合打包成单一二进制镜像,即为可装载的内核模块
- module_init()函数:模块装载时被调用,不是真正的函数调用,是一个宏调用。唯一参数是初始化函数,通常注册资源、初始化硬件等。如果驱动编译进内核,则module_init()函数在内核启动时调用。
- module_exit()函数:模块卸载时调用,进行模块的清理工作。如果驱动编译到内核,module_exit()函数永远不会调用,因为内核代码不用卸载。
- MODULE_LICENSE(“GPL”) : 指定模块版权,记录信息
模块操作命令
-
加载模块
-
insmod XXX.ko
为模块分配内核内存、将模块代码和数据装入内存、通过内核符号表解析模块中的内核引用、调用模块初始化函数(module_init)
insmod要加载的模块有依赖模块,且其依赖的模块尚未加载,那么该insmod操作将失败
-
modprobe XXX.ko
加载模块时会同时加载该模块所依赖的其他模块,提供了模块的依赖性分析、错误检查、错误报告
-
-
卸载模块
- rmmod XXX.ko
-
查看模块信息
- lsmod
查看系统中加载的所有模块及模块间的依赖关系 - modinfo (模块路径)
查看详细信息,内核模块描述信息,编译系统信息
- lsmod
linux内核中EXPORT_SYMBOL宏和EXPORT_SYMBOL_GPL宏的作用
模块被加载后,动态链接到内核。只有被显示导出之后的内核函数才能被模块调用。
- EXPORT_SYMBOL宏用于导出内核模块中的全局变量和函数,使其可以被其他模块调用。
- EXPORT_SYMBOL_GPL宏用于导出内核模块中的全局变量和函数,但只能被GPL授权的模块调用。
用户空间与内核通信方式有哪些?
1)系统调用。用户空间进程通过系统调用进入内核空间,访问指定的内核空间数据;
2)驱动程序。用户空间进程可以使用封装后的系统调用接口访问驱动设备节点,以和运行在内核空间的驱动程序通信;
3)共享内存mmap。在代码中调用接口,实现内核空间与用户空间的地址映射,在实时性要求很高的项目中为首选,省去拷贝数据的时间等资源,但缺点是不好控制;
4)copy_to_user()、copy_from_user(),是在驱动程序中调用接口,实现用户空间与内核空间的数据拷贝操作,应用于实时性要求不高的项目中。
系统调用的作用?
- 为应用程序提供访问硬件资源的统一接口,以至于应用程序不必关心具体的硬件操作细节。
- 对系统内核进行保护,保证系统的稳定和安全,因为系统调用规定了用户进程进入内核的具体方式以及所能访问的数据范围。
系统调用与普通函数调用的区别
系统调用:
1.使用INT和IRET指令,内核和应用程序使用的是不同的堆栈,因此存在堆栈的切换,从用户态切换到内核态,从而可以使用特权指令操控设备
2.依赖于内核,不保证移植性
3.在用户空间和内核上下文环境间切换,开销较大
4.是操作系统的一个入口点
普通函数调用:
1.使用CALL和RET指令,调用时没有堆栈切换
2.平台移植性好
3.属于过程调用,调用开销较小
4.一个普通功能函数的调用