龙旗科技Linux驱动开发 二面试题
1. 详细介绍你做过的Linux驱动项目,架构设计和技术难点
答案要点:
- 项目背景:开发XX设备驱动(传感器/显示屏/摄像头),基于XX平台(高通/MTK/RK),内核版本
- 驱动架构: 设备类型:字符设备/平台设备/I2C设备/SPI设备分层设计:应用层接口、驱动核心层、硬件抽象层使用的子系统:input子系统/V4L2/IIO等
- 技术难点: 中断处理:高频中断优化,上下半部设计并发控制:多进程访问同步,使用的锁机制性能优化:DMA传输、缓冲区设计、功耗优化调试定位:死锁、内存泄漏、时序问题
- 项目成果:稳定性测试结果、性能指标、解决的关键问题
2. I2C驱动的工作原理?如何编写I2C设备驱动?
答案要点:
1. I2C总线特点
- 两线制:SCL(时钟)、SDA(数据),支持多主多从
- 7位/10位设备地址,速率:标准100Kbps、快速400Kbps、高速3.4Mbps
- 起始/停止条件、应答机制
2. Linux I2C驱动框架
- I2C核心层:提供统一接口
- I2C总线驱动(adapter):控制I2C控制器硬件
- I2C设备驱动(client):控制具体I2C设备
3. 编写I2C设备驱动
// 定义设备ID表
static const struct i2c_device_id mydev_id[] = {
{ "mydevice", 0 },
{ }
};
// 设备树匹配表
static const struct of_device_id mydev_of_match[] = {
{ .compatible = "vendor,mydevice", },
{ }
};
// probe函数
static int mydev_probe(struct i2c_client *client,
const struct i2c_device_id *id) {
struct mydev_data *data;
u8 buf[2];
// 分配私有数据
data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
i2c_set_clientdata(client, data);
// 读取设备ID
i2c_smbus_read_byte_data(client, REG_ID);
// 或使用i2c_transfer
struct i2c_msg msg = {
.addr = client->addr,
.flags = I2C_M_RD,
.len = 2,
.buf = buf,
};
i2c_transfer(client->adapter, &msg, 1);
return 0;
}
// 驱动结构
static struct i2c_driver mydev_driver = {
.driver = {
.name = "mydevice",
.of_match_table = mydev_of_match,
},
.probe = mydev_probe,
.remove = mydev_remove,
.id_table = mydev_id,
};
module_i2c_driver(mydev_driver);
4. 常用I2C操作函数
i2c_smbus_read_byte_data()/i2c_smbus_write_byte_data()i2c_transfer():更灵活,支持复杂时序
3. SPI驱动的工作原理?与I2C有什么区别?
答案要点:
1. SPI总线特点
- 四线制:SCLK(时钟)、MOSI(主出从入)、MISO(主入从出)、CS(片选)
- 全双工通信,速度更快(可达几十MHz)
- 主从模式,一主多从(通过不同CS选择)
2. SPI与I2C对比
线数 |
4线(+每个从设备1根CS) |
2线 |
速度 |
更快(几十MHz) |
较慢(400Kbps) |
通信方式 |
全双工 |
半双工 |
硬件复杂度 |
较高(需要更多引脚) |
较低 |
应用场景 |
高速设备(Flash、LCD) |
低速设备(传感器、EEPROM) |
3. SPI驱动编写
// 设备树匹配
static const struct of_device_id mydev_of_match[] = {
{ .compatible = "vendor,mydevice", },
{ }
};
// probe函数
static int mydev_probe(struct spi_device *spi) {
struct mydev_data *data;
u8 tx_buf[2] = {0x01, 0x02};
u8 rx_buf[2];
// 配置SPI参数
spi->mode = SPI_MODE_0; // CPOL=0, CPHA=0
spi->bits_per_word = 8;
spi->max_speed_hz = 1000000; // 1MHz
spi_setup(spi);
// 写数据
spi_write(spi, tx_buf, 2);
// 读数据
spi_read(spi, rx_buf, 2);
// 全双工传输
struct spi_transfer xfer = {
.tx_buf = tx_buf,
.rx_buf = rx_buf,
.len = 2,
};
struct spi_message msg;
spi_message_init(&msg);
spi_message_add_tail(&xfer, &msg);
spi_sync(spi, &msg);
return 0;
}
static struct spi_driver mydev_driver = {
.driver = {
.name = "mydevice",
.of_match_table = mydev_of_match,
},
.probe = mydev_probe,
.remove = mydev_remove,
};
module_spi_driver(mydev_driver);
4. SPI模式
- Mode 0:CPOL=0, CPHA=0(空闲低电平,第一个边沿采样)
- Mode 1:CPOL=0, CPHA=1
- Mode 2:CPOL=1, CPHA=0
- Mode 3:CPOL=1, CPHA=1
4. 字符设备驱动的编写流程?file_operations结构体的作用?
答案要点:
1. 字符设备驱动框架
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
static dev_t dev_num;
static struct cdev my_cdev;
static struct class *my_class;
// 实现文件操作函数
static int my_open(struct inode *inode, struct file *filp) {
printk("Device opened\n");
return 0;
}
static ssize_t my_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos) {
char kernel_buf[100] = "Hello from kernel";
if (copy_to_user(buf, kernel_buf, count))
return -EFAULT;
return count;
}
static ssize_t my_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos) {
char kernel_buf[100];
if (copy_from_user(kernel_buf, buf, count))
return -EFAULT;
printk("Received: %s\n", kernel_buf);
return count;
}
static int my_release(struct inode *inode, struct file *filp) {
printk("Device closed\n");
return 0;
}
// file_operations结构体
static struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = my_open,
.read = my_read,
.write = my_write,
.release = my_release,
};
// 初始化函数
static int __init my_init(void) {
// 1. 分配设备号
alloc_chrdev_region(&dev_num, 0, 1, "mydevice");
// 2. 初始化cdev
cdev_init(&my_cdev, &my_fops);
my_cdev.owner = THIS_MODULE;
// 3. 添加cdev到系统
cdev_add(&my_cdev, dev_num, 1);
// 4. 创建设备类
my_class = class_create(THIS_MODULE, "myclass");
// 5. 创建设备节点 /dev/mydevice
device_create(my_class, NULL, dev_num, NULL, "mydevice");
return 0;
}
// 退出函数
static void __exit my_exit(void) {
device_destroy(my_class, dev_num);
class_destroy(my_class);
cdev_del(&my_cdev);
unregister_chrdev_region(dev_num, 1);
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
2. file_operations常用成员
open:打开设备read:读取数据write:写入数据unlocked_ioctl:设备控制mmap:内存映射poll:轮询release:关闭设备
5. 中断处理的上半部和下半部?tasklet和workqueue的区别?
答案要点:
1. 为什么分上下半部
- 中断处理要快,不能长时间占用CPU
- 上半部(硬中断):紧急处理,关中断执行,尽快返回
- 下半部(软中断):延迟处理,开中断执行,可以被打断
2. tasklet实现
// 定义tasklet
static void my_tasklet_func(unsigned long data) {
// 延迟处理的工作
printk("Tasklet running\n");
}
DECLARE_TASKLET(my_tasklet, my_tasklet_func, 0);
// 中断处理函数
static irqreturn_t my_irq_handler(int irq, void *dev_id) {
// 上半部:快速处理
// 读取硬件状态,清除中断标志
// 调度tasklet
tasklet_schedule(&my_tasklet);
return IRQ_HANDLED;
}
3. workqueue实现
// 定义工作队列
static struct work_struct my_work;
static void my_work_func(struct work_struct *work) {
// 可以睡眠的延迟处理
msleep(100); // 允许睡眠
printk("Work running\n");
}
// 初始化
INIT_WORK(&my_work, my_work_func);
// 中断中调度
static irqreturn_t my_irq_
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
嵌入式面试八股文全集 文章被收录于专栏
这是一个全面的嵌入式面试专栏。主要内容将包括:操作系统(进程管理、内存管理、文件系统等)、嵌入式系统(启动流程、驱动开发、中断管理等)、网络通信(TCP/IP协议栈、Socket编程等)、开发工具(交叉编译、调试工具等)以及实际项目经验分享。专栏将采用理论结合实践的方式,每个知识点都会附带相关的面试真题和答案解析。

