Linux 串行通信与中断

Reh*_*b11 2 linux signals serial-port linux-kernel interrupt-handling

我有两个设备通过串行端口相互连接。一个作为主人,另一个作为奴隶。

主设备是一个基于 ARM 的套件,运行 Linux。

目前的情况是master发送命令然后轮询com端口直到slave回复。

现在我不想使用轮询。我需要处理器执行其他任务,直到从机回复。

我知道解决方案是使用中断,但找不到更多详细信息。我找到了一些使用信号的解决方案。它以非阻塞模式读取 tty,然后在数据准备好时发送 io 信号。

那么,串行通信中中断和信号有什么区别呢?当使用中断时我应该编写设备驱动程序或内核模块等吗?为了不使用轮询,还有其他有效的解决方案吗?

Sam*_*nko 6

为了不使用轮询,还有其他有效的解决方案吗?

多线程方法

前段时间我遇到了同样的任务,我发现解决它的最好方法是使用pthreads。基本上,我接下来做了:

  • 生产者线程(主线程):等待串口有新数据;一旦有新数据可用——将其写入循环缓冲区产生数据)并通过以下方式唤醒消费者线程pthread_cond_signal()
  • 消费者线程:等待生产者线程唤醒我(使用pthread_cond_wait()函数);一旦醒来——从循环缓冲区读取新数据(消耗数据)并正确处理它
  • 就我而言,串行端口以非阻塞模式打开,因为我想在函数中提供超时等待新数据poll()。如果您不需要这个——您可以以阻塞模式打开串行端口,然后仅使用阻塞read()调用(如@sawdust 在评论中建议的那样)

这样你就不会浪费CPU时间等待新数据到达,同时你也不需要弄乱信号(使用信号有很多复杂性,所以我决定避免它)。

一体机接口

唯一执行异步读取的其他 API 是POSIX AIO接口中的aio_read()。但据我了解,基于信号。唯一的区别是:aio_read()

  • SIGIO信号告诉你有新数据可供读取,需要你手动读取
  • aio_read()为您提供读取数据(同样read(),只是异步)。

我不建议您使用它,因为这个 API 似乎并不广泛,并且与信号驱动方法相比没有任何好处。

那么,串行通信中中断和信号有什么区别呢?

  • 中断是低级概念,因此仅在内核中处理
  • 信号是操作系统的概念,用于内核在发生某些事件时通知用户空间应用程序

换句话说,当你的串口硬件产生中断时,它会在内核中处理,并且内核产生SIGIO信号,通知你的用户空间应用程序有新事件(例如,有新数据可供读取)。

当使用中断时我应该编写设备驱动程序或内核模块等吗?

通常是的,您只能在内核中处理中断。但在这种情况下你不需要。它已经在内核中为您完成,更具体地说,在行规则代码中。它为您生成信号(当发生中断时),您可以在用户空间应用程序中使用该信号。

如果您想要一些详细信息:

  • 驱动程序代码流程:tty_flip_buffer_push() -> tty_schedule_flip()-> queue_work(...)-> flush_to_ldisc()-> receive_buf()-> tty_ldisc_receive_buf()->.receive_buf2()
  • 行规代码流程:n_tty_receive_buf2()-> n_tty_receive_buf_common()->__receive_buf()
  • __receive_buf()做:

    if (read_cnt(ldata)) {
        kill_fasync(&tty->fasync, SIGIO, POLL_IN);
        wake_up_interruptible_poll(&tty->read_wait, POLLIN);
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 它发送SIGIO信号,如下所示:kill_fasync()-> kill_fasync_rcu()-> send_sigio()-> send_sigio_to_task()-> do_send_sig_info()->send_signal()

因此,当串口驱动程序中发生相应的中断时,您可以依靠内核向您传递消息(通过信号)。

也可以看看

[1]串行编程 HOWTO:异步输入示例

[2] TTY 揭秘

[3] LDD3:异步通知