Linux内核学习笔记四——系统调用
一 用户空间和内核空间
Linux内核将这4G字节虚拟地址空间的空间分为两部分:
l 将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为“内核空间”。
l 将较低的3G字节(从虚拟地址 0x00000000到0xBFFFFFFF),供各个进程使用,称为“用户空间)。
因为每个进程可以通过系统调用进入内核,因此Linux内核由系统内的所有进程共享。于是从具体进程的角度来看,
每个进程可以拥有4G字节的虚拟空间。如此划分提供对系统内核安全保护机制。
二 系统调用
用户空间的进程和内核空间程序如何进行交互?——系统调用
l 为用户空间提供统一的抽象接口;
l 保证系统的安全访问和稳定;
l 控制进程用户空间与内核空间的切换;
1 系统调用的层次关系
Linux内部体系结构:
图片来自:http://blog.chinaunix.net/uid-26838492-id-3162146.html
系统调用过程如下:
Unix系统设计理念:提供机制而不是策略
将编程问题分成两个部分:机制(Mechanism)和策略(Policy)。对外应用程序提供接口(系统调用API),
而不用去关心如何实现——机制;真正的实现在系统内部,系统提供实现接口算法而不关心如何使用——策略。
2 系统调用程序执行
通知内核的机制是靠软中断实现的:
通过引发一个异常来促使系统切换到内核态去执行异常处理程序。此时的异常处理程序实际上就是系统调用处理程序。
通过异常陷入到内核中,如何执行相应的系统调用:
在x86上, 系统调用号是通过eax寄存器传递给内核的。在陷入内核之前,用户空间就把相应系统调用所对应的号放入eax中了。
这样系统调用处理程序一旦运行,就可以从eax中得到数据。
call *sys_call_table(, %eax, 4)
由于系统调用表中的表项是以32位(4字节)类型存放的,所以内核需要将给定的系统调用号乘以4,然后用所得的结果在该表中查询其位置。
通过异常陷入到内核中,如何传递参数给系统调用以及回传给用户空间:
把这些参数也存放在寄存器里。在x86系统上,ebx、ecx、edx、esi和edi按照顺序存放前五个参数。需要六个或六个以上参数的情况不多见,
此时,应该用一个单独的寄存器存放指向所有这些参数在用户空间地址的指针。给用户空间的返回值也通过寄存器传递。在x86系统上,它存放在
eax寄存器中。
3 系统调用的实现
一个Linux的系统调用在实现时并不需要太关心它和系统调用处理程序之间的关系。给Linux添加一个新的系统调用是件相对容易的工作。
怎样设计和实现一个系统调用是难题所在,而把它加到内核里却无须太多周折。
实现一个新的系统调用的第一步是决定它的用途。它要做些什么:
每个系统调用都应该有一个明确的用途。在Linux中不提倡采用多用途的系统调用(一个系统调用通过传递不同的参数值来选择完成不同的工作)。
ioctl()就应该被视为一个反例。
新系统调用的参数、返回值和错误码又该是什么:
系统调用的接口应该力求简洁,参数尽可能少。系统调用的语义和行为非常关键;因为应用程序依赖于它们,所以它们应力求稳定,不做改动。
设计接口的时候要尽量为将来多做考虑。你是不是对函数做了不必要的限制:
系统调用设计得越通用越好。不要假设这个系统调用现在怎么用将来也一定就是这么用。系统调用的目的可能不变,
但它的用法却可能改变。这个系统调用可移植吗?别对机器的字节长度和字节序做假设。记住Unix的格言:“提供机制而不是策略”。
添加系统调用要谨慎!