“平安的祝福 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”
一、基本概念
1.1API和系统调用区别:
API只是一个函数定义;系统调用通过软中断向内核发出一个明确的请求。
Libc库定义的一些API引用了封装例程(wrapper routine,唯一目的就是发布系统调用):一般每个系统调用对应一个封装例程;库再用这些封装例程定义出给用户的API
1.2大部分封装例程返回一个整数,其值的含义依赖于相应的系统调用:
-1在多数情况下表示内核不能满足进程的请求;
Libc中定义的errno变量包含特定的出错码;
1.3应用程序、封装例程、系统调用处理程序及系统调用服务例程之间的关系
1.4当用户态进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数。
在Linux中是通过执行int $0x80来执行系统调用的,这条汇编指令产生向量为128的编程异常
1.5传参:
内核实现了很多不同的系统调用,进程必须指明需要哪个系统调用,这需要传递一个名为系统调用号的参数,使用eax寄存器
1.6系统调用也需要输入输出参数,例如
实际的值,用户态进程地址空间的变量的地址,甚至是包含指向用户态函数的指针的数据结构的地址
1.7system_call是linux中所有系统调用的入口点,每个系统调用至少有一个参数,即由eax传递的系统调用号
步骤:1.一个应用程序调用fork()封装例程,那么在执行int $0x80之前就把eax寄存器的值置为2(即__NR_fork)。这个寄存器的设置是libc库中的封装例程进行的,因此用户一般不关心系统调用号。
2.进入sys_call之后,立即将eax的值压入内核堆栈。
二、具体例子
2.1系统调用函数getpid编写的程序:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main(void)
{
printf("The current process ID is %d\n",getpid());
return 0;
}
2.2使用嵌入式汇编实现的getpid程序:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main(void)
{
int t;
asm volatile(
"mov $0,%%ebx\n\t"
"mov $0x14,%%eax\n\t" //其中getpid的系统调用数是20
"int $0x80\n\t"
"mov %%eax,%0"
:"=m"(t)
);
printf("The current process ID is %d\n",t);
return 0;
}
2.3分析汇编代码系统调用工作过程:
首先将ebx寄存器清空,将getpid的系统调用号传入寄存器eax,然后调用系统软中断。系统中断处理完成后,eax寄存器存储的是系统调用getpid的返回值,将eax寄存器的值赋值给用户空间的参数。
2.4程序的输出结果一样:
三、总结:
通过这次实验,大致了解用户程序如何执行系统调用的。流程:用户程序调用API——》封装例程——》中断向量实现系统程序的切换——》系统调用具体的汇编程序。