compiler-construction - 系统调用如何工作?

标签 compiler-construction process operating-system interrupt system-calls

据我所知,用户可以拥有一个进程,每个进程都有一个地址空间(其中包含有效的内存位置,该进程可以引用)。我知道进程可以调用系统调用并向其传递参数,就像任何其他库函数一样。这似乎表明所有系统调用都通过共享内存等方式位于进程地址空间中。但也许,这只是一个幻觉,因为在高级编程语言中,系统调用看起来像任何其他函数,当一个进程调用它。

但是,现在让我更深入地分析一下幕后发生的事情。编译器如何编译系统调用?它可能会将进程提供的系统调用名称和参数推送到堆栈中,然后放入“TRAP”之类的汇编指令 - 基本上是调用软件中断的汇编指令。

该 TRAP 汇编指令由硬件执行,首先将模式位从用户切换到内核,然后将代码指针设置为中断服务例程的开始。从此时起,ISR 在内核模式下执行,从堆栈中获取参数(这是可能的,因为内核可以访问任何内存位置,甚至是用户进程拥有的内存位置)并执行系统调用并在end 放弃 CPU,这再次切换模式位,用户进程从停止的地方开始。

我的理解正确吗?

附上我的理解的粗略图: enter image description here

最佳答案

你的理解非常接近;诀窍在于,大多数编译器永远不会编写系统调用,因为程序调用的函数(例如 getpid(2)chdir(2) 等)实际上是提供的通过标准 C 库。标准 C 库包含系统调用的代码,无论是通过 INT 0x80 还是 SYSENTER 调用。如果没有库来进行系统调用,这将是一个奇怪的程序。 (尽管perl提供了一个syscall()函数可以直接进行系统调用!很疯狂,对吧?)

接下来是内存。操作系统内核有时可以轻松地访问用户进程内存的地址空间。当然,保护模式是不同的,用户提供的数据必须复制到内核的 protected 地址空间中,以防止在系统调用进行时修改用户提供的数据>:

static int do_getname(const char __user *filename, char *page)
{
    int retval;
    unsigned long len = PATH_MAX;

    if (!segment_eq(get_fs(), KERNEL_DS)) {
        if ((unsigned long) filename >= TASK_SIZE)
            return -EFAULT;
        if (TASK_SIZE - (unsigned long) filename < PATH_MAX)
            len = TASK_SIZE - (unsigned long) filename;
    }

    retval = strncpy_from_user(page, filename, len);
    if (retval > 0) {
        if (retval < len)
            return 0;
        return -ENAMETOOLONG;
    } else if (!retval)
        retval = -ENOENT;
    return retval;
}

虽然它本身不是系统调用,但它是一个由系统调用函数调用的辅助函数,用于将文件名复制到内核的地址空间中。它检查以确保整个文件名位于用户的数据范围内,调用从用户空间复制字符串的函数,并在返回之前执行一些健全性检查。

get_fs() 和类似的函数是 Linux 的 x86 根的残余。这些函数具有适用于所有架构的工作实现,但名称仍然过时。

所有与段相关的额外工作都是因为内核和用户空间可能共享可用地址空间的某些部分。在 32 位平台上(数字很容易理解),内核通常有 1 GB 的虚拟地址空间,用户进程通常有 3 GB 的虚拟地址空间。

当进程调用内核时,内核将“修复”页表权限以允许其访问整个范围,并获得预填充的好处 TLB entries用于用户提供的内存。巨大的成功。但是当内核必须上下文切换回用户空间时,它必须刷新 TLB 以删除内核地址空间页面上缓存的权限。

但问题在于,1 GB 的虚拟地址空间不足以容纳大型计算机上的所有内核数据结构。维护缓存文件系统和 block 设备驱动程序、网络堆栈以及系统上所有进程的内存映射的元数据可能需要大量数据。

因此可以使用不同的“分割”:用户两个gig,内核两个gig,用户一个gig,内核三个gig,等等。随着内核空间的增加,用户进程的空间会减少。所以有一个4:4内存分割为用户进程提供 4 GB,为内核提供 4 GB,并且内核必须修改段描述符才能访问用户内存。 TLB 在进入和退出系统调用时会被刷新,这是一个相当显着的速度损失。但它可以让内核维护更大的数据结构。

64 位平台更大的页表和地址范围可能使前面的所有内容看起来很奇怪。无论如何,我当然希望如此。

关于compiler-construction - 系统调用如何工作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6241627/

相关文章:

c++ - 具有实时过程的银行家算法

compiler-construction - Solaris 5-10 CC编译器缺少符号错误会导致链接失败

C++。如何知道方法/变量的定义位置?或者如何询问编译器?

arrays - 了解处理可变长度数据,特别关注 C(99) 中的可变长度数组

c - C 中的外部命令行子进程在结束之前不会给出输出

c - 解析和 evecvp 问题

c++ - 为什么 getenv() 返回一个非常量字符串

linux - 接收信号时阻塞的系统调用会发生什么?

创建一个不可停止的程序

c++ - Tilera 交叉编译 - 链接错误