SPI 子系统 ↔ 操作系统

用户态进程的 IO 请求和 SPI 设备驱动的数据传输请求,在抽象层面面对的是同一个问题:如何协调多个请求者,公平高效地访问一份共享资源。

下面从几个维度逐条对比。


1. 调度

操作系统:            CPU 只有一个,多个进程想用 →
                      调度器(CFS/O(1))决定下一个谁跑

SPI 子系统:          SPI 总线只有一条,多个 message 想发 →
                      消息泵(spi_pump_messages)决定下一个谁传

OS 的调度器从 runqueue 里挑进程,SPI 的消息泵从 master->queue 里挑 message。代码里对应:

// spi.c:1428
static int __spi_queued_transfer(struct spi_device *spi, struct spi_message *msg)
{
    list_add_tail(&msg->queue, &master->queue);     // 入队
    kthread_queue_work(&master->kworker, &master->pump_messages);  // 唤醒调度
}

注释也很有意思——仲裁算法"未指定",但核心就是 FIFO:

The master’s main job is to process its message queue
If there are multiple spi_device children, the i/o queue arbitration algorithm is unspecified


2. 同步与异步 IO

操作系统:            read() 可阻塞(同步)→ 进程睡眠等数据
                      或 aio_read/epoll(异步)→ 回调通知

SPI 子系统:          spi_sync() → completion 等待唤醒
                      spi_async() → complete 回调通知
// 同步路径 → 类似阻塞 read()
spi_sync(spi, &msg) {
    msg.complete = spi_complete;    // 内部 completion
    msg.context  = &done;
    spi_async(spi, &msg);           // 入队
    wait_for_completion(&done);     // 睡下去等
    return msg.status;
}

// 异步路径 → 类似 aio_read
msg.complete = my_callback;
msg.context  = my_data;
spi_async(spi, &msg);               // 入队就返回,完成后调 my_callback

同步路径就像调 read() 时进程被 blocking,异步路径就像投递一个 AIO 请求。


3. 中断与下半部

操作系统:            硬件中断 → 中断处理程序(上半个)→ 软中断/tasklet/workqueue(下半个)
                      上半个快进快出,下半个做重活

SPI 子系统:          控制器 FIFO 中断 → 中断处理器(逐字节搬运)→ spi_finalize_current_message(唤醒等待者)
                      类似地上半搬运数据,下半唤醒上层

spi-imx 为例的中断处理模式:

// 中断上半部:抄 FIFO
static irqreturn_t spi_imx_isr(int irq, void *dev_id)
{
    // 从 FIFO 读一个字节到 rx_buf
    // 或往 FIFO 写一个字节从 tx_buf
    // 如果传输完成 → spi_finalize_current_transfer
}

4. 锁机制

操作系统:            自旋锁(短临界区,中断上下文可用)
                      互斥锁(长临界区,可睡眠)

SPI 子系统:          queue_lock(自旋锁)→ 保护 message 入队,中断中可用
                      io_mutex(互斥锁)→ 保护对控制器的长操作

同一段代码中两种锁共存:

struct spi_master {
    spinlock_t queue_lock;       // 自旋锁 → 入队操作
    struct mutex io_mutex;       // 互斥锁 → 总线访问
    spinlock_t bus_lock_spinlock;
    struct mutex bus_lock_mutex; // 互斥锁 → 总线锁定
};

5. 优先级

操作系统:            nice 值 / RT 优先级(SCHED_FIFO)
                      高优先级进程抢占低优先级

SPI 子系统:          master->rt 标志 → 消息泵线程 SCHED_FIFO + MAX_RT_PRIO-1
                      → 接近硬实时优先级
// spi.c:1268
if (master->rt) {
    sched_setscheduler(master->kworker_task, SCHED_FIFO, &param);
    // param.sched_priority = MAX_RT_PRIO - 1 ≈ 98(Linux 默认 RT 最高 99)
}

如果某个 SPI 设备对延迟敏感(比如音频编解码器),controller 驱动设 master->rt = true,消息泵线程就获得了接近实时的调度优先级。


6. 内存管理

操作系统:            malloc/free,mmap
                      DMA 区域映射

SPI 子系统:          spi_alloc_master / spi_master_put
                      spi_map_msg / spi_unmap_msg(DMA 映射)

全局 buf 就像一个小型内核缓冲区池——给短传输节省一次 kmalloc:

static u8 *buf;
// spi_init 中一次分配,spi_write_then_read 复用
buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);

7. 设备即文件

操作系统:            open() → write() → close()
                      所有东西都是文件

SPI 子系统:          spidev → open → ioctl(SPI_IOC_MESSAGE) → close
                      SPI 设备通过 spidev 变成了 /dev/spidev0.0
static const struct file_operations spidev_fops = {
    .owner    = THIS_MODULE,
    .unlocked_ioctl = spidev_ioctl,
    .open     = spidev_open,
    .release  = spidev_release,
};

一张表收尾

维度 操作系统 SPI 子系统
资源 CPU SPI 总线
请求 进程 spi_message
调度器 CFS / SCHED_FIFO spi_pump_messages + kthread
同步 IO blocking read/write spi_sync + completion
异步 IO aio / epoll spi_async + complete 回调
中断处理 上半部 + 下半部 ISR + spi_finalize_current_message
spinlock / mutex queue_lock / io_mutex
优先级 nice / RT master->rt → SCHED_FIFO
内存管理 kmalloc / vmalloc spi_alloc_master / spi_map_msg
用户态接口 系统调用 spidev ioctl

SPI 子系统就是一个针对单条 SPI 总线做了极简设计的微型操作系统——它有调度器(消息泵)、同步/异步 IO 模型、中断驱动、多种锁机制、甚至支持实时优先级。只不过它不管理 CPU 时间片,而管理 spi_message 在总线上的传输顺序。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐