跳到主要内容位置

关中断与自旋锁

问题来自于学习DS18B20温度传感器Linux驱动,因为设备脉冲信号时间很短,很可能错失中断,且需要关中断

在read函数中完成硬件操作:启动温度转换,休眠等待1s,再读取温度。关中断使用spin_lock_irqsave(&ds18b20_spinlock, flags);,恢复中断使用spin_unlock_irqrestore(&ds18b20_spinlock, flags);

/* 实现对应的open/read/write等函数,填入file_operations结构体 */
static ssize_t ds18b20_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
int err;
unsigned long flags;
unsigned char kern_buf[9];
int i;
int result_buf[2];
if (size != 8)
{
return -EINVAL;
}
/* 1.启动温度转换 */
// 1.1 关中断
spin_lock_irqsave(&ds18b20_spinlock, flags);
// 1.2 发出reset信号并等待回应
err = ds18b20_reset_and_wait_ack();
if (err)
{
spin_unlock_irqrestore(&ds18b20_spinlock, flags);
printk("ds18b20_reset_and_wait_ack first err\n");
return err;
}
// 1.3 发出ROM命令:skip rom,0xcc
ds18b20_send_cmd(0xcc);
// 1.4 发出FUN命令:启动温度转换,0x44
ds18b20_send_cmd(0x44);
// 1.5 恢复中断
spin_unlock_irqrestore(&ds18b20_spinlock, flags);
/* 2.等待温度转换成功: 可能长达1s,需要休眠等待 */
schedule_timeout_interruptible(msecs_to_jiffies(1000));
/* 3.读取温度 */
/* 3.1 关中断 */
spin_lock_irqsave(&ds18b20_spinlock, flags);
/* 3.2 发出reset信号并等待回应 */
err = ds18b20_reset_and_wait_ack();
if (err)
{
spin_unlock_irqrestore(&ds18b20_spinlock, flags);
printk("ds18b20_reset_and_wait_ack second err\n");
return err;
}
/* 3.3 发出ROM命令:skip rom,0xcc */
ds18b20_send_cmd(0xcc);
/* 3.4 发出FUN命令:read scratchpad,0xbe */
ds18b20_send_cmd(0xbe);
/* 3.5 读9byte的数据 */
for ( i = 0; i < 9; i++)
{
ds18b20_read_byte_data(&kern_buf[i]);
}
/* 3.6 恢复中断 */
spin_unlock_irqrestore(&ds18b20_spinlock, flags);
/* 3.7 校验CRC */
err = ds18b20_verify_crc(kern_buf);
if (err)
{
printk("ds18b20_verify_crc err\n");
return err;
}
/* 4.copy_to_user */
ds18b20_calc_val(kern_buf, result_buf);
err = copy_to_user(buf, result_buf, 8); //在imx6ull上一个int是4字节
return 8;
}
/* 定义自己的file_operations结构体 */
static struct file_operations gpio_key_drv = {
.owner = THIS_MODULE,
.read = ds18b20_read,
};

关中断与自旋锁等知识见《Linux设备驱动开发详解 - 宋宝华》7.5自旋锁 7.5.1 自旋锁的使用。 自旋锁可以保证临界区不受别的CPU和本CPU内的抢占进程打扰,但是得到锁的代码路径在执行临界区的时候,还可能受到中断和底半部(BH,稍后的章节会介绍)的影响。

spin_lock_irq(spinlock_t *lock): spin_lock(lock) + local_irq_disable()
spin_unlock_irq(spinlock_t *lock): spin_unlock(lock) + local_irq_enable()
spin_lock_irqsave(spinlock_t *lock, unsigned long flags)spin_lock(lock) + local_irq_save(flags) //关中断并保存状态字
spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)spin_unlock(lock) + local_irq_restore(flags) //开中断并恢复状态字

这两对函数的区别在于 IRQ(中断请求)的状态的保存和恢复方式。

  1. spin_lock_irq()spin_unlock_irq()
    • spin_lock_irq() 函数会先禁用 IRQ,然后获取自旋锁。
    • spin_unlock_irq() 函数会先释放自旋锁,然后启用 IRQ。

这对函数中,IRQ 的状态不会保存和恢复。如果在获取锁期间发生中断,中断处理程序将不会执行,因为 IRQ 被禁用,这可能会导致一些问题。

  1. spin_lock_irqsave()spin_unlock_irqrestore()
    • spin_lock_irqsave() 函数会先保存当前 CPU 的 IRQ 状态,并禁用 IRQ,然后获取自旋锁。
    • spin_unlock_irqrestore() 函数会先释放自旋锁,然后恢复保存的 IRQ 状态。

这对函数会在获取锁之前保存当前 CPU 的 IRQ 状态,并在释放锁后恢复保存的 IRQ 状态。这样,在获取锁期间发生的中断不会被忽略,而是在释放锁后恢复执行中断处理程序。

注意:在使用 spin_lock_irqsave()spin_unlock_irqrestore() 时,必须将 unsigned long 类型的变量用作第二个参数,用于保存和恢复 IRQ 状态。