系统调用机制及简单的嵌入式汇编/API实现

原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/
本文主要参考了实验楼中"使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用"的实验.
部分则讲解系统调用的处理过程.


文章目录

  • 系统调用
    • 系统中断
    • 系通调用的编程实现
    • 系统调用在系统内部的表现
  • Linux-5.0内核编译
    • 下载Linux-5.0内核源代码及相关工具
    • 编译Linux
    • 通过QEMU虚拟机加载内核
    • 构造MenuOS
  • 系统调用编程实现
    • API库函数实现
    • C语言嵌入式汇编实现

 
 

系统调用

  系统调用是操作系统中耳熟能详的一个名词了, 本科上OS时的记忆已经有点模糊, 乘着这次作业刚好可以回顾一下.
  了解系统调用之前, 先来看看中断.

系统中断

  中断通常分为两类: 同步(synchronous)中断和异步(asynchronous)中断, 顾名思义, 前者是在一条指令中止执行后CPU才会发出的; 后者则是有其他硬件设备按照CPU时钟信号随机产生的. 我们也常常成同步中断和异步中断为异常(exception)和中断(interrupt).
  Intel的文档中把中断和异常分为以下几个大类:

  • 中断
    • 可屏蔽(maskable)中断
    • 非屏蔽中断
  • 异常
    • 故障(fault)
    • 陷阱(trap)
    • 异常终止(abort)
    • 编程异常(programmed exception)

  编程异常通常是由我们这些程序猿引发的, 众所周知, 在linux系统中, 凡是涉及到资源管理和设备交互这些事情, 通常是由OS管理, 当我们需要调用系统的功能时, 通常通过系统调用将这些服务交由OS来做. 系统调用就是OS为用户态进程与硬件设备进行交互提供的一组接口, 系统调用的好处多多, 不仅把用户(client, 编程用户: 程序猿)从底层的硬件编程中解放出来, 还极大地提高了系统的安全性, 使得我们开发的程序有了可移植性.
关于以上的更多详细介绍, 可以参考维基百科: 中断

系通调用的编程实现

  系统调用的缘由和原理我们知道了, 但是如何实现系统调用, 才是认识过程的第二次飞跃. Linux内核的底层代码都是通过C语言实现的, 在C语言的库中, 就有一些API引用了封装例程, 封装例程的目的, 就是进行系统调用. 我们常见的printf(), chdir() 其实都属于系统调用的范畴.

系统调用在系统内部的表现

  结合前面两个标题, 我们对于中断, 系统调用有了泛泛的了解, 更深入一点, 系统调用的过程中, 究竟发生了什么?
  我们知道, 在进行中断的时候, 通常系统会发出中断号, 中断号的目的, 就是为了查找中断服务程序的地址, 通常中断号 ∗ * 4 即为中断向量的地址, 而中断向量通过中断描述表( I n t e r r u p t d e s c r i p t i o n t a b l e , I D T Interrupt description table, IDT Interruptdescriptiontable,IDT)则可以找到相应的中断服务程序.
  系统调用既然属于中断的一种, 在执行系统调用时, 必然也会有一个中断号, 那便是128号, 系统调用有那么多种, 仅仅一个处理程序怎么应付的过来? 系统调用号就起到了作用, 其类似于中断号, 也是在一个系统调用分派表(dispatch table)中对需要的系统调用子例程查找, 完成系统调用.
  下面具体介绍本次实验.

Linux-5.0内核编译

下载Linux-5.0内核源代码及相关工具

wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.0.1.tar.xz
tar -xvf linux-5.0.1.tar
cd linux-5.0.1
sudo apt install build-essential flex bison libssl-dev libelf-dev libncurses-dev

编译Linux

make i386_defconfig
make -j4

  我在这里采用的是32位的缺省设置, 由于编译过程默认采用单核, 这里用-j4参数使其并行编译, 大概用了5分钟左右.编译结果:
系统调用机制及简单的嵌入式汇编/API实现_第1张图片

通过QEMU虚拟机加载内核

sudo apt install qemu
qemu-system-i386 -kernel linux-5.0.1/arch/x86/boot/bzImage
qemu-system-x86_64 -kernel linux-5.0.1/arch/x86/boot/bzImage

系统调用机制及简单的嵌入式汇编/API实现_第2张图片

构造MenuOS

git clone https://github.com/mengning/menu.git
cd menu
sudo apt-get install libc6-dev-i386 # 在64位环境下编译32位需安装
make rootfs
cd ..
qemu-system-i386 -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd
rootfs.img

系统调用机制及简单的嵌入式汇编/API实现_第3张图片

系统调用编程实现

API库函数实现

#include 
#include 

int main()
{
    char *buffer;
    buffer = getcwd(NULL, 0);
    printf("Current dir is: %s\n", buffer);
    chdir("/tmp");
    buffer = getcwd(NULL, 0);
    printf("Current dir is: %s\n", buffer);
}

  代码结果:
系统调用机制及简单的嵌入式汇编/API实现_第4张图片

C语言嵌入式汇编实现

#include 
#include 

int main()
{
    char *path = "/";
    char *pwd;
    int flag;
    
    asm volatile(
    "movl %1, %%edi\n\t"
    "movl $0x0c, %%eax"
    "int $0x80"
    "movl %%eax, %0\n\t"
    :"=m"(flag)
    :"m" (path) 
    );
    
    pwd = getcwd(NULL, 0);
    printf("%d\n %s\n", flag, pwd);
    
    return 0;
}

  这里我作弊一下, 我先将.C文件编译成.S汇编文件, 略微分析一下再写的答案.
  在上面的API编程中, chdir是有参数的, 而在嵌入式汇编中, 要将参数传递给函数, 必须先将该参数传递给寄存器, 由系统调用服务例程运行时取出该参数. 故在这里我们将path传递给edi寄存器. chdir在系统调用中的调用号为12号, 故将12的16进制传递给eax寄存器, 这里也需要解释一下, eax寄存器在系统调用时, 通常作为系统调用号的暂存器传递给系统调用服务例程. int 0x80就不用解释了. 接下来将返回值赋值给flag变量, 若chdir 成功, 则flag应该为1, 否则为0, 然后输出当前的工作目录. 如下图:
系统调用机制及简单的嵌入式汇编/API实现_第5张图片

你可能感兴趣的:(系统调用机制及简单的嵌入式汇编/API实现)