从 /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, 

因此,bashcat 都在终端上同时读取。当我们在终端#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 命令之前重新激活规范模式。