Compiler construction 系统调用是如何工作的?

Compiler construction 系统调用是如何工作的?,compiler-construction,process,operating-system,interrupt,system-calls,Compiler Construction,Process,Operating System,Interrupt,System Calls,我知道用户可以拥有一个进程,每个进程都有一个地址空间(其中包含有效的内存位置,该进程可以引用)。我知道进程可以调用系统调用并向其传递参数,就像任何其他库函数一样。这似乎表明,通过共享内存等方式,所有系统调用都位于进程地址空间中。但这可能只是一种错觉,因为在高级编程语言中,当进程调用系统调用时,系统调用看起来与任何其他函数一样 但是,现在让我更深入地分析引擎盖下发生的事情。编译器如何编译系统调用?它可能会将进程提供的系统调用名和参数推送到堆栈中,然后将汇编指令放在堆栈中,比如说“TRAP”之类的东

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

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

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

我的理解正确吗

随函附上我的理解简图:
是的,你说得很对。但有一个细节是,当编译器编译系统调用时,它将使用系统调用的编号而不是名称。例如,这里有一个(对于旧版本,但概念仍然相同)。

您实际上调用了C运行时库。插入陷阱的不是编译器,而是C库将陷阱包装到库调用中。你其余的理解是正确的。

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

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

static int do_getname(const char\u user*文件名,char*页)
{
内部检索;
无符号长透镜=路径_最大值;
if(!segment_eq(get_fs(),KERNEL_DS)){
如果((无符号长)文件名>=任务大小)
返回-默认值;
if(任务大小-(无符号长)文件名<路径最大值)
len=任务大小-(无符号长)文件名;
}
retval=strncpy_from_user(第页,文件名,len);
如果(返回值>0){
if(retval
虽然它本身不是一个系统调用,但它是一个由系统调用函数调用的助手函数,该函数将文件名复制到内核的地址空间。它检查以确保整个文件名位于用户的数据范围内,调用从用户空间复制字符串的函数,并在返回前执行一些健全性检查

get_fs()
和类似的函数是Linux x86根目录的遗留部分。这些函数在所有体系结构中都有可用的实现,但名称仍然陈旧

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

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

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

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


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

普通程序通常不会“编译系统调用”。对于每个系统调用,通常需要一个相应的用户空间库函数(通常在类Unix系统的libc中实现)。例如,
mkd
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;
}
#include < stdio.h  >    
#include < stdlib.h >    
int main()    
{    
    printf("Running ps with "system" system call ");    
    system("ps ax");    
    printf("Done.\n");    
    exit(0);    
}