JPEG标准的中文文档PDF
15.4系统调用进程同内核交互是通过一组定义好的函数来进行的,这些函数称为系统调用。在讨论支持网络的系统调用之前,我们先来看看系统调用机制的本身。从进程到内核中的受保护的环境的转换是与机器和实现相关的。在下面的讨论中,我们使用Net/3在386上的实现来说明如何实现有关的操作。在BSD内核中,每一个系统调用均被编号,当进程执行一个系统调用时,硬件被配置成仅传送控制给一个内核函数。将标识系统调用的整数作为参数传给该内核函数。在386实现中,这个内核函数为syscall。利用系统调用的编号,syscall在表中找到请求的系统调用的sysent结构。表中的每一个单元均为一个sysent结构。
struct sysent {
int sy_narg; /* number of arguments */
int (*sy_call)(); /* implementing function */
}; /* system call table entry */
表中有几个项是从sysent数组中来的,该数组是在kern/init_sysent.c中定义的。
struct sysent[] = {
/* . . . */
{ 3, recvmsg }, /* 27 = recvmsg */
{ 3, sendmsg }, /* 28 = sendmsg */
{ 6, recvfrom }, /* 29 = recvfrom */
{ 3, accept }, /* 30 = accept */
{ 3, getpeername }, /* 31 = getpeername */
{ 3, getsockname }, /* 32 = getsockname */
/* . . . */
};
recvmsg系统调用在系统调用表中的第27个项,它有两个参数,利用内核中的recvmsg函数实现。syscall将参数从调用进程复制到内核中,并且分配一个数组来保存系统调用的结果。然后,当系统调用执行完成后,syscall将结果返回给进程。syscall将控制交给与系统调用相对应的内核函数。在386实现中,调用有点像:
struct sysent *callp;
error = (*callp->sy_call)(p, args, rval);
这里指针callp指向相关的sysent结构;指针p则指向调用系统调用的进程表项;args作为参数传给系统调用,它是一个32 bit长的字数组;而rval则是一个用来保存系统调用的返回结果的数组,数组有两个元素,每个元素是一个32 bit长的字。当我们用“系统调用”这个词时,我们指的是被syscall调用的内核中的函数,而不是应用调用的进程中的函数。syscall期望系统调用函数(即sy_call指向的函数)在没有差错时返回0,否则返回非0的差错代码。如果没有差错出现,内核将rval中的值作为系统调用(应用调用的)的返回值传送给进程。如果有差错,syscall忽略rval中的值,并以与机器相关的方式返回差错代码给进程,使得进程能从外部变量errno中得到差错代码。应用调用的函数则返回-1或一个空指针表示应用应该查看errno获得差错信息。在386上的实现,设置进位比特(carry bit)来表示syscall的返回值是一个差错代码。进程中的系统调用残桩将差错代码赋给errno,并返回-1或空指针给应用。如果没有设置进位。
想深入了解如何在内核中添加系统调用吗?看看这个指南 Linux内核添加系统调用。还在好奇ReactOS是如何实现系统调用的吗?这里有一篇有趣的文章漫谈兼容内核之一ReactOS怎样实现系统调用。如果你对更详细的源码实现感兴趣,可以下载syscall_intercept系统调用拦截库源码来看看。
这些资源不仅帮助你更好地理解系统调用的机制,还能让你掌握在不同环境下的具体实现和应用。再也不用担心读不懂复杂的内核源码了,赶紧去看看吧!