使用库函数 API 和 C 代码中嵌入汇编代码两种方式使用同一个系统调用。
实验内容
本次的实验是使用库函数 API 和 C 代码中嵌入汇编代码两种方式使用同一个系统调用。我选择了4号系统调用write。在屏幕上打印输出“hello world!”,对应的API就是printf。
1.用API实现
创建hello.c文件,代码如下:
#include <stdio.h>
#include <string.h>
int main()
{
char* msg = "Hello World!";
printf("%s\n", msg);
return 0;
}
实验结果:
2.用C 代码中嵌入汇编代码
创建hello-asm.c,代码如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
char* msg = "hello world!";
int len = 12;
int result = 0;
asm volatile (
"movl %2, %%edx;\n\r" /*传入参数:要显示的字符串长度*/
"movl %1, %%ecx;\n\r" /*传入参赛:文件描述符(stdout)*/
"movl $1, %%ebx;\n\r" /*传入参数:要显示的字符串*/
"movl $4, %%eax;\n\r" /*系统调用号:4 sys_write*/
"int $0x80" /*触发系统调用中断*/
:"=m"(result) /*输出部分*/
:"m"(msg),"r"(len) /*输入部分:绑定字符串和字符串长度变量*/
:"%eax");
return 0;
}
分析:
- 要在 "asm" 内使用寄存器 %eax,%eax 的前面应该再加一个 %,换句话说就是 %%eax,因为 "asm" 使用 %0、%1 等来标识变量。
- 任何带有一个 % 的数都看作是输入/输出操作数,而不认为是寄存器。
- 在汇编中用 %序号 来代表这些输入/输出操作数, 序号从 0 开始。
- 为了与操作数区分开来, 寄存器用两个%引出,如:%%eax。$表示当前位置。
- 返回值:目前进程的进程ID。
- 在Linux系统中是通过激活0x80中断来触发系统调用的,需要调用的系统调用号实现赋值给eax存储器,如果有传入参数可赋值给ebx寄存器,如果多于1个则按顺序赋值给ebx、ecx、edx、esi、edi、ebp,如果超过6个则通过指针变量指向另一片堆栈区,如果无参数传入则赋值为0。
实验结果:
学习总结:
- intel x86 CPU有四种不同的执行级别0-3,linux只使用了其中的0级和3级分贝来表示内核态和用户态。
- 一般来说在linux中,地址空间是一个显著的标志:0xc0000000以上的地址空间只能在内核态下访问,0x00000000-0xbfffffff的地址空间在两种状态下都可访问。(地址空间指逻辑地址不是物理地址)。
- Linux系统调用的三层机制:xyz()(API函数)、system_call(系统调用处理入口) 、 sys_xyz()(系统调用内核处理函数)。
- Libc库定义个一些API引用了封装例程(wrapper routine,唯一的目的就是发布系统调用,程序员在写代码的时候不需要用汇编指令来触发一个系统调用,而是直接触发一个函数就能进行系统调用了。)
- system_call是linux中所有系统调用的入口点,每个系统调用至少有一个参数,即由eax传递的系统调用号。
- API(应用程序编程接口)是函数定义,一个API可以对应多个系统调用,他们之间是多对多的关系
系统调用在操作系统中的位置:
系统调用的三层机制
API/libc与系统调用的关系:
既然API与libc对软中断进行了封装,那我们先一起看看它们之间的具体关系。一般情况下,应用程序通过应用编程接口(API)而不是直接通过系统调用来编程。这点很重要,因为应用程序使用的这种编程接口实际上并不需要和内核提供的系统调用一一对应。一个API定义了一组应用程序使用的编程接口。从程序员的角度看,系统调用无关紧要,他们只需要跟API打交道就可以了。相反,内核只跟系统调用打交道;库函数及应用程序是怎么使用系统调用不是内核所关心的。
系统调用实现机制
与调用函数一样,系统调用也需要输入输出参数。每个系统调用至少有一个参数,即系统调用号(由eax传递),其他参数依次由ebx、ecx、edx、esi、edi、ebp传入。 由于使用寄存器传递参数,因此对参数的长度做了限制:
(1)每个参数的长度不能超过寄存器的长度,即32位
(2)在系统调用号(eax)之外,参数的个数不能超过6个(ebx、ecx、edx、esi、edi、ebp)如果超过六个,可以传入一个地址,地址所在地存放多个参数
系统调用的三个层次依次是:xyz函数(API)、system_ call(中断向量)和 sys_ xyz(中断服务程序)。