当前位置: 首页 > 文档资料 > FreeBSD 开发手册 >

11.3 系统调用

优质
小牛编辑
130浏览
2023-12-01

11.3.1 默认的调用规范

通常, FreeBSD 的内核使用 C 语言的调用规范。 此外, 虽然我们使用 int 80h 来访问内核, 但是我们常常通过调用一个函数来执行 int 80h, 而不是直接访问。

这个规范是非常方便的, 比 Microsoft® 的 MS-DOS® 上使用的规范更加优越。 为什么呢? 因为 UNIX® 的规范允许任何语言所写的程序访问内核。

汇编语言也可以这样做, 比如, 我们可以编写一个文件:

kernel:
    int 80h ; Call kernel
    ret

open:
    push    dword mode
    push    dword flags
    push    dword path
    mov eax, 5
    call    kernel
    add esp, byte 12
    ret

这是一种非常清晰而易于移植的编码方法。 如果你需要将代码移植到一个使用和 FreeBSD 完全不同中断或参数传递方式的 UNIX 系统, 那么你所需要做的仅仅是改变那一段内核程序。

但是汇编程序员喜欢使用一些技巧来削减程序执行所需的时钟周期数。 以上的例子需要一个 call/ret 组合, 我们可以通过压栈 push 一个额外的双字节来去除它。

open:
    push    dword mode
    push    dword flags
    push    dword path
    mov eax, 5
    push    eax     ; Or any other dword
    int 80h
    add esp, byte 16

在上一个 open 的例子中, 我们放在 EAX 中的数字 5 表示了一个内核函数。

11.3.2 另一种调用规范

FreeBSD 是一个非常灵活的系统。 它提供了访问内核的其他方式。 但是, 如果你要用到它, 你的系统必须安装 Linux emulation。

Linux 是一个类 UNIX 操作系统。 但是, 它的内核在传递参数的时候, 使用和 MS-DOS 相同系统调用规范。 比如在 UNIX 的规范中, 代表内核函数的数字存放在 EAX 中。 但是在 Linux 中, 参数不进行压栈而是存放在 EBX, ECX, EDX, ESI, EDI, EBP

open:
    mov eax, 5
    mov ebx, path
    mov ecx, flags
    mov edx, mode
    int 80h

这种做法与 UNIX 的常规做法相比有一个严重的缺点, 至少现在汇编语言程序需要注意的: 每次进行系统调用, 你必须对寄存器的值进行压栈和出栈操作, 这让你的程序变得冗长而效率低下。 因此, FreeBSD 提供给你了一个其他的选择。

如果你确实选择了 Linux 的规范, 你必要让你的系统知道这一点。 在你的程序完成汇编和连接之后, 你需要对可执行文件进行标识:

% brandelf -t Linux filename

11.3.3 你该选择哪个规范?

如果你专门为 FreeBSD 写代码, 你必须使用 UNIX 的规范: 因为它快速, 你可以在寄存器中存取全局变量, 而且不需要进行可执行的标识, 更不需要在你的系统上安装 Linux emulation。

如果你想做一些可以在 Linux 上运行的程序, 并且你打算尽可能地给 FreeBSD 用户提供最有效率地程序。 我将在我讲解完汇编基础之后, 告诉你怎样完成这样地程序。

11.3.4 系统调用号

要通知内核你调用了什么系统服务, 将代表调用的数字放入 EAX 中。 当然, 你需要知道那些系统调用号代表了什么。

11.3.4.1 syscalls 文件

系统调用号列在了 syscalls 文件里面。 locate syscalls 去寻找这个文件的不同格式, 它们都是从 syscalls.master 中自动生成的。

你可以在 /usr/src/sys/kern/syscalls.master 下找到这个描述默认情况下 UNIX 系统调用规范的文件。 如果你需要使用在 Linux emulation 中使用的规范, 请参阅 /usr/src/sys/i386/linux/syscalls.master

注意: FreeBSD 和 Linux 不仅仅是使用不同的调用规范, 有时候它们也使用不同的系统调用号来表示相同函数。

syscalls.master 描述了如何使用这些调用:

0  STD NOHIDE  { int nosys(void); } syscall nosys_args int
1   STD NOHIDE  { void exit(int rval); } exit rexit_args void
2   STD POSIX   { int fork(void); }
3   STD POSIX   { ssize_t read(int fd, void *buf, size_t nbyte); }
4   STD POSIX   { ssize_t write(int fd, const void *buf, size_t nbyte); }
5   STD POSIX   { int open(char *path, int flags, int mode); }
6   STD POSIX   { int close(int fd); }
etc...

最左边的一列告诉我们需要放入 EAX 的数字。

最右边的一列告诉我们那些需要被压栈 push 的参数, 它们的压栈 push 顺序是 从右到左

例如,要打开 open 一个文件, 我们需要先首先压栈 push 模式字 mode, 然后是压栈标志字 flags, 然后是压栈保存路径 path 的地址。