在Linux中,系统调用是用户空间访问内核的惟一手段,它们是内核惟一的合法入口。实际上,其他的像设备文件和/proc之类的方法,最终也还是要通过系统调用进行的。
通常情况下,应用程序通过应用编程插口(API)而不是直接通过系统调用来编程,并且这些编程插口实际上并不须要和内核提供的系统调用对应。一个API定义了一组应用程序使用的编程插口。它们可以实现成一个系统调用,也可以通过调用多个系统调用来实现,就算不使用任何系统调用也不存在问题。实际上,API可以在各类不同的操作系统上实现,给应用程序提供完全相同的插口,而它们本身在这种系统上的实现却可能迥异。
在Unix世界中,最流行的应用编程插口是基于POSIX标准的,Linux是与POSIX兼容的。
从程序员的角度看,她们只须要给API打交道就可以了,而内核只跟系统调用打交道;库函数及应用程序是怎样使用系统调用不是内核关心的。
系统调用(在linux中常叫做syscalls)一般通过函数进行调用。它们一般都须要定义一个或几个参数(输入)并且可能形成一些副作用。这种副作用通过一个long类型的返回值来表示成功(0值)或则错误(负值)。在系统调用出现错误的时侯会把错误码写入errno全局变量。通过调用perror()函数,可以把该变量翻译成用户可以理解的错误字符串。
系统调用的实现有两个非常之处:1)函数申明中都有asmlinkage限定词,用于通知编译器仅从栈中提取该函数的参数。2)系统调用getXXX()在内核中被定义为sys_getXXX()。这是Linux中所有系统调用都应当遵循的命名规则。
系统调用号:在linux中,每位系统调用都赋于一个系统调用号,通过这个独一无二的号就可以关联系统调用。当用户空间的进程执行一个系统调用的时侯,这个系统调用号就被拿来指明究竟要执行那个系统调用;进程不会提到系统调用的名称。系统调用号一旦分配就不能再有任何变更(否则编译好的应用程序都会崩溃),假若一个系统调用被删掉,它所占用的系统调用号也不容许被回收借助。Linux有一个”未使用”系统调用sys_ni_syscall(),它不仅返回-ENOSYS外不做任何其他工作,这个错误号就是专门针对无效的系统调用而设的。似乎很罕见,但假如有一个系统调用被删掉,这个函数就要负责“填补空位”。
内核记录了系统调用表中所有已注册过的系统调用的列表,储存在sys_call_table中。它与体系结构有关,通常在entry.s中定义。这个表中为每一个有效的系统调用指定了惟一的系统调用号。
用户空间的程序难以直接执行内核代码。它们不能直接调用内核空间的函数,由于内核留驻在受保护的地址空间上,应用程序应当以某种形式通知系统,告诉内核自己须要执行一个系统调用,系统系统切换到内核态linux 内核调用,这样内核就可以代表应用程序来执行该系统调用了。这些通知内核的机制是通过软中断实现的。x86系统上的软中断由int$0x80指令形成。这条指令会触发一个异常造成系统切换到内核态并执行第128号异常处理程序,而该程序正是系统调用处理程序,名子叫system_call().它与硬件体系结构紧密相关,一般在entry.s文件中通过汇编语言编撰。
所有的系统调用深陷内核的形式都是一样的红旗linux系统,所以仅仅是深陷内核空间是不够的。因而必须把系统调用号一并传给内核。在x86上,这个传递动作是通过在触发软中断前把调用号放入eax寄存器实现的。这样系统调用处理程序一旦运行,就可以从eax中得到数据。上述所说的system_call()通过将给定的系统调用号与NR_syscalls做比较来检测其有效性。假如它小于或则等于NR_syscalls,该函数就返回-ENOSYS.否则,就执行相应的系统调用:call*sys_call_table(,%eax,4);
因为系统调用表中的表项是以32位(4字节)类型储存的,所以内核须要将给定的系统调用号除以4,之后用所得到的结果在该表中查询器位置。如图图一所示:
里面早已提及,不仅系统调用号以外,还须要一些外部的参数输入。最简单的办法就是像传递系统调用号一样把这种参数也储存在寄存器里。在x86系统上ebx,ecx,edx,esi和edi根据次序储存前5个参数。须要六个或六个以上参数的情况不多见,此时,应当用一个单独的寄存器储存指向所有那些参数在用户空间地址的表针。给用户空间的返回值也通过寄存器传递。在x86系统上,它储存在eax寄存器中。
系统调用必须仔细检测它们所有的参数是否合法有效。系统调用在内核空间执行。假如任由用户将不合法的输入传递给内核,这么系统的安全和稳定将面临极大的考验。最重要的一种检测就是检测用户提供的表针是否有效,内核在接收一个用户空间的表针之前,内核必需要保证:
1)表针指向的显存区域属于用户空间
2)表针指向的显存区域在进程的地址空间里
3)若果是读,读显存应当标记为可读。若果是写,该显存应当标记为可写。
内核提供了两种方式来完成必须的检测和内核空间与用户空间之间数据的来回拷贝。这两个方式必须有一个被调用。
copy_to_user():向用户空间写入数据,须要3个参数。第一个参数是进程空间中的目的显存地址。第二个是内核空间内的源地址
.第三个是须要拷贝的数据宽度(字节数)。
copy_from_user():向用户空间读取数据,须要3个参数。第一个参数是进程空间中的目的显存地址。第二个是内核空间内的源地
址.第三个是须要拷贝的数据宽度(字节数)。
注意:这两个都有可能造成阻塞。当包含用户数据的页被换出到硬碟上而不是在数学显存上的时侯,这些情况才会发生。此时linux 内核调用,进程都会休眠,直至缺页处理程序将该页从硬碟重新换回到化学显存。
内核在执行系统调用的时侯处于进程上下文,current表针指向当前任务,即引起系统调用的那种进程。在进程上下文中,内核可以休眠(例如在系统调用阻塞或显式调用schedule()的时侯)但是可以被占领。当系统调用返回的时侯,控制权一直在system_call()中,它最终会负责切换到用户空间并让用户进程继续执行下去。
给linux添加一个系统调用时间很简单的事情,如何设计和实现一个系统调用是困局所在。实现系统调用的第一步是决定它的用途,这个用途是明晰且惟一的,不要尝试编撰多用途的系统调用。ioctl则是一个背面教材。新系统调用的参数,返回值和错误码该是哪些linux教程下载,这种都很关键。一旦一个系统调用编撰完成后,把它注册成为一个即将的系统调用是件繁杂的工作,通常下边几步:
1)在系统调用表(通常坐落entry.s)的最后加入一个表项。从0开始算起,系统表项在该表中的位置就是它的系统调用号。如第
10个系统调用分配到系统调用号为9
2)任何体系结构,系统调用号都必须定义于include/asm/unistd.h中
3)系统调用必须被编译进内核映像(不能编译成模块)。这只要把它放进kernel/下的一个相关文件就可以。
一般,系统调用靠C库支持,用户程序通过包含标准头文件并和C库链接,就可以使用系统调用(或则使用库函数,再由库函数实际调用)。幸好的是linux本身提供了一组宏用于直接对系统调用进行访问。它会设置好寄存器并调用int$0x80指令。这种宏是_syscalln(),其中n的范围是从0到6.代表须要传递给系统调用的参数个数。这是因为该宏必须了解究竟有多少参数根据哪些顺序压入寄存器。以open系统调用为例:
open()系统调用定义如下是:
longopen(constchar*filename,intflags,intmode)
直接调用此系统调用的宏的方式为:
#defineNR_open5
_syscall3(long,open,constchar*,filename,int,flags,int,mode)
这样,应用程序就可以直接使用open().调用open()系统调用直接把里面的宏放置在应用程序中就可以了。对于每位宏来说,都有2+2*n个参数。每位参数的意义简单明了,这儿就不详尽说明了。
以上就是Linux中系统调用不是内核的合法入口的详细内容,更多请关注小闻网其它相关文章!
评论(0)