前言:
目前朋友们对“system函数调用”大约比较讲究,我们都想要剖析一些“system函数调用”的相关内容。那么小编同时在网上网罗了一些对于“system函数调用””的相关知识,希望小伙伴们能喜欢,兄弟们快快来学习一下吧!系统调用System call为获得操作系统提供的服务提供了接口。这些调用中,虽然特定的底层的任务(比如,直接访问硬件)会用汇编指令编写,但一般以函数的形式用C语言和C++语言编写。
举例
在我们讨论操作系统是如何使得系统调用被获取到的前,让我们先用一个例子来展示系统调用是怎么被使用的:写一个简单的程序,它会从一个文件中读取数据并且拷贝到另一个文件。程序的第一个输入是两个文件的名字,输入文件和输出文件。由于取决与操作系统的设计,这些名字可以以多种方式指定。其中一种方式是把文件名作为命令的一部分传递给程序,下面是UNIX中的cp命令:
cp in.txt out.txt
这个命令会拷贝输入文件in.txt的内容然后输出到out.txt中。第二张程序的方式是去询问用户文件的名字。在一个交互系统中,这种方式将要求一系列的系统调用,首先在屏幕上打印一个提示信息,然后读取来自键盘的字符以识别出两个文件的名字。在以鼠标和图标为基础的系统中,文件名字的菜单通常会显示在窗口中。用户可以使用鼠标选择源文件的名字,然后可以打开一个目的文件所在的窗口。这一系列需要许多I/O系统调用。
一旦获得了两个文件的名字,程序将打开输入文件,并且创造和打开输出文件。这些操作中的另一个系统调用。因为每个系统调用可能发生的错误情形都必须得到处理。比如说,当程序试图打开一个输入文件时,它也许会发现没有这个名字的文件或是这个文件的访问权限得到了保护。在这样的情况下,程序应该输出一条错误信息(另一个系列的系统调用)并且然后异常终止(另一个系统调用)。如果输入文件存在,那么我们必须创建一个新的输出文件。我们也许会发现已经有一个同名的输出文件存在。这种i情况下,也许会造成程序的异常退出(一个系统调用)或是删除已经存在的文件(另一个系统调用)并且创造一个新的(又是一个系统调用)。另外一种在交互时系统中的情形是,通过一系列的系统调用以输出提示信息和读取终端响应的方式,询问用户是否替换存在的文件还是终止程序。
当两个文件被配置好后,我们会在循环里从输出文件读取内容(一个系统调用)并且把内容写入输出文件(另一个系统调用)。每次读和写必须返回相关各种可能错误条件的状态信息。在输入文件侧,程序也许会发现已经到达文件的末尾或是在读取中有一个硬件错误(比如说奇偶错误)。写操作也会遇到各种错误,当然,这取决于输出的设备(比如说,没有更多的磁盘空间了)。
最终,在全部拷贝完后,程序会关闭这两个文件(两个系统调用),在控制台打印一个信息或是在窗口上(需要更多的系统调用),并且最终正常终止程序(最后一个系统调用)。这些系统调用的顺序如下图。
应用程序接口
正如你看到的,简单的程序也许会大量使用操作系统。操作系统进场每秒会执行数以千计的系统调用。然而,大部分的程序员从来都没有看到过这种层次下的细节。一般来说,应用开发人员,根据应用程序接口application programming interface(API)来设计程序。API是指一组应用编程人员可以获取的功能,包括每个功能传递的参数和程序员可以预料的返回值。最常见的三个应用程序程序员可以使用的API是Windows系统的Windows API,基于POSIX的系统的POSIX API(实际上包括UNIX,Linux和mac OS的所有版本),以及在Java虚拟机上运行程序的Java API。机。程序员通过由操作系统提供的代码库访问API。对于在使用C语言编写程序的UNIX和Linux中,该库称为libc。请注意,每个操作系统对于每个系统调用都有其自己的名称。
在幕后,构成API的功能,通常代表应用程序程序员,调用实际的系统调用。例如,Windows函数CreateProcess()(毫不奇怪,它用于创建新进程)实际上是在Windows内核中调用NT CreateProcess()系统调用。
为什么应用编程人员会更喜欢根据API编程而不是调用真正的系统调用呢?这么做的原因有好几个。一个好处是对程序移植性的考量。通过使用API,一个应用编程人员,可以让他的设计的程序在使用相同API 的任何系统中编译并且运行(虽然,在现实中,架构上的差异会让这个似乎有点困难)。此外,对于应用程序员而言,相比于API,真正的系统调用通常太细节了,工作起来很困难。但是,API中的功能与其内核中相关的系统调用之间通常存在很强的相关性。实际上,许多POSIX和Windows API与UNIX,Linux和Windows操作系统提供的本机系统调用相似。
处理系统调用的另一个重要因素是运行时环境(RTE),即执行以给定编程语言编写的应用程序所需的全套软件,包括其编译器或解释器以及其他软件,例如库和加载器。 RTE提供了一个系统调用接口system-call interface,该接口作为操作系统提供的系统调用的链接。系统调用接口拦截API中的函数调用,并在操作系统中调用必要的系统调用。通常,用一个数字与每个系统调用相关联,并且系统调用接口维护根据这些数字编制索引的表。然后,系统调用接口在操作系统内核中调用预期的系统调用,并返回系统调用的状态。
调用者不需要知道系统调用是怎么被实现的或是在执行期间做了什么。相比之下,调用者只需要遵循API并且理解操作系统会在系统调用的结果出来后做什么。因此,操作系统接口大部分的细节通过API的形式向程序员隐藏了,下图展示了操作系统是怎么处理应用调用一个open()的系统调用的。
系统调用以不同的方式发生,具体取决于所使用的计算机。通常,需要更多的信息,而不仅仅是所需系统调用的身份。根据特定的操作系统和调用,确切的类型和数量有所不同。例如,要获取输入,我们可能需要指定用作源的文件或设备,以及应将输入读入的内存缓冲区的地址和长度。当然,设备或文件和长度可能在调用中是隐含的。
有三种方式用来传递参数给操作系统。最简单的方式是把参数传递给寄存器。然而,在一些情况下,有比寄存器数量更多的参数。在这类情况中,参数通常被存在块中或表中,位于内存,并且块的地址作为一个参数传递给寄存器,如下图:
Linux使用结合使用这些方法。如果有五个或更少的参数,使用寄存器。如果有比5个更多的参数,就是用块。参数也可以被程序放在或压入栈中并且被操作系统弹出。一些操作系统更喜欢块或栈的方式,因为这样的方式不会限制传递参数的长度数量。
系统调用的类别
系统调用可以大致分为六大类:进程控制,文件管理,设备管理,信息维护,通信和保护。下面,我们简要讨论操作系统提供的系统调用的类型。下面总结了操作系统通常提供的系统调用类型。
Process controlcreate process, terminate processload, executeget process attributes, set process attributeswait event, signal eventallocate and free memoryFile mamagementcreate file, delete fileopen, closeread, write, repositionget file attribute, set file attributeDevice managementrequest device, release deviceread, write, repositionget device attributes, set device attributeslogically attach or detach devicesInformation maintenanceget time or date, set time or dateget system data, set system dataget process, file, or device attributesset process, file, or device attributesCommunicationscreate, delete communication connectionsend, receive messagestransfer status informationattach or detach remote devicesProtectionget file permissionsset file permissions进程控制
一个正在运行的程序必须能够正常(end())或异常(abort())停止执行。如果如果系统调用以异常终止当前运行的程序,或者该程序遇到问题并导致错误,则内存中会生成dump并生成错误消息。dump将写入磁盘上的特殊日志文件中,调试器debuger(一种系统程序,旨在帮助程序员查找和纠正错误或bug)可能会检查dump以确定问题的原因。在正常或异常情况下,操作系统都必须将控制权转移给调用命令解释器。然后,命令解释器读取下一个命令。在交互式系统中,命令解释器仅继续下一条命令并假定用户将发出适当的命令以响应任何错误。在GUI系统中,弹出窗口可能会警告用户该错误并寻求指导。如果发生错误,某些系统可能会允许采取特殊的恢复操作。如果程序在其输入中发现错误并希望异常终止,则它可能还需要定义错误级别。更高级别的错误参数可以指示更严重的错误。然后可以通过将正常终止定义为级别0的错误来组合正常终止和异常终止。命令解释器或后续程序可以使用此错误级别来自动确定下一步操作。
一个运行着一个程序的进程可能想要load()和execute()另一个程序。此功能允许命令解释器按照例如用户命令或单击鼠标的指示执行程序。一个有趣的问题是,在加载的程序终止时应在哪里返回控制权。此问题与现有程序是否丢失,保存或允许与新程序并发继续执行有关。
如果当新的程序终止时返回控制权给现有程序,我们必须保存现有程序的内存快照;因此,必须有一种有效的的机制让一个程序去调用另一个程序。如果两个程序并发的执行,我们必须创建一个新的进程来实现。通常,有一个指定的系统调用来实现这个(create_process())。
如果我们创建了一个新的进程,或这也许是一组进程,我们应该有能力去控制它的执行。这样的控制需要具有确定和重置进程属性的能力,包括进程的优先级,其最大允许执行时间等(get_process_attribute()和set process attribute())。如果发现它不正确或不再需要,我们可能还想终止我们创建的过程(terminate_process())。
有了新的进程后,我们还需要等待他们完成执行。我们也许会想到等待一段特定的时间(wait_time())。更有可能的是我们想要等待一个指定事件的发生(wait_event())。当一个时间发生时,进程应该发出信息(signal_event())。
两个或多个进程共享数据是很普遍的。为了保证分享数据的完整性,操作系统通常系统系统调用以允许一个进程给共享数据上锁。然后,除非这个锁被解开,其他的进程无法访问这个数据。一般来说,这样的系统调用包括了acquire_lock()和release_lock()。这样的系统调用被用来处理并发进程间的协作。
进程控制有很多方面和变化,我们接下来,使用两个示例(一个涉及单任务系统,另一个涉及多任务系统)来阐明这些概念。 Arduino是一个简单的硬件平台,由微控制器和响应各种事件(例如光,温度和气压变化)的输入传感器组成。要为Arduino编写程序,我们首先在PC上编写程序,然后通过USB连接将已编译的程序(称为sketch)从PC上载到Arduino的闪存中。标准的Arduino平台不提供操作系统。取而代之的是,一小块称为bootloader的软件将sketch加载到Arduino内存中的特定区域(如下图)。加载sketch后,它开始运行,等待它被编程为需要响应的事件。例如,如果Arduino的温度传感器检测到温度已超过某个特定阈值,则sketch可能会让Arduino为风扇启动电动机。 Arduino被认为是单任务系统,因为一次只能在内存中显示一个sketch。如果加载了另一个sketch,它将替换现有草图。此外,Arduino除了硬件输入传感器外没有提供任何用户界面。
Free BSD(派生自Berkeley UNIX)是多任务系统的一个示例。当用户登录系统时,将运行用户选择的shell,用来等待用户请求的命令和运行程序。但是,由于Free BSD是一个多任务系统,因此命令解释器可以在执行另一个程序的同时继续运行(如下图)。要启动新进程,shel程序将执行fork()系统调用。然后,通过exec()系统调用将所选程序加载到内存中,并执行该程序。然后,根据发出命令的方式,shhell程序将等待进程完成,或者“在后台”运行该进程。在后一种情况下,shell程序会立即等待输入另一个命令。当进程在后台运行时,它不能直接从键盘接收输入,因为shell程序正在使用此资源。因此,I/O操作通过文件或GUI界面完成。同时,用户可以自由地要求Shell运行其他程序,监视正在运行的进程的进度,更改该程序的优先级,等等。该进程完成后,它将执行exit()系统调用以终止,并向调用过程返回状态代码0或非零错误代码。然后,此状态或错误代码可用于shell程序或其他程序。
文件管理
在这里,我们说几个处理文件的常见系统调用。
我们首先需要能够create()和delete()文件。这两个系统调用都需要文件名,也并且可能需要文件的某些属性。创建文件后,我们需要open()并使用它。我们还可以read(),write()或reposition()(例如,快退或跳至文件末尾)。最后,我们需要close()文件,表明我们不再使用它。
如果我们具有用于在文件系统中组织文件的目录结构,则可能需要对目录进行相同的操作。另外,对于文件或目录,我们需要能够确定各种属性的值,并在必要时进行设置。文件属性包括文件名,文件类型,保护代码,记账信息等。此功能至少需要两个系统调用,分别是get_file_attribute()和set_file_attribute()。一些操作系统会提供更多调用,例如对文件move()和copy()的调用。其他人可能会提供使用代码和其他系统调用执行这些操作的API,其他的可能会提供执行任务的系统程序。如果系统程序可以被其他程序调用,则每个系统程序都可以被其他系统程序视为API。
设备管理
一个进程可能需要若干资源来执行-主内存,磁盘驱动器,对文件的访问等等。如果资源可用,则可以授予这些资源,并将控制权交予用户进程。否则,该过程将不得不等待,直到有足够的资源可用为止。
可以将操作系统控制的各种资源视为设备。这些设备中的一些是物理设备(例如,磁盘驱动器),而其他设备可以看作是抽象或虚拟设备(例如,文件)。具有多个用户的系统可能会要求我们首先request()一个设备,以确保对其进行独占使用。在完成设备处理后,我们release()它。这些函数类似于文件的open()和close()系统调用。其他操作系统允许对设备进行非托管访问。这样一来,就可能是发生设备争用和死锁的可能性。
一旦设备被请求(并分配给我们),我们就可以对设备进行read(),write()和(可能地)reposition(),就像处理文件一样。实际上,I/O设备和文件之间的相似性是如此之大,以至于包括UNIX在内的许多操作系统都将两者合并为一个组合的文件-设备结构。在这种情况下,文件和设备上都会使用一组系统调用。有时,I/O设备通过特殊的文件名,目录位置或文件属性来标识。
即使基础系统调用不同,用户界面也可以使文件和设备看起来相似。这是构建操作系统和用户界面的许多设计决策的另一个示例。
信息维护
存在许多系统调用,仅是为了在用户程序和操作系统之间传输信息。例如,大多数系统都有一个系统调用来返回当前的time()和date()。其他系统调用可能返回和系统有关的信息,例如操作系统的版本号,可用内存或磁盘空间的数量,等等。
另一组系统调用有助于调试程序。许多系统提供对dump()内存的系统调用。此规定对于调试很有用。 strace程序(在Linux系统上可用)列出了每个系统调用的执行时间。甚至微处理器都提供了称为单步的CPU模式,在该模式下,每条指令后CPU都会执行trap。trap通常由调试器捕获。
许多操作系统为程序提供了时间配置文件以指明程序在特定位置或一组位置上执行的时间。时间配置文件需要跟踪工具或定期的计时器中断。每次发生定时器中断时,都会记录程序计数器的值。使用足够频繁的计时器中断,可以获得程序各个部分所花费时间的统计信息。
另外,操作系统保留有关其所有进程的信息,并且系统调用用于访问此信息。通常,调用还用于获取和设置进程信息(get_process_attribute()和set_process_attribute())。
通信
进程间通信有两种常见的模型:消息传递模型和共享内存模型。在消息传递模型中,通信的进程以互相传输信息来交换消息。可以直接或通过公用邮箱间接在进程之间交换消息。必须先打开连接,然后才能进行通讯。无论是同一系统上的另一个进程还是通过通信网络连接的另一台计算机上的进程,必须知道另一个通信者的名称。众所周知,网络中的每台计算机都有一个主机名。主机还具有网络标识符,例如IP地址。同样,每个进程都有一个进程名,并且此名称被转换为标识符,操作系统可以通过该标识符来引用该进程。get_hostid()和get_processid()系统调用执行此转换。然后,根据系统的通讯模型,将标识符传递到文件系统提供的通用的通用open()和close()调用或特定的open_connection()和close connection()系统调用。接收方进程通常必须授予其许可,才能使用accept connection()调用进行通信。将要接收连接的大多数进程都是特定的守护进程。他们执行一个waiting_connection()调用,并在被唤醒后,建立连接。通信的源(称为客户端)和接收的守护程序(称为服务器)通过使用read_message()和write_message()系统调用来交换消息。close_connection()调用用来终止通信。
在共享内存模型中,进程使用shared_memory_create()和shared_memory_attach()系统调用来创建并访问其他进程拥有的内存区域。回想一下,通常情况下,操作系统会尝试阻止一个进程访问另一进程的内存。共享内存要求两个或更多进程同意删除此限制。然后,他们可以通过在共享区域中读写数据来交换信息。数据的形式由进程决定,不受操作系统的控制。进程也负责确保它们不会同时写入同一位置。
刚才讨论的两种模型在操作系统中都很常见,而且大多数系统都实现了这两种功能消息传递对于交换少量数据很有用,因为不需要避免冲突。与用于计算机间通信的共享内存相比,它更容易实现。共享内存可以最大程度地提高通讯速度和便利性,因为共享内存可以在计算机内部以内存传输速度完成。但是,在进程之间的共享内存的保护和同步方面需要额外的留心。
保护
保护提供了一种机制,用于控制对计算机系统提供的资源的访问。从历史上看,仅在具有多个用户的多程序计算机系统上才考虑保护。但是,随着网络和Internet的出现,从服务器到移动手持设备的所有计算机系统都必须关心保护。
通常,提供保护的系统调用包括set_Permission()并get_Permission(),该操作可操纵资源,例如文件和磁盘的权限。 allow_user()和deny_user()系统调用指定是否可以允许特定用户访问某些资源。
标签: #system函数调用