从 /dev/pts/x 读取时的竞争条件
Race condition when reading from /dev/pts/x
我在 this article 中读到,当进程从 tty 设备读取时,比如 /dev/pts/1
,tty driver/line 规程(在熟化模式下)缓冲数据,并且仅当按下 enter
时,数据被传递给进程。我运行下面的实验:
我打开一个终端,记下它正在使用的 tty。假设它是 /dev/pts/0
。
现在我打开另一个终端,为了方便使用 /dev/pts/1
和 运行 一个进程,它只执行以下功能:
// passing /dev/pts/0 to the function
int read_from_tty(char *tty)
{
int bytes_read = 0;
int fd = 0;
char buffer[100];
fd = open(tty, O_RDWR);
if(-1 == fd)
{
printf("Couldn't open pts\n");
return 1;
}
printf("File opened: %d\n", fd);
while(1)
{
bytes_read = read(fd, buffer, 100);
if(-1 == bytes_read)
{
perror("read");
}
if(-1 == write(1, buffer, bytes_read))
{
perror("write:");
}
memset(buffer, 0, 100);
}
}
现在我开始在 /dev/pts/0
终端中输入字符,我发现字符主要出现在 /dev/pts/1
终端中,但在大约 10 个字符中我看到一个字符出现在 /dev/pts/0
终端。
如果文章中写的是真的,貌似,字符应该在行规中缓冲,只有当我按下回车时才会传递给其中一个读取进程(假设bash
是只是坐在read
)。
有人可以解释一下吗?
编辑
进一步检查一下。我将以下行添加到上面的代码中:
...
bytes_read = read(fd, buffer, 100);
if(-1 == bytes_read)
{
perror("read");
}
printf("Bytes read: %d\n", bytes_read);
if(-1 == write(1, buffer, bytes_read))
{
...
我可以看到当我读取/dev/pts/0
时,它一次只读取1个字节。然而,如果我 运行 它与 /dev/pts/1
(实际上是从 stdin 读取),它会读取整行。
有人可以解释一下吗?
实际上,bash
在从终端读取时将终端设置为非规范模式,当它到达行尾时,它将规范模式的终端设置回 运行 命令行.
同样的体验可以用两个终端完成:
- 1 号航站楼 (/dev/pts/6):启动
strace /bin/bash
- 2 号航站楼:发射
strace cat /dev/pts/6
终端#1 上的 bash shell 停用规范模式并调用 pselect() 等待输入:
$ strace /bin/bash
[...]
ioctl(0, TCGETS, {B38400 opost isig -icanon -echo ...}) = 0
[...]
pselect6(1, [0], NULL, NULL, NULL, {[], 8}
在终端 #2 上,cat
命令仅调用阻塞 read() 从终端获取字符:
$ strace cat /dev/pts/6
[...]
openat(AT_FDCWD, "/dev/pts/6", O_RDONLY) = 3
[...]
read(3,
因此,bash
和 cat
都在终端上同时读取。当我们在终端#1 中键入字符时,pselect() returns 指示字符可用,然后 bash
调用阻塞 read () 获取字符。但是来自 cat
的并发 read() 静止调用 pselect() 和 read( ) 由 bash
。有时,bash
可以在 cat
.
之前得到一个字符
这是一个示例,其中 pselect() returns 因为一个字符可用(我输入了“Y”)和随后的 read( ) 被调用以在终端#1 上获取它:
pselect6(1, [0], NULL, NULL, NULL, {[], 8}) = 1 (in [0])
read(0,
但是read()从cat
在另一个终端上成功获取了read()之前的字符bash
:
write(1, "Y", 1Y) = 1
read(3,
有时,bash
可以获取到 cat
之前输入的字符。通常,当它在 read() 调用时被阻塞(也就是说它错过了 pselect() 检测到的字符,但它会能够在 cat
)...
对 read() 的调用之一之前获取后续键入的字符之一
旁注
当我们在 bash 下启动 stty -a
时,显示显示终端处于规范模式:
$ stty -a
[...]
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke -flusho -extproc
这是因为 bash
在启动 stty
命令之前重新激活规范模式。
我在 this article 中读到,当进程从 tty 设备读取时,比如 /dev/pts/1
,tty driver/line 规程(在熟化模式下)缓冲数据,并且仅当按下 enter
时,数据被传递给进程。我运行下面的实验:
我打开一个终端,记下它正在使用的 tty。假设它是 /dev/pts/0
。
现在我打开另一个终端,为了方便使用 /dev/pts/1
和 运行 一个进程,它只执行以下功能:
// passing /dev/pts/0 to the function
int read_from_tty(char *tty)
{
int bytes_read = 0;
int fd = 0;
char buffer[100];
fd = open(tty, O_RDWR);
if(-1 == fd)
{
printf("Couldn't open pts\n");
return 1;
}
printf("File opened: %d\n", fd);
while(1)
{
bytes_read = read(fd, buffer, 100);
if(-1 == bytes_read)
{
perror("read");
}
if(-1 == write(1, buffer, bytes_read))
{
perror("write:");
}
memset(buffer, 0, 100);
}
}
现在我开始在 /dev/pts/0
终端中输入字符,我发现字符主要出现在 /dev/pts/1
终端中,但在大约 10 个字符中我看到一个字符出现在 /dev/pts/0
终端。
如果文章中写的是真的,貌似,字符应该在行规中缓冲,只有当我按下回车时才会传递给其中一个读取进程(假设bash
是只是坐在read
)。
有人可以解释一下吗?
编辑
进一步检查一下。我将以下行添加到上面的代码中:
...
bytes_read = read(fd, buffer, 100);
if(-1 == bytes_read)
{
perror("read");
}
printf("Bytes read: %d\n", bytes_read);
if(-1 == write(1, buffer, bytes_read))
{
...
我可以看到当我读取/dev/pts/0
时,它一次只读取1个字节。然而,如果我 运行 它与 /dev/pts/1
(实际上是从 stdin 读取),它会读取整行。
有人可以解释一下吗?
实际上,bash
在从终端读取时将终端设置为非规范模式,当它到达行尾时,它将规范模式的终端设置回 运行 命令行.
同样的体验可以用两个终端完成:
- 1 号航站楼 (/dev/pts/6):启动
strace /bin/bash
- 2 号航站楼:发射
strace cat /dev/pts/6
终端#1 上的 bash shell 停用规范模式并调用 pselect() 等待输入:
$ strace /bin/bash
[...]
ioctl(0, TCGETS, {B38400 opost isig -icanon -echo ...}) = 0
[...]
pselect6(1, [0], NULL, NULL, NULL, {[], 8}
在终端 #2 上,cat
命令仅调用阻塞 read() 从终端获取字符:
$ strace cat /dev/pts/6
[...]
openat(AT_FDCWD, "/dev/pts/6", O_RDONLY) = 3
[...]
read(3,
因此,bash
和 cat
都在终端上同时读取。当我们在终端#1 中键入字符时,pselect() returns 指示字符可用,然后 bash
调用阻塞 read () 获取字符。但是来自 cat
的并发 read() 静止调用 pselect() 和 read( ) 由 bash
。有时,bash
可以在 cat
.
这是一个示例,其中 pselect() returns 因为一个字符可用(我输入了“Y”)和随后的 read( ) 被调用以在终端#1 上获取它:
pselect6(1, [0], NULL, NULL, NULL, {[], 8}) = 1 (in [0])
read(0,
但是read()从cat
在另一个终端上成功获取了read()之前的字符bash
:
write(1, "Y", 1Y) = 1
read(3,
有时,bash
可以获取到 cat
之前输入的字符。通常,当它在 read() 调用时被阻塞(也就是说它错过了 pselect() 检测到的字符,但它会能够在 cat
)...
旁注
当我们在 bash 下启动 stty -a
时,显示显示终端处于规范模式:
$ stty -a
[...]
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke -flusho -extproc
这是因为 bash
在启动 stty
命令之前重新激活规范模式。