使用库函数 API 和 C 代码中嵌入汇编代码两种方式使用同一个系统调用
1. 实验过程
选择64号系统调用,获取当前进程的父进程pid
(1)使用库函数getppid,获取父进程pid
- 使用gedit创建并编辑getppid.c
#include <stdio.h>
#include <unistd.h>
int main(){
long ppid;
ppid = getppid();
printf("the parent pid is:%ld\n",ppid);
return 0;
}
- 使用gcc编译getppid.c文件
(2)C代码中嵌入汇编代码使用getppid
- 使用gedit创建并编辑getppid_asm.c文件
#include <stdio.h>
int main()
{
long ppid;
// 使用内联汇编获取父进程ID
asm volatile(
"mov $64,%%eax\n\t" // 将系统调用号64(getppid)放入eax寄存器
"int $0x80\n\t" // 执行中断0x80,触发系统调用
"mov %%eax,%0\n\t" // 将eax寄存器的值(系统调用的结果)移动到变量ppid
:"=m"(ppid) // 输出操作,将eax的值赋给ppid
);
printf("the parent pid is:%ld\n",ppid);
return 0;
}
- 使用gcc编译getppid_asm.c文件
(3)运行两个文件
2. 系统调用工作过程
(1)系统调用基本原理
在 Linux 系统中,系统调用是操作系统内核提供的接口,用于用户态程序与内核进行交互。为了执行系统调用,程序需要:
- 将系统调用号传递给内核,以标识具体要执行的系统调用。
- 传递所需的参数。
- 使用特定的机制,如中断(在 x86 架构中通常是
int 0x80
或syscall
指令)进入内核态。
系统调用通过 int 0x80
中断触发。
(2)代码详细解析
asm volatile(
"mov $64,%%eax\n\t" // 1. 将系统调用号 64(getppid)放入 eax 寄存器
"int $0x80\n\t" // 2. 触发中断 0x80,进入内核
"mov %%eax,%0\n\t" // 3. 将 eax 寄存器中的返回值(父进程 PID)存入变量 ppid
:"=m"(ppid) // 4. 指定输出操作,将 eax 的值存入 ppid
);
详细步骤:
-
设置系统调用号:
mov $64,%%eax
:通过mov
指令将值64
赋值给寄存器eax
。在 Linux 的 x86 系统中,系统调用号通过eax
寄存器传递。
- 这里
64
代表getppid()
系统调用的编号。不同的系统调用有不同的编号,如exit
是1
,fork
是2
。
-
触发中断:
int $0x80
:通过执行int 0x80
中断,进入内核态,内核根据eax
中的系统调用号(这里是64
)确定执行的系统调用。这个指令将会切换到内核态,执行getppid()
系统调用。
-
返回值处理:
mov %%eax,%0
:系统调用完成后,内核会将返回结果存储在eax
寄存器中。在getppid()
的情况下,eax
中会存储父进程的 PID。- 这条指令将
eax
寄存器中的值(即父进程的 PID)移动到 C 变量ppid
中,%0
对应的是输出约束"=m"(ppid)
,表示将eax
的值存储到内存位置ppid
。
汇编代码中的参数传递方式:
-
输入/输出约束:
在内联汇编中,"=m"(ppid)
表示一个输出操作,=m
是 GCC 的汇编约束,表示将汇编代码中的值写入一个内存位置,而这个内存位置对应的 C 变量是ppid
。这里的
volatile
关键字告诉编译器不要优化这个汇编代码块,以确保它不会被删除或重排序。
- 系统调用参数传递方式
在 x86 架构中,传统上 Linux 系统调用的参数通过以下方式传递:
- 系统调用号通过
eax
寄存器传递。 - 第 1 个参数通过
ebx
传递。 - 第 2 个参数通过
ecx
传递。 - 第 3 个参数通过
edx
传递。 - 第 4 个参数通过
esi
传递。 - 第 5 个参数通过
edi
传递。 - 第 6 个参数通过
ebp
传递。
不过,getppid()
不需要传递参数,它只是返回当前进程的父进程 ID。
3. 总结
系统调用是操作系统为用户态程序提供的接口,用于与内核进行交互。其工作机制包括以下关键步骤:
-
系统调用号的传递:
每个系统调用都有唯一的编号,用户态程序通过将该编号存入特定寄存器(如eax
)来告诉内核它希望执行的操作。例如,在本文的汇编代码中,64
对应的是getppid()
系统调用。 -
切换到内核态:
用户态程序无法直接访问内核功能,需要通过特定的机制切换到内核态。在 x86 架构中,int 0x80
指令就是触发从用户态到内核态切换的方式。这个中断会导致 CPU 执行特定的中断服务例程,进入内核态执行相应的系统调用。 -
参数和返回值的传递:
系统调用的参数通过特定的寄存器传递(如ebx
,ecx
等),返回值通常存储在eax
寄存器中。在执行完系统调用后,返回结果会通过指定的寄存器传回用户态,用户态程序可以通过汇编或 C 代码读取这些寄存器中的返回值。 -
系统调用完成后返回用户态:
系统调用完成后,内核会切换回用户态,用户程序继续执行。这一过程是通过内核态返回用户态的机制来完成的。
通过这种方式,用户程序能够安全地与操作系统进行通信,而无需直接操作硬件资源或内核数据。