4.3. 访问串口设备

像 Unix 系统中的所有设备一样,串口通过设备特殊文件进行访问,这些文件位于/dev目录中。有两种类型的设备文件与串口驱动程序相关,每种类型的设备文件每个端口都有一个。设备的表现会略有不同,这取决于我们打开的是哪个设备文件。我们将介绍这些差异,因为它将帮助您理解您可能看到的与串口设备相关的一些配置和建议,但在实践中您只需要使用其中一个。在未来的某个时候,其中一个甚至可能会完全消失。

在两类串口设备中,最重要的是主设备号为 4 的一类,其设备特殊文件名为ttyS0, ttyS1等等。第二种类型的主设备号为 5,设计用于通过端口拨出(呼出);其设备特殊文件名为cua0, cua1等等。在 Unix 世界中,计数通常从零开始,而外行人倾向于从一开始。这给人们带来了一些困惑,因为COM1由以下表示/dev/ttyS0, COM2/dev/ttyS1等等。任何熟悉 IBM PC 式硬件的人都知道COM3以及更高的端口号实际上从未真正标准化。

cua,或“呼出”设备,被创建来解决调制解调器串口设备上避免冲突的问题,这些调制解调器必须支持传入和传出连接。不幸的是,它们也带来了自身的问题,现在很可能会被淘汰。让我们简要地看一下这个问题。

Linux,像 Unix 一样,允许一个设备或任何其他文件同时被多个进程打开。不幸的是,这对于 tty 设备来说很少有用,因为这两个进程几乎肯定会相互干扰。幸运的是,设计了一种机制,允许进程在打开 tty 设备之前,检查该设备是否已被另一个进程打开。该机制使用所谓的锁文件。其思想是,当一个进程想要打开一个 tty 设备时,它会检查一个特殊位置是否存在一个文件,该文件的命名方式与它打算打开的设备类似。如果该文件不存在,则进程创建它并打开 tty 设备。如果该文件存在,则进程假定另一个进程已经打开了 tty 设备,并采取适当的措施。使锁文件管理系统工作的最后一个巧妙技巧是将创建锁文件的进程 ID (pid) 写入锁文件本身;我们稍后会详细讨论这一点。

锁文件机制在您为锁文件定义了位置并且所有程序都知道在哪里找到它们的情况下,工作得非常好。唉,Linux 并非总是如此。直到 Linux 文件系统标准定义了锁文件的标准位置,从那时起tty锁文件才开始正常工作。曾经至少有四个,甚至可能更多的位置被软件开发人员选择来存储锁文件/usr/spool/locks/, /var/spool/locks/, /var/lock//usr/lock/。混乱造成了混乱。程序在不同的位置打开锁文件,而这些锁文件本应控制单个 tty 设备;就好像根本没有使用锁文件一样。

这些cua设备被创建来提供解决此问题的方法。与其依赖使用锁文件来防止想要使用串口设备的程序之间的冲突,不如决定内核可以提供一种简单的仲裁方式来决定应该授予谁访问权限。如果ttyS设备已经被打开,尝试打开cua将导致一个错误,程序可以将其解释为设备已被使用。如果cua设备已经被打开,并且尝试打开ttyS,请求将被阻塞;也就是说,它将被暂停并等待直到cua设备被另一个进程关闭。如果您有一个配置为拨入访问的调制解调器,并且您偶尔想在同一设备上拨出,那么这种方法效果很好。但是,在有多个程序想要在同一设备上呼出的环境中,它效果不佳。解决争用问题的唯一方法是使用锁文件!又回到了原点。

可以说,Linux 文件系统标准最终提供了解决方案,现在强制要求锁文件存储在/var/lock目录中,并且按照惯例,例如,ttyS1设备的锁文件名是LCK..ttyS1。这些cua锁文件也应该放在这个目录中,但是使用cua设备现在不被鼓励使用。

这些cua设备可能会在一段时间内仍然存在,以提供一段向后兼容期,但随着时间的推移,它们将被淘汰。如果您想知道该使用什么,请坚持使用ttyS设备,并确保您的系统符合 Linux FSSTND 标准,或者至少所有使用串口设备的程序在锁文件位于何处达成一致。大多数处理串口 tty 设备的软件都提供一个编译时选项来指定锁文件的位置。通常,这会显示为一个名为类似LOCKDIRMakefile或在配置头文件中。如果您自己编译软件,最好更改此设置以与 FSSTND 指定的位置一致。如果您使用的是预编译的二进制文件,并且不确定程序将在哪里写入其锁文件,则可以使用以下命令来获取提示
strings binaryfile | grep lock
如果找到的位置与您系统的其余部分不一致,您可以尝试从外部可执行文件想要使用的锁目录创建一个符号链接回到/var/lock/。这很丑陋,但它会起作用。

4.3.1. 串口设备特殊文件

次设备号对于两种类型的串口设备是相同的。如果您的调制解调器在 COM1: 到 COM4: 的端口之一上,则其次设备号将是 COM 端口号加 63。如果您使用的是特殊的串口硬件,例如高性能多端口串口控制器,您可能需要为其创建特殊的设备文件;它可能不会使用标准设备驱动程序。《Serial-HOWTO》应该能够帮助您找到适当的详细信息。

假设您的调制解调器在 COM2: 上。其次设备号将为 65,主设备号在正常使用情况下将为 4。应该有一个名为ttyS1的设备具有这些编号。列出/dev/目录中的串口 tty。第五列和第六列分别显示主设备号和次设备号

$ ls -l /dev/ttyS*
0 crw-rw----   1 uucp     dialout    4,  64 Oct 13  1997 /dev/ttyS0
0 crw-rw----   1 uucp     dialout    4,  65 Jan 26 21:55 /dev/ttyS1
0 crw-rw----   1 uucp     dialout    4,  66 Oct 13  1997 /dev/ttyS2
0 crw-rw----   1 uucp     dialout    4,  67 Oct 13  1997 /dev/ttyS3

如果没有主设备号为 4 和次设备号为 65 的设备,您将必须创建一个。成为超级用户并键入

# mknod -m 666 /dev/ttyS1 c 4 65
# chown uucp.dialout /dev/ttyS1
各种 Linux 发行版对于谁应该拥有串口设备使用了略有不同的策略。有时它们将由 root 拥有,有时它们将由另一个用户拥有,例如uucp在我们的示例中。现代发行版有一个专门用于拨出设备的组,任何被允许使用这些设备的用户都会被添加到这个组中。

有些人建议将/dev/modem创建一个指向您的调制解调器设备的符号链接,这样普通用户就不必记住有些不直观的ttyS1。但是,您不能在一个程序中使用modem,而在另一个程序中使用真实的设备文件名。它们的锁文件将具有不同的名称,并且锁定机制将无法工作。