【问题标题】:How is system call return value passed back to user process?系统调用返回值如何传回用户进程?
【发布时间】:2020-08-24 12:06:47
【问题描述】:

假设我们有一个单核 CPU 正在运行

int filedesc = open("foo.txt", O_RDONLY);

filedesc是用户进程中的一个变量,当open开始执行时cpu获取上下文切换并运行内核进程,open的返回值如何传递给filedesc

另外,相比

FILE *file = fopen("foo.txt", "r");

使用fopen 读取/文件由于缓冲而快得多,但在它调用open 的引擎盖下,我想知道在这种情况下open 是否仍然会一个接一个地检索一个字节?如果是这样,每个字节都会有上下文切换开销,因为fopen 缓冲区在用户进程中,系统调用返回值在我的第一个问题中来回传递场景,它为什么运行得更快?提前致谢!

【问题讨论】:

  • 我建议你看看你正在使用的 c-library 的来源。似乎 glibc 的源代码可以在以下地址找到:sourceware.org/git/?p=glibc.git;a=blob;f=libio/…
  • “它怎么跑得更快”——你是如何测量这个的?
  • 嗨 @th33lf 我看了 Sorber 教授的关于 open/fopen 的教程 youtube.com/…
  • @mzoz 在那个例子中,他并没有自己计时open。他调用openreadwrite,其中open 可能构成开销的一小部分。它只被调用一次,而其他调用是在循环中进行的!大部分时间都花在了读写上。
  • "系统调用返回值如何传回用户进程?"在内核和用户空间之间有一个应用程序二进制接口 (ABI) 规范,它定义了(除其他外)参数如何传递给系统调用并返回值。

标签: c process io operating-system systems-programming


【解决方案1】:

“fopen 由于缓冲而快得多 [然后 fopen],但在后台它调用 open...”
一般来说,根据定义,如果function1() 实现包括调用function2(),则直接调用function2(),并且如果使用与function1() 调用时相同的选项集,将始终具有更短的执行时间。如果您看到fopen()open() 的相反情况,那么它表明您直接调用open() 时使用的选项集必须不同于在fopen() 中调用它时使用的选项集。但是内部do_sys_open() 的实现具有相同数量的参数open(),因此无法实现速度差异。你应该质疑你的基准测试技术。

关于返回值如何返回给用户...
Linux 系统调用是使用SYSCALL_DEFINEn 的变体定义的。以下open()example implementation 说明了这一点,并表明在函数do_sys_open() 的封装中,其中一个参数包括const char __user *macro 和函数中,允许它跟踪来自哪个用户呼叫已启动:

long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
{
    struct open_flags op;
    int fd = build_open_flags(flags, mode, &op);
    struct filename *tmp;

    if (fd)
        return fd;

    tmp = getname(filename);
    if (IS_ERR(tmp))
        return PTR_ERR(tmp);

    fd = get_unused_fd_flags(flags);
    if (fd >= 0) {
        struct file *f = do_filp_open(dfd, tmp, &op);
        if (IS_ERR(f)) {
            put_unused_fd(fd);
            fd = PTR_ERR(f);
        } else {
            fsnotify_open(f);
            fd_install(fd, f);
        }
    }
    putname(tmp);
    return fd;
}

SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
{
    if (force_o_largefile())
        flags |= O_LARGEFILE;

    return do_sys_open(AT_FDCWD, filename, flags, mode);
}

【讨论】:

    【解决方案2】:

    我猜你在这里有点困惑。当您说fopen() 更快时,您的实际意思是fread()fwrite()read()write() 更快。这可能适用于许多实现,因为 C 标准库在用户空间中使用缓冲,而大多数 POSIX 实现不使用用户空间中的缓冲。但是,它们可能会在内核空间中使用缓冲。

    假设您正在复制一个 1kb 的文件。如果一次一个字节地执行此操作,使用read() 从文件中获取一个字节并使用write() 将其复制到另一个字节中,您最终会调用相应的系统调用 1024 次。每次,都会有一个从用户空间到内核空间的上下文切换。另一方面,如果你使用的 C 库实现在内部使用了一个 512 字节的缓冲区,那么它实际上只转换为两个系统调用,即使你调用了 freadfwrite 数千次。因此,它似乎比直接使用 read/write() 快得多。

    但是,除了一次复制一个字节,您还可以在应用程序中使用足够大的缓冲区,尽可能少地调用read/write,以直接通过系统调用获得相同或更好的性能。换句话说,并不是标准库 API 比系统调用快(这是不可能的,因为库在内部调用系统调用),只是使用更大的缓冲区调用read/write 系统调用更有效,因为上下文切换开销和标准库已经考虑到这一点。

    【讨论】:

    • 谢谢我已经编辑了我的问题以消除歧义。你能确认缓冲区是在内核空间中维护的吗?用户空间中的用户进程如何访问缓冲区?非常感谢!
    • 不,缓冲区(在 C 库的情况下)在用户空间中维护。这就是重点。当您调用fwrite() 时,数据不会立即发送到内核,而是在本地缓冲。因此,可以在没有任何开销的情况下发生多个 fwrite。然后,当完成足够多的写入以填充缓冲区时,或根据其他条件,最终调用write() 系统调用,将数据从用户空间复制到内核空间缓冲区。目的是尽量减少需要发生的次数。
    • 一旦数据到达内核空间,它可能会在实际提交到文件之前被文件系统驱动程序再次缓冲,但这超出了我们讨论的范围。
    • @mzoz 真的很好奇,你为什么接受另一个答案?
    • 嗨@th33lf,因为另一个解决了用户和内核进程之间数据传递的详细机制,我更感兴趣..感谢您的时间!
    猜你喜欢
    • 2012-09-14
    • 2012-02-11
    • 2012-10-07
    • 1970-01-01
    • 2012-03-10
    • 2014-12-10
    • 2011-12-22
    • 2021-02-18
    • 2012-05-07
    相关资源
    最近更新 更多