标志在 termios 库中是如何表示的?

How are flags represented in the termios library?

我是 C 语言和驱动程序编程的新手。目前,我正在编写一个用户 space 驱动程序以使用 Debian 通过 USB 与 RS232 通信。在研究时,我遇到了以下代码。

tty.c_cflag     &=  ~PARENB;            // No Parity
tty.c_cflag     &=  ~CSTOPB;            // 1 Stop Bit
tty.c_cflag     &=  ~CSIZE;
tty.c_cflag     |=  CS8;                // 8 Bits

我理解这些行的后果,但是,只有当每个控制标志常量(PARENB、CSTOPB 等)是这些标志组合的相同长度时,这些操作才有意义。我似乎无法通过任何文档来验证这一点(到目前为止,这是我对 C 的主要不满之一,很难找到易于理解的文档。)来确认这一点。

我想确保我正确理解了程序,因为它是一种纯粹的归纳法,我不确定为什么这些标志会这样存储。有人可以验证这些发现,或者指出一些我可能忽略的地方吗?

例如。

tty.c_cflag hypothetically is 4-bits long, each of the flags from the
previous code block corresponding to bits 3, 2, 1, 0. Then I believe the
following is how these are stored, if we were to say flags PARENB (3) and
CSTOPB (2) are high, and the other two flags are disabled. 

tty.c_cflag = 1100
PARENB = 1000
CSTOPB = 0100
CSIZE = 0000
CS8 = 0000

在 C 语言中,您能找到的最好的文档是源代码本身,您可以在计算机上的 /usr/include/termios.h 找到它(实际上分布在其中的一个或多个包含文件中)— 这里是bsd based termios.h for apples 我的回答基于,值可能会根据您的 Unix 风格而改变。

在那里,你会发现你的tty对象是struct termios类型,定义如下:

struct termios {
    tcflag_t    c_iflag;    /* input flags */
    tcflag_t    c_oflag;    /* output flags */
    tcflag_t    c_cflag;    /* control flags */
    tcflag_t    c_lflag;    /* local flags */
    cc_t        c_cc[NCCS]; /* control chars */
    speed_t     c_ispeed;   /* input speed */
    speed_t     c_ospeed;   /* output speed */
};

所以c_cflag的类型是tcflag_t,定义如下:

typedef unsigned long   tcflag_t;

unsigned long预计为4字节,即32位。

那么您在示例中使用的所有标志都定义如下;使用 8 字节值:

#define PARENB      0x00001000  /* parity enable */
#define CSTOPB      0x00000400  /* send 2 stop bits */
#define CSIZE       0x00000300  /* character size mask */
#define CS8         0x00000300      /* 8 bits */

话虽这么说,它的工作方式是 c_cflag 用作位数组,这意味着每个位对于一个函数都很重要。这是一种常用的方法,因为位运算在处理能力上"cheap"(你的CPU一个周期可以做一个位运算),而在内存space上"cheap",如而不是使用 32 个布尔值的数组来存储值(一种大小为 1 字节的布尔类型来存储一个二进制值),您可以每个字节存储 8 个二进制值。

另一个优势和优化是因为您的 CPU 至少是 32 位,并且在 2015 年可能是 64 位,它可以对一个 [= 中的 32 个值应用掩码133=]循环.

位掩码的另一种表示形式是创建如下所示的结构:

struct tcflag_t {
    bool cignore;
    uint8_t csize;
    bool cstopb;
    bool cread;
    bool parenb;
    bool hupcl;
    bool clocal;
    bool ccts_oflow;
    bool crts_iflow;
    bool cdtr_iflow;
    bool ctdr_oflow;
    bool ccar_oflow;
};

这将是 12 个字节。要更改它们,您必须执行 12 次操作。

然后您可以对字节执行的操作遵循布尔逻辑,该逻辑由真值表定义:

And (&), Or (|) and Not (~)真值表:

| a | b | & |    | a | b | | |    | a | ~ |
| - | - | - |    | - | - | - |    | 0 | 1 |
| 0 | 0 | 0 |    | 0 | 0 | 0 |    | 1 | 0 |
| 0 | 1 | 0 |    | 0 | 1 | 1 |
| 1 | 0 | 0 |    | 1 | 0 | 1 |
| 1 | 1 | 1 |    | 1 | 1 | 1 |

我们通常将 And 运算符昵称为 "force to zero",将 Or 运算符昵称为 "force to 1",因为 除非两个值都是 1,否则 And 将导致 0,除非两个值都是 0,否则 Or会 结果 1.

因此,如果我们认为 tty.c_cflag = 0x00000000 并且您想启用奇偶校验:

tty.c_cflag |= PARENB;

然后 tty.c_cflag 将包含 0x00001000,即 0b1000000000000

那么你要设置 7 位大小:

tty.c_cflag |= CS7;

tty.c_cflag将包含0x00001200,即0b1001000000000


现在,让我们回到您的问题:您的 "equivalent" 示例并不真正具有代表性,因为您认为 CSIZECS8 不包含任何值。

那么让我们看一下您从示例中获取的代码:

tty.c_cflag     &=  ~PARENB;            // No Parity
tty.c_cflag     &=  ~CSTOPB;            // 1 Stop Bit
tty.c_cflag     &=  ~CSIZE;
tty.c_cflag     |=  CS8;                // 8 Bits

这里,tty.c_cflag包含一个未知值:

0b????????????????????????????????

而且您知道您不需要奇偶校验、一个停止位和 8 位的数据大小。所以你在这里 取反 "set parity" 值将其关闭:

~PARENB == 0b0111111111111

然后使用 And 运算符,您将位强制为零:

tty.c_cflag &= ~PARENB —→ 0b???????????????????0????????????

然后你对CSTOPB做同样的事情:

tty.c_cflag &= ~CSTOPB —→ 0b???????????????????0?0??????????

最后 CSIZE:

tty.c_cflag &= ~CSIZE  —→ 0b???????????????????0?000????????

对于CSIZE,目标是确保重置数据长度的两位值。 然后通过强制 1 值设置正确的长度:

tty.c_cflag |= CS8     —→ 0b???????????????????0?011????????

其实把CSIZE改成00再把CS8改成11是没用的,因为 直接 tty.c_cflag |= CS8 会变成 11。但这是一个很好的做法,以防万一 你想从 CS8 更改为 CS7,这将只设置两个位之一,即 另一个保持原始值。

最后,当您打开串行端口时,库将检查这些值以 配置端口,并为您没有强制使用的所有其他值使用默认值 你将能够使用你的串行端口。

我希望我的解释能帮助你更好地理解标志设置是怎么回事 在串行端口上,以及完全使用位掩码。仅供参考,同样的原则是 用于很多其他事情,例如 IPv4 网络掩码、文件 I/O 等

宏的实际值取决于平台(例如,在 Linux CSTOPB 上定义为 0100 而在某些 BSD 上它是 02000)。这就是为什么你不应该对它们的确切值做出假设。

例如,CSIZECS8 具有相同的值确实很常见,但在某些平台上它们可能不相同,因此您首先使用 [=13= 的补码进行 AND ] 掩码(它将影响字符大小的所有位设置为零),然后在这些位的值中进行或运算。如果您假设 CS8 与掩码是相同的模式,您可以省略第一步,但是代码会做错事,并且在没有任何警告的情况下以非常模糊的方式,在一个平台上这个假设不成立。

这里 PARENBCSTOPB 是单独的位标志(正好是一个 1 位),可以通过按位或 | 设置,并通过按位与清除 & 他们的补充 ~。同时,包括 CS8 在内的字符大小可以有任意数量的 1 位,包括零 - 它们更像是存储在较大整数的特定位中的小整数。 CSIZE 是一个掩码,在所有表示字符大小的地方(CS5CS6CS7CS8 中的任何一个)都有 1 位 - 这掩码可用于精确提取字符大小(例如,测试 if ((tty.c_flag & CSIZE) == CS8)),或在设置前清除它(如 tty.c_flag &= ~CSIZE 的情况)。