首页 > 解决方案 > Are write failures to cgroup tasks deterministically non-persistent?

问题描述

Consider the following program.

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>

void
setup() {
    system("mkdir /sys/fs/cgroup/cpuset/TestingCpuset");
    system("echo 0,1 > /sys/fs/cgroup/cpuset/TestingCpuset/cpuset.cpus");
    system("echo 0 > /sys/fs/cgroup/cpuset/TestingCpuset/cpuset.mems");
}

int
main() {
    setup();
    // Picked to be the pid of a ordinary thread or process on the currently
    // running system.
    const char* validPid = "30100";
    const char* invalidPid = "2";
    const char* taskPath = "/sys/fs/cgroup/cpuset/TestingCpuset/tasks";
    int fd = open(taskPath, O_WRONLY);
    if (fd < 0) {
        fprintf(stderr, "Failed to open %s; errno %d: %s\n", taskPath, errno,
                strerror(errno));
    }
    int retVal = write(fd, invalidPid, strlen(invalidPid));
    if (retVal < 0) {
        fprintf(stderr, "Invalid write of %s to fd %d; errno %d: %s\n",
                invalidPid, fd, errno, strerror(errno));
    }

    retVal = write(fd, validPid, strlen(validPid));
    if (retVal < 0) {
        fprintf(stderr, "Invalid write of %s to fd %d; errno %d: %s\n",
                validPid, fd, errno, strerror(errno));
    }
}

The output of this program (run under sudo) is:

Invalid write of 2 to fd 3; errno 22: Invalid argument

Note that the subsequent write does not fail; the first write failure did not induce a failure of the next write.

Is this lack of failure persistence deterministic and reliable?

I've looked at the write man page, but it doesn't say anything about persistence of failures.

标签: clinuxsystem-callscgroups

解决方案


Linux 中通常没有与文件描述符相关的错误状态。请参阅下面的链接。

但是,在我们继续之前,请不要使用< 0. 如果发生错误(对于open()or ,write()返回值为") 其中/返回一个负值(实际上并不表示错误,而是一个包装的无符号整数成功值,用于对普通文件的非常大的写入),因此,内核现在限制所有读取/写入略小于 2 GiB。如果每个人都使用 来检查错误,我们根本不会发现它。-1write()read()write()-1< 0

在我看来,最好稍微偏执并捕捉意外错误,而不是假设并可能默默地丢失数据。


这种缺乏故障持久性是确定性和可靠的吗?

对于 下的内核伪文件/sys/,答案是肯定的:每次写入都被视为一个单独的操作。以前对同一描述符的读取或写入不会影响当前写入的结果。

写入 sysfs 伪文件只需调用由伪文件表示的可调参数的 store() 方法;参见fs/sysfs/file.c:sysfs_kf_bin_write()。根本没有记录任何状态。

(我们可以讨论可调参数是否可以记录以前的分配尝试并基于此更改其行为,但我们只是说 Linus Torvalds 根本不会故意让这种东西“飞”起来。)


通常,Linux 内核不会在文件描述中存储任何错误状态。如果我们查看fs/read_write.c:write()(查找SYSCALL_DEFINE3(write,),我们可以看到write()当前内核中的系统调用调用ksys_write(),它验证描述符是否有效(EBADF否则返回错误),然后调用vfs_write(). (需要注意的是,如果成功,与描述符相关的文件位置会使用 更新file_pos_write();文件位置不会自动更新。因此,Linux 中对同一文件描述符的多线程并发写入应该使用pwrite()orpwritev()而不是write(),以避免比赛窗口 wrt. 文件位置更新)。

无论如何,vfs_write()做一些错误检查(EBADF, EINVAL, EFAULT)和簿记,并调用__vfs_write(),这是一个包装函数,它调用适当的文件系统特定函数,要么file->fop->write()要么file->fop->write_iter()

(我们还可以查看fs/file_table.c了解 Linux 内核如何管理其内部文件描述符表(每个用户空间进程),include/linux/fdtable.h:struct fdtable了解描述符表本身,以及include/ linux/fs.h:结构文件,用于定义Linux文件描述。这些结构中根本没有任何与“错误状态”相关的成员。但是,注意f_op成员是有用的struct file:成员是一个指针到一个struct file_operations结构,其中包含与此特定打开文件相关的基本文件操作的每个文件系统处理程序(请参阅include/linux/fs.h:struct file_operations)。)

(注意,在 Linux 中,系统调用返回单个整数。对于错误情况,该整数包含错误编号。零值和正值都被认为是成功的。C 库errno完全在用户空间中维护。如果使用syscall(),则需要检测错误情况并且可以根据需要自行维护errno。因此,当您看到内核系统调用返回 say-EINVAL时,这意味着它EINVAL向用户空间返回错误。C 库负责将其转换为-1with errno == EINVAL。)

同样,描述符中没有记录错误状态,每个操作都是独立发生的,与之前的操作无关(文件位置除外,在撰写本文时,文件位置本身并未原子更新)。一些文件系统理论上可以跟踪操作,并维护与描述符相关的内部错误状态,但同样,除非这是文件系统的一个有据可查的特性,否则其他实现很荣幸,Linux 内核开发人员实际上不太可能允许这样的事物。


重要的是要认识到 Linux 内核开发人员必须遵循两个关键原则(因为 Linus 强制执行):公共内核接口(系统调用、/proc 和 /sys 伪文件)在内核版本之间是稳定且兼容的(请参阅此 LKML 消息) ; 理智的实践胜过理论,即使是由某些标准规定的。例如,请参阅Torvalds 的 Wikiquotes或他在Linux Kernel 邮件列表(marc.info mirror;lkml.org here)上的帖子。

我相信他的意见的原因是,正如他自己所说,“因为他们知道他们不必这样做”。我(尝试)自己这样做,这就是为什么这个答案包含足够的参考资料,以便您自己验证。


推荐阅读