(1)库函数:
调用函数库的一个程序
在用户地址空间执行
它的运行时间属于“用户”时间
属于过程调用,开销较小
(2)系统调用:
调用系统内核的服务
在内核地址空间执行
运行时间属于“系统”时间
需要切换到内核上下文环境再切换回来,开销较大。
注:许多C函数库的程序是通过系统调用来实现的。
系统调用是操作系统为用户提供的,用来能够让用户使用系统提供的各种功能。
系统调用和一般函数调用的区别:
(1)前者和后者运行在不同状态。在操作系统中有内核态(管态)和用户态(算态)之分。确切的说是系统调用的功能运行在内核态,而一般的函数调用运 行在用户态,系统调用提供一个在用户态调用内核态功能的接口。
(2)两者调用方式不同,一般的函数调用在保护现场后,会直接转到被调用的函数地址执行,执行完毕后返回原程序继续执行;而系统调用则不能直接调 用,需在利用软中断,声明进入内核态后,才能根据系统调用号进行调用。
标准库,系统调用和中断的调用关系:
在c语言中,我们可以调用标准库中的write函数执行写操作,write的函数原型如下:
ssize write(int __fd, __const void *__buf, size_t __n ) __wur;
前面说到系统调用是操作系统为用户提供的接口,但不同的操作系统系统调用的实现和提供方式都有可能不同,Linux,Unix以及Windows上 的系统调用并不完全相同,甚至大相径庭。
“解决计算机的问题可以通过增加层来实现”。
标准库是对系统调用的封装,/usr/include/unistd.h定义了POSIX标准的接口。这是向上分析。
对于更底一层来说,write最终调用的是sys_write即4号调用,系统调用号在/arch/x86/include/asm中定义,
通过#define _syscallN宏将汇编代码封装成c函数,
#define _syscallN(type, name, type1, arg1, type2, arg2, . . . ) \
type name(type1 arg1,type2 arg2) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2))); \
. . . . . .
__syscall_return(type,__res); \
}
__asm__告诉编译器嵌入一段汇编代码,volatile告诉编译器不要对下面一段代码进行优化。
函数write(1, “hello”, 5)作用是以stdout方式输出hello字符串,将函数展开后应该得到类似下面的汇编代码,
xor eax, eax ;clean up the registers,SAVE_ALL,
xor ebx, ebx
xor edx, edx
xor ecx, ecx
mov al, 4 ;syscall write
mov bl, 1 ;stdout is 1
pop ecx ;get the address of the string from the stack
mov dl, 5 ;length of the string
int 0x80
将系统调用号存入al,4号调用为sys_write调用,ecx存入要打印数据地址,dl存入数据长度,bl标记输出方式。
此时,数据已经准备好,int 0x80号中断为linux下系统调用的入口。当执行到int 0x80后,查找中断向量表(在/arch/x86/include/asm/irq_vectors.h中定义),调用int 0x80号中断服务,cpu由用户态转为内核态;根据eax(存有的系统调用号)查找具体的系统调用,执行完后返回原程序,继续执行。
上面这种基于int指令的系统调用在较新的一些处理器上性能不佳,在最近的linux版本(linux2.5+)中,又引入了一种新的系统调用机 制,sysenter和sysexit,这两个指令时intel在奔腾2代处理器开始支持的一组专门针对系统调用的指令。详细内容参考Intel的Cpu 指令手册,以及linux源代码。推荐文章http://www.ibm.com/developerworks/cn/linux/kernel/l- k26ncpu/index.html
Linux上的系统调用和Windows上的系统调用不同:
Linux, unlike windows, provides a direct way to interface with the kernel through the int 0x80 interface. A complete listing of the Linux syscall table can be found here. Windows on the other hand, does not have a direct kernel interface. The system must be interfaced by loading the address of the function that needs to be executed from a DLL (Dynamic Link Library). The key difference between the two is the fact that the address of the functions found in windows will vary from OS version to OS version while the int 0x80 syscall numbers will remain constant. Windows programmers did this so that they could make any change needed to the kernel without any hassle; Linux on the contrary has fixed numbering system for all kernel level functions, and if they were to change, there would be a million angry programmers (and a lot of broken code).(节自http://www.vividmachines.com/shellcode/shellcode.html)
Windws为程序员提供api,但系统调用入口是可变的,我们可以使用特殊的工具查找系统调用的地址(只需知道它的名字即可),然后使用call 指令调用。
附:
Linux System Call Table:http://www.informatik.htw-dresden.de/~beck/ASM/syscall_list.html
Windows System Call Table:http://www.metasploit.com/users/opcode/syscalls.html
###########################################################################
从程序完成的功能来看,函数库提供的函数通常是不需要操作系统的服务. 函数是在用户空间内执行的,除非函数涉及到I/O操作等,一般是不会切到核心态的。系统调用是要求操作系统为用户提供进程,提供某种服务,通常是涉及系统的硬件资源和一些敏感的软件资源等。
函数库的函数,尤其与输入输出相关的函数,大多必须通过Linux的系统调用来完成。因此我们可以将函数库的函数当成应用程序设计人员与系统调用程序之间的一个中间层,通过这个中间层,我们可以用一致的接口来安全的调用系统调用。这样程序员可以只要写一次代码就能够在不同版本的linux系统间使用积压种具体实现完全不同的系统调用。至于如何实现对不同的系统调用的兼容性问题,那是函数库开发者所关心的问题。
从程序执行效率来看,系统调用的执行效率大多要比函数高,尤其是处理输入输出的函数。当处理的数据量比较小时,函数库的函数执行效率可能比较好,因为函数库的作法是将要处理的数据先存入缓冲区内,等到缓冲区装满了,再将数据一次写入或者读出。这种方式处理小量数据时效率比较高,但是在进行系统调用时,因为用户进程从用户模式进入系统核心模式,中间涉及了许多额外的任务的切换工作,这些操作称为上下文切换,此类的额外工作会影响系统的执行效率。但是当要处理的数据量比较大时,例如当输入输出的数据量超过文件系统定义的尽寸时,利用系统调用可获得较高的效率。
从程序的可移植性的角度来看,相对于系统调用,C语言的标准备函数库(ANSI C) 具备较高的可移植性,在不同的系统环境下,只要做很少的修改,通常情况是不需要修改的。