Linux面试高频(Linux驱动)
Linux驱动
1 什么是模块?⭐⭐⭐
在 Linux 驱动中,模块是一种具有特定功能的可动态加载和卸载的代码单元。它能在不重新编译内核的情况下,为内核添加新功能或驱动新硬件。比如网卡驱动模块,可使内核支持特定型号的网卡。模块有独立的代码和数据空间,通过特定的接口与内核其他部分交互,如初始化函数用于在加载时进行资源分配等操作,清理函数用于在卸载时释放资源。常见的模块包括设备驱动模块、文件系统模块、网络协议模块等。
2 驱动类型有几种⭐⭐⭐⭐⭐
Linux 驱动类型主要有以下几种:
字符设备驱动:以字符为单位进行数据传输,像键盘、鼠标、串口设备等,应用程序可通过系统调用对其进行读写等操作,通常按字节流方式处理数据。
块设备驱动:以块为单位传输数据,常见的如硬盘、U 盘等存储设备,数据读写以固定大小的块为单位,支持随机访问,能提高数据传输效率。
网络设备驱动:负责网络设备与内核网络子系统间的通信,如网卡驱动,实现数据包的发送和接收,使设备能接入网络进行数据通信。
3 字符设备驱动框架编程流程?⭐⭐⭐⭐⭐
在 Linux 内核中进行字符设备驱动框架编程,一般可以按照以下流程进行:
1. 准备工作
包含必要的头文件:在驱动代码文件开头,包含 Linux 内核中与字符设备驱动开发相关的头文件,例如 <linux/init.h>、<linux/module.h>、<linux/fs.h> 等,这些头文件提供了驱动开发所需的各种数据结构和函数声明
定义必要的全局变量:定义字符设备的主设备号、次设备号、设备类指针、设备指针等全局变量,方便后续在不同函数中使用。
2. 实现文件操作结构体
文件操作结构体 struct file_operations 定义了用户空间对设备文件进行各种操作时对应的内核函数,是字符设备驱动的核心部分。需要实现以下常见的操作函数:
open 函数:当用户空间程序调用 open() 系统调用打开设备文件时,内核会调用该函数,一般用于对设备进行初始化、检查设备状态等操作。
read 函数:对应 read() 系统调用,用于从设备读取数据到用户空间缓冲区。
write 函数:对应 write() 系统调用,用于将用户空间缓冲区的数据写入设备。
release 函数:当用户空间程序调用 close() 系统调用关闭设备文件时,内核会调用该函数,通常用于释放设备占用的资源。
以下是一个简单的文件操作结构体示例:
static struct file_operations my_fops = { .owner = THIS_MODULE, .open = my_open, .read = my_read, .write = my_write, .release = my_release, };
3.分配和注册设备号
设备号用于唯一标识一个字符设备,由主设备号和次设备号组成。可以通过以下两种方式分配设备号:
静态分配:手动指定一个主设备号,但需要确保该主设备号未被其他设备使用,使用 register_chrdev_region() 函数进行注册。
动态分配:由内核自动分配一个可用的主设备号,使用 alloc_chrdev_region() 函数进行分配和注册。
示例代码如下:
static dev_t dev; static int major; // 动态分配设备号 static int __init my_init(void) { if (alloc_chrdev_region(&dev, 0, 1, "my_device") < 0) { printk(KERN_ERR "Failed to allocate device number\n"); return -1; } major = MAJOR(dev); printk(KERN_INFO "Allocated major number: %d\n", major); return 0; }
4.创建设备类和设备节点
创建设备类:使用 class_create() 函数创建一个设备类,设备类是一种抽象的表示,用于将相关的设备组织在一起。
创建设备节点:使用 device_create() 函数在 /dev 目录下创建设备节点,用户空间程序可以通过该设备节点来访问字符设备。
示例代码如下:
static struct class *my_class; static struct device *my_device; static int __init my_init(void) { // 分配设备号代码... // 创建设备类 my_class = class_create(THIS_MODULE, "my_class"); if (IS_ERR(my_class)) { printk(KERN_ERR "Failed to create device class\n"); unregister_chrdev_region(dev, 1); return PTR_ERR(my_class); } // 创建设备节点 my_device = device_create(my_class, NULL, dev, NULL, "my_device"); if (IS_ERR(my_device)) { printk(KERN_ERR "Failed to create device node\n"); class_destroy(my_class); unregister_chrdev_region(dev, 1); return PTR_ERR(my_device); } return 0; }
5.注册字符设备
使用 cdev_init() 函数将文件操作结构体与字符设备对象关联起来,然后使用 cdev_add() 函数将字符设备对象注册到内核中。
示例代码如下:
static struct cdev my_cdev; static int __init my_init(void) { // 分配设备号、创建设备类和设备节点代码... // 初始化字符设备对象 cdev_init(&my_cdev, &my_fops); my_cdev.owner = THIS_MODULE; // 注册字符设备 if (cdev_add(&my_cdev, dev, 1) < 0) { printk(KERN_ERR "Failed to add character device\n"); device_destroy(my_class, dev); class_destroy(my_class); unregister_chrdev_region(dev, 1); return -1; } return 0; }
6.实现初始化和退出函数
初始化函数:在驱动模块加载时,内核会调用初始化函数,在该函数中完成设备号分配、设备类和设备节点创建、字符设备注册等操作。
退出函数:在驱动模块卸载时,内核会调用退出函数,在该函数中完成字符设备注销、设备节点和设备类销毁、设备号释放等操作。
示例代码如下:
static void __exit my_exit(void) { cdev_del(&my_cdev); device_destroy(my_class, dev); class_destroy(my_class); unregister_chrdev_region(dev, 1); printk(KERN_INFO "Character device module unloaded\n"); } module_init(my_init); module_exit(my_exit);
7.编译和加载驱动模块
编写 Makefile:编写 Makefile 文件,指定编译驱动模块所需的内核源码路径和编译规则。
编译驱动模块:在终端中执行 make 命令,编译生成 .ko 格式的驱动模块文件
加载和卸载驱动模块:使用 insmod 命令加载驱动模块,使用 rmmod 命令卸载驱动模块。
通过以上步骤,就可以完成一个基本的字符设备驱动框架的编程。不同的字符设备可能还需要根据具体需求实现更多的功能,如中断处理、设备资源管理等。
4 什么是并发,驱动中产生竞态的原因有哪些?⭐⭐⭐⭐
并发是指在同一时间段内,有多个任务或操作在同时进行或交替执行,这些任务可以是不同的进程、线程或内核中的不同执行路径等。
在驱动中产生竞态的原因主要有以下几点:
多进程或多线程访问:多个进程或线程可能同时访问驱动中的共享资源,如全局变量、设备寄存器等,如果没有适当的同步机制,就会导致数据不一致等竞态问题。
中断处理:中断可能在任意时刻发生,若中断处理程序和其他正常执行路径同时访问同一资源,比如中断处理程序修改了某个设备状态变量,而主程序也在对该变量进行操作,就容易引发竞态。
内核抢占:在支持内核抢占的系统中,一个正在执行的驱动程序可能被更高优先级的任务抢占,当它再次恢复执行时,可能与抢占期间执行的其他代码产生对共享资源的竞态。
5 解决竞态的途径有哪些?分别有什么特点?⭐⭐⭐⭐⭐
在驱动开发中,解决竞态的途径及特点如下:
自旋锁
特点:当一个进程获取自旋锁时,如果锁已被占用,它会持续循环等待,直到锁被释放。优点是开销小、响应快,适用于锁被持有的时间短、不希望进程睡眠的场景。缺点是会浪费 CPU 资源,且不能在持有自旋锁时进行可能导致睡眠的操作。
互斥锁
特点:获取互斥锁的进程若发现锁已被占用,会进入睡眠状态,直到锁被释放。它能避免自旋锁的忙等待问题,适合锁被持有的时间较长的情况。不过,由于涉及进程状态切换,开销相对较大,且不能在中断上下文中使用。
信号量
特点:可
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
该专栏面向嵌入式开发工程师、C++开发工程师,包括C语言、C++,操作系统,ARM架构、RTOS、Linux基础、Linux驱动、Linux系统移植、计算机网络、数据结构与算法、数电基础、模电基础、5篇面试题目、HR面试常见问题汇总和嵌入式面试简历模板等文章。超全的嵌入式软件工程师面试题目和高频知识点总结! 另外,专栏分为两个部分,大家可以各取所好,为了有更好的阅读体验,后面会持续更新!!!