2011-04-06 32 views
10

比方说,有一个简单的程序,如:系统调用如何转换为CPU指令?

#include<stdio.h> 

void main() 
{ 
    int x; 
    printf("Cool"); 
    fd = open("/tmp/cool.txt", O_READONLY) 
} 

open是一个系统调用在这里。我猜想当shell运行它时,它会让其他几百个系统调用来实现它?对于像int x这样的声明怎么样 - 在某些时候它应该在背景中有一些额外的系统调用来从计算机获取内存?

我不确定系统调用和普通的东西之间的界限是什么......最后,所有的东西都需要操作系统的帮助?

或者它像是C生成一个可执行的代码(代码),它可以在处理器上运行,并且不需要操作系统帮助,直到系统调用到达时 - 在此时它必须执行某些操作来加载操作系统指令etc ...

有点含糊不清:)请澄清。

+0

Idk如果这将有所帮助,但是:“软件<->(例如系统调用)OS /内核<- Drivers ->硬件”。某些时候,操作系统(例如它是否包括UI,标准支持工具?)和内核(操作系统的“心脏”)和驱动程序(例如内核驱动程序的一部分?)之间的界限是模糊的。零件向左需要零件向右。例如。使用C API - >在Filesystem上执行操作 - >与IDE设备交谈,... – 2011-04-06 18:06:30

+0

谢谢。我的问题是尽可能减少内核等的抽象。因此,让我们说有一个可执行文件 - 称为new(使用gcc new.c new创建)。我使用像hexdump新的工具,如0000 01E5 A000等,这是INTEL的CPU代码? – Nishant 2011-04-06 18:19:12

+0

“也许”。 C和C++(通常)被编译为[机器代码](http://en.wikipedia.org/wiki/Machine_code)。但是,在hexdump中查看的确切数据可能不会与CPU指令相关 - 例如,它可能是可执行文件中的其他未执行数据,例如[COFF头](http:///en.wikipedia.org/wiki/COFF)或字符串数​​据。然而,这段代码将使用C运行时并且执行大量系统调用“只是为了运行”。也就是说,CPU可以在可执行文件中执行机器代码 - 但只能在其编译的上下文中执行。 – 2011-04-06 23:38:15

回答

25

我没有按顺序回答问题,所以我在回答问题前加上前缀。我冒昧地编辑了一下它们。您没有指定处理器架构,但我假设您想知道x86,所以处理器级别的细节将与x86相关。其他体系结构可以有不同的表现(内存管理,系统调用如何进行等)。我也使用Linux作为例子。

c编译器是否生成可执行代码,可直接在处理器上运行而无需OS协助,直至系统调用到达为止,此时必须执行某些操作才能加载操作系统指令?

是的,这是正确的。编译器生成可以直接在处理器上运行的本机机器码。但是,从编译器获得的可执行文件包含代码和其他所需数据,例如,有关将内存中的代码加载到何处的说明。在Linux上,ELF格式通常用于可执行文件。

如果进程完全加载到内存中并具有足够的堆栈空间,则在进行系统调用之前不需要进一步的操作系统帮助。进行系统调用时,它只是调用OS的机器代码中的一条指令。程序本身不需要以任何方式“加载操作系统指令”。处理器处理将执行转移到OS代码。

在x86架构上使用Linux,机器代码进行系统调用的一种方式是使用软件中断向量128将执行传输到操作系统。在x86汇编(Intel语法)中,表示为int 0x80。然后,Linux将在执行系统调用之前根据调用程序放入处理器寄存器的值执行任务:系统调用编号位于eax处理器寄存器中,系统调用参数位于其他处理器寄存器中。操作系统完成后,它将在eax寄存器中返回一个结果,并且可能修改了系统调用参数指向的缓冲区。但请注意,这不是进行系统调用的唯一方法。

但是,如果进程不完全在内存中,并且执行转移到此时不在内存中的代码的一部分,则处理器会导致页面错误,从而将执行移至操作系统,然后将进程的所需部分加载到内存中,并将执行转移回进程,然后该进程可以正常继续执行,甚至不会发现任何事情发生。

我不完全确定下一点,所以拿一粒盐。维基百科关于stack overflow的文章(计算机错误,不是这个网站:)似乎表明堆栈通常是固定大小的,所以int x;不应该导致操作系统运行,除非那部分堆栈不在内存中(见前面段)。如果你有一个具有动态堆栈大小的系统(如果它甚至是可能的,但据我所知,它是),当堆栈空间用完时,int x;也可能导致页面错误,提示操作系统分配更多堆栈空间的过程。

页面错误导致执行转移到操作系统,但不是通常意义上的系统调用。系统调用是当您希望为您执行一些工作时对操作系统的明确调用。页面错误和其他此类事件是隐含的。硬件中断不断将执行从您的进程转移到OS,以便它可以对它们做出反应。之后,它将执行转移回您的进程或其他进程。

在多任务操作系统上,即使只有一个处理器/内核,也可以一次运行多个程序。这是通过一次只运行一个程序来完成的,但是可以快速切换程序。硬件定时器中断确保控制及时传回操作系统,以便一个进程不会将CPU全部占用。当控制传递给操作系统并且已经完成了它所需要的操作时,它可能始终启动与被中断的过程不同的过程。操作系统完全透明地处理所有这些,所以你不必考虑它,你的过程也不会注意到它。从你的过程看,它正在不断地执行。

简而言之:只有当您明确提出要求时,您的程序才会执行系统调用。操作系统也可以根据需要将进程中的部分进程和内存交换出去,并且通常在后台执行与进程相关且无关的事情,但通常不需要考虑这些。 (您可以减少页面错误的量,不过,通过保持你的程序尽可能的小,而这样的事情)

在这种情况下open()是一个明确的系统调用,但我想,当外壳运行它,它使得其他几百个系统调用来实现它。

不,壳与您的c程序中的open()调用无关。你的程序会进行一次系统调用,而shell根本不会出现。

shell只会影响程序启动时的程序。当您使用shell启动程序时,shell会执行系统调用来分离第二个进程,然后执行execve系统调用以将其本身替换为您的程序。之后,你的程序就可以控制。在控制权到达你的main()函数之前,它执行一些初始化代码,这是由编译器放在那里的。如果您想查看某个进程所进行的系统调用,则可以在Linux上使用strace来查看它们。例如,只需说strace ls即可查看ls在执行过程中所做的系统调用。如果你编译一个只有main()函数的c程序立即返回,你可以用strace看到初始化代码所做的系统调用。

该过程如何从计算机等获取其内存?它必须再次涉及一些系统调用吗?我不确定系统调用和普通的东西之间的界限是什么。最终的一切都需要操作系统的帮助,对吧?

是的,系统调用。当您的程序通过execve系统调用加载到内存中时,它会为您的进程获取足够的内存。当您需要更多内存并致电malloc()时,如果系统调用内存缓冲区内存不足以提供您的进程,系统将调用它来增大进程的数据段。

并非所有事情都需要操作系统的明确帮助。如果您有足够的内存,将所有输入内存中,并将输出数据写入内存,则根本不需要操作系统。也就是说,只要你只对内存中已有的数据进行计算,不需要更多的内存,并且不需要与外部世界进行通信,你就不需要操作系统。另一方面,一个不与外部世界进行通信的程序是一个非常无用的程序,因为它不能得到任何输入,并且不能给出任何输出。即使你计算pi的百万分之一,如果你不输出它给用户也没有关系。

此回答得到了相当于大,所以万一我错过了某些东西或者没有足够清楚地解释某些东西,请给我留言,我会尽量详细说明。如果有人发现任何错误,一定要将它们指出来。

+0

感谢您的好解释。如果我需要更清晰的话,我会阅读它并发布commnents。对我来说,描绘整个事物总是很困难,但是会试图得出一些想法,并询问是否仍有不清楚的地方。 – Nishant 2011-04-10 08:42:53

+0

不错的阅读你的答案..但是“当你需要更多的内存和调用malloc()时,它会做一个brk系统调用来增长数据段”..不应该是堆段吗? – Laz 2013-03-23 14:40:32

+0

@RamBhat:嗯,不,至少在x86上,真的没有“堆段”这样的东西。虽然堆存在于数据段中,而malloc()通常从堆中分配内存,必要时增加数据段(和堆),所以从这个意义上说,你是正确的。 (另外,对不起,我花了很长时间才找到你) – 2014-02-12 21:40:05