请考虑以下程序。
#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));
}
}
此程序的输出(在sudo
下运行)是
Invalid write of 2 to fd 3; errno 22: Invalid argument
请注意,后续写入不会失败;第一次写入失败不会导致下一次写入失败。
这种缺乏故障持久性的确定性和可靠性吗?
我已经看过写手册页,但是它并没有说明失败的持续性。
答案 0 :(得分:1)
在Linux中,通常没有与文件描述符相关的错误状态。请参阅下面的链接。
但是,在进行进一步操作之前,请不要使用< 0
检查错误。如果发生错误(对于open()
或write()
,返回值为-1
。如果write()
成功,则返回写入的字符数。即使是sysfs写入,您确实应该检查一下。刚好有一个文件系统/内核错误(或错误“家族”),其中read()
/ write()
返回了-1
以外的负值(实际上并没有表示有错误,但是对普通文件进行非常大的写操作时,包装的是无符号整数成功值),因此,内核现在将所有读/写操作的上限限制为小于2 GiB。如果每个人都使用{{1 }},我们根本不会发现这一点。
在我看来,最好是稍稍偏执并捕获意外错误,而不是假设并可能无声地丢失数据。
这种缺乏故障持久性的确定性和可靠性吗?
对于< 0
下的内核伪文件,答案是肯定的:每次写入都被视为单独的操作。先前从同一描述符读取或写入的描述符不会影响当前写入的结果。
写入sysfs伪文件只需调用伪文件表示的可调参数的store()方法;参见fs/sysfs/file.c:sysfs_kf_bin_write()。根本没有记录状态。
(我们可以讨论一个可调参数是否可以记录以前的分配尝试并基于此尝试改变其行为,但是我们只能说Linus Torvalds根本不会故意让这种事情“飞起来” )
通常,Linux内核在文件描述中不存储任何错误状态。如果我们查看fs/read_write.c:write()(寻找/sys/
),我们会发现当前内核中的SYSCALL_DEFINE3(write,
系统调用会调用write()
,这将验证描述符是否有效(返回{ {1}}错误),并调用ksys_write()
。 (应注意,如果成功,则使用EBADF
更新与描述符相关的文件位置;文件位置不会自动更新。因此,在Linux中对同一文件描述符的多线程并发写入应使用{{ 3}}或pwrite()
而不是vfs_write()
,以避免比赛窗口中出现文件位置更新)。
无论如何,file_pos_write()
进行一些错误检查(write()
,vfs_write()
,EBADF
)和簿记,并调用EINVAL
,这是一个包装函数,调用适当的特定于文件系统的函数EFAULT
或__vfs_write()
。
(我们还可以查看pwritev()
来了解Linux内核如何管理其内部文件描述符表(每个用户空间进程),fs/file_table.c来查看描述符表本身,以及{{3} }来定义Linux文件描述。在这些结构中,根本没有与“错误状态”相关的成员,但是,请注意file->fop->write()
中的file->fop->write_iter()
成员是很有用的:是指向f_op
结构的指针,该结构包含用于与该特定打开文件相关的基本文件操作的每个文件系统处理程序(请参见include/linux/fdtable.h:struct fdtable)。
(请注意,在Linux中,系统调用返回单个整数。对于错误情况,此整数包含负错误号。零和正值被视为成功。 C库完全在用户空间中维护struct file
。如果您使用struct file_operations
,则需要检测错误情况并根据需要自己维护errno
。因此,当您看到内核syscall返回{ {1}},这意味着它将错误syscall()
返回到用户空间。C库负责使用errno
将其转换为-EINVAL
。)
同样,描述符中没有记录任何错误状态,并且每个操作都是独立发生的,与先前的操作无关(文件位置除外,在撰写本文时,文件位置本身并未自动更新)。从理论上讲,某些文件系统可以跟踪操作,并保持与描述符相关的内部错误状态,但是同样,除非这是文件系统的一个有据可查的功能,否则其他实现不赞成这样做,因此Linux不太可能内核开发人员实际上会允许这样的事情。
重要的是要意识到Linux内核开发人员必须遵循两个关键原则(因为Linus强制执行):公共内核接口(syscalls,/ proc和/ sys伪文件)是稳定的并且在内核版本之间兼容(请参见{ {3}});和理智的实践胜过理论,即使有某些标准要求这样做也是如此。例如,参见include/linux/fs.h:struct file或他在include/linux/fs.h:struct file_operations上的帖子(marc.info镜像; this LKML message)。
正如他本人所说,我相信他的观点的原因是“因为他们知道自己不必这样做”。我(尝试)自己做,这就是为什么此答案包含足够的参考资料,以便您可以自己进行验证的原因。
答案 1 :(得分:0)
写syscall可能由于许多原因而失败-状态没有任何持久性。