续标题:用户使用中断调用int 0x80和放在寄存器eax中的功能号来使用内核提供的各种功能服务。这些操作系统提供的功能被称之为系统调用功能。
一、角度1--------开凿运河-----以(0x80号)128号中断为运河源头
1
区分内核态和用户态:是一种处理器“硬件设计”,应该涉及段寄存器,即硬件设计段寄存器的那两位特权级位。cpu在取指执行时,若将要取操作的段的特权级大于当前特权级。则无法取指执行。
2
(1)宏定义
#define _syscall3(type,name,atype,a,btype,b,ctype,c)
type name(atype a, btype b, ctype c)
{
long _ _res;
_ _asm_ _volatile(“int0x80”
:”=a”(_ _res)
:””(_ _NR_##name), ”b”((long)(a)),”c”((long)(b)),“d”((long)(c))));
if(_ _res>=0)
return (type)__res;
errno=-_ _res;
return -1;
}
(2)宏展开sysca113 (int, write,int, fd, const char *,buf, off t, count)
输入Mov ebx,a
Mov ecx,b
Mov edx,c
输出Mov eax,_NR_write
展开后,只是明确了int 0x80的输入输出,int指令执行完后返回一个值,但问题是不知道int 0x80到底干什么?
(3)int 0x80中断干什么,需要预先初始化
初始化时
在linux/init/main.c中函数void main(void)调用void sched_init(void)
void sched_init(void)
{
……..
……..
set_system_gate(0x80,&system_call);
}
在linux/include/asm/system.h中
#define set_system_gate(n, addr)
_set_gate(&idt[n],15,3,addr); //idt是中断向量表基址
#define _set_gate(gate_addr, type, dpl, addr)
__asm__(“movw %%dx,%%ax”
“movw %0,%%dx”
“movl %%eax,%1”
“movl %%edx,%2”
:
:”i”((short)(0x8000+(dpl<<13)+type<<8))),
“o”(*(( char*)(gate_addr))),
”o”(*(4+(char*)(gate_addr))),
“d”((char*)(addr),
”a”(0x00080000))
set_system_gate(0x80,&system_call);即_set_gate(&idt[0x80], 15, 3, &system_call)
宏展开:
__asm__(“movw %%dx,%%ax”
“movw %0,%%dx”
“movl %%eax,%1”
“movl %%edx,%2”:
:”i”((short)(0x8000+(3 <<13)+15 <<8))),
“o”(*(( char*)(&idt[0x80]))), //局部第0x80段
”o”(*(4+(char*)(&idt[0x80]))),
“d”((char*)(&system_call),
”a”(0x00080000))
内嵌汇编转变一下
movl $0x00080000,%%eax
movl $&system_call,%%edx
movw %%dx,%%ax
movw $0xef00,%%dx
movl %%eax,地址位局部段第一个字节
movl %%edx,地址位局部段第四个字节
这样DPL变成3,用户可以进来。进来后RPL就成了0,就可以在内核逛了。
1)DPL=3
2)RPL=0
3)这个0x80中断的处理程序是system-call.s。对于0x80中断system-call.s是系统调用入口。
所以汇编语句int 0x80的作用是中断进入系统调用入口(系统调用入口system-call.s是初始化时设置的跳转函数,中断可以说是跳转)。接口函数API是包装一下语句int 0x80
3
system-call.s代码刨析
简单的说:system_call.s本程序主要实现系统调用(system_call)中断intOxSO的入口处理过程以及信号检测处理(从代码第80行开始同时给出了两个系统功能的底层接口,分别是sys_execve和sys_fork。还列出了处理过程类似 的协处理器出错(intl6)、设备不存在(int7)、时钟中断(int32)、硬盘中断(int46)、软盘中断(int38)的中断处理程序
更细节的看源代码
二、角度2-------建造码头------用宏包裹int 0x80指令
1
接口函数API(Application Programming Interface)(.v申请;.n程序;.n接口)
(1)把系统调用的编号存入 EAX;
(2)把函数参数存入其它通用寄存器;
(3)触发 0x80 号中断(int 0x80),int 是汇编指令,编译后就是一段明确的二进制指令。就像pop,push。具体再往下延申就于系统调用远了些。
2
接口函数API的宏
内核接口函数的实现:0~3个参数的接口函数例子:
(1)0个参数
①0个参数类的接口函数的所需指令和所需指令的执行序列一样,做宏减少代码量。
#define syscallO(type, name) type name(void)
{
long _ _res;
_ asm volatile ( int $0x80"
:"=a〃 (res)
:”0”(NR##name)) ;
if ( res〉= 0)
return (type) res;
errno = - _res;
return -1;
}
(2)1个参数
①1个参数类的接口函数的所需指令和所需指令的执行序列一样,做宏减少代码量。
#define _syscalll (type, name, atype, a)
type name(atype a)
{
long _ res;
_ asm volatile ( int $0x80"
:"=a〃 (res)
:〃0〃 (NR##name), 〃b" ((long) (a))) ;
if ( res >= 0)
return (type) res;
errno = - _res;
return -1;
}
(3)2个参数
①2个参数类的接口函数的所需指令和所需指令的执行序列一样,做宏减少代码量。
#define sysca112(type, name, atype, a, btype, b)
type name(atype a, btype b)
{
long _ res;
_ asm _ volatile {“int $0x80〃
:”=a〃 (res)
:〃0〃 (NR##name), 〃b〃 ((long) (a)), “c” ((long) (b))) ;
if ( res >= 0)
return (type) res;
errno = - _res;
return -1;
}
(4)3个参数
1在linux/include/unistd.h中
#ifdef LIBRARY
①系统调用的编号的定义
宏定义系统调用符号常数,用作系统调用函数表中的索引值。
#define _ _NR_write 4
②3个参数类的接口函数的所需指令和所需指令的执行序列一样,做宏减少代码量。
#define _syscall3(type,name,atype,a,btype,b,ctype,c)
type name(atype a, btype b, ctype c)
{
long _ res;
_ asm volatile(“int0x80”
:”=a”( res)
:””( NR##name), ”b”((long)(a)),”c”((long)(b)),“d”((long)©)));
if( _res>=0)
return (type)_res;
errno=- _res;
return -1;
}
#endif
③系统调用的函数原型定义int write(int fildes, const char * buf, off_t count);
3、利用上诉的4个宏定义接口函数API
(1)在linux/lib/write.c中只有三句
#define LIBRARY
include <unistd. h>
sysca113 (int, write,int, fd, const char *,buf, off_t, count)
(2)要理解嵌入式汇编宏函数,就要先了解宏函数
sysca113 (int, write,int, fd, const char *,buf, off_t, count)
宏展开为
int write(int fd, const char *buf, off_t count)
{
long _ res;
_ asm volatile(“int0x80”
:”=a”( res)
:””( NR##write), ”b”((long)(fd)),”c”((long)(buf)),“d”((long)(count)));
if( _res>=0)
return (int)_res;
errno=- _res;
return -1;
}
在实际情形中,带参数的宏得到参数,一般会做两件事,一执行汇编指令int的操作,二返回一个值。
4、当然系统调用接口函数原型可以是利用宏定义:
sysca113 (int, write,int, fd, const char *,buf, off_t, count)
也可以是直接定义:
int open(const char * filename, int flag, ...)
{
register int res;
va_list arg;
va_start(arg,flag);
__asm__("int $0x80"
:"=a" (res)
:"0" (__NR_open),"b" (filename),"c" (flag),
"d" (va_arg(arg,int)));
if (res>=0)
return res;
errno = -res;
return -1;
}
因为sys_open有:(1)把系统调用的编号存入 EAX; (2)把函数参数存入其它通用寄存器;
(3)触发 0x80 号中断(int 0x80)
这三个接口函数必有要素外,还使用了可变参数