Linux驱动基础
1.私有数据
在Linux驱动开发中,私有数据(private data) 是一个重要概念,它允许驱动程序为每个设备实例存储特定的信息。以下是详细说明:
1. 私有数据的概念
私有数据是一个指向任意数据结构的指针,通常用于:
- 存储设备特定的信息
- 在文件操作函数之间传递数据
- 管理设备的硬件资源
- 保持设备状态信息
2. 设置和获取私有数据
2.1 在设备结构体中设置私有数据
#include <linux/fs.h>
#include <linux/cdev.h>
struct my_device_data {
struct cdev cdev;
int device_id;
void __iomem *reg_base;
spinlock_t lock;
// 其他设备特定数据
};
static int my_open(struct inode *inode, struct file *filp)
{
struct my_device_data *dev_data;
// 从inode获取cdev,然后获取包含cdev的设备结构体
dev_data = container_of(inode->i_cdev, struct my_device_data, cdev);
// 将设备数据存储为文件的私有数据
filp->private_data = dev_data;
return 0;
}
2.2 在probe函数中设置
static int my_probe(struct platform_device *pdev)
{
struct my_device_data *dev_data;
int ret;
// 分配设备数据结构
dev_data = devm_kzalloc(&pdev->dev, sizeof(*dev_data), GFP_KERNEL);
if (!dev_data)
return -ENOMEM;
// 初始化设备数据
dev_data->device_id = 0;
spin_lock_init(&dev_data->lock);
// 将私有数据存储到平台设备中
platform_set_drvdata(pdev, dev_data);
// 硬件初始化
dev_data->reg_base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(dev_data->reg_base))
return PTR_ERR(dev_data->reg_base);
return 0;
}
3. 在文件操作中使用私有数据
static ssize_t my_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos)
{
struct my_device_data *dev_data = filp->private_data;
ssize_t ret;
if (!dev_data)
return -ENODEV;
spin_lock(&dev_data->lock);
// 使用设备数据进行读取操作
// ...
spin_unlock(&dev_data->lock);
return ret;
}
static ssize_t my_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos)
{
struct my_device_data *dev_data = filp->private_data;
if (!dev_data)
return -ENODEV;
// 使用设备数据进行写入操作
// ...
return count;
}
static int my_release(struct inode *inode, struct file *filp)
{
// 清除私有数据引用
filp->private_data = NULL;
return 0;
}
static const struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = my_open,
.release = my_release,
.read = my_read,
.write = my_write,
// 其他操作...
};
4. 完整的驱动示例
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#define DEVICE_NAME "mydevice"
#define MAX_DEVICES 2
struct my_device_data {
struct cdev cdev;
struct device *device;
int device_id;
unsigned long usage_count;
spinlock_t lock;
char buffer[1024];
};
static int major;
static struct class *my_class;
static struct my_device_data devices[MAX_DEVICES];
static int my_open(struct inode *inode, struct file *filp)
{
struct my_device_data *dev_data;
dev_data = container_of(inode->i_cdev, struct my_device_data, cdev);
filp->private_data = dev_data;
spin_lock(&dev_data->lock);
dev_data->usage_count++;
spin_unlock(&dev_data->lock);
printk(KERN_INFO "Device %d opened, usage count: %lu\n",
dev_data->device_id, dev_data->usage_count);
return 0;
}
static int my_release(struct inode *inode, struct file *filp)
{
struct my_device_data *dev_data = filp->private_data;
spin_lock(&dev_data->lock);
dev_data->usage_count--;
spin_unlock(&dev_data->lock);
printk(KERN_INFO "Device %d closed, usage count: %lu\n",
dev_data->device_id, dev_data->usage_count);
return 0;
}
static ssize_t my_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos)
{
struct my_device_data *dev_data = filp->private_data;
ssize_t ret;
if (*f_pos >= sizeof(dev_data->buffer))
return 0;
if (*f_pos + count > sizeof(dev_data->buffer))
count = sizeof(dev_data->buffer) - *f_pos;
if (copy_to_user(buf, dev_data->buffer + *f_pos, count))
return -EFAULT;
*f_pos += count;
return count;
}
static ssize_t my_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos)
{
struct my_device_data *dev_data = filp->private_data;
if (*f_pos >= sizeof(dev_data->buffer))
return -ENOSPC;
if (*f_pos + count > sizeof(dev_data->buffer))
count = sizeof(dev_data->buffer) - *f_pos;
if (copy_from_user(dev_data->buffer + *f_pos, buf, count))
return -EFAULT;
*f_pos += count;
return count;
}
static const struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = my_open,
.release = my_release,
.read = my_read,
.write = my_write,
};
static int __init my_init(void)
{
int i, ret;
dev_t dev;
// 分配设备号
ret = alloc_chrdev_region(&dev, 0, MAX_DEVICES, DEVICE_NAME);
if (ret < 0)
return ret;
major = MAJOR(dev);
// 创建设备类
my_class = class_create(THIS_MODULE, DEVICE_NAME);
if (IS_ERR(my_class)) {
unregister_chrdev_region(dev, MAX_DEVICES);
return PTR_ERR(my_class);
}
// 初始化每个设备
for (i = 0; i < MAX_DEVICES; i++) {
devices[i].device_id = i;
spin_lock_init(&devices[i].lock);
devices[i].usage_count = 0;
memset(devices[i].buffer, 0, sizeof(devices[i].buffer));
// 初始化cdev
cdev_init(&devices[i].cdev, &my_fops);
devices[i].cdev.owner = THIS_MODULE;
// 添加cdev到系统
ret = cdev_add(&devices[i].cdev, MKDEV(major, i), 1);
if (ret) {
printk(KERN_ERR "Failed to add cdev for device %d\n", i);
continue;
}
// 创建设备节点
devices[i].device = device_create(my_class, NULL,
MKDEV(major, i), NULL,
"mydevice%d", i);
if (IS_ERR(devices[i].device)) {
cdev_del(&devices[i].cdev);
printk(KERN_ERR "Failed to create device for device %d\n", i);
}
}
printk(KERN_INFO "My device driver loaded\n");
return 0;
}
static void __exit my_exit(void)
{
int i;
dev_t dev = MKDEV(major, 0);
for (i = 0; i < MAX_DEVICES; i++) {
if (devices[i].device)
device_destroy(my_class, MKDEV(major, i));
cdev_del(&devices[i].cdev);
}
class_destroy(my_class);
unregister_chrdev_region(dev, MAX_DEVICES);
printk(KERN_INFO "My device driver unloaded\n");
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Example driver using private data");
5. 关键要点
- 线程安全:在多线程环境中访问私有数据时,需要使用适当的锁机制
- 内存管理:确保在设备生命周期内私有数据保持有效
- 错误处理:始终检查私有数据指针是否为NULL
- 资源清理:在模块卸载时正确释放所有资源
私有数据是Linux驱动开发中的核心概念,正确使用它可以创建更加模块化和可维护的驱动程序。
2.Linux并发与竞争
1. 并发与竞争概述
1.1 基本概念
- 并发:多个执行单元同时或并行执行
- 竞争:多个执行单元对共享资源的同时访问导致的不确定性
- 临界区:访问共享资源的代码段
- 竞态条件:执行结果依赖于并发执行顺序的情况
1.2 并发来源
/* 并发的主要来源 */
1. 多核CPU真正并行执行
2. 单核CPU上的任务抢占
3. 中断处理程序
4. 底半部机制(软中断、tasklet、工作队列)
5. 内核抢占
6. 睡眠与唤醒
2. 内核同步机制
2.1 原子操作
2.1.1 整型原子操作
#include <linux/atomic.h>
atomic_t counter = ATOMIC_INIT(0);
// 基本操作
void atomic_set(atomic_t *v, int i);
int atomic_read(atomic_t *v);
// 算术操作
void atomic_add(int i, atomic_t *v);
void atomic_sub(int i, atomic_t *v);
void atomic_inc(atomic_t *v);
void atomic_dec(atomic_t *v);
// 返回操作结果
int atomic_inc_return(atomic_t *v);
int atomic_dec_return(atomic_t *v);
// 测试操作
int atomic_inc_and_test(atomic_t *v); // 结果为0返回1
int atomic_dec_and_test(atomic_t *v); // 结果为0返回1
2.1.2 位原子操作
#include <linux/bitops.h>
unsigned long word = 0;
void set_bit(int nr, volatile unsigned long *addr);
void clear_bit(int nr, volatile unsigned long *addr);
void change_bit(int nr, volatile unsigned long *addr);
int test_bit(int nr, volatile unsigned long *addr);
int test_and_set_bit(int nr, volatile unsigned long *addr);
int test_and_clear_bit(int nr, volatile unsigned long *addr);
2.2 自旋锁
2.2.1 基本自旋锁
#include <linux/spinlock.h>
DEFINE_SPINLOCK(my_lock);
unsigned long flags;
// 获取锁
spin_lock(&my_lock);
// 临界区
spin_unlock(&my_lock);
// 中断上下文安全版本
spin_lock_irqsave(&my_lock, flags); // 保存中断状态并禁用中断
// 临界区
spin_unlock_irqrestore(&my_lock, flags); // 恢复中断状态
// 简单版本(已知中断未使能)
spin_lock_irq(&my_lock);
spin_unlock_irq(&my_lock);
2.2.2 读写自旋锁
#include <linux/rwlock.h>
DEFINE_RWLOCK(my_rwlock);
// 读者
read_lock(&my_rwlock);
// 读取临界区
read_unlock(&my_rwlock);
// 写者
write_lock(&my_rwlock);
// 写入临界区
write_unlock(&my_rwlock);
2.3 信号量
2.3.1 计数信号量
#include <linux/semaphore.h>
struct semaphore my_sem;
// 初始化
sema_init(&my_sem, 1); // 二进制信号量
sema_init(&my_sem, 5); // 计数信号量
// 获取信号量
void down(struct semaphore *sem); // 不可中断
int down_interruptible(struct semaphore *sem); // 可被信号中断
int down_trylock(struct semaphore *sem); // 非阻塞
// 释放信号量
void up(struct semaphore *sem);
2.3.2 互斥信号量
#include <linux/mutex.h>
struct mutex my_mutex;
// 初始化
mutex_init(&my_mutex);
// 使用
mutex_lock(&my_mutex);
// 临界区
mutex_unlock(&my_mutex);
// 非阻塞版本
if (mutex_trylock(&my_mutex)) {
// 临界区
mutex_unlock(&my_mutex);
} else {
// 处理无法获取锁的情况
}
2.4 完成量
#include <linux/completion.h>
struct completion my_completion;
// 初始化
init_completion(&my_completion);
// 等待完成
wait_for_completion(&my_completion);
// 通知完成
complete(&my_completion); // 唤醒一个等待者
complete_all(&my_completion); // 唤醒所有等待者
2.5 读写信号量
#include <linux/rwsem.h>
struct rw_semaphore my_rwsem;
// 初始化
init_rwsem(&my_rwsem);
// 读者
down_read(&my_rwsem);
// 读取临界区
up_read(&my_rwsem);
// 写者
down_write(&my_rwsem);
// 写入临界区
up_write(&my_rwsem);
3. 实际应用示例
3.1 设备驱动中的并发控制
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/spinlock.h>
#include <linux/semaphore.h>
struct my_device {
struct cdev cdev;
atomic_t open_count;
spinlock_t data_lock;
struct mutex io_mutex;
struct semaphore sem;
char *buffer;
size_t buffer_size;
};
static struct my_device my_dev;
static int my_open(struct inode *inode, struct file *filp)
{
// 使用原子操作计数
if (atomic_inc_return(&my_dev.open_count) > 1) {
atomic_dec(&my_dev.open_count);
return -EBUSY;
}
filp->private_data = &my_dev;
return 0;
}
static int my_release(struct inode *inode, struct file *filp)
{
atomic_dec(&my_dev.open_count);
return 0;
}
static ssize_t my_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos)
{
struct my_device *dev = filp->private_data;
unsigned long flags;
ssize_t ret;
// 使用互斥锁保护IO操作
if (mutex_lock_interruptible(&dev->io_mutex))
return -ERESTARTSYS;
// 使用自旋锁保护共享数据
spin_lock_irqsave(&dev->data_lock, flags);
// 读取数据到用户空间
if (count > dev->buffer_size - *f_pos)
count = dev->buffer_size - *f_pos;
if (copy_to_user(buf, dev->buffer + *f_pos, count)) {
ret = -EFAULT;
} else {
*f_pos += count;
ret = count;
}
spin_unlock_irqrestore(&dev->data_lock, flags);
mutex_unlock(&dev->io_mutex);
return ret;
}
static ssize_t my_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos)
{
struct my_device *dev = filp->private_data;
unsigned long flags;
ssize_t ret;
// 使用信号量限制并发写入者数量
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
// 使用互斥锁和自旋锁
mutex_lock(&dev->io_mutex);
spin_lock_irqsave(&dev->data_lock, flags);
if (copy_from_user(dev->buffer + *f_pos, buf, count)) {
ret = -EFAULT;
} else {
*f_pos += count;
ret = count;
}
spin_unlock_irqrestore(&dev->data_lock, flags);
mutex_unlock(&dev->io_mutex);
up(&dev->sem);
return ret;
}
3.2 中断处理中的并发
#include <linux/interrupt.h>
struct my_device_data {
spinlock_t lock;
struct work_struct work;
struct workqueue_struct *wq;
void __iomem *reg_base;
};
static irqreturn_t my_interrupt_handler(int irq, void *dev_id)
{
struct my_device_data *dev = dev_id;
unsigned long flags;
// 在中断处理中使用自旋锁
spin_lock_irqsave(&dev->lock, flags);
// 读取中断状态
// 清除中断标志
// 处理紧急事务
spin_unlock_irqrestore(&dev->lock, flags);
// 调度底半部处理
queue_work(dev->wq, &dev->work);
return IRQ_HANDLED;
}
// 底半部工作函数
static void my_work_handler(struct work_struct *work)
{
struct my_device_data *dev = container_of(work,
struct my_device_data, work);
// 这里可以使用睡眠操作
// 处理耗时任务
}
4. 选择正确的锁机制
4.1 锁选择指南
| 场景 | 推荐机制 | 原因 |
|---|---|---|
| 短期保护,中断上下文 | 自旋锁 | 不会睡眠,响应快 |
| 长期保护,进程上下文 | 互斥锁 | 可睡眠,不浪费CPU |
| 多读少写 | 读写锁/信号量 | 提高并发性 |
| 简单计数 | 原子操作 | 无锁,高效 |
| 任务同步 | 完成量 | 专门用于同步 |
4.2 最佳实践
// 1. 按固定顺序获取锁,避免死锁
void correct_order(void)
{
mutex_lock(&lock_a);
mutex_lock(&lock_b);
// 临界区
mutex_unlock(&lock_b);
mutex_unlock(&lock_a);
}
// 2. 避免在持有锁时调用可能睡眠的函数
void bad_practice(struct mutex *lock)
{
mutex_lock(lock);
copy_to_user(...); // 可能睡眠
mutex_unlock(lock); // 危险!
}
// 3. 使用锁的适当变体
int good_practice(struct mutex *lock)
{
if (mutex_lock_interruptible(lock)) // 可被信号中断
return -ERESTARTSYS;
// 临界区
mutex_unlock(lock);
return 0;
}
5. 调试和检测
5.1 锁调试功能
// 配置内核选项
CONFIG_DEBUG_SPINLOCK=y
CONFIG_DEBUG_MUTEXES=y
CONFIG_DEBUG_ATOMIC_SLEEP=y
// 锁统计信息
CONFIG_LOCK_STAT=y
5.2 死锁检测
// 使用lockdep内核功能
#include <linux/lockdep.h>
// 在初始化时注册锁
lockdep_set_class(&my_lock, &my_lock_key);
// 检查锁的使用情况
spin_lock_nested(&my_lock, SINGLE_DEPTH_NESTING);
正确理解和使用Linux内核的并发控制机制是编写稳定、高效驱动程序的关键。应根据具体场景选择合适的同步机制,并遵循最佳实践来避免竞态条件和死锁。
3.Linux等待队列
1. 等待队列概述
等待队列是Linux内核中用于实现进程睡眠和唤醒的机制,主要用于:
- 进程在等待特定条件时进入睡眠状态
- 当条件满足时唤醒等待的进程
- 实现高效的同步和事件等待
2. 等待队列基本操作
2.1 定义和初始化
#include <linux/wait.h>
#include <linux/sched.h>
// 静态定义和初始化
DECLARE_WAIT_QUEUE_HEAD(my_wq);
// 动态初始化
wait_queue_head_t my_wq;
init_waitqueue_head(&my_wq);
// 定义等待队列条目
DECLARE_WAITQUEUE(wait, current);
2.2 基本睡眠函数
// 不可中断睡眠(通常不建议使用)
wait_event(wq, condition);
wait_event_timeout(wq, condition, timeout);
// 可中断睡眠(推荐使用)
wait_event_interruptible(wq, condition);
wait_event_interruptible_timeout(wq, condition, timeout);
// 可终止睡眠
wait_event_killable(wq, condition);
wait_event_killable_timeout(wq, condition, timeout);
2.3 唤醒函数
// 唤醒一个进程
wake_up(wq_head);
wake_up_interruptible(wq_head);
// 唤醒所有进程
wake_up_all(wq_head);
wake_up_interruptible_all(wq_head);
// 唤醒一个进程并同步
wake_up_sync(wq_head);
wake_up_interruptible_sync(wq_head);
3. 等待队列使用模式
3.1 基本使用示例
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/wait.h>
#include <linux/sched.h>
static DECLARE_WAIT_QUEUE_HEAD(data_wq);
static int data_ready = 0;
static char shared_data[256];
// 生产者:产生数据后唤醒消费者
static void producer_function(const char *data)
{
// 生产数据
strncpy(shared_data, data, sizeof(shared_data) - 1);
shared_data[sizeof(shared_data) - 1] = '\0';
// 设置条件变量
data_ready = 1;
// 唤醒等待队列中的所有进程
wake_up_interruptible(&data_wq);
printk(KERN_INFO "Producer: data ready, waking up consumers\n");
}
// 消费者:等待数据可用
static ssize_t consumer_function(struct file *filp, char __user *buf, size_t count)
{
int ret;
// 等待数据就绪(可中断睡眠)
ret = wait_event_interruptible(data_wq, data_ready != 0);
if (ret) {
// 被信号中断
return -ERESTARTSYS;
}
// 数据已就绪,进行处理
if (copy_to_user(buf, shared_data, min(count, sizeof(shared_data)))) {
return -EFAULT;
}
// 重置条件
data_ready = 0;
return min(count, sizeof(shared_data));
}
3.2 手动等待队列操作
static ssize_t advanced_wait_example(struct file *filp, char __user *buf, size_t count)
{
wait_queue_entry_t wait;
int ret = 0;
// 初始化等待队列条目
init_waitqueue_entry(&wait, current);
// 添加到等待队列
add_wait_queue(&data_wq, &wait);
// 设置进程状态
set_current_state(TASK_INTERRUPTIBLE);
while (!data_ready) {
// 检查是否有信号 pending
if (signal_pending(current)) {
ret = -ERESTARTSYS;
break;
}
// 调度其他进程运行
schedule();
set_current_state(TASK_INTERRUPTIBLE);
}
// 恢复运行状态
set_current_state(TASK_RUNNING);
// 从等待队列移除
remove_wait_queue(&data_wq, &wait);
if (!ret) {
// 处理数据
if (copy_to_user(buf, shared_data, min(count, sizeof(shared_data)))) {
ret = -EFAULT;
} else {
ret = min(count, sizeof(shared_data));
data_ready = 0;
}
}
return ret;
}
4. 完整驱动示例
4.1 简单的字符设备驱动
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#define DEVICE_NAME "waitq_example"
#define BUFFER_SIZE 1024
struct waitq_device {
struct cdev cdev;
dev_t devno;
wait_queue_head_t read_wq;
wait_queue_head_t write_wq;
char *buffer;
size_t data_len;
int read_avail;
int write_avail;
struct mutex lock;
};
static struct waitq_device *waitq_dev;
static int waitq_open(struct inode *inode, struct file *filp)
{
struct waitq_device *dev = container_of(inode->i_cdev,
struct waitq_device, cdev);
filp->private_data = dev;
return 0;
}
static int waitq_release(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t waitq_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos)
{
struct waitq_device *dev = filp->private_data;
ssize_t ret;
// 等待数据可读
if (wait_event_interruptible(dev->read_wq, dev->read_avail)) {
return -ERESTARTSYS;
}
// 保护共享数据
mutex_lock(&dev->lock);
// 读取数据
count = min(count, dev->data_len);
if (copy_to_user(buf, dev->buffer, count)) {
ret = -EFAULT;
} else {
ret = count;
dev->data_len = 0;
dev->read_avail = 0;
dev->write_avail = 1;
// 唤醒可能的写者
wake_up_interruptible(&dev->write_wq);
}
mutex_unlock(&dev->lock);
return ret;
}
static ssize_t waitq_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos)
{
struct waitq_device *dev = filp->private_data;
ssize_t ret;
count = min(count, (size_t)BUFFER_SIZE);
// 等待缓冲区可写
if (wait_event_interruptible(dev->write_wq, dev->write_avail)) {
return -ERESTARTSYS;
}
mutex_lock(&dev->lock);
// 写入数据
if (copy_from_user(dev->buffer, buf, count)) {
ret = -EFAULT;
} else {
dev->data_len = count;
dev->read_avail = 1;
dev->write_avail = 0;
ret = count;
// 唤醒读者
wake_up_interruptible(&dev->read_wq);
}
mutex_unlock(&dev->lock);
return ret;
}
static const struct file_operations waitq_fops = {
.owner = THIS_MODULE,
.open = waitq_open,
.release = waitq_release,
.read = waitq_read,
.write = waitq_write,
};
static int __init waitq_init(void)
{
int ret;
// 分配设备结构
waitq_dev = kzalloc(sizeof(*waitq_dev), GFP_KERNEL);
if (!waitq_dev)
return -ENOMEM;
// 分配缓冲区
waitq_dev->buffer = kzalloc(BUFFER_SIZE, GFP_KERNEL);
if (!waitq_dev->buffer) {
ret = -ENOMEM;
goto free_dev;
}
// 初始化等待队列
init_waitqueue_head(&waitq_dev->read_wq);
init_waitqueue_head(&waitq_dev->write_wq);
// 初始化互斥锁
mutex_init(&waitq_dev->lock);
// 初始状态:可写,不可读
waitq_dev->write_avail = 1;
waitq_dev->read_avail = 0;
waitq_dev->data_len = 0;
// 分配设备号
ret = alloc_chrdev_region(&waitq_dev->devno, 0, 1, DEVICE_NAME);
if (ret < 0)
goto free_buffer;
// 初始化cdev
cdev_init(&waitq_dev->cdev, &waitq_fops);
waitq_dev->cdev.owner = THIS_MODULE;
// 添加cdev到系统
ret = cdev_add(&waitq_dev->cdev, waitq_dev->devno, 1);
if (ret)
goto unregister_dev;
printk(KERN_INFO "Wait queue example driver loaded\n");
return 0;
unregister_dev:
unregister_chrdev_region(waitq_dev->devno, 1);
free_buffer:
kfree(waitq_dev->buffer);
free_dev:
kfree(waitq_dev);
return ret;
}
static void __exit waitq_exit(void)
{
cdev_del(&waitq_dev->cdev);
unregister_chrdev_region(waitq_dev->devno, 1);
kfree(waitq_dev->buffer);
kfree(waitq_dev);
printk(KERN_INFO "Wait queue example driver unloaded\n");
}
module_init(waitq_init);
module_exit(waitq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Wait queue example driver");
5. 高级用法和模式
5.1 带超时的等待
static ssize_t wait_with_timeout(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos)
{
struct my_device *dev = filp->private_data;
long timeout_ret;
// 等待最多2秒钟
timeout_ret = wait_event_interruptible_timeout(dev->wq,
dev->data_ready,
msecs_to_jiffies(2000));
if (timeout_ret == 0) {
// 超时
return -ETIMEDOUT;
} else if (timeout_ret < 0) {
// 被信号中断
return -ERESTARTSYS;
}
// 条件满足,处理数据
return process_data(dev, buf, count);
}
5.2 独占等待
static ssize_t exclusive_wait(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos)
{
wait_queue_entry_t wait;
int ret = 0;
init_waitqueue_entry(&wait, current);
wait.flags |= WQ_FLAG_EXCLUSIVE; // 设置为独占等待
add_wait_queue(&my_wq, &wait);
set_current_state(TASK_INTERRUPTIBLE);
while (!condition) {
if (signal_pending(current)) {
ret = -ERESTARTSYS;
break;
}
schedule();
set_current_state(TASK_INTERRUPTIBLE);
}
set_current_state(TASK_RUNNING);
remove_wait_queue(&my_wq, &wait);
return ret;
}
5.3 轮询结合等待队列
static unsigned int waitq_poll(struct file *filp, poll_table *wait)
{
struct waitq_device *dev = filp->private_data;
unsigned int mask = 0;
poll_wait(filp, &dev->read_wq, wait);
poll_wait(filp, &dev->write_wq, wait);
mutex_lock(&dev->lock);
if (dev->read_avail)
mask |= POLLIN | POLLRDNORM; // 可读
if (dev->write_avail)
mask |= POLLOUT | POLLWRNORM; // 可写
mutex_unlock(&dev->lock);
return mask;
}
6. 最佳实践和注意事项
6.1 使用准则
- 总是使用可中断版本:除非有特殊需求,否则使用
_interruptible版本 - 保护共享数据:等待队列通常需要与锁配合使用
- 条件检查:在睡眠前和唤醒后都要检查条件
- 避免虚假唤醒:使用循环检查条件
6.2 常见错误
// 错误:没有在循环中检查条件
wait_event_interruptible(wq, condition); // 可能虚假唤醒
// 正确:手动实现循环检查
while (!condition) {
wait_event_interruptible(wq, condition);
}
// 错误:忘记恢复进程状态
set_current_state(TASK_INTERRUPTIBLE);
schedule();
// 忘记: set_current_state(TASK_RUNNING);
// 错误:在持有锁时睡眠
mutex_lock(&lock);
wait_event_interruptible(wq, condition); // 可能死锁!
mutex_unlock(&lock);
6.3 性能考虑
选择合适的唤醒函数:
wake_up()- 唤醒所有等待者wake_up_interruptible()- 只唤醒可中断等待者wake_up_sync()- 同步唤醒,不重新调度
避免惊群效应:使用独占等待标志
WQ_FLAG_EXCLUSIVE
等待队列是Linux内核中实现进程同步的核心机制,正确使用可以创建高效、响应及时的驱动程序。
4.IO多路复用
1. IO多路复用概述
IO多路复用是一种同时监控多个文件描述符的机制,允许单个进程处理多个IO操作。主要解决高并发场景下的IO效率问题。
1.1 核心概念
- 文件描述符(File Descriptor): Linux中一切皆文件,socket、设备等都通过fd访问
- 就绪通知(Ready Notification): 当fd可读、可写或出现异常时通知应用程序
- 事件驱动(Event-Driven): 基于事件触发而非轮询
1.2 主要技术
- select: 最古老的多路复用机制
- poll: select的改进版本
- epoll: Linux特有的高性能多路复用
2. select系统调用
2.1 select基本原理
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
// 相关操作宏
void FD_ZERO(fd_set *set); // 清空集合
void FD_SET(int fd, fd_set *set); // 添加fd到集合
void FD_CLR(int fd, fd_set *set); // 从集合移除fd
int FD_ISSET(int fd, fd_set *set); // 检查fd是否在集合中
2.2 select使用示例
#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#define MAX_FD 1024
int main() {
fd_set readfds;
struct timeval timeout;
int ret;
while (1) {
// 清空文件描述符集合
FD_ZERO(&readfds);
// 添加标准输入到监控集合
FD_SET(STDIN_FILENO, &readfds);
// 设置超时时间为5秒
timeout.tv_sec = 5;
timeout.tv_usec = 0;
// 调用select
ret = select(STDIN_FILENO + 1, &readfds, NULL, NULL, &timeout);
if (ret == -1) {
perror("select");
break;
} else if (ret == 0) {
printf("Timeout occurred! No data after 5 seconds.\n");
} else {
// 检查标准输入是否可读
if (FD_ISSET(STDIN_FILENO, &readfds)) {
char buf[1024];
ssize_t n = read(STDIN_FILENO, buf, sizeof(buf) - 1);
if (n > 0) {
buf[n] = '\0';
printf("Read from stdin: %s", buf);
}
}
}
}
return 0;
}
2.3 select的局限性
- 文件描述符数量限制(FD_SETSIZE,通常1024)
- 每次调用需要重新设置fd_set
- 线性扫描所有fd,效率随fd数量增加而下降
3. poll系统调用
3.1 poll基本原理
#include <poll.h>
struct pollfd {
int fd; // 文件描述符
short events; // 请求的事件
short revents; // 返回的事件
};
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
// 事件标志
#define POLLIN 0x001 // 有数据可读
#define POLLPRI 0x002 // 有紧急数据可读
#define POLLOUT 0x004 // 可写,不会阻塞
#define POLLERR 0x008 // 发生错误
#define POLLHUP 0x010 // 挂起
#define POLLNVAL 0x020 // 无效的fd
3.2 poll使用示例
#include <stdio.h>
#include <stdlib.h>
#include <poll.h>
#include <unistd.h>
#include <string.h>
#define MAX_FDS 10
int main() {
struct pollfd fds[2];
int timeout = 5000; // 5秒超时
int ret;
// 监控标准输入
fds[0].fd = STDIN_FILENO;
fds[0].events = POLLIN;
// 监控标准输出(通常总是可写)
fds[1].fd = STDOUT_FILENO;
fds[1].events = POLLOUT;
while (1) {
ret = poll(fds, 2, timeout);
if (ret == -1) {
perror("poll");
break;
} else if (ret == 0) {
printf("Poll timeout after 5 seconds.\n");
} else {
// 检查每个文件描述符
for (int i = 0; i < 2; i++) {
if (fds[i].revents & POLLIN) {
printf("fd %d is ready for reading\n", fds[i].fd);
char buf[1024];
ssize_t n = read(fds[i].fd, buf, sizeof(buf) - 1);
if (n > 0) {
buf[n] = '\0';
printf("Read: %s", buf);
}
}
if (fds[i].revents & POLLOUT) {
printf("fd %d is ready for writing\n", fds[i].fd);
}
if (fds[i].revents & POLLERR) {
printf("Error on fd %d\n", fds[i].fd);
}
}
}
}
return 0;
}
4. epoll系统调用
4.1 epoll基本原理
epoll是Linux特有的高性能IO多路复用机制,解决了select/poll的性能问题。
#include <sys/epoll.h>
// 创建epoll实例
int epoll_create(int size);
int epoll_create1(int flags);
// 控制epoll事件
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// 等待事件
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
struct epoll_event {
uint32_t events; // epoll事件
epoll_data_t data; // 用户数据
};
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
// 事件标志
#define EPOLLIN 0x001 // 可读
#define EPOLLOUT 0x004 // 可写
#define EPOLLET 0x800 // 边缘触发
#define EPOLLONESHOT 0x4000000 // 一次性事件
4.2 epoll使用示例
#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#define MAX_EVENTS 10
int main() {
int epoll_fd;
struct epoll_event event, events[MAX_EVENTS];
int ret;
// 创建epoll实例
epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
perror("epoll_create1");
exit(EXIT_FAILURE);
}
// 添加标准输入到epoll监控
event.events = EPOLLIN;
event.data.fd = STDIN_FILENO;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &event) == -1) {
perror("epoll_ctl");
close(epoll_fd);
exit(EXIT_FAILURE);
}
printf("Epoll started, monitoring stdin...\n");
while (1) {
// 等待事件,超时5秒
ret = epoll_wait(epoll_fd, events, MAX_EVENTS, 5000);
if (ret == -1) {
perror("epoll_wait");
break;
} else if (ret == 0) {
printf("No events within 5 seconds.\n");
continue;
}
// 处理所有就绪的事件
for (int i = 0; i < ret; i++) {
if (events[i].events & EPOLLIN) {
printf("File descriptor %d is ready for reading\n",
events[i].data.fd);
char buf[1024];
ssize_t n = read(events[i].data.fd, buf, sizeof(buf) - 1);
if (n > 0) {
buf[n] = '\0';
printf("Read %zd bytes: %s", n, buf);
// 如果是"quit"则退出
if (strncmp(buf, "quit", 4) == 0) {
printf("Exiting...\n");
close(epoll_fd);
return 0;
}
} else if (n == 0) {
printf("File descriptor %d closed\n", events[i].data.fd);
// 从epoll中移除
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
}
}
if (events[i].events & EPOLLERR) {
printf("Error on file descriptor %d\n", events[i].data.fd);
}
}
}
close(epoll_fd);
return 0;
}
4.3 水平触发 vs 边缘触发
// 水平触发(默认)
event.events = EPOLLIN; // 水平触发
// 边缘触发
event.events = EPOLLIN | EPOLLET; // 边缘触发
水平触发(Level-Triggered):
- 只要文件描述符可读/可写,就会持续通知
- 类似于select/poll的行为
- 编程更简单,不容易丢失事件
边缘触发(Edge-Triggered):
- 只有当文件描述符状态发生变化时通知
- 需要一次性读取所有可用数据
- 性能更好,但编程更复杂
4.4 边缘触发示例
#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#define MAX_EVENTS 10
#define BUFFER_SIZE 1024
// 设置文件描述符为非阻塞
int set_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) return -1;
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
int main() {
int epoll_fd;
struct epoll_event event, events[MAX_EVENTS];
int ret;
epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
perror("epoll_create1");
exit(EXIT_FAILURE);
}
// 设置标准输入为非阻塞
if (set_nonblocking(STDIN_FILENO) == -1) {
perror("set_nonblocking");
close(epoll_fd);
exit(EXIT_FAILURE);
}
// 使用边缘触发
event.events = EPOLLIN | EPOLLET;
event.data.fd = STDIN_FILENO;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &event) == -1) {
perror("epoll_ctl");
close(epoll_fd);
exit(EXIT_FAILURE);
}
printf("Epoll ET mode started...\n");
while (1) {
ret = epoll_wait(epoll_fd, events, MAX_EVENTS, 5000);
if (ret == -1) {
perror("epoll_wait");
break;
} else if (ret == 0) {
printf("Timeout...\n");
continue;
}
for (int i = 0; i < ret; i++) {
if (events[i].events & EPOLLIN) {
printf("EPOLLIN event on fd %d\n", events[i].data.fd);
// 边缘触发需要读取所有可用数据
while (1) {
char buf[BUFFER_SIZE];
ssize_t n = read(events[i].data.fd, buf, sizeof(buf) - 1);
if (n > 0) {
buf[n] = '\0';
printf("Read %zd bytes: %s", n, buf);
} else if (n == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// 没有更多数据可读
printf("No more data to read\n");
break;
} else {
perror("read");
break;
}
} else { // n == 0
printf("File descriptor closed\n");
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
break;
}
}
}
}
}
close(epoll_fd);
return 0;
}
5. 三种机制对比
| 特性 | select | poll | epoll |
|---|---|---|---|
| 性能 | O(n) | O(n) | O(1) |
| 最大fd数 | FD_SETSIZE(1024) | 无限制 | 无限制 |
| 工作效率 | 随fd增加下降 | 随fd增加下降 | 高效 |
| 内存使用 | 固定大小 | 动态分配 | 动态分配 |
| 触发方式 | 水平触发 | 水平触发 | 水平/边缘触发 |
| 内核通知 | 每次重建fd_set | 每次重建pollfd数组 | 事件驱动 |
6. 实际应用:简单的TCP服务器
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <errno.h>
#define MAX_EVENTS 64
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int server_fd, epoll_fd;
struct sockaddr_in address;
struct epoll_event event, events[MAX_EVENTS];
int opt = 1;
int addrlen = sizeof(address);
// 创建服务器socket
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 设置socket选项
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT,
&opt, sizeof(opt))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
// 绑定socket
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 开始监听
if (listen(server_fd, 10) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
printf("Server listening on port %d\n", PORT);
// 创建epoll实例
epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
perror("epoll_create1");
exit(EXIT_FAILURE);
}
// 添加服务器socket到epoll
event.events = EPOLLIN;
event.data.fd = server_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) == -1) {
perror("epoll_ctl: server_fd");
exit(EXIT_FAILURE);
}
while (1) {
int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
if (nfds == -1) {
perror("epoll_wait");
break;
}
for (int i = 0; i < nfds; i++) {
if (events[i].data.fd == server_fd) {
// 新的客户端连接
int client_fd = accept(server_fd,
(struct sockaddr *)&address,
(socklen_t*)&addrlen);
if (client_fd < 0) {
perror("accept");
continue;
}
printf("New client connected: fd=%d\n", client_fd);
// 添加客户端socket到epoll
event.events = EPOLLIN | EPOLLET; // 边缘触发
event.data.fd = client_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event) == -1) {
perror("epoll_ctl: client_fd");
close(client_fd);
}
} else {
// 客户端数据可读
int client_fd = events[i].data.fd;
char buffer[BUFFER_SIZE];
while (1) {
ssize_t count = read(client_fd, buffer, sizeof(buffer) - 1);
if (count > 0) {
buffer[count] = '\0';
printf("Received from client %d: %s", client_fd, buffer);
// 回显数据
write(client_fd, buffer, count);
} else if (count == 0) {
// 客户端断开连接
printf("Client %d disconnected\n", client_fd);
close(client_fd);
break;
} else {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// 没有更多数据
break;
} else {
perror("read");
close(client_fd);
break;
}
}
}
}
}
}
close(server_fd);
close(epoll_fd);
return 0;
}
7. 最佳实践
选择合适的机制:
- 少量连接:select/poll
- 大量连接:epoll
边缘触发注意事项:
- 必须使用非阻塞IO
- 必须一次性读取所有数据
- 性能更好但编程更复杂
错误处理:
- 总是检查系统调用返回值
- 处理EAGAIN/EWOULDBLOCK错误
- 及时关闭文件描述符
性能优化:
- 使用边缘触发减少系统调用
- 合理设置缓冲区大小
- 避免不必要的内存拷贝
IO多路复用是现代高性能网络编程的核心技术,正确使用可以显著提高程序的并发处理能力。
5.信号驱动IO
1. 信号驱动IO概述
信号驱动IO是一种异步IO模型,当文件描述符就绪时,内核通过信号通知应用程序,而不是让应用程序阻塞等待。
1.1 工作原理
- 应用程序启用文件描述符的信号驱动IO
- 应用程序继续执行其他任务(不阻塞)
- 当IO就绪时,内核发送SIGIO信号
- 信号处理函数执行实际的IO操作
1.2 适用场景
- 需要异步处理IO的应用程序
- 需要同时处理多个IO源
- 实时性要求较高的应用
- 不适合高性能服务器(信号处理开销较大)
2. 信号驱动IO设置
2.1 基本设置步骤
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
static int fd;
// SIGIO信号处理函数
void sigio_handler(int sig) {
char buf[256];
ssize_t n;
printf("SIGIO received, fd is ready for IO\n");
// 读取数据
n = read(fd, buf, sizeof(buf) - 1);
if (n > 0) {
buf[n] = '\0';
printf("Read %zd bytes: %s\n", n, buf);
} else if (n == 0) {
printf("End of file\n");
} else {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
perror("read");
}
}
}
int setup_sigio(int file_fd) {
struct sigaction sa;
int flags;
fd = file_fd;
// 设置信号处理函数
memset(&sa, 0, sizeof(sa));
sa.sa_handler = sigio_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0; // 不要使用SA_RESTART
if (sigaction(SIGIO, &sa, NULL) == -1) {
perror("sigaction");
return -1;
}
// 设置文件描述符的属主,以便接收信号
if (fcntl(fd, F_SETOWN, getpid()) == -1) {
perror("fcntl F_SETOWN");
return -1;
}
// 获取当前文件状态标志
flags = fcntl(fd, F_GETFL);
if (flags == -1) {
perror("fcntl F_GETFL");
return -1;
}
// 启用异步IO和non-blocking
if (fcntl(fd, F_SETFL, flags | O_ASYNC | O_NONBLOCK) == -1) {
perror("fcntl F_SETFL");
return -1;
}
printf("Signal-driven IO setup complete for fd %d\n", fd);
return 0;
}
2.2 完整示例:标准输入监控
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
static volatile sig_atomic_t got_signal = 0;
static int signal_fd;
void sigio_handler(int sig) {
got_signal = 1;
signal_fd = -1; // 在实际应用中需要确定是哪个fd
}
int main() {
struct sigaction sa;
int flags;
int count = 0;
printf("Signal-driven IO demo for stdin\n");
printf("Process PID: %d\n", getpid());
// 设置信号处理函数
memset(&sa, 0, sizeof(sa));
sa.sa_handler = sigio_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGIO, &sa, NULL) == -1) {
perror("sigaction");
exit(EXIT_FAILURE);
}
// 设置标准输入的信号驱动IO
if (fcntl(STDIN_FILENO, F_SETOWN, getpid()) == -1) {
perror("fcntl F_SETOWN");
exit(EXIT_FAILURE);
}
flags = fcntl(STDIN_FILENO, F_GETFL);
if (flags == -1) {
perror("fcntl F_GETFL");
exit(EXIT_FAILURE);
}
if (fcntl(STDIN_FILENO, F_SETFL, flags | O_ASYNC | O_NONBLOCK) == -1) {
perror("fcntl F_SETFL");
exit(EXIT_FAILURE);
}
printf("Signal-driven IO is set up. Type something (or 'quit' to exit):\n");
while (1) {
// 主循环可以做其他工作
printf("Working... (%d)\n", count++);
sleep(2);
// 检查是否有信号到达
if (got_signal) {
char buf[256];
ssize_t n;
printf("Processing SIGIO...\n");
// 读取所有可用数据
while ((n = read(STDIN_FILENO, buf, sizeof(buf) - 1)) > 0) {
buf[n] = '\0';
printf("Read: %s", buf);
// 检查退出条件
if (strncmp(buf, "quit", 4) == 0) {
printf("Exiting...\n");
exit(EXIT_SUCCESS);
}
}
if (n == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
perror("read");
}
}
got_signal = 0;
}
}
return 0;
}
3. 高级用法:多文件描述符处理
3.1 使用实时信号
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#define MAX_FDS 10
typedef struct {
int fd;
char name[32];
} fd_info_t;
static fd_info_t fd_list[MAX_FDS];
static int fd_count = 0;
// 使用实时信号可以携带更多信息
void sigrtmin_handler(int sig, siginfo_t *info, void *context) {
int fd = info->si_fd; // 触发信号的文件描述符
printf("Real-time signal received for fd: %d\n", fd);
// 查找对应的文件描述符信息
for (int i = 0; i < fd_count; i++) {
if (fd_list[i].fd == fd) {
printf("IO ready on %s (fd=%d)\n", fd_list[i].name, fd);
break;
}
}
}
int setup_sigio_rt(int fd, const char *name) {
struct sigaction sa;
int flags;
if (fd_count >= MAX_FDS) {
fprintf(stderr, "Too many file descriptors\n");
return -1;
}
// 存储文件描述符信息
fd_list[fd_count].fd = fd;
strncpy(fd_list[fd_count].name, name, sizeof(fd_list[fd_count].name) - 1);
fd_count++;
// 设置实时信号处理(第一次调用时设置)
if (fd_count == 1) {
memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = sigrtmin_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO; // 使用sa_sigaction
if (sigaction(SIGRTMIN, &sa, NULL) == -1) {
perror("sigaction SIGRTMIN");
return -1;
}
}
// 设置文件描述符属性
if (fcntl(fd, F_SETSIG, SIGRTMIN) == -1) {
perror("fcntl F_SETSIG");
return -1;
}
if (fcntl(fd, F_SETOWN, getpid()) == -1) {
perror("fcntl F_SETOWN");
return -1;
}
flags = fcntl(fd, F_GETFL);
if (flags == -1) {
perror("fcntl F_GETFL");
return -1;
}
if (fcntl(fd, F_SETFL, flags | O_ASYNC | O_NONBLOCK) == -1) {
perror("fcntl F_SETFL");
return -1;
}
printf("Real-time signal IO setup for %s (fd=%d)\n", name, fd);
return 0;
}
3.2 套接字信号驱动IO
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8080
#define BUFFER_SIZE 1024
static int server_fd;
static volatile sig_atomic_t data_available = 0;
void sigio_handler(int sig) {
data_available = 1;
}
void setup_socket_sigio(int sock_fd) {
struct sigaction sa;
int flags;
// 设置信号处理
memset(&sa, 0, sizeof(sa));
sa.sa_handler = sigio_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGIO, &sa, NULL) == -1) {
perror("sigaction");
exit(EXIT_FAILURE);
}
// 设置套接字属性
if (fcntl(sock_fd, F_SETOWN, getpid()) == -1) {
perror("fcntl F_SETOWN");
exit(EXIT_FAILURE);
}
flags = fcntl(sock_fd, F_GETFL);
if (flags == -1) {
perror("fcntl F_GETFL");
exit(EXIT_FAILURE);
}
if (fcntl(sock_fd, F_SETFL, flags | O_ASYNC | O_NONBLOCK) == -1) {
perror("fcntl F_SETFL");
exit(EXIT_FAILURE);
}
}
int main() {
struct sockaddr_in address;
int addrlen = sizeof(address);
int opt = 1;
// 创建服务器套接字
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 设置套接字选项
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT,
&opt, sizeof(opt))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
// 绑定和监听
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
printf("Server listening on port %d\n", PORT);
printf("Process PID: %d\n", getpid());
// 设置信号驱动IO
setup_socket_sigio(server_fd);
while (1) {
// 主循环可以处理其他任务
printf("Server is doing other work...\n");
sleep(3);
if (data_available) {
printf("Handling incoming connection...\n");
int client_fd;
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
char buffer[BUFFER_SIZE];
// 接受连接
client_fd = accept(server_fd, (struct sockaddr *)&client_addr,
&client_len);
if (client_fd >= 0) {
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);
printf("Accepted connection from %s:%d\n",
client_ip, ntohs(client_addr.sin_port));
// 读取客户端数据
ssize_t n = read(client_fd, buffer, sizeof(buffer) - 1);
if (n > 0) {
buffer[n] = '\0';
printf("Received: %s\n", buffer);
// 发送响应
const char *response = "Hello from signal-driven server!\n";
write(client_fd, response, strlen(response));
}
close(client_fd);
printf("Connection closed\n");
} else {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
perror("accept");
}
}
data_available = 0;
}
}
close(server_fd);
return 0;
}
4. 信号驱动IO的限制和问题
4.1 常见问题及解决方案
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
// 问题1:信号丢失
// 解决方案:使用信号队列或实时信号
// 问题2:多个文件描述符的信号区分
// 解决方案1:在信号处理函数中检查所有fd
void sigio_handler_check_all(int sig) {
printf("SIGIO received, checking all monitored fds...\n");
// 这里需要维护一个被监控的fd列表
// 检查每个fd的IO状态
}
// 解决方案2:使用不同的信号
void setup_multiple_signals() {
struct sigaction sa;
// 为不同的fd设置不同的信号
// 但这受限于可用信号数量
memset(&sa, 0, sizeof(sa));
sa.sa_handler = sigio_handler_check_all;
sigemptyset(&sa.sa_mask);
// 可以绑定多个信号到同一个处理函数
sigaction(SIGIO, &sa, NULL);
sigaction(SIGURG, &sa, NULL); // 用于带外数据
}
// 问题3:可移植性
// 不同Unix系统对信号驱动IO的支持不同
void check_system_support() {
#ifdef O_ASYNC
printf("O_ASYNC is supported\n");
#else
printf("O_ASYNC is NOT supported\n");
#endif
#ifdef F_SETOWN
printf("F_SETOWN is supported\n");
#else
printf("F_SETOWN is NOT supported\n");
#endif
}
// 问题4:性能考虑
// 信号处理的开销可能比轮询更大
void performance_considerations() {
printf("Signal-driven IO performance notes:\n");
printf("1. Signal handling has overhead\n");
printf("2. Not suitable for high-rate IO\n");
printf("3. Good for low-rate, responsive applications\n");
printf("4. Consider epoll for high-performance servers\n");
}
5. 与其他IO模型对比
5.1 性能测试示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/time.h>
#include <string.h>
#define TEST_DURATION 5 // 测试5秒
static volatile int signal_count = 0;
static struct timeval start_time, end_time;
void test_sigio_handler(int sig) {
signal_count++;
}
// 信号驱动IO性能测试
void test_signal_driven_io() {
struct sigaction sa;
int pipefd[2];
int flags;
char buffer[1024];
// 创建管道用于测试
if (pipe(pipefd) == -1) {
perror("pipe");
return;
}
// 设置信号处理
memset(&sa, 0, sizeof(sa));
sa.sa_handler = test_sigio_handler;
sigemptyset(&sa.sa_mask);
sigaction(SIGIO, &sa, NULL);
// 设置写端为信号驱动
flags = fcntl(pipefd[1], F_GETFL);
fcntl(pipefd[1], F_SETFL, flags | O_ASYNC);
fcntl(pipefd[1], F_SETOWN, getpid());
printf("Starting signal-driven IO performance test...\n");
gettimeofday(&start_time, NULL);
signal_count = 0;
// 持续写入数据触发信号
int writes = 0;
while (1) {
struct timeval current_time;
gettimeofday(¤t_time, NULL);
if (current_time.tv_sec - start_time.tv_sec >= TEST_DURATION) {
break;
}
// 写入数据触发SIGIO
if (write(pipefd[1], buffer, sizeof(buffer)) > 0) {
writes++;
}
// 从读端读取数据,避免管道满
read(pipefd[0], buffer, sizeof(buffer));
}
gettimeofday(&end_time, NULL);
double elapsed = (end_time.tv_sec - start_time.tv_sec) +
(end_time.tv_usec - start_time.tv_usec) / 1000000.0;
printf("Signal-driven IO test results:\n");
printf(" Duration: %.2f seconds\n", elapsed);
printf(" Signals received: %d\n", signal_count);
printf(" Writes performed: %d\n", writes);
printf(" Signals per second: %.2f\n", signal_count / elapsed);
close(pipefd[0]);
close(pipefd[1]);
}
6. 最佳实践
6.1 使用准则
// 1. 总是使用非阻塞IO配合信号驱动IO
flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | O_ASYNC | O_NONBLOCK);
// 2. 在信号处理函数中做最少的工作
void efficient_handler(int sig) {
// 只设置标志,在主循环中处理IO
io_ready_flag = 1;
}
// 3. 使用实时信号避免信号丢失
fcntl(fd, F_SETSIG, SIGRTMIN); // 使用实时信号
// 4. 正确处理EAGAIN/EWOULDBLOCK
ssize_t n = read(fd, buf, size);
if (n == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
// 没有数据可读,正常情况
}
// 5. 考虑信号队列限制
printf("Signal queue length: %ld\n", sysconf(_SC_SIGQUEUE_MAX));
6.2 错误处理
int robust_sigio_setup(int fd) {
struct sigaction sa;
int flags;
// 检查文件描述符有效性
if (fcntl(fd, F_GETFD) == -1) {
perror("Invalid file descriptor");
return -1;
}
// 设置信号处理
memset(&sa, 0, sizeof(sa));
sa.sa_handler = sigio_handler;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGIO, &sa, NULL) == -1) {
perror("sigaction failed");
return -1;
}
// 设置文件属主
if (fcntl(fd, F_SETOWN, getpid()) == -1) {
perror("fcntl F_SETOWN failed");
return -1;
}
// 启用异步IO
flags = fcntl(fd, F_GETFL);
if (flags == -1) {
perror("fcntl F_GETFL failed");
return -1;
}
if (fcntl(fd, F_SETFL, flags | O_ASYNC | O_NONBLOCK) == -1) {
perror("fcntl F_SETFL failed");
return -1;
}
return 0;
}
信号驱动IO提供了一种异步处理IO的方式,但在高性能场景下通常不如epoll高效。它适用于需要快速响应但IO频率不高的应用程序。
6.Linux定时器
1. Linux定时器概述
Linux提供了多种定时器机制,用于在特定时间点或周期性地执行任务。
1.1 定时器类型
- 间隔定时器:setitimer,传统的UNIX定时器
- POSIX定时器:timer_create,更灵活的定时器
- 内核定时器:用于内核模块开发
- 高精度定时器:hrtimer,纳秒级精度
2. 间隔定时器 (setitimer)
2.1 基本用法
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>
#include <string.h>
// 定时器信号处理函数
void timer_handler(int sig) {
static int count = 0;
printf("Timer expired %d times\n", ++count);
}
int main() {
struct itimerval timer;
struct sigaction sa;
printf("Setting up interval timer...\n");
printf("Process PID: %d\n", getpid());
// 设置信号处理
memset(&sa, 0, sizeof(sa));
sa.sa_handler = timer_handler;
sigaction(SIGALRM, &sa, NULL);
// 配置定时器:首次1秒后触发,之后每2秒触发
timer.it_value.tv_sec = 1; // 第一次到期时间
timer.it_value.tv_usec = 0;
timer.it_interval.tv_sec = 2; // 间隔时间
timer.it_interval.tv_usec = 0;
// 启动定时器
if (setitimer(ITIMER_REAL, &timer, NULL) == -1) {
perror("setitimer");
exit(EXIT_FAILURE);
}
printf("Timer started. First expiration in 1 second, then every 2 seconds.\n");
// 主循环
while (1) {
pause(); // 等待信号
}
return 0;
}
2.2 三种定时器类型
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>
#include <string.h>
void real_timer_handler(int sig) {
printf("REAL timer: wall clock time elapsed\n");
}
void virtual_timer_handler(int sig) {
printf("VIRTUAL timer: process CPU time elapsed\n");
}
void prof_timer_handler(int sig) {
printf("PROF timer: process CPU time (system + user) elapsed\n");
}
int main() {
struct itimerval timer;
struct sigaction sa;
// 设置信号处理函数
memset(&sa, 0, sizeof(sa));
// 实时定时器(实际时间)
sa.sa_handler = real_timer_handler;
sigaction(SIGALRM, &sa, NULL);
// 虚拟定时器(用户态CPU时间)
sa.sa_handler = virtual_timer_handler;
sigaction(SIGVTALRM, &sa, NULL);
// 统计分析定时器(总CPU时间)
sa.sa_handler = prof_timer_handler;
sigaction(SIGPROF, &sa, NULL);
// 设置实时定时器:每3秒
timer.it_value.tv_sec = 3;
timer.it_value.tv_usec = 0;
timer.it_interval.tv_sec = 3;
timer.it_interval.tv_usec = 0;
setitimer(ITIMER_REAL, &timer, NULL);
// 设置虚拟定时器:每1秒用户CPU时间
timer.it_value.tv_sec = 1;
timer.it_value.tv_usec = 0;
timer.it_interval.tv_sec = 1;
timer.it_interval.tv_usec = 0;
setitimer(ITIMER_VIRTUAL, &timer, NULL);
// 设置统计分析定时器:每2秒总CPU时间
timer.it_value.tv_sec = 2;
timer.it_value.tv_usec = 0;
timer.it_interval.tv_sec = 2;
timer.it_interval.tv_usec = 0;
setitimer(ITIMER_PROF, &timer, NULL);
printf("All timers started. Doing some work...\n");
// 模拟一些CPU工作
int i = 0;
while (1) {
// 用户态工作
for (long j = 0; j < 1000000; j++) {
i += j % 100;
}
// 系统调用(进入内核态)
usleep(100000); // 睡眠100ms
if (i > 1000000000) break; // 防止溢出
}
return 0;
}
3. POSIX定时器
3.1 基本用法
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <string.h>
timer_t timer_id;
// 定时器通知处理函数
void timer_callback(union sigval val) {
static int count = 0;
int *user_data = (int *)val.sival_ptr;
printf("POSIX Timer callback: count=%d, user_data=%d\n",
++count, *user_data);
// 修改用户数据
(*user_data)++;
}
int main() {
struct sigevent sev;
struct itimerspec its;
int user_data = 100;
printf("Creating POSIX timer...\n");
// 设置定时器事件
memset(&sev, 0, sizeof(sev));
sev.sigev_notify = SIGEV_THREAD; // 创建新线程执行回调
sev.sigev_value.sival_ptr = &user_data; // 传递用户数据
sev.sigev_notify_function = timer_callback;
sev.sigev_notify_attributes = NULL; // 使用默认线程属性
// 创建定时器
if (timer_create(CLOCK_REALTIME, &sev, &timer_id) == -1) {
perror("timer_create");
exit(EXIT_FAILURE);
}
// 设置定时器:首次1秒后,之后每2秒
its.it_value.tv_sec = 1;
its.it_value.tv_nsec = 0;
its.it_interval.tv_sec = 2;
its.it_interval.tv_nsec = 0;
// 启动定时器
if (timer_settime(timer_id, 0, &its, NULL) == -1) {
perror("timer_settime");
exit(EXIT_FAILURE);
}
printf("POSIX timer started. Running for 10 seconds...\n");
// 运行一段时间
sleep(10);
// 删除定时器
timer_delete(timer_id);
printf("Timer deleted. Exiting.\n");
return 0;
}
3.2 多种通知方式
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <string.h>
timer_t timer1, timer2, timer3;
// 信号处理函数
void signal_handler(int sig, siginfo_t *info, void *context) {
printf("Signal %d received from timer, user_data=%d\n",
sig, *(int *)info->si_value.sival_ptr);
}
// 线程回调函数
void thread_callback(union sigval val) {
printf("Thread callback: user_data=%d\n", *(int *)val.sival_ptr);
}
int main() {
struct sigevent sev;
struct itimerspec its;
struct sigaction sa;
int user_data1 = 100, user_data2 = 200, user_data3 = 300;
printf("Testing different POSIX timer notifications...\n");
// 方式1:信号通知
memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = signal_handler;
sa.sa_flags = SA_SIGINFO;
sigaction(SIGUSR1, &sa, NULL);
memset(&sev, 0, sizeof(sev));
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = SIGUSR1;
sev.sigev_value.sival_ptr = &user_data1;
timer_create(CLOCK_REALTIME, &sev, &timer1);
// 方式2:线程回调
memset(&sev, 0, sizeof(sev));
sev.sigev_notify = SIGEV_THREAD;
sev.sigev_value.sival_ptr = &user_data2;
sev.sigev_notify_function = thread_callback;
timer_create(CLOCK_REALTIME, &sev, &timer2);
// 方式3:不通知(用于统计)
memset(&sev, 0, sizeof(sev));
sev.sigev_notify = SIGEV_NONE;
timer_create(CLOCK_REALTIME, &sev, &timer3);
// 启动定时器1:每3秒,信号通知
its.it_value.tv_sec = 3;
its.it_value.tv_nsec = 0;
its.it_interval.tv_sec = 3;
its.it_interval.tv_nsec = 0;
timer_settime(timer1, 0, &its, NULL);
// 启动定时器2:每5秒,线程回调
its.it_value.tv_sec = 5;
its.it_value.tv_nsec = 0;
its.it_interval.tv_sec = 5;
its.it_interval.tv_nsec = 0;
timer_settime(timer2, 0, &its, NULL);
printf("Timers started. Running for 20 seconds...\n");
sleep(20);
// 清理
timer_delete(timer1);
timer_delete(timer2);
timer_delete(timer3);
printf("All timers deleted.\n");
return 0;
}
4. 高精度定时器 (hrtimer)
4.1 用户空间高精度定时器
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <string.h>
timer_t timer_id;
void high_res_handler(union sigval val) {
struct timespec ts;
static int count = 0;
// 获取高精度时间
clock_gettime(CLOCK_MONOTONIC, &ts);
printf("High-res timer: count=%d, time=%.6f seconds\n",
++count, ts.tv_sec + ts.tv_nsec / 1e9);
}
int main() {
struct sigevent sev;
struct itimerspec its;
printf("High-resolution timer test (nanosecond precision)\n");
// 创建高精度定时器
memset(&sev, 0, sizeof(sev));
sev.sigev_notify = SIGEV_THREAD;
sev.sigev_notify_function = high_res_handler;
if (timer_create(CLOCK_MONOTONIC, &sev, &timer_id) == -1) {
perror("timer_create CLOCK_MONOTONIC");
exit(EXIT_FAILURE);
}
// 设置高精度定时:每100毫秒
its.it_value.tv_sec = 0;
its.it_value.tv_nsec = 100000000; // 100ms
its.it_interval.tv_sec = 0;
its.it_interval.tv_nsec = 100000000; // 100ms
if (timer_settime(timer_id, 0, &its, NULL) == -1) {
perror("timer_settime");
exit(EXIT_FAILURE);
}
printf("High-resolution timer started (100ms intervals). Running for 5 seconds...\n");
sleep(5);
// 获取定时器剩余时间
struct itimerspec curr_its;
timer_gettime(timer_id, &curr_its);
printf("Remaining time: %ld.%09ld seconds\n",
curr_its.it_value.tv_sec, curr_its.it_value.tv_nsec);
timer_delete(timer_id);
printf("High-resolution timer deleted.\n");
return 0;
}
5. 定时器在应用中的使用
5.1 超时处理示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>
#include <string.h>
#include <errno.h>
static volatile sig_atomic_t timeout_occurred = 0;
void timeout_handler(int sig) {
timeout_occurred = 1;
printf("Timeout! Operation took too long.\n");
}
// 带超时的读取操作
ssize_t read_with_timeout(int fd, void *buf, size_t count, int timeout_sec) {
struct itimerval timer, old_timer;
struct sigaction sa, old_sa;
ssize_t n;
// 设置超时处理
memset(&sa, 0, sizeof(sa));
sa.sa_handler = timeout_handler;
sigaction(SIGALRM, &sa, &old_sa);
// 设置定时器
timer.it_value.tv_sec = timeout_sec;
timer.it_value.tv_usec = 0;
timer.it_interval.tv_sec = 0;
timer.it_interval.tv_usec = 0;
timeout_occurred = 0;
setitimer(ITIMER_REAL, &timer, &old_timer);
// 执行读取操作
n = read(fd, buf, count);
// 恢复原来的定时器和信号处理
setitimer(ITIMER_REAL, &old_timer, NULL);
sigaction(SIGALRM, &old_sa, NULL);
if (timeout_occurred) {
errno = ETIMEDOUT;
return -1;
}
return n;
}
int main() {
char buf[256];
printf("Read with timeout example. You have 5 seconds to type something:\n");
ssize_t n = read_with_timeout(STDIN_FILENO, buf, sizeof(buf) - 1, 5);
if (n == -1) {
if (errno == ETIMEDOUT) {
printf("Read operation timed out.\n");
} else {
perror("read");
}
} else {
buf[n] = '\0';
printf("Successfully read: %s", buf);
}
return 0;
}
5.2 周期性任务调度
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <sys/time.h>
#include <string.h>
typedef struct {
int task_id;
int execution_count;
long total_execution_time;
} task_context_t;
timer_t timer_id;
task_context_t task_ctx = {1, 0, 0};
void periodic_task(union sigval val) {
task_context_t *ctx = (task_context_t *)val.sival_ptr;
struct timespec start, end;
// 记录开始时间
clock_gettime(CLOCK_MONOTONIC, &start);
// 模拟任务执行
printf("Task %d executing... (count=%d)\n",
ctx->task_id, ++ctx->execution_count);
// 模拟一些工作
for (volatile long i = 0; i < 1000000; i++) {
// 空循环模拟工作
}
// 记录结束时间并计算执行时间
clock_gettime(CLOCK_MONOTONIC, &end);
long execution_time = (end.tv_sec - start.tv_sec) * 1000000 +
(end.tv_nsec - start.tv_nsec) / 1000;
ctx->total_execution_time += execution_time;
printf("Task %d completed in %ld microseconds\n",
ctx->task_id, execution_time);
}
int main() {
struct sigevent sev;
struct itimerspec its;
printf("Periodic task scheduler started.\n");
// 创建定时器
memset(&sev, 0, sizeof(sev));
sev.sigev_notify = SIGEV_THREAD;
sev.sigev_value.sival_ptr = &task_ctx;
sev.sigev_notify_function = periodic_task;
if (timer_create(CLOCK_MONOTONIC, &sev, &timer_id) == -1) {
perror("timer_create");
exit(EXIT_FAILURE);
}
// 设置周期性执行:每1秒
its.it_value.tv_sec = 1;
its.it_value.tv_nsec = 0;
its.it_interval.tv_sec = 1;
its.it_interval.tv_nsec = 0;
if (timer_settime(timer_id, 0, &its, NULL) == -1) {
perror("timer_settime");
exit(EXIT_FAILURE);
}
printf("Periodic task scheduled every 1 second. Running for 10 seconds...\n");
sleep(10);
// 停止定时器
its.it_value.tv_sec = 0;
its.it_value.tv_nsec = 0;
its.it_interval.tv_sec = 0;
its.it_interval.tv_nsec = 0;
timer_settime(timer_id, 0, &its, NULL);
printf("\nTask statistics:\n");
printf(" Executions: %d\n", task_ctx.execution_count);
printf(" Total execution time: %ld microseconds\n", task_ctx.total_execution_time);
printf(" Average execution time: %.2f microseconds\n",
(float)task_ctx.total_execution_time / task_ctx.execution_count);
timer_delete(timer_id);
printf("Scheduler stopped.\n");
return 0;
}
6. 内核定时器示例
6.1 简单内核模块定时器
/*
* 内核定时器示例模块
* 需要root权限加载:insmod timer_module.ko
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/timer.h>
static struct timer_list my_timer;
static int count = 0;
void timer_callback(struct timer_list *t) {
printk(KERN_INFO "Kernel timer callback: count = %d\n", ++count);
// 重新设置定时器(1秒后)
mod_timer(&my_timer, jiffies + HZ);
}
static int __init timer_init(void) {
printk(KERN_INFO "Initializing kernel timer module\n");
// 初始化定时器
timer_setup(&my_timer, timer_callback, 0);
// 启动定时器(1秒后到期)
mod_timer(&my_timer, jiffies + HZ);
return 0;
}
static void __exit timer_exit(void) {
// 删除定时器
del_timer(&my_timer);
printk(KERN_INFO "Kernel timer module unloaded, total callbacks: %d\n", count);
}
module_init(timer_init);
module_exit(timer_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Simple kernel timer example");
7. 最佳实践和注意事项
7.1 定时器使用准则
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <string.h>
// 1. 避免在信号处理函数中调用不可重入函数
void safe_timer_handler(int sig) {
// 安全:只设置标志或使用原子操作
static volatile sig_atomic_t flag = 0;
flag = 1;
// 不安全:printf, malloc等不可重入函数
// printf("Timer!\n"); // 危险!
}
// 2. 处理定时器竞争条件
void race_condition_example() {
printf("Timer race condition considerations:\n");
printf("1. 设置定时器和检查标志需要同步\n");
printf("2. 考虑使用原子操作或锁\n");
printf("3. 在信号处理中做最少的工作\n");
}
// 3. 资源清理
void cleanup_timer(timer_t timer_id) {
// 停止定时器
struct itimerspec its;
memset(&its, 0, sizeof(its));
timer_settime(timer_id, 0, &its, NULL);
// 删除定时器
timer_delete(timer_id);
printf("Timer cleaned up successfully.\n");
}
// 4. 错误处理
int robust_timer_create(timer_t *timer_id, void (*handler)(union sigval)) {
struct sigevent sev;
memset(&sev, 0, sizeof(sev));
sev.sigev_notify = SIGEV_THREAD;
sev.sigev_notify_function = handler;
if (timer_create(CLOCK_REALTIME, &sev, timer_id) == -1) {
perror("Failed to create timer");
return -1;
}
return 0;
}
int main() {
printf("Timer best practices:\n");
printf("1. 选择适合的定时器类型\n");
printf("2. 考虑精度和性能需求\n");
printf("3. 正确处理信号和线程安全\n");
printf("4. 总是清理定时器资源\n");
printf("5. 处理可能的错误情况\n");
return 0;
}
8. 性能比较和选择指南
| 定时器类型 | 精度 | 性能 | 适用场景 |
|---|---|---|---|
| setitimer | 微秒 | 中等 | 简单间隔定时 |
| POSIX定时器 | 纳秒 | 高 | 复杂定时需求 |
| 高精度定时器 | 纳秒 | 很高 | 实时应用 |
| 内核定时器 | 毫秒 | 很高 | 内核开发 |
Linux定时器提供了灵活的定时功能,根据应用需求选择合适的定时器类型非常重要。对于用户空间应用,推荐使用POSIX定时器;对于高性能需求,考虑高精度定时器;对于内核开发,使用内核定时器API。
7.Linux ioctl 详解
1. ioctl 概述
ioctl(input/output control)是设备驱动程序中用来处理特殊设备控制命令的系统调用,用于实现设备特定的操作。
1.1 基本概念
- 设备特定操作:无法用标准文件操作(read/write)实现的设备控制
- 用户空间与内核空间通信:用户程序通过ioctl向驱动程序发送命令
- 灵活性强:可以传递任意类型的数据结构
2. ioctl 基本用法
2.1 用户空间接口
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
2.2 简单示例
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
// 简单的ioctl示例
int main() {
int fd;
const char *device = "/dev/mydevice";
// 打开设备文件
fd = open(device, O_RDWR);
if (fd < 0) {
perror("open");
return -1;
}
int value = 100;
// 发送ioctl命令
if (ioctl(fd, 0x1234, &value) < 0) {
perror("ioctl");
close(fd);
return -1;
}
printf("ioctl command sent successfully\n");
close(fd);
return 0;
}
3. ioctl 命令编码
3.1 命令编码规范
Linux内核提供了标准的ioctl命令编码宏:
#include <linux/ioctl.h>
// ioctl命令编码宏
_IO(type, nr) // 无参数命令
_IOR(type, nr, datatype) // 读命令(从驱动读取数据)
_IOW(type, nr, datatype) // 写命令(向驱动写入数据)
_IOWR(type, nr, datatype) // 读写命令
3.2 命令编码示例
// ioctl_commands.h - 用户空间和内核空间共享的头文件
#ifndef IOCTL_COMMANDS_H
#define IOCTL_COMMANDS_H
#include <linux/ioctl.h>
// 魔术字 - 每个设备唯一
#define MYDEVICE_IOC_MAGIC 'k'
// 命令序列号
#define MYDEVICE_IOC_RESET _IO(MYDEVICE_IOC_MAGIC, 0)
#define MYDEVICE_IOC_GET_STATUS _IOR(MYDEVICE_IOC_MAGIC, 1, int)
#define MYDEVICE_IOC_SET_VALUE _IOW(MYDEVICE_IOC_MAGIC, 2, int)
#define MYDEVICE_IOC_GET_VALUE _IOR(MYDEVICE_IOC_MAGIC, 3, int)
#define MYDEVICE_IOC_SET_CONFIG _IOW(MYDEVICE_IOC_MAGIC, 4, struct myconfig)
#define MYDEVICE_IOC_GET_CONFIG _IOR(MYDEVICE_IOC_MAGIC, 5, struct myconfig)
// 最大命令号
#define MYDEVICE_IOC_MAXNR 5
// 配置结构体
struct myconfig {
int speed;
int mode;
char name[32];
};
#endif
4. 完整的驱动示例
4.1 字符设备驱动 with ioctl
// mydevice_driver.c - 内核模块
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/ioctl.h>
#include "ioctl_commands.h" // 共享的头文件
#define DEVICE_NAME "mydevice"
#define MAX_DEVICES 1
struct mydevice_data {
struct cdev cdev;
dev_t devno;
int value;
int status;
struct myconfig config;
struct mutex lock; // 保护并发访问
};
static struct mydevice_data mydev;
static int device_open_count = 0;
static int mydevice_open(struct inode *inode, struct file *filp)
{
mutex_lock(&mydev.lock);
if (device_open_count) {
mutex_unlock(&mydev.lock);
return -EBUSY;
}
device_open_count++;
mutex_unlock(&mydev.lock);
filp->private_data = &mydev;
return 0;
}
static int mydevice_release(struct inode *inode, struct file *filp)
{
mutex_lock(&mydev.lock);
device_open_count--;
mutex_unlock(&mydev.lock);
return 0;
}
static long mydevice_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct mydevice_data *dev = filp->private_data;
int ret = 0;
int tmp;
struct myconfig config;
// 检查命令类型
if (_IOC_TYPE(cmd) != MYDEVICE_IOC_MAGIC) {
pr_err("Invalid ioctl magic\n");
return -ENOTTY;
}
if (_IOC_NR(cmd) > MYDEVICE_IOC_MAXNR) {
pr_err("Invalid ioctl command number\n");
return -ENOTTY;
}
mutex_lock(&dev->lock);
switch (cmd) {
case MYDEVICE_IOC_RESET:
dev->value = 0;
dev->status = 0;
memset(&dev->config, 0, sizeof(dev->config));
pr_info("Device reset\n");
break;
case MYDEVICE_IOC_GET_STATUS:
tmp = dev->status;
if (copy_to_user((int __user *)arg, &tmp, sizeof(tmp))) {
ret = -EFAULT;
}
break;
case MYDEVICE_IOC_SET_VALUE:
if (copy_from_user(&tmp, (int __user *)arg, sizeof(tmp))) {
ret = -EFAULT;
} else {
dev->value = tmp;
pr_info("Value set to %d\n", dev->value);
}
break;
case MYDEVICE_IOC_GET_VALUE:
tmp = dev->value;
if (copy_to_user((int __user *)arg, &tmp, sizeof(tmp))) {
ret = -EFAULT;
}
break;
case MYDEVICE_IOC_SET_CONFIG:
if (copy_from_user(&config, (struct myconfig __user *)arg, sizeof(config))) {
ret = -EFAULT;
} else {
dev->config = config;
pr_info("Config set: speed=%d, mode=%d, name=%s\n",
dev->config.speed, dev->config.mode, dev->config.name);
}
break;
case MYDEVICE_IOC_GET_CONFIG:
if (copy_to_user((struct myconfig __user *)arg, &dev->config, sizeof(dev->config))) {
ret = -EFAULT;
}
break;
default:
ret = -ENOTTY;
break;
}
mutex_unlock(&dev->lock);
return ret;
}
static const struct file_operations mydevice_fops = {
.owner = THIS_MODULE,
.open = mydevice_open,
.release = mydevice_release,
.unlocked_ioctl = mydevice_ioctl,
.compat_ioctl = mydevice_ioctl, // 兼容32位应用
};
static int __init mydevice_init(void)
{
int ret;
dev_t dev;
// 分配设备号
ret = alloc_chrdev_region(&dev, 0, MAX_DEVICES, DEVICE_NAME);
if (ret < 0) {
pr_err("Failed to allocate device number\n");
return ret;
}
mydev.devno = dev;
// 初始化互斥锁
mutex_init(&mydev.lock);
// 初始化设备数据
mydev.value = 0;
mydev.status = 0;
memset(&mydev.config, 0, sizeof(mydev.config));
strncpy(mydev.config.name, "default", sizeof(mydev.config.name) - 1);
// 初始化cdev
cdev_init(&mydev.cdev, &mydevice_fops);
mydev.cdev.owner = THIS_MODULE;
// 添加cdev到系统
ret = cdev_add(&mydev.cdev, mydev.devno, MAX_DEVICES);
if (ret) {
pr_err("Failed to add cdev\n");
unregister_chrdev_region(mydev.devno, MAX_DEVICES);
return ret;
}
pr_info("MyDevice driver loaded, major=%d, minor=%d\n",
MAJOR(mydev.devno), MINOR(mydev.devno));
return 0;
}
static void __exit mydevice_exit(void)
{
cdev_del(&mydev.cdev);
unregister_chrdev_region(mydev.devno, MAX_DEVICES);
mutex_destroy(&mydev.lock);
pr_info("MyDevice driver unloaded\n");
}
module_init(mydevice_init);
module_exit(mydevice_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Example device driver with ioctl");
4.2 用户空间测试程序
// test_mydevice.c - 用户空间测试程序
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#include "ioctl_commands.h" // 共享的头文件
int main() {
int fd;
int ret;
int value, status;
struct myconfig config;
printf("MyDevice ioctl test program\n");
// 打开设备文件
fd = open("/dev/mydevice", O_RDWR);
if (fd < 0) {
perror("open");
printf("Make sure the device driver is loaded and device node exists\n");
return -1;
}
printf("Device opened successfully\n");
// 测试1: 重置设备
printf("\n1. Resetting device...\n");
if (ioctl(fd, MYDEVICE_IOC_RESET) < 0) {
perror("ioctl RESET");
} else {
printf("Device reset successful\n");
}
// 测试2: 设置值
printf("\n2. Setting value to 42...\n");
value = 42;
if (ioctl(fd, MYDEVICE_IOC_SET_VALUE, &value) < 0) {
perror("ioctl SET_VALUE");
} else {
printf("Value set to %d\n", value);
}
// 测试3: 读取值
printf("\n3. Reading value...\n");
if (ioctl(fd, MYDEVICE_IOC_GET_VALUE, &value) < 0) {
perror("ioctl GET_VALUE");
} else {
printf("Value read: %d\n", value);
}
// 测试4: 读取状态
printf("\n4. Reading status...\n");
if (ioctl(fd, MYDEVICE_IOC_GET_STATUS, &status) < 0) {
perror("ioctl GET_STATUS");
} else {
printf("Status: %d\n", status);
}
// 测试5: 设置配置
printf("\n5. Setting configuration...\n");
memset(&config, 0, sizeof(config));
config.speed = 9600;
config.mode = 1;
strncpy(config.name, "test_config", sizeof(config.name) - 1);
if (ioctl(fd, MYDEVICE_IOC_SET_CONFIG, &config) < 0) {
perror("ioctl SET_CONFIG");
} else {
printf("Configuration set:\n");
printf(" Speed: %d\n", config.speed);
printf(" Mode: %d\n", config.mode);
printf(" Name: %s\n", config.name);
}
// 测试6: 读取配置
printf("\n6. Reading configuration...\n");
if (ioctl(fd, MYDEVICE_IOC_GET_CONFIG, &config) < 0) {
perror("ioctl GET_CONFIG");
} else {
printf("Configuration read:\n");
printf(" Speed: %d\n", config.speed);
printf(" Mode: %d\n", config.mode);
printf(" Name: %s\n", config.name);
}
close(fd);
printf("\nTest completed\n");
return 0;
}
5. 高级 ioctl 用法
5.1 可变长度数据传递
// 处理可变长度数据的ioctl示例
// 在共享头文件中添加
#define MYDEVICE_IOC_SEND_DATA _IOW(MYDEVICE_IOC_MAGIC, 6, struct iovec)
#define MYDEVICE_IOC_RECV_DATA _IOWR(MYDEVICE_IOC_MAGIC, 7, struct iovec)
// 在驱动中处理可变长度数据
case MYDEVICE_IOC_SEND_DATA: {
struct iovec iov;
void *buffer;
if (copy_from_user(&iov, (struct iovec __user *)arg, sizeof(iov))) {
ret = -EFAULT;
break;
}
// 分配内核缓冲区
buffer = kmalloc(iov.iov_len, GFP_KERNEL);
if (!buffer) {
ret = -ENOMEM;
break;
}
// 拷贝用户数据到内核
if (copy_from_user(buffer, iov.iov_base, iov.iov_len)) {
ret = -EFAULT;
kfree(buffer);
break;
}
// 处理数据...
pr_info("Received %zu bytes of data\n", iov.iov_len);
kfree(buffer);
break;
}
5.2 权限检查
#include <linux/cred.h>
static long mydevice_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
// 检查用户权限
if (!capable(CAP_SYS_ADMIN)) {
pr_warn("Permission denied for non-admin user\n");
return -EPERM;
}
// 或者检查特定命令的权限
switch (cmd) {
case MYDEVICE_IOC_RESET:
if (!capable(CAP_SYS_ADMIN)) {
return -EPERM;
}
break;
// ... 其他命令处理
}
return 0;
}
6. 内核与用户空间数据结构兼容性
6.1 处理32/64位兼容性
// 使用 compat_ioctl 处理32位应用
#ifdef CONFIG_COMPAT
static long mydevice_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
void __user *argp = compat_ptr(arg);
switch (cmd) {
case MYDEVICE_IOC_SET_CONFIG32:
// 处理32位版本的数据结构
break;
// ... 其他兼容命令
default:
return mydevice_ioctl(filp, cmd, (unsigned long)argp);
}
return 0;
}
#endif
static const struct file_operations mydevice_fops = {
.owner = THIS_MODULE,
.open = mydevice_open,
.release = mydevice_release,
.unlocked_ioctl = mydevice_ioctl,
.compat_ioctl = mydevice_compat_ioctl,
};
7. 调试和错误处理
7.1 ioctl 调试技巧
// 在驱动中添加调试信息
static long mydevice_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
// 记录ioctl调用
pr_debug("ioctl called: cmd=0x%x, arg=0x%lx\n", cmd, arg);
switch (cmd) {
case MYDEVICE_IOC_SET_VALUE:
pr_debug("SET_VALUE: arg=%ld\n", arg);
break;
// ... 其他命令
}
return ret;
}
// 在用户空间检查错误
void check_ioctl_error(int ret, const char *operation) {
if (ret < 0) {
switch (errno) {
case ENOTTY:
fprintf(stderr, "%s: Invalid ioctl command\n", operation);
break;
case EFAULT:
fprintf(stderr, "%s: Bad address\n", operation);
break;
case EPERM:
fprintf(stderr, "%s: Permission denied\n", operation);
break;
case EINVAL:
fprintf(stderr, "%s: Invalid argument\n", operation);
break;
default:
perror(operation);
break;
}
}
}
8. 最佳实践
8.1 ioctl 设计准则
// 1. 使用唯一的魔术字
#define MYDEVICE_IOC_MAGIC 'k' // 确保不与其他驱动冲突
// 2. 验证命令参数
if (_IOC_DIR(cmd) & _IOC_READ) {
// 检查读权限
ret = access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
} else if (_IOC_DIR(cmd) & _IOC_WRITE) {
// 检查写权限
ret = access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
}
// 3. 使用适当的锁机制
mutex_lock(&dev->lock);
// 处理命令
mutex_unlock(&dev->lock);
// 4. 处理并发访问
static atomic_t ioctl_in_use = ATOMIC_INIT(0);
if (atomic_cmpxchg(&ioctl_in_use, 0, 1) != 0) {
return -EBUSY;
}
// 处理命令
atomic_set(&ioctl_in_use, 0);
// 5. 提供清晰的错误码
- ENOTTY: 无效的命令
- EFAULT: 错误的内存地址
- EINVAL: 无效的参数
- EPERM: 权限不足
- EBUSY: 设备忙
8.2 安全性考虑
// 1. 验证用户输入
case MYDEVICE_IOC_SET_CONFIG:
if (copy_from_user(&config, (void __user *)arg, sizeof(config))) {
return -EFAULT;
}
// 验证配置参数
if (config.speed < 0 || config.speed > 1000000) {
return -EINVAL;
}
// 确保字符串以null结尾
config.name[sizeof(config.name) - 1] = '\0';
break;
// 2. 限制缓冲区大小
#define MAX_DATA_SIZE 4096
case MYDEVICE_IOC_SEND_DATA:
if (data_size > MAX_DATA_SIZE) {
return -E2BIG;
}
ioctl是Linux设备驱动开发中非常重要的机制,它提供了设备特定操作的灵活接口。正确使用ioctl需要仔细设计命令编码、处理并发访问、确保安全性,并提供清晰的错误处理。
8.Linux中断处理
1. Linux中断概述
中断是硬件与CPU通信的一种机制,当硬件需要CPU处理时,通过中断信号通知CPU。
1.1 中断类型
- 硬件中断:由硬件设备产生(键盘、鼠标、网卡等)
- 软件中断:由软件指令产生
- 异常:CPU执行指令时产生的错误
1.2 中断处理特点
- 异步执行:中断可以在任何时候发生
- 中断上下文:执行环境限制较多
- 上半部/下半部:将中断处理分为紧急和非紧急部分
2. 中断处理基础
2.1 注册中断处理程序
#include <linux/interrupt.h>
// 中断处理函数原型
irqreturn_t interrupt_handler(int irq, void *dev_id);
// 注册中断处理函数
int request_irq(unsigned int irq, irq_handler_t handler,
unsigned long flags, const char *name, void *dev);
// 释放中断
void free_irq(unsigned int irq, void *dev_id);
2.2 简单中断示例
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#define GPIO_IRQ 17 // GPIO引脚号
static int irq_number;
// 中断处理函数
static irqreturn_t gpio_interrupt_handler(int irq, void *dev_id)
{
printk(KERN_INFO "Interrupt occurred on GPIO %d\n", irq);
// 读取GPIO状态或其他处理
// 注意:在中断上下文中不能睡眠!
return IRQ_HANDLED; // 中断已处理
}
static int __init my_interrupt_init(void)
{
int ret;
printk(KERN_INFO "Initializing interrupt module\n");
// 注册中断处理程序
ret = request_irq(GPIO_IRQ, gpio_interrupt_handler,
IRQF_TRIGGER_RISING, "my_gpio_irq", NULL);
if (ret) {
printk(KERN_ERR "Failed to request IRQ %d\n", GPIO_IRQ);
return ret;
}
irq_number = GPIO_IRQ;
printk(KERN_INFO "Interrupt handler registered for IRQ %d\n", GPIO_IRQ);
return 0;
}
static void __exit my_interrupt_exit(void)
{
free_irq(irq_number, NULL);
printk(KERN_INFO "Interrupt handler unregistered\n");
}
module_init(my_interrupt_init);
module_exit(my_interrupt_exit);
MODULE_LICENSE("GPL");
3. 中断标志和类型
3.1 中断标志
// 中断触发类型
IRQF_TRIGGER_RISING // 上升沿触发
IRQF_TRIGGER_FALLING // 下降沿触发
IRQF_TRIGGER_HIGH // 高电平触发
IRQF_TRIGGER_LOW // 低电平触发
// 中断处理标志
IRQF_SHARED // 共享中断
IRQF_ONESHOT // 一次性中断,处理完成后需要重新使能
IRQF_TIMER // 定时器中断
IRQF_NO_THREAD // 禁止线程化处理
IRQF_EARLY_RESUME // 早期恢复
3.2 共享中断示例
#include <linux/module.h>
#include <linux/interrupt.h>
#define SHARED_IRQ 9
static int irq = SHARED_IRQ;
static int dev_id = 1;
// 共享中断处理函数
static irqreturn_t shared_interrupt_handler(int irq, void *dev_id)
{
int *my_id = (int *)dev_id;
// 检查是否是我们的设备产生的中断
if (!is_my_device_interrupt()) {
return IRQ_NONE; // 不是我们的中断
}
printk(KERN_INFO "Shared interrupt handled by device %d\n", *my_id);
// 处理中断
handle_device_interrupt();
return IRQ_HANDLED;
}
static int __init shared_irq_init(void)
{
int ret;
// 注册共享中断
ret = request_irq(irq, shared_interrupt_handler,
IRQF_SHARED | IRQF_TRIGGER_RISING,
"my_shared_irq", &dev_id);
if (ret) {
printk(KERN_ERR "Failed to request shared IRQ\n");
return ret;
}
printk(KERN_INFO "Shared interrupt handler registered\n");
return 0;
}
static void __exit shared_irq_exit(void)
{
free_irq(irq, &dev_id);
printk(KERN_INFO "Shared interrupt handler unregistered\n");
}
4. 中断上下文限制
4.1 中断上下文的特点
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
static int test_irq;
static irqreturn_t test_interrupt_handler(int irq, void *dev_id)
{
printk(KERN_INFO "Interrupt context information:\n");
// 检查当前上下文
if (in_interrupt()) {
printk(KERN_INFO " - We are in interrupt context\n");
}
if (in_irq()) {
printk(KERN_INFO " - We are in hard IRQ context\n");
}
if (in_softirq()) {
printk(KERN_INFO " - We are in softirq context\n");
}
// 中断上下文中不能做的事情:
// 1. 不能访问用户空间
// 2. 不能执行可能睡眠的操作
// 3. 不能调用可能阻塞的函数
// 错误的做法(会导致内核错误):
// copy_to_user(...); // 可能睡眠
// kmalloc(GFP_KERNEL); // 可能睡眠
// mutex_lock(...); // 可能睡眠
// 正确的做法:
// copy_to_user 的替代:先缓存数据,在进程上下文中处理
// kmalloc(GFP_ATOMIC); // 使用原子分配
// spin_lock(...); // 使用自旋锁
return IRQ_HANDLED;
}
5. 底半部机制
由于中断处理程序的限制,Linux将中断处理分为两部分:
- 顶半部:紧急处理,在中断上下文中执行
- 底半部:非紧急处理,可以睡眠和阻塞
5.1 软中断 (Softirq)
// 软中断是内核底层的底半部机制
// 通常由内核核心代码使用,驱动程序较少直接使用
5.2 Tasklet
#include <linux/interrupt.h>
// Tasklet声明和初始化
DECLARE_TASKLET(my_tasklet, tasklet_function, (unsigned long)data);
DECLARE_TASKLET_DISABLED(my_tasklet, tasklet_function, (unsigned long)data);
// Tasklet处理函数
void tasklet_function(unsigned long data)
{
// 这里可以执行较耗时的操作
// 但仍然在软中断上下文中,不能睡眠
printk(KERN_INFO "Tasklet executing with data: %lu\n", data);
}
// 在中断处理函数中调度tasklet
static irqreturn_t irq_handler_with_tasklet(int irq, void *dev_id)
{
// 紧急处理
handle_urgent_work();
// 调度tasklet处理非紧急工作
tasklet_schedule(&my_tasklet);
return IRQ_HANDLED;
}
// 初始化和清理
static int __init tasklet_example_init(void)
{
// 初始化tasklet
tasklet_init(&my_tasklet, tasklet_function, 0x1234);
return 0;
}
static void __exit tasklet_example_exit(void)
{
// 禁用并等待tasklet完成
tasklet_kill(&my_tasklet);
}
5.3 工作队列 (Workqueue)
#include <linux/workqueue.h>
// 工作队列声明
static struct workqueue_struct *my_wq;
static struct work_struct my_work;
// 工作处理函数
void work_handler(struct work_struct *work)
{
// 在进程上下文中执行,可以睡眠
printk(KERN_INFO "Work queue handler executing\n");
// 可以执行耗时的操作
// 可以调用可能睡眠的函数
msleep(1000); // 在工作队列中可以睡眠
// 可以分配内存(可能睡眠)
char *buf = kmalloc(1024, GFP_KERNEL);
if (buf) {
// 处理数据
kfree(buf);
}
}
// 在中断处理函数中调度工作
static irqreturn_t irq_handler_with_workqueue(int irq, void *dev_id)
{
// 紧急处理
handle_urgent_work();
// 调度工作队列处理非紧急工作
queue_work(my_wq, &my_work);
return IRQ_HANDLED;
}
// 延迟工作队列
static struct delayed_work my_delayed_work;
void delayed_work_handler(struct work_struct *work)
{
printk(KERN_INFO "Delayed work executed after 2 seconds\n");
}
static int __init workqueue_example_init(void)
{
// 创建工作队列
my_wq = create_singlethread_workqueue("my_workqueue");
if (!my_wq) {
return -ENOMEM;
}
// 初始化工作
INIT_WORK(&my_work, work_handler);
INIT_DELAYED_WORK(&my_delayed_work, delayed_work_handler);
// 调度延迟工作(2秒后执行)
queue_delayed_work(my_wq, &my_delayed_work, msecs_to_jiffies(2000));
return 0;
}
static void __exit workqueue_example_exit(void)
{
// 取消延迟工作
cancel_delayed_work(&my_delayed_work);
// 刷新并销毁工作队列
flush_workqueue(my_wq);
destroy_workqueue(my_wq);
}
6. 完整的中断处理示例
6.1 网络设备中断处理
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
struct my_net_device {
struct net_device *netdev;
int irq;
struct napi_struct napi;
spinlock_t lock;
struct work_struct tx_work;
};
// NAPI轮询函数
int my_napi_poll(struct napi_struct *napi, int budget)
{
struct my_net_device *dev = container_of(napi, struct my_net_device, napi);
int work_done = 0;
// 处理接收的数据包
while (work_done < budget && has_rx_packets(dev)) {
if (process_rx_packet(dev) < 0) {
break;
}
work_done++;
}
// 如果所有数据包都处理完了,禁用NAPI
if (work_done < budget) {
napi_complete_done(napi, work_done);
// 重新使能中断
enable_irq(dev->irq);
}
return work_done;
}
// 中断处理函数
static irqreturn_t netdev_interrupt_handler(int irq, void *dev_id)
{
struct my_net_device *dev = dev_id;
// 禁用中断,使用NAPI处理
disable_irq_nosync(irq);
// 调度NAPI
if (napi_schedule_prep(&dev->napi)) {
__napi_schedule(&dev->napi);
}
return IRQ_HANDLED;
}
// 发送工作队列处理函数
void tx_work_handler(struct work_struct *work)
{
struct my_net_device *dev = container_of(work, struct my_net_device, tx_work);
// 处理发送队列
spin_lock_bh(&dev->lock);
process_tx_queue(dev);
spin_unlock_bh(&dev->lock);
}
// 网络设备打开函数
int netdev_open(struct net_device *ndev)
{
struct my_net_device *dev = netdev_priv(ndev);
int ret;
// 初始化NAPI
netif_napi_add(ndev, &dev->napi, my_napi_poll, 64);
// 注册中断处理程序
ret = request_irq(dev->irq, netdev_interrupt_handler,
IRQF_SHARED, ndev->name, dev);
if (ret) {
netif_napi_del(&dev->napi);
return ret;
}
// 初始化工作队列
INIT_WORK(&dev->tx_work, tx_work_handler);
// 启动设备
netif_start_queue(ndev);
return 0;
}
6.2 中断处理的状态管理
#include <linux/interrupt.h>
#include <linux/bitops.h>
struct my_device {
int irq;
unsigned long status;
spinlock_t lock;
wait_queue_head_t wait_queue;
};
#define STATUS_IRQ_ENABLED 0
#define STATUS_IRQ_PENDING 1
#define STATUS_DEVICE_READY 2
static irqreturn_t stateful_interrupt_handler(int irq, void *dev_id)
{
struct my_device *dev = dev_id;
unsigned long flags;
spin_lock_irqsave(&dev->lock, flags);
// 标记中断待处理
set_bit(STATUS_IRQ_PENDING, &dev->status);
// 处理硬件状态
read_device_status(dev);
// 唤醒等待进程
wake_up_interruptible(&dev->wait_queue);
spin_unlock_irqrestore(&dev->lock, flags);
return IRQ_HANDLED;
}
// 使能/禁用中断
void enable_my_irq(struct my_device *dev)
{
unsigned long flags;
spin_lock_irqsave(&dev->lock, flags);
if (!test_and_set_bit(STATUS_IRQ_ENABLED, &dev->status)) {
enable_irq(dev->irq);
}
spin_unlock_irqrestore(&dev->lock, flags);
}
void disable_my_irq(struct my_device *dev)
{
unsigned long flags;
spin_lock_irqsave(&dev->lock, flags);
if (test_and_clear_bit(STATUS_IRQ_ENABLED, &dev->status)) {
disable_irq(dev->irq);
}
spin_unlock_irqrestore(&dev->lock, flags);
}
7. 中断统计和调试
7.1 中断统计信息
#include <linux/interrupt.h>
#include <linux/proc_fs.h>
// 查看系统中断统计
static int __init check_interrupts(void)
{
int i;
printk(KERN_INFO "Interrupt statistics:\n");
for (i = 0; i < NR_IRQS; i++) {
struct irq_desc *desc = irq_to_desc(i);
if (desc && desc->action) {
printk(KERN_INFO "IRQ %d: %s, count: %lu\n",
i, desc->action->name, kstat_irqs(i));
}
}
return 0;
}
// 在/proc/interrupts中的自定义信息
static int proc_interrupts_show(struct seq_file *m, void *v)
{
int i;
seq_printf(m, "%-16s %10s %10s %-20s\n",
"IRQ", "COUNT", "DEVICE", "TYPE");
for (i = 0; i < NR_IRQS; i++) {
struct irq_desc *desc = irq_to_desc(i);
if (desc && desc->action) {
seq_printf(m, "%-16d %10lu %10s %-20s\n",
i, kstat_irqs(i),
desc->action->name, "edge");
}
}
return 0;
}
7.2 调试技巧
// 中断调试宏
#define DEBUG_INTERRUPTS 1
#ifdef DEBUG_INTERRUPTS
#define IRQ_DEBUG(fmt, args...) \
printk(KERN_DEBUG "IRQ_DEBUG: " fmt, ##args)
#else
#define IRQ_DEBUG(fmt, args...)
#endif
static irqreturn_t debug_interrupt_handler(int irq, void *dev_id)
{
IRQ_DEBUG("Entering interrupt handler for IRQ %d\n", irq);
// 记录进入时间
unsigned long start_jiffies = jiffies;
// 中断处理...
// 记录处理时间
unsigned long duration = jiffies - start_jiffies;
if (duration > HZ/100) { // 超过10ms
printk(KERN_WARNING "Interrupt handler for IRQ %d took %lu jiffies\n",
irq, duration);
}
IRQ_DEBUG("Exiting interrupt handler for IRQ %d\n", irq);
return IRQ_HANDLED;
}
8. 最佳实践
8.1 中断处理准则
// 1. 保持中断处理程序简短
static irqreturn_t good_handler(int irq, void *dev_id)
{
// 只做必要的最小工作
acknowledge_interrupt();
read_critical_data();
// 调度底半部处理其他工作
schedule_work(&my_work);
return IRQ_HANDLED;
}
// 2. 使用适当的锁
static irqreturn_t handler_with_lock(int irq, void *dev_id)
{
struct my_device *dev = dev_id;
unsigned long flags;
// 使用自旋锁保护共享数据
spin_lock_irqsave(&dev->lock, flags);
// 访问共享数据
dev->interrupt_count++;
spin_unlock_irqrestore(&dev->lock, flags);
return IRQ_HANDLED;
}
// 3. 正确处理错误
static int __init robust_irq_init(void)
{
int ret;
ret = request_irq(irq, handler, flags, name, dev);
if (ret == -EBUSY) {
printk(KERN_ERR "IRQ %d is busy\n", irq);
// 尝试其他IRQ或共享
} else if (ret == -EINVAL) {
printk(KERN_ERR "Invalid IRQ parameters\n");
} else if (ret) {
printk(KERN_ERR "Failed to request IRQ: %d\n", ret);
}
return ret;
}
// 4. 资源清理
static void __exit cleanup_irq(void)
{
// 确保所有底半部处理完成
flush_workqueue(my_wq);
tasklet_kill(&my_tasklet);
// 释放中断
free_irq(irq_number, dev_id);
// 销毁工作队列
destroy_workqueue(my_wq);
}
8.2 性能优化
// 1. 使用NAPI处理网络中断
// 2. 合理选择底半部机制
// - 短时间任务:tasklet
// - 可能睡眠的任务:工作队列
// - 网络设备:NAPI
// 3. 中断亲和性(设置CPU亲和性)
static void set_irq_affinity(int irq, int cpu)
{
cpumask_t mask;
cpumask_clear(&mask);
cpumask_set_cpu(cpu, &mask);
if (irq_set_affinity(irq, &mask)) {
printk(KERN_WARNING "Failed to set IRQ affinity\n");
}
}
// 4. 避免中断风暴
static irqreturn_t rate_limited_handler(int irq, void *dev_id)
{
static unsigned long last_time = 0;
unsigned long now = jiffies;
// 限制中断频率(最少间隔10ms)
if (time_before(now, last_time + HZ/100)) {
return IRQ_HANDLED;
}
last_time = now;
// 正常中断处理...
return IRQ_HANDLED;
}
中断处理是Linux设备驱动开发中的核心内容。正确的中断处理需要考虑性能、并发、资源管理等多个方面。通过合理使用顶半部和底半部机制,可以创建高效可靠的中断处理程序。
9.Linux平台总线模型
1. 平台总线模型概述
平台总线模型是Linux内核中用于描述片上系统(SoC)设备的一种抽象机制,用于管理那些没有物理总线的"伪"设备。
1.1 基本概念
- 平台设备(Platform Device):集成在SoC中的设备,如GPIO控制器、中断控制器等
- 平台驱动(Platform Driver):与平台设备匹配的驱动程序
- 平台总线(Platform Bus):虚拟总线,负责设备和驱动的匹配
1.2 平台总线模型优势
- 统一设备管理接口
- 支持设备树(Device Tree)描述
- 简化片上系统设备驱动开发
2. 平台设备注册
2.1 静态注册平台设备
#include <linux/platform_device.h>
// 平台设备资源定义
static struct resource my_device_resources[] = {
[0] = {
.start = 0x10000000, // 设备寄存器基地址
.end = 0x100000FF, // 设备寄存器结束地址
.flags = IORESOURCE_MEM, // 内存资源
},
[1] = {
.start = 42, // 中断号
.end = 42,
.flags = IORESOURCE_IRQ, // 中断资源
},
};
// 平台设备定义
static struct platform_device my_platform_device = {
.name = "my_device", // 设备名称,用于匹配驱动
.id = -1, // 设备实例ID
.num_resources = ARRAY_SIZE(my_device_resources),
.resource = my_device_resources,
.dev = {
.platform_data = NULL, // 设备私有数据
},
};
// 模块初始化时注册设备
static int __init my_device_init(void)
{
int ret;
ret = platform_device_register(&my_platform_device);
if (ret) {
pr_err("Failed to register platform device\n");
return ret;
}
pr_info("Platform device registered successfully\n");
return 0;
}
// 模块退出时注销设备
static void __exit my_device_exit(void)
{
platform_device_unregister(&my_platform_device);
pr_info("Platform device unregistered\n");
}
2.2 动态注册平台设备
#include <linux/platform_device.h>
static struct platform_device *my_device;
static int __init dynamic_device_init(void)
{
int ret;
// 动态分配平台设备
my_device = platform_device_alloc("my_dynamic_device", -1);
if (!my_device) {
pr_err("Failed to allocate platform device\n");
return -ENOMEM;
}
// 设置设备资源(可选)
struct resource res[] = {
{
.start = 0x20000000,
.end = 0x200000FF,
.flags = IORESOURCE_MEM,
},
};
ret = platform_device_add_resources(my_device, res, ARRAY_SIZE(res));
if (ret) {
pr_err("Failed to add resources\n");
goto err_put_device;
}
// 添加设备到系统
ret = platform_device_add(my_device);
if (ret) {
pr_err("Failed to add platform device\n");
goto err_put_device;
}
pr_info("Dynamic platform device registered\n");
return 0;
err_put_device:
platform_device_put(my_device);
return ret;
}
static void __exit dynamic_device_exit(void)
{
platform_device_unregister(my_device);
pr_info("Dynamic platform device unregistered\n");
}
3. 平台驱动注册
3.1 基本平台驱动
#include <linux/platform_device.h>
#include <linux/io.h>
struct my_driver_data {
void __iomem *reg_base;
int irq_number;
struct device *dev;
};
// 探测函数 - 当设备与驱动匹配时调用
static int my_driver_probe(struct platform_device *pdev)
{
struct my_driver_data *data;
struct resource *res;
int ret;
pr_info("Platform driver probe called for device: %s\n", pdev->name);
// 分配驱动私有数据
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data) {
return -ENOMEM;
}
// 获取内存资源
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "No memory resource\n");
return -ENODEV;
}
// 映射设备寄存器
data->reg_base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(data->reg_base)) {
dev_err(&pdev->dev, "Failed to map registers\n");
return PTR_ERR(data->reg_base);
}
// 获取中断资源
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (res) {
data->irq_number = res->start;
dev_info(&pdev->dev, "IRQ number: %d\n", data->irq_number);
}
// 获取平台数据
if (pdev->dev.platform_data) {
dev_info(&pdev->dev, "Got platform data\n");
// 处理平台数据...
}
data->dev = &pdev->dev;
// 保存私有数据
platform_set_drvdata(pdev, data);
// 设备初始化...
dev_info(&pdev->dev, "Device probed successfully\n");
return 0;
}
// 移除函数 - 当设备移除或驱动卸载时调用
static int my_driver_remove(struct platform_device *pdev)
{
struct my_driver_data *data = platform_get_drvdata(pdev);
dev_info(&pdev->dev, "Platform driver remove called\n");
// 清理资源
// 设备寄存器会自动取消映射(因为使用了devm_ioremap_resource)
return 0;
}
// 平台驱动定义
static struct platform_driver my_platform_driver = {
.probe = my_driver_probe,
.remove = my_driver_remove,
.driver = {
.name = "my_device", // 与平台设备名称匹配
.owner = THIS_MODULE,
.of_match_table = NULL, // 设备树匹配表(后面介绍)
},
};
// 模块初始化
static int __init my_driver_init(void)
{
int ret;
ret = platform_driver_register(&my_platform_driver);
if (ret) {
pr_err("Failed to register platform driver\n");
return ret;
}
pr_info("Platform driver registered successfully\n");
return 0;
}
// 模块退出
static void __exit my_driver_exit(void)
{
platform_driver_unregister(&my_platform_driver);
pr_info("Platform driver unregistered\n");
}
module_init(my_driver_init);
module_exit(my_driver_exit);
MODULE_LICENSE("GPL");
4. 设备树支持
4.1 设备树绑定
// 在驱动中添加设备树支持
#include <linux/of.h>
#include <linux/of_device.h>
// 设备树兼容性匹配表
static const struct of_device_id my_driver_of_match[] = {
{ .compatible = "vendor,my-device", .data = NULL },
{ .compatible = "vendor,my-device-v2", .data = (void *)1 },
{}, // 空条目,表示结束
};
MODULE_DEVICE_TABLE(of, my_driver_of_match);
// 从设备树获取资源
static int my_driver_probe_dt(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
const struct of_device_id *match;
int ret;
if (!np) {
dev_err(&pdev->dev, "No device tree node\n");
return -ENODEV;
}
// 检查设备兼容性
match = of_match_device(my_driver_of_match, &pdev->dev);
if (!match) {
dev_err(&pdev->dev, "No compatible device found\n");
return -ENODEV;
}
dev_info(&pdev->dev, "Compatible with: %s\n", match->compatible);
// 从设备树获取寄存器地址
ret = of_address_to_resource(np, 0, &pdev->resource[0]);
if (ret) {
dev_err(&pdev->dev, "Failed to get memory resource from DT\n");
return ret;
}
// 从设备树获取中断
pdev->num_resources = 1; // 内存资源
ret = of_irq_to_resource_table(np, &pdev->resource[1], 1);
if (ret > 0) {
pdev->num_resources += ret;
}
return 0;
}
// 更新平台驱动定义
static struct platform_driver my_platform_driver = {
.probe = my_driver_probe,
.remove = my_driver_remove,
.driver = {
.name = "my_device",
.owner = THIS_MODULE,
.of_match_table = my_driver_of_match, // 添加设备树匹配表
},
};
4.2 设备树节点示例
// 设备树源文件 (.dts)
/ {
compatible = "vendor,board";
my_device: my_device@10000000 {
compatible = "vendor,my-device", "vendor,my-device-v2";
reg = <0x10000000 0x100>; // 寄存器地址和大小
interrupts = <0 42 4>; // 中断号
clock-frequency = <50000000>; // 自定义属性
vendor,custom-property = "hello"; // 厂商特定属性
// 子节点
child-device {
compatible = "vendor,child-device";
reg = <0x10000100 0x20>;
};
};
};
5. 完整的平台设备驱动示例
5.1 简单的GPIO控制器驱动
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#define DRIVER_NAME "my_gpio_controller"
struct my_gpio_data {
void __iomem *reg_base;
struct gpio_chip gpio_chip;
int ngpios;
spinlock_t lock;
};
static int my_gpio_get(struct gpio_chip *chip, unsigned offset)
{
struct my_gpio_data *data = gpiochip_get_data(chip);
u32 value;
value = readl(data->reg_base);
return !!(value & (1 << offset));
}
static void my_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
{
struct my_gpio_data *data = gpiochip_get_data(chip);
unsigned long flags;
u32 reg;
spin_lock_irqsave(&data->lock, flags);
reg = readl(data->reg_base);
if (value)
reg |= (1 << offset);
else
reg &= ~(1 << offset);
writel(reg, data->reg_base);
spin_unlock_irqrestore(&data->lock, flags);
}
static int my_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
{
// 设置GPIO为输入方向
return 0;
}
static int my_gpio_direction_output(struct gpio_chip *chip, unsigned offset, int value)
{
// 设置GPIO为输出方向并设置值
my_gpio_set(chip, offset, value);
return 0;
}
static int my_gpio_probe(struct platform_device *pdev)
{
struct my_gpio_data *data;
struct device_node *np = pdev->dev.of_node;
struct resource *res;
int ret;
dev_info(&pdev->dev, "Probing GPIO controller\n");
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
// 获取寄存器资源
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "No memory resource\n");
return -ENODEV;
}
data->reg_base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(data->reg_base)) {
dev_err(&pdev->dev, "Failed to map registers\n");
return PTR_ERR(data->reg_base);
}
// 从设备树获取GPIO数量
if (of_property_read_u32(np, "ngpios", &data->ngpios)) {
data->ngpios = 32; // 默认值
}
// 初始化自旋锁
spin_lock_init(&data->lock);
// 设置GPIO芯片
data->gpio_chip.label = DRIVER_NAME;
data->gpio_chip.parent = &pdev->dev;
data->gpio_chip.owner = THIS_MODULE;
data->gpio_chip.get = my_gpio_get;
data->gpio_chip.set = my_gpio_set;
data->gpio_chip.direction_input = my_gpio_direction_input;
data->gpio_chip.direction_output = my_gpio_direction_output;
data->gpio_chip.base = -1; // 动态分配基号
data->gpio_chip.ngpio = data->ngpios;
data->gpio_chip.of_node = np;
// 注册GPIO控制器
ret = devm_gpiochip_add_data(&pdev->dev, &data->gpio_chip, data);
if (ret) {
dev_err(&pdev->dev, "Failed to register GPIO chip\n");
return ret;
}
platform_set_drvdata(pdev, data);
dev_info(&pdev->dev, "GPIO controller registered with %d GPIOs\n", data->ngpios);
return 0;
}
static int my_gpio_remove(struct platform_device *pdev)
{
dev_info(&pdev->dev, "GPIO controller removed\n");
return 0;
}
static const struct of_device_id my_gpio_of_match[] = {
{ .compatible = "vendor,my-gpio", },
{},
};
MODULE_DEVICE_TABLE(of, my_gpio_of_match);
static struct platform_driver my_gpio_driver = {
.probe = my_gpio_probe,
.remove = my_gpio_remove,
.driver = {
.name = DRIVER_NAME,
.of_match_table = my_gpio_of_match,
.owner = THIS_MODULE,
},
};
module_platform_driver(my_gpio_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Simple GPIO controller platform driver");
6. 电源管理支持
6.1 挂起和恢复
#include <linux/pm.h>
static int my_driver_suspend(struct device *dev)
{
struct my_driver_data *data = dev_get_drvdata(dev);
dev_info(dev, "Suspending device\n");
// 保存设备状态
// 禁用中断
// 进入低功耗模式
return 0;
}
static int my_driver_resume(struct device *dev)
{
struct my_driver_data *data = dev_get_drvdata(dev);
dev_info(dev, "Resuming device\n");
// 恢复设备状态
// 重新使能中断
// 退出低功耗模式
return 0;
}
static const struct dev_pm_ops my_driver_pm_ops = {
.suspend = my_driver_suspend,
.resume = my_driver_resume,
.freeze = my_driver_suspend,
.thaw = my_driver_resume,
.poweroff = my_driver_suspend,
.restore = my_driver_resume,
};
// 在平台驱动中包含电源管理操作
static struct platform_driver my_platform_driver = {
.probe = my_driver_probe,
.remove = my_driver_remove,
.driver = {
.name = "my_device",
.owner = THIS_MODULE,
.of_match_table = my_driver_of_match,
.pm = &my_driver_pm_ops, // 添加电源管理支持
},
};
7. 多设备实例支持
7.1 支持多个设备实例
// 在驱动中支持多个设备实例
static int my_driver_probe(struct platform_device *pdev)
{
struct my_driver_data *data;
int instance_id;
// 获取设备实例ID
instance_id = pdev->id;
if (instance_id < 0) {
// 从设备树获取实例信息
instance_id = of_alias_get_id(pdev->dev.of_node, "my_device");
if (instance_id < 0)
instance_id = 0; // 默认实例
}
dev_info(&pdev->dev, "Probing device instance %d\n", instance_id);
// 为每个实例分配独立的数据结构
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->instance_id = instance_id;
// 设备特定的初始化...
platform_set_drvdata(pdev, data);
return 0;
}
8. 调试和最佳实践
8.1 调试技巧
// 在驱动中添加调试支持
#define DEBUG
#ifdef DEBUG
#define DRV_DEBUG(fmt, args...) \
dev_dbg(&pdev->dev, "DEBUG: " fmt, ##args)
#else
#define DRV_DEBUG(fmt, args...)
#endif
static int my_driver_probe(struct platform_device *pdev)
{
DRV_DEBUG("Starting probe\n");
// 打印设备资源信息
struct resource *res;
int i;
for (i = 0; i < pdev->num_resources; i++) {
res = &pdev->resource[i];
if (resource_type(res) == IORESOURCE_MEM) {
DRV_DEBUG("Memory resource: %pr\n", res);
} else if (resource_type(res) == IORESOURCE_IRQ) {
DRV_DEBUG("IRQ resource: %pr\n", res);
}
}
return 0;
}
8.2 最佳实践
// 1. 使用设备资源管理API
static int good_probe(struct platform_device *pdev)
{
// 使用devm_系列函数自动管理资源
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
data->reg_base = devm_ioremap_resource(&pdev->dev, res);
data->irq = platform_get_irq(pdev, 0);
// 错误时资源会自动释放
return 0;
}
// 2. 正确的错误处理
static int robust_probe(struct platform_device *pdev)
{
int ret;
ret = some_initialization();
if (ret) {
dev_err(&pdev->dev, "Initialization failed: %d\n", ret);
return ret;
}
// 使用goto进行清晰的错误处理
ret = setup_resource_a();
if (ret)
goto err_cleanup_a;
ret = setup_resource_b();
if (ret)
goto err_cleanup_b;
return 0;
err_cleanup_b:
cleanup_resource_b();
err_cleanup_a:
cleanup_resource_a();
return ret;
}
// 3. 支持模块参数
static unsigned int debug_level;
module_param(debug_level, uint, 0644);
MODULE_PARM_DESC(debug_level, "Debug level (0=off, 1=basic, 2=verbose)");
平台总线模型是现代Linux设备驱动开发的核心机制,特别是在嵌入式系统和SoC平台上。通过合理使用平台设备和驱动,结合设备树描述,可以创建可移植、易维护的设备驱动程序。
10.Linux设备树详解及实例
1. 设备树概述
设备树(Device Tree)是一种描述硬件配置的数据结构,用于将硬件信息从内核代码中分离出来。
1.1 设备树基本概念
- 源文件:
.dts- 设备树源文件,.dtsi- 包含文件 - 编译文件:
.dtb- 设备树二进制文件 - 节点:描述设备或总线的基本单位
- 属性:描述设备的特性、地址、中断等
1.2 设备树优势
- 硬件信息与内核代码分离
- 支持同一内核镜像在不同硬件平台运行
- 简化BSP(板级支持包)开发
2. 设备树语法基础
2.1 基本结构
// 示例:简单的设备树
/dts-v1/;
/ {
// 根节点
compatible = "vendor,board-name", "vendor,board-family";
model = "Vendor Board Version 1.0";
#address-cells = <1>; // 子节点地址用1个32位数字表示
#size-cells = <1>; // 子节点大小用1个32位数字表示
// CPU节点
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a53";
device_type = "cpu";
reg = <0x0>;
clock-frequency = <1200000000>;
};
};
// 内存节点
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x40000000>; // 起始地址0x80000000,大小1GB
};
// 保留内存
reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;
linux,cma {
compatible = "shared-dma-pool";
reusable;
size = <0x10000000>; // 256MB
alignment = <0x2000>;
linux,cma-default;
};
};
};
2.2 常用属性详解
/ {
// 兼容性属性 - 最重要的属性
compatible = "vendor,board", "vendor,soc", "arm,board";
// 模型描述
model = "Vendor Development Board";
// 地址和大小单元定义
#address-cells = <1>; // 地址用1个cell表示
#size-cells = <1>; // 大小用1个cell表示
// 中断控制器属性
interrupt-parent = <&intc>; // 指向中断控制器
// 选择总线
chosen {
bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2 rootwait";
stdout-path = &uart0;
};
// 别名
aliases {
serial0 = &uart0;
ethernet0 = ð0;
mmc0 = &sdhci0;
};
};
3. 常用设备节点实例
3.1 UART串口设备
/ {
soc {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
ranges;
uart0: serial@10000000 {
compatible = "vendor,uart", "ns16550a";
reg = <0x10000000 0x1000>;
interrupts = <0 10 4>; // SPI 10, 电平触发
clocks = <&clk_uart>;
clock-frequency = <115200>;
status = "okay";
// FIFO设置
fifo-size = <16>;
auto-flow-control;
};
uart1: serial@10001000 {
compatible = "vendor,uart";
reg = <0x10001000 0x1000>;
interrupts = <0 11 4>;
clocks = <&clk_uart>;
status = "disabled"; // 默认禁用
};
};
};
3.2 I2C控制器及设备
/ {
soc {
i2c0: i2c@20000000 {
compatible = "vendor,i2c";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x20000000 0x1000>;
interrupts = <0 20 4>;
clocks = <&clk_i2c>;
clock-frequency = <100000>; // 标准模式100kHz
status = "okay";
// I2C设备:EEPROM
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
pagesize = <16>;
};
// I2C设备:温度传感器
temp_sensor: lm75@48 {
compatible = "national,lm75";
reg = <0x48>;
};
// I2C设备:RTC
rtc: ds1339@68 {
compatible = "dallas,ds1339";
reg = <0x68>;
};
};
i2c1: i2c@20001000 {
compatible = "vendor,i2c";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x20001000 0x1000>;
interrupts = <0 21 4>;
clocks = <&clk_i2c>;
clock-frequency = <400000>; // 快速模式400kHz
status = "okay";
// I2C设备:音频编解码器
codec: wm8960@1a {
compatible = "wlf,wm8960";
reg = <0x1a>;
#sound-dai-cells = <0>;
};
};
};
};
3.3 SPI控制器及设备
/ {
soc {
spi0: spi@30000000 {
compatible = "vendor,spi";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x30000000 0x1000>;
interrupts = <0 30 4>;
clocks = <&clk_spi>;
num-cs = <2>; // 2个片选信号
status = "okay";
// SPI Flash设备
flash@0 {
compatible = "jedec,spi-nor";
reg = <0>; // CS0
spi-max-frequency = <50000000>;
#address-cells = <1>;
#size-cells = <1>;
partition@0 {
label = "bootloader";
reg = <0x00000000 0x00100000>;
};
partition@100000 {
label = "kernel";
reg = <0x00100000 0x00500000>;
};
partition@600000 {
label = "rootfs";
reg = <0x00600000 0x01a00000>;
};
};
// SPI ADC设备
adc@1 {
compatible = "ti,adc128s052";
reg = <1>; // CS1
spi-max-frequency = <1000000>;
vref-supply = <&vref_reg>;
};
};
};
};
3.4 GPIO控制器
/ {
soc {
gpio0: gpio@40000000 {
compatible = "vendor,gpio";
reg = <0x40000000 0x1000>;
interrupts = <0 40 4>;
#gpio-cells = <2>;
gpio-controller;
#interrupt-cells = <2>;
interrupt-controller;
ngpios = <32>;
status = "okay";
};
gpio1: gpio@40001000 {
compatible = "vendor,gpio";
reg = <0x40001000 0x1000>;
interrupts = <0 41 4>;
#gpio-cells = <2>;
gpio-controller;
#interrupt-cells = <2>;
interrupt-controller;
ngpios = <16>;
status = "okay";
};
};
};
3.5 以太网控制器
/ {
soc {
eth0: ethernet@50000000 {
compatible = "vendor,eth";
reg = <0x50000000 0x1000>;
interrupts = <0 50 4>;
clocks = <&clk_eth>;
phy-mode = "rgmii";
phy-handle = <&phy0>;
status = "okay";
mdio {
#address-cells = <1>;
#size-cells = <0>;
phy0: ethernet-phy@0 {
reg = <0>;
reset-gpios = <&gpio0 15 GPIO_ACTIVE_LOW>;
reset-assert-us = <10000>;
reset-deassert-us = <10000>;
};
};
};
};
};
4. 时钟和电源管理
4.1 时钟控制器
/ {
clocks {
#address-cells = <1>;
#size-cells = <1>;
ranges;
osc24m: osc24m {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <24000000>;
clock-output-names = "osc24m";
};
clk_pll: pll@10010000 {
compatible = "vendor,pll";
reg = <0x10010000 0x1000>;
#clock-cells = <1>;
clocks = <&osc24m>;
clock-output-names = "pll_cpu", "pll_ddr", "pll_periph";
};
clk_uart: uart_clk {
compatible = "fixed-factor-clock";
#clock-cells = <0>;
clocks = <&clk_pll 2>;
clock-div = <4>;
clock-mult = <1>;
clock-output-names = "uart_clk";
};
};
};
4.2 电源管理
/ {
regulators {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <0>;
reg_3v3: regulator@0 {
compatible = "regulator-fixed";
reg = <0>;
regulator-name = "3V3";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
regulator-always-on;
};
reg_1v8: regulator@1 {
compatible = "regulator-fixed";
reg = <1>;
regulator-name = "1V8";
regulator-min-microvolt = <1800000>;
regulator-max-microvolt = <1800000>;
gpio = <&gpio0 12 GPIO_ACTIVE_HIGH>;
enable-active-high;
};
vref_reg: regulator@2 {
compatible = "regulator-fixed";
reg = <2>;
regulator-name = "vref";
regulator-min-microvolt = <2500000>;
regulator-max-microvolt = <2500000>;
};
};
};
5. 引脚控制(Pinctrl)
5.1 引脚复用配置
/ {
soc {
pinctrl: pinctrl@60000000 {
compatible = "vendor,pinctrl";
reg = <0x60000000 0x1000>;
// UART0引脚配置
uart0_pins: uart0-pins {
pins = "PA0", "PA1";
function = "uart0";
bias-disable;
};
// I2C0引脚配置
i2c0_pins: i2c0-pins {
pins = "PA2", "PA3";
function = "i2c0";
bias-pull-up;
};
// SPI0引脚配置
spi0_pins: spi0-pins {
pins = "PA4", "PA5", "PA6", "PA7";
function = "spi0";
bias-disable;
};
// 以太网引脚配置
eth_pins: eth-pins {
pins = "PB0", "PB1", "PB2", "PB3",
"PB4", "PB5", "PB6", "PB7",
"PB8", "PB9", "PB10", "PB11";
function = "eth";
drive-strength = <40>;
bias-disable;
};
// LED引脚配置
led_pins: led-pins {
pins = "PC0", "PC1", "PC2";
function = "gpio";
bias-pull-up;
output-high;
};
};
// 在设备节点中引用pinctrl
uart0: serial@10000000 {
compatible = "vendor,uart";
reg = <0x10000000 0x1000>;
interrupts = <0 10 4>;
clocks = <&clk_uart>;
pinctrl-names = "default";
pinctrl-0 = <&uart0_pins>;
status = "okay";
};
i2c0: i2c@20000000 {
compatible = "vendor,i2c";
reg = <0x20000000 0x1000>;
interrupts = <0 20 4>;
clocks = <&clk_i2c>;
pinctrl-names = "default";
pinctrl-0 = <&i2c0_pins>;
status = "okay";
};
};
};
6. 完整开发板实例
6.1 完整的开发板设备树
// vendor-board-v1.dts
/dts-v1/;
#include "vendor-soc.dtsi" // 包含SoC通用定义
/ {
model = "Vendor Development Board V1.0";
compatible = "vendor,board-v1", "vendor,soc";
// 内存配置
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x40000000>; // 1GB RAM
};
// 保留内存
reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;
// CMA区域
linux,cma {
compatible = "shared-dma-pool";
reusable;
size = <0x10000000>; // 256MB
alignment = <0x2000>;
linux,cma-default;
};
// DSP专用内存
dsp_reserved: dsp@88000000 {
reg = <0x88000000 0x08000000>; // 128MB
no-map;
};
};
// 启动参数
chosen {
bootargs = "console=ttyS0,115200 earlycon root=/dev/mmcblk0p2 rootwait";
stdout-path = "serial0:115200n8";
};
// 别名
aliases {
serial0 = &uart0;
serial1 = &uart1;
ethernet0 = ð0;
mmc0 = &sdhci0;
spi0 = &spi0;
i2c0 = &i2c0;
i2c1 = &i2c1;
};
// LED定义
leds {
compatible = "gpio-leds";
led-0 {
label = "heartbeat";
gpios = <&gpio0 0 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "heartbeat";
default-state = "off";
};
led-1 {
label = "mmc0";
gpios = <&gpio0 1 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "mmc0";
default-state = "off";
};
led-2 {
label = "cpu";
gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "cpu0";
default-state = "off";
};
};
// 按键定义
gpio-keys {
compatible = "gpio-keys";
button-0 {
label = "User Button 0";
gpios = <&gpio0 3 GPIO_ACTIVE_LOW>;
linux,code = <KEY_PROG1>;
};
button-1 {
label = "User Button 1";
gpios = <&gpio0 4 GPIO_ACTIVE_LOW>;
linux,code = <KEY_PROG2>;
};
};
// 背光控制
backlight: backlight {
compatible = "pwm-backlight";
pwms = <&pwm 0 50000 0>; // PWM通道0,50kHz
brightness-levels = <0 4 8 16 32 64 128 255>;
default-brightness-level = <6>;
enable-gpios = <&gpio0 5 GPIO_ACTIVE_HIGH>;
};
// 音频编解码器
sound {
compatible = "simple-audio-card";
simple-audio-card,name = "Vendor-Board-Sound";
simple-audio-card,format = "i2s";
simple-audio-card,mclk-fs = <256>;
simple-audio-card,cpu {
sound-dai = <&i2s0>;
};
simple-audio-card,codec {
sound-dai = <&codec>;
};
};
};
// 启用/禁用具体设备
&uart0 {
pinctrl-names = "default";
pinctrl-0 = <&uart0_pins>;
status = "okay";
};
&uart1 {
status = "disabled"; // 默认禁用UART1
};
&i2c0 {
pinctrl-names = "default";
pinctrl-0 = <&i2c0_pins>;
status = "okay";
// 扩展板上的设备
expansion_eeprom: eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
pagesize = <16>;
};
};
&i2c1 {
pinctrl-names = "default";
pinctrl-0 = <&i2c1_pins>;
status = "okay";
// 音频编解码器
codec: wm8960@1a {
compatible = "wlf,wm8960";
reg = <0x1a>;
#sound-dai-cells = <0>;
};
};
&spi0 {
pinctrl-names = "default";
pinctrl-0 = <&spi0_pins>;
status = "okay";
flash@0 {
compatible = "winbond,w25q128", "jedec,spi-nor";
reg = <0>;
spi-max-frequency = <50000000>;
#address-cells = <1>;
#size-cells = <1>;
partition@0 {
label = "bootloader";
reg = <0x000000 0x100000>;
};
partition@100000 {
label = "kernel";
reg = <0x100000 0x500000>;
};
partition@600000 {
label = "rootfs";
reg = <0x600000 0xa00000>;
};
};
};
ð0 {
pinctrl-names = "default";
pinctrl-0 = <ð_pins>;
phy-mode = "rgmii";
status = "okay";
fixed-link {
speed = <1000>;
full-duplex;
};
};
&sdhci0 {
pinctrl-names = "default";
pinctrl-0 = <&sdhci0_pins>;
bus-width = <4>;
cd-gpios = <&gpio0 6 GPIO_ACTIVE_LOW>;
wp-gpios = <&gpio0 7 GPIO_ACTIVE_HIGH>;
status = "okay";
};
&usb0 {
dr_mode = "host";
status = "okay";
};
&pwm {
pinctrl-names = "default";
pinctrl-0 = <&pwm0_pins>;
status = "okay";
};
7. 设备树驱动实例
7.1 解析设备树的驱动程序
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/gpio/consumer.h>
#include <linux/regulator/consumer.h>
struct my_device_data {
struct device *dev;
void __iomem *regs;
struct gpio_desc *reset_gpio;
struct regulator *vcc_supply;
int irq;
u32 clock_frequency;
const char *device_name;
};
static const struct of_device_id my_device_of_match[] = {
{ .compatible = "vendor,my-device" },
{ .compatible = "vendor,my-device-v2" },
{},
};
MODULE_DEVICE_TABLE(of, my_device_of_match);
static int my_device_parse_dt(struct platform_device *pdev,
struct my_device_data *data)
{
struct device_node *np = pdev->dev.of_node;
int ret;
if (!np) {
dev_err(&pdev->dev, "No device tree node found\n");
return -EINVAL;
}
// 获取设备名称
ret = of_property_read_string(np, "device-name", &data->device_name);
if (ret) {
data->device_name = "default";
}
// 获取时钟频率
ret = of_property_read_u32(np, "clock-frequency", &data->clock_frequency);
if (ret) {
data->clock_frequency = 1000000; // 默认1MHz
}
// 获取GPIO
data->reset_gpio = devm_gpiod_get_optional(&pdev->dev, "reset",
GPIOD_OUT_HIGH);
if (IS_ERR(data->reset_gpio)) {
return PTR_ERR(data->reset_gpio);
}
// 获取电源
data->vcc_supply = devm_regulator_get_optional(&pdev->dev, "vcc");
if (IS_ERR(data->vcc_supply)) {
if (PTR_ERR(data->vcc_supply) == -EPROBE_DEFER)
return -EPROBE_DEFER;
data->vcc_supply = NULL;
}
// 获取中断
data->irq = platform_get_irq(pdev, 0);
if (data->irq < 0) {
dev_info(&pdev->dev, "No IRQ specified\n");
}
return 0;
}
static int my_device_probe(struct platform_device *pdev)
{
struct my_device_data *data;
int ret;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->dev = &pdev->dev;
// 解析设备树
ret = my_device_parse_dt(pdev, data);
if (ret)
return ret;
// 映射寄存器
data->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(data->regs))
return PTR_ERR(data->regs);
// 使能电源
if (data->vcc_supply) {
ret = regulator_enable(data->vcc_supply);
if (ret) {
dev_err(&pdev->dev, "Failed to enable regulator\n");
return ret;
}
}
// 硬件复位
if (data->reset_gpio) {
gpiod_set_value_cansleep(data->reset_gpio, 0);
msleep(10);
gpiod_set_value_cansleep(data->reset_gpio, 1);
msleep(100);
}
platform_set_drvdata(pdev, data);
dev_info(&pdev->dev, "Device %s probed, clock: %u Hz\n",
data->device_name, data->clock_frequency);
return 0;
}
static int my_device_remove(struct platform_device *pdev)
{
struct my_device_data *data = platform_get_drvdata(pdev);
if (data->vcc_supply)
regulator_disable(data->vcc_supply);
return 0;
}
static struct platform_driver my_device_driver = {
.probe = my_device_probe,
.remove = my_device_remove,
.driver = {
.name = "my-device",
.of_match_table = my_device_of_match,
},
};
module_platform_driver(my_device_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Device Tree aware device driver");
8. 设备树编译和调试
8.1 编译设备树
# Makefile示例
DTC = dtc
DTS = vendor-board-v1.dts
DTB = vendor-board-v1.dtb
all: $(DTB)
$(DTB): $(DTS)
$(DTC) -I dts -O dtb -o $(DTB) $(DTS)
clean:
rm -f $(DTB)
# 内核中的编译
obj-y += vendor-board-v1.dtb
8.2 设备树调试命令
# 查看设备树
cat /proc/device-tree/model
ls /proc/device-tree/
# 使用dtc工具反编译
dtc -I fs /proc/device-tree
# 查看特定设备
find /proc/device-tree -name "*uart*"
# 内核启动参数添加设备树调试
bootargs="console=ttyS0,115200 earlycon devicetree=debug"
设备树是现代Linux嵌入式系统的核心组成部分,通过合理设计设备树结构,可以创建高度可移植和可维护的硬件描述。掌握设备树语法和驱动程序的设备树解析是嵌入式Linux开发的重要技能。

