系统调用实现的运行库
const char * str = "Hello world!\n";
void print()
{
asm(
"movl $13,%%edx \n\t" //最后一个参数edx %%说明表示是寄存器
"movl $0,%%ecx \n\t"//参数ecx
"movl $0,%%ebx \n\t"//参数
"movl $4,%%eax \n\t"//eax 4 对应系统调用write()操作
"int $0x80 \n\t"//触发系统调用
::"r"(str):"edx","ecx","ebx");
}
void exit()
{
asm(
"movl $42,%%ecx \n\t"// 进程退出码
"movl $1,%%eax \n\t"//调用号
"int $0x80 \n\t"//产生系统调用
);
}
系统调用的弊端
首先声明:程序对系统的调用开销很大。许多设计都为了减少对系统的调用,如缓冲区、堆分配算法等
系统调用完成了应用程序和内核交流的工作,因此理论上只需要系统调用就可以完成一些程序,但是:
事实上,包括Linux,大部分操作系统的系统调用都有两个特点:
-
使用不便。
操作系统提供的系统调用接口往往过于原始,程序员须要了解很多与操作系统相关的细节。如果没有进行很好的包装,使用起来不方便。(包装使得抽象) -
各个操作系统之间系统调用不兼容。
首先 Windows系统和Linux系统之间的系统调用就基本上完全不同,虽然它们的内容很多都一样,但是定义和实现大不一样。即使是同系列的操作系统的系统调用都不一样,比如 Linux 和 UNIX就不相同。
为了解决这个问题,万能法则:“解决计算机的问题可以通过增加层来实现”,于是运行库挺身而出,它作为系统调用与程序之间的一个抽象层可以保持着这样的特点:
-
使用简便。因为运行库本身就是语言级别的,它一般都设计相对比较友好。
-
形式统一。运行库有它的标准,叫做标准库,凡是所有遵循这个标准的运行库理论上都是相互兼容的,不会随着操作系统或编译器的变化而变化。
运行时库将不同的操作系统的系统调用包装为统一固定的接口,使得同样的代码,在不同的操作系统下都可以直接编译,并产生一致的效果。这就是源代码级上的可移植性。
但是运行库也有运行库的缺陷,比如C语言的运行库为了保证多个平台之间能够相互通用,于是它只能取各个平台之间功能的交集。比如 Windows和Linux都支持文件读写,那么运行库就可以有文件读写的功能;但是Windows原生支持图形和用户交互系统,而Linux却不是原生支持的(通过XWindows),那么CRT就只能把这部分功能省去。因此,一旦程序用到了那些CRT 之外的接口,程序就很难保持各个平台之间的兼容性了。