Linux面试题整理(二):Linux驱动面试问题整理
linux-driver
interview-001
Linux设备驱动分类,阐述它们的区别,并分别列举一些实际设备。
- Linux主要的设备包含字符设备,块设备和网络设备
- 字符设备以字节为单位进行数据传输,顺序访问,通常没有缓冲区,常用的字符流设备有按键,LED灯,串口,鼠标等
- 块设备以数据块为单位进行数据传输,随机访问,通常有缓冲区,可以进行文件系统的挂载,常用的块流设备有U盘,SD卡,EMMC,固态硬盘等
- 网络设备不直接对应文件系统中的文件,基于网络协议(TCP/IP,蓝牙,CAN等),通常不支持随机访问,常用的网络设备有网卡,WIFI模组,蓝牙模组,CAN等
interview-002
Linux中引入模块机制的好处。
- 动态扩展内核功能,模块机制允许开发者在不需要重新编译整个内核的情况下,动态地向内核添加或移除功能
- 优化内核大小,通过模块机制,Linux内核可以实现最小化,即内核中只包含一些基本功能,如模块管理、内存管理等
- 便于开发和调试,模块机制使得开发者可以独立地开发和调试各个功能模块,而不会影响到整个内核的稳定性
- 提高系统稳定性,由于模块机制允许在运行时动态加载和卸载功能模块,因此可以在不重启系统的情况下修复或更新内核中的某些功能模块。这有助于提高系统的稳定性,减少因内核更新而导致的系统停机时间
interview-003
说明驱动模块的操作有哪些命令,加载和移除对应驱动中的接口。
驱动加载和移除的命令包含如下。
- insmod,加载模块,执行驱动中的module_init指定的函数
- rmmod,移除模块,执行驱动中的module_exit指定的函数
- lsmod,查看系统已经加载的模块
- depmod,扫描/lib/modules下的所有模块文件,会在/lib/modules/$(uname -r)目录下生成模块依赖关系
- modprobe,提供了加载、卸载和查询内核模块的功能,根据生成的模块依赖关系,加载和移除相应的模块
interview-004
查看驱动打印信息的命令,如何查看内核中已注册的字符设备和中断号。
- 查看驱动模块打印信息使用dmesg命令,配合grep可以查询指定的驱动打印信息,当然也可以修改”/proc/sys/kernel/printk”打印所有信息查看
- 通过”cat /proc/deivces”查看内核已注册的字符设备
- 通过”cat /proc/interrupts”查看正在使用的中断号
interview-005
字符型驱动设备创建设备文件的方法。
- 使用mknod手动创建设备文件,mknod <设备文件名> <设备类型> <主设备号> <子设备号>,举例:mknod /dev/chardev c 4 46子设备号>主设备号>设备类型>设备文件名>
- 在内核中使用class_create来创建设备类,然后通过device_create创建设备,关联到已经注册字符设备的设备号
interview-006
简述主设备号和次设备号的用途。执行mknod chardev c 4 46,请分析使用的是那一类设备驱动程序。
- 主设备号用于区分不同种类的设备,次设备号用于区分同一个类型的多个设备
- 常见字符设备主设备号代表类型, 1-内存设备,4-串口tty设备,5-console设备,10-杂项设备,13-输入设备,因此chartest主设备号为4,表示串口tty设备
interview-007
驱动注册字符设备的相关函数和接口说明。
注册字符设备的相关函数包含如下。
//根据范围注册申请一组设备号(from为起始的主从设备号),仅申请设备号,设备后续创建
int register_chrdev_region(dev_t from, unsigned count, const char *name);
//动态申请一组设备号值
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
//初始化字符型设备结构体
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
- cdev: 字符设备结构体
- fops: 设备支持的访问接口结构体
//将申请设备号和字符型设备功能结构在内核中添加并绑定
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
- p: 字符设备结构体
- dev: 关联设备号
- count: 添加内核的设备数目,用于分配连续的次设备号
interview-008
简述copy_to_user和copy_from_user主要功能,一般用于file_operations结构的哪些函数。
- copy_to_user和copy_from_user是Linux内核中的关键函数,它们主要用于在用户空间和内核空间之间安全地传输数据,其中copy_to_user用于用于将数据从内核空间复制到用户空间,copy_from_user函数用于将数据从用户空间复制到内核空间。
- file_operation中的ioctl,read, write函数会用到这两个函数,用于实现应用层和内核之间的数据交互。
interview-009
简述设备驱动模型构成,platform总线的匹配规则,注册驱动和注册设备需要先后顺序吗?
- 设备驱动模型由总线,设备,驱动三部分组成
- Platform总线是Linux内核中用于管理不直接挂接在物理总线上的设备(如LED、LCD、RTC等)的一种平台总线,其主要匹配支持platform访问的节点(一般为根节点,或者在其compatible属性中包含”simple-bus”, “simple-mfd”, “isa”, “arm,amba-bus”节点的子节点),与可访问设备字节中的compatible属性字符串匹配则加载驱动。
- 对于片上设备和大部分板级设备,都是需要先注册设备,再注册驱动。部分支持热插拔的设备(USB,HDMI)等,则是先注册驱动,当设备插入时才注册设备,匹配驱动执行
interview-010
简述下Linux中断机制,注册和注销中断的相关函数。中断和轮询哪个效率高?怎样决定是采用中断方式还是采用轮询方式去实现驱动。
- Linux中断机制是指当CPU在执行程序的过程中,出现了某些突发事件急待处理时,CPU会暂停当前程序的执行,转去处理突发事件,处理完毕后又返回原程序被中断的位置继续执行。对于Linux中断,一般由外设模块触发,上报对应的中断控制器GIC,最终由相应的CPU内核进行处理
- 注册和注销中断的相关函数主要有request_irq和free_irq,其中request_irq函数用于向内核注册一个中断处理函数,free_irq函数用于注销一个已经注册的中断处理函数
- 中断是CPU处于被动状态下来接受设备的信号,具有即时响应和高效利用CPU的优点,轮询则是CPU主动去查询设备是否有请求,实现相对简单。大部分设备请求频率较低,适合使用中断的方式请求;对于频繁请求CPU的设备或者有大量数据请求的网络设备,可能需要CPU频繁地处理数据,轮询反而效率更高。
interview-011
IRQ(Interrupt Request)和FIQ(Fast Interrupt Request)在CPU实现中有什么区别。
- 优先级。FIQ具有更高的优先级,当FIQ发生时,CPU会在当前指令执行完成后立即响应FIQ中断,而忽略其他IRQ中断
- 上下文保存。FIQ模式会保存完整的CPU上下文,包括更多的寄存器状态,以确保在中断处理完成后能够恢复到正确的执行状态。以确保在中断处理完成后能够恢复到正确的执行状态
- 使用场景。IRQ适用于一般性的外部设备中断处理,如定时器中断、串口中断、按键中断等;FIQ适用于对实时性要求非常高的事件处理,如快速的数据传输、紧急的硬件错误处理、实时操作系统中的任务调度等
- IRQ中断处理程序相对简单,因为它可以使用通用的寄存器和堆栈,并且可以在中断处理程序中调用其他函数。FIQ中断处理程序相对复杂,因为它需要使用专门的寄存器和堆栈,并且不能在中断处理程序中调用其他函数,否则会破坏寄存器和堆栈的状态
interview-012
中断的为啥分上半部分和下半部分,讲下如何实现。
- 中断分为上半部分和下半部分主要是为了提高系统效率和响应性,同时尽量减少中断处理对其他任务的干扰。这种设计允许中断处理程序快速响应中断并处理最关键和紧急的任务(上半部),而将那些不那么紧急或耗时的任务推迟到稍后执行。
- 上半部是中断处理的第一阶段,它通常在硬件中断触发后立即执行。上半部的主要任务是:尽快响应中断,处理最关键和紧急的任务,必须在中断上下文中执行,执行速度要快,不能阻塞(如不能调用睡眠函数),不能抢占(不会被其他中断打断)。
- 下半部是在中断响应结束后执行的,它负责处理那些不那么紧急的任务。下半部可以延迟执行,因此可以在更合适的时机被调度。下半部可以使用软中断,tasklet,workqueue等实现
interview-013
驱动使用什么函数接口访问绝对物理地址,为什么需要。
- 驱动中使用ioremap函数将物理地址映射到内核的虚拟地址空间中,使得内核可以通过虚拟地址来访问物理内存。
- 现代操作系统中,为了提供进程隔离、隐藏内存细节、增强可扩展性和提供存储保护,通常都会引入内存管理单元(MMU)。MMU的主要功能包括虚拟到物理地址的转换和内存保护。在启用MMU的系统中,内核中操作的都是虚拟地址,而不是物理地址。要访问物理内存,就必须先将物理地址映射为虚拟地址。内核不能直接访问物理地址空间,而需要通过某种机制将物理地址映射到内核地址空间中,然后才能对物理内存进行访问。
void __iomem *iomem; // 映射物理内存到虚拟地址空间
iomem = ioremap(DEVICE_PHYS_ADDR, DEVICE_SIZE);
interview-014
什么是内存屏障。常见的内存屏障有哪些,应用场景是什么。
内存屏障(Memory Barrier)是一种硬件或软件机制,用于确保特定的内存操作按照预期的顺序执行。在多处理器系统或多线程环境中,由于处理器和编译器的优化,内存操作的执行顺序可能与程序代码中的顺序不一致,这可能导致数据竞争和其他并发问题。内存屏障的作用就是强制内存操作按照特定的顺序执行,从而保证程序的正确性。对于ARM核来说,内存屏障的实现方法主要基于DMB(数据内存屏障),DSB(数据同步屏障)和DIB(指令同步屏障)。
常见的内存屏障有读屏障,写屏障和全屏障。
内存屏障主要应用场景如下所示。
- 多线程编程,确保共享变量的读写操作按照正确的顺序执行,避免数据竞争
- 硬件设备驱动,确保对硬件寄存器的读写操作按照正确的顺序执行,避免硬件错误
- 编译器优化中,防止对内存操作的重排序,保证程序的正确性