20242828-《Linux内核原理分析》第五周作业

使用库函数 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 0x80syscall 指令)进入内核态。

系统调用通过 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
);

详细步骤:

  1. 设置系统调用号

    • mov $64,%%eax:通过 mov 指令将值 64 赋值给寄存器 eax。在 Linux 的 x86 系统中,系统调用号通过 eax 寄存器传递。
    • 这里 64 代表 getppid() 系统调用的编号。不同的系统调用有不同的编号,如 exit1fork2
  2. 触发中断

    • int $0x80:通过执行 int 0x80 中断,进入内核态,内核根据 eax 中的系统调用号(这里是 64)确定执行的系统调用。这个指令将会切换到内核态,执行 getppid() 系统调用。
  3. 返回值处理

    • mov %%eax,%0:系统调用完成后,内核会将返回结果存储在 eax 寄存器中。在 getppid() 的情况下,eax 中会存储父进程的 PID。
    • 这条指令将 eax 寄存器中的值(即父进程的 PID)移动到 C 变量 ppid 中,%0 对应的是输出约束 "=m"(ppid),表示将 eax 的值存储到内存位置 ppid

汇编代码中的参数传递方式:

  • 输入/输出约束
    在内联汇编中,"=m"(ppid) 表示一个输出操作,=m 是 GCC 的汇编约束,表示将汇编代码中的值写入一个内存位置,而这个内存位置对应的 C 变量是 ppid

    这里的 volatile 关键字告诉编译器不要优化这个汇编代码块,以确保它不会被删除或重排序。

  1. 系统调用参数传递方式

在 x86 架构中,传统上 Linux 系统调用的参数通过以下方式传递:

  • 系统调用号通过 eax 寄存器传递。
  • 第 1 个参数通过 ebx 传递。
  • 第 2 个参数通过 ecx 传递。
  • 第 3 个参数通过 edx 传递。
  • 第 4 个参数通过 esi 传递。
  • 第 5 个参数通过 edi 传递。
  • 第 6 个参数通过 ebp 传递。

不过,getppid() 不需要传递参数,它只是返回当前进程的父进程 ID。

3. 总结

系统调用是操作系统为用户态程序提供的接口,用于与内核进行交互。其工作机制包括以下关键步骤:

  1. 系统调用号的传递
    每个系统调用都有唯一的编号,用户态程序通过将该编号存入特定寄存器(如 eax)来告诉内核它希望执行的操作。例如,在本文的汇编代码中,64 对应的是 getppid() 系统调用。

  2. 切换到内核态
    用户态程序无法直接访问内核功能,需要通过特定的机制切换到内核态。在 x86 架构中,int 0x80 指令就是触发从用户态到内核态切换的方式。这个中断会导致 CPU 执行特定的中断服务例程,进入内核态执行相应的系统调用。

  3. 参数和返回值的传递
    系统调用的参数通过特定的寄存器传递(如 ebx, ecx 等),返回值通常存储在 eax 寄存器中。在执行完系统调用后,返回结果会通过指定的寄存器传回用户态,用户态程序可以通过汇编或 C 代码读取这些寄存器中的返回值。

  4. 系统调用完成后返回用户态
    系统调用完成后,内核会切换回用户态,用户程序继续执行。这一过程是通过内核态返回用户态的机制来完成的。

通过这种方式,用户程序能够安全地与操作系统进行通信,而无需直接操作硬件资源或内核数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值