本实验选择的系统调用号为34,在syscall_32.tbl中对应如下项:
41 i386 dup sys_dup
功能描述:
Dup() duplicates an existing object descriptor and returns its value to
the calling process (fildes2 = dup(fildes)). The argument fildes is a
small non-negative integer index in the per-process descriptor table.
The value must be less than the size of the table, which is returned by
getdtablesize(2). The new descriptor returned by the call is the lowest
numbered descriptor currently not in use by the process.
RETURN VALUES
Upon successful completion, the new file descriptor is returned. Otherwise, a value of -1 is returned and the global integer variable errno is set to indicate the error.
使用 libc 中提供的库函数调用 的 C 代码以及运行结果如图1所示:
其中,给 dup 函数传递的参数是1.
其中的程序成功通过新复制的 fild 进行了写入操作,在屏幕上输出字符串.
直接在 C 代码中使用内联汇编调用该系统调用的代码及运行结果如图2所示:
从图中可以看出对 ebx 寄存器赋值为1,达到了传递参数的效果.同样在屏幕上输出了字符串.可知fild 复制成功.
汇编代码调用系统调用的程序如下:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char const *argv[])
{
int new_fildes=0;
char *szTemp="test_dup\n";
asm volatile(
"mov $1,%%ebx\n\t" //传递参数
"mov $41,%%eax\n\t" //设置系统调用号
"int $0x80\n\t" //进行软中断触发
"mov %%eax,%0\n\t" //保存返回值
:"=m"(new_fildes)
);
printf("new fildes: %d\n",new_fildes);
write(new_fildes,szTemp,strlen(szTemp));
return 0;
}
系统调用的总入口是0x80中断,用 eax 寄存器存储具体的中断调用号,本例中是41.除了系统调用号以外,大部分系统调用都还需要一些外部的参数输人。所以,在发生异常的时候,应该把这些参数从用户空间传给内核。最简单的办法就是像传递系统调用号一样把这些参数也存放在寄存器里。在x86系统上,ebx, ecx, edx, esi和edi按照顺序存放前五个参数。需要六个或六个以上参数的情况不多见,此时,应该用一个单独的寄存器存放指向所有这些参数在用户空间地址的指针。 给用户空间的返回值也通过寄存器传递。在x86系统上,它存放在eax寄存器中。接下来许多关于系统调用处理程序的描述都是针对x86版本的。但不用担心,所有体系结构的实现都很类似。最终依据具体调用号调用对应的系统调用处理过程并使用传递的参数.
总结
系统调用在用户空间进程和硬件设备之间的接口,它和普通库函数调用非常相似,只是系统调用由操作系统核心提供,运行于核心态,而普通的函数调用由函数库或用户自己提供,运行于用户态。主要有三个作用:
它为用户空间提供了一种统一的硬件的抽象接口。比如当需要读些文件的时候,应用程序就可以不去管磁盘类型和介质,甚至不用去管文件所在的文件系统到底是哪种类型。
系统调用保证了系统的稳定和安全。作为硬件设备和应用程序之间的中间人,内核可以基于权限和其他一些规则对需要进行的访问进行裁决。举例来说,这样可以避免应用程序不正确地使用硬件设备,窃取其他进程的资源,或做出其他什么危害系统的事情。
每个进程都运行在虚拟系统中,而在用户空间和系统的其余部分提供这样一层公共接口,也是出于这种考虑。如果应用程序可以随意访问硬件而内核又对此一无所知的话,几乎就没法实现多任务和虚拟内存,当然也不可能实现良好的稳定性和安全性。在Linux中,系统调用是用户空间访问内核的惟一手段;除异常和中断外,它们是内核惟一的合法入口。
API与系统调用并不存在一一对应的关系,只是为用户提供的标准接口,提高了程序移植性。用户在请求系统调用的时候,一般只与API打交道。而内核只与系统调用打交道,至于API是怎样申请系统调用的,是由Glibc等标准制定者负责的。UNIX尊奉的一句话是:Provide mechanism, not policy,即提供机制而不是策略。这里的机制就是:
用户空间的程序无法直接执行内核代码。它们不能直接调用内核空间中的函数,因为内核驻留在受保护的地址空间上。如果进程可以直接在内核的地址空间上读写的话,系统安全就会失去控制。所以,应用程序应该以某种方式通知系统,告诉内核自己需要执行一个系统调用,希望系统切换到内核态,这样内核就可以代表应用程序来执行该系统调用了。
通知内核的机制是靠软件中断实现的。首先,用户程序为系统调用设置参数。其中一个参数是系统调用编号。参数设置完成后,程序执行“系统调用”指令。x86系统上的软中断由int产生。这个指令会导致一个异常:产生一个事件,这个事件会致使处理器切换到内核态并跳转到一个新的地址,并开始执行那里的异常处理程序。此时的异常处理程序实际上就是系统调用处理程序。它与硬件体系结构紧密相关。
新地址的指令会保存程序的状态,计算出应该调用哪个系统调用,调用内核中实现那个系统调用的函数,恢复用户程序状态,然后将控制权返还给用户程序。系统调用是设备驱动程序中定义的函数最终被调用的一种方式。