标志在 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" 示例并不真正具有代表性,因为您认为 CSIZE
和 CS8
不包含任何值。
那么让我们看一下您从示例中获取的代码:
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
)。这就是为什么你不应该对它们的确切值做出假设。
例如,CSIZE
和 CS8
具有相同的值确实很常见,但在某些平台上它们可能不相同,因此您首先使用 [=13= 的补码进行 AND ] 掩码(它将影响字符大小的所有位设置为零),然后在这些位的值中进行或运算。如果您假设 CS8
与掩码是相同的模式,您可以省略第一步,但是代码会做错事,并且在没有任何警告的情况下以非常模糊的方式,在一个平台上这个假设不成立。
这里 PARENB
和 CSTOPB
是单独的位标志(正好是一个 1 位),可以通过按位或 |
设置,并通过按位与清除 &
他们的补充 ~
。同时,包括 CS8
在内的字符大小可以有任意数量的 1 位,包括零 - 它们更像是存储在较大整数的特定位中的小整数。 CSIZE
是一个掩码,在所有表示字符大小的地方(CS5
、CS6
、CS7
、CS8
中的任何一个)都有 1 位 - 这掩码可用于精确提取字符大小(例如,测试 if ((tty.c_flag & CSIZE) == CS8)
),或在设置前清除它(如 tty.c_flag &= ~CSIZE
的情况)。
我是 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" 示例并不真正具有代表性,因为您认为 CSIZE
和 CS8
不包含任何值。
那么让我们看一下您从示例中获取的代码:
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
)。这就是为什么你不应该对它们的确切值做出假设。
例如,CSIZE
和 CS8
具有相同的值确实很常见,但在某些平台上它们可能不相同,因此您首先使用 [=13= 的补码进行 AND ] 掩码(它将影响字符大小的所有位设置为零),然后在这些位的值中进行或运算。如果您假设 CS8
与掩码是相同的模式,您可以省略第一步,但是代码会做错事,并且在没有任何警告的情况下以非常模糊的方式,在一个平台上这个假设不成立。
这里 PARENB
和 CSTOPB
是单独的位标志(正好是一个 1 位),可以通过按位或 |
设置,并通过按位与清除 &
他们的补充 ~
。同时,包括 CS8
在内的字符大小可以有任意数量的 1 位,包括零 - 它们更像是存储在较大整数的特定位中的小整数。 CSIZE
是一个掩码,在所有表示字符大小的地方(CS5
、CS6
、CS7
、CS8
中的任何一个)都有 1 位 - 这掩码可用于精确提取字符大小(例如,测试 if ((tty.c_flag & CSIZE) == CS8)
),或在设置前清除它(如 tty.c_flag &= ~CSIZE
的情况)。