β

linux0.11系统调用,_syscall

雅乐网 » 雅乐网 67 阅读

unistd.h

linux0.11 中,include/unistd.h 中定义了72个系统调用号

#ifdef __LIBRARY__

#define __NR_setup	0	/* used only by init, to get system going */
#define __NR_exit	1
#define __NR_fork	2
#define __NR_read	3
#define __NR_write	4
#define __NR_open	5
#define __NR_close	6
#define __NR_waitpid	7
#define __NR_creat	8
#define __NR_link	9
#define __NR_unlink	10
#define __NR_execve	11
#define __NR_chdir	12
#define __NR_time	13
#define __NR_mknod	14
#define __NR_chmod	15
#define __NR_chown	16
#define __NR_break	17
#define __NR_stat	18
#define __NR_lseek	19
#define __NR_getpid	20
#define __NR_mount	21
#define __NR_umount	22
#define __NR_setuid	23
#define __NR_getuid	24
#define __NR_stime	25
#define __NR_ptrace	26
#define __NR_alarm	27
#define __NR_fstat	28
#define __NR_pause	29
#define __NR_utime	30
#define __NR_stty	31
#define __NR_gtty	32
#define __NR_access	33
#define __NR_nice	34
#define __NR_ftime	35
#define __NR_sync	36
#define __NR_kill	37
#define __NR_rename	38
#define __NR_mkdir	39
#define __NR_rmdir	40
#define __NR_dup	41
#define __NR_pipe	42
#define __NR_times	43
#define __NR_prof	44
#define __NR_brk	45
#define __NR_setgid	46
#define __NR_getgid	47
#define __NR_signal	48
#define __NR_geteuid	49
#define __NR_getegid	50
#define __NR_acct	51
#define __NR_phys	52
#define __NR_lock	53
#define __NR_ioctl	54
#define __NR_fcntl	55
#define __NR_mpx	56
#define __NR_setpgid	57
#define __NR_ulimit	58
#define __NR_uname	59
#define __NR_umask	60
#define __NR_chroot	61
#define __NR_ustat	62
#define __NR_dup2	63
#define __NR_getppid	64
#define __NR_getpgrp	65
#define __NR_setsid	66
#define __NR_sigaction	67
#define __NR_sgetmask	68
#define __NR_ssetmask	69
#define __NR_setreuid	70
#define __NR_setregid	71

以及几个宏函数

#define _syscall0(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; \
}

#define _syscall1(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; \
}

#define _syscall2(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; \
}

#define _syscall3(type,name,atype,a,btype,b,ctype,c) \
type name(atype a,btype b,ctype c) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
	: "=a" (__res) \
	: "0" (__NR_##name),"b" ((long)(a)),"c" ((long)(b)),"d" ((long)(c))); \
if (__res>=0) \
	return (type) __res; \
errno=-__res; \
return -1; \
}

#endif /* __LIBRARY__ */

这4个 syscall 函数可以用来展开成为系统调用函数,后面的数字表示函数有几个参数。它的形式参数

type 函数返回类型
name 函数名
atype 第一个参数类型
a 第一个参数
btype 第二个参数类型
b 第二个参数
ctype 第三个参数类型
c 第三个参数

使用syscall

以write为例, 在unistd.h中,write函数声明如下:

int write(int fd, const char * buf, off_t count);

只要按照syscall的参数格式填上去

type 函数返回类型 int
name 函数名 write
atype 第一个参数类型 int
a 第一个参数 fd
btype 第二个参数类型 const char *
b 第二个参数 buf
ctype 第三个参数类型 off_t
c 第三个参数 count

lib/write.c 中是他的实现

#define __LIBRARY__
#include <unistd.h>

_syscall3(int,write,int,fd,const char *,buf,off_t,count)

展开后就变成了(为了展示清除额外手动添加了换行)

int write(int fd,const char * buf,off_t count)
{ long __res; 
  __asm__ volatile ("int $0x80" 
    : "=a" (__res) 
    : "0" (__NR_write),"b" ((long)(fd)),"c" ((long)(buf)),"d" ((long)(count))
    ); 
  if (__res>=0)
   return (int) __res; 
  errno=-__res; 
  return -1;
}

这是一个内嵌汇编,通过int 0x80 中断来实现系统调用。

第一个冒号后面是输出,输出的寄存器前面要有等号,”=a” 表示 输出存放在 eax 寄存器中,最终汇编结束时会赋值给 变量 __res

第二个冒号后面是输入,把输出和输入用到的寄存器按顺序编号(从输出寄存器开始从左到右,再从输入寄存器从左到右),”0″ 表示的是第一个寄存器,也就是输出用的eax,它的值被赋值为__NR_write, 也就是系统调用号。后面”b”, “c”, “d”表示寄存器ebx,ecx和edx,用来存放write的三个参数。可以用的寄存器符号表示表:

符号 表示 符号 表示
a eax m 使用内存地址
b ebx o 使用内存地址可以加偏移
c ecx I 使用常数0-31
d edx J 使用常数0-63
S esi K 使用常数0-255
D edi L 使用常数0-65535
q 动态分配可寻址寄存器

eax, ebx, ecx, edx

M 使用常数0-3 r 使用任意动态分配的寄存器 N 使用1字节常数0-255 g 使用通用有效地址、

(可寻址寄存器或内存地址)

O 使用常数0-31 A eax与edx联合(64位) = 输出操作数 + 操作数可读可写 & 早期会变的操作数,表示使用完操作数之前,内容会被修改

int 0x80

在初始化的main函数中的sched_init中,有下面的代码

void sched_init(void)
{
	...
	set_system_gate(0x80,&system_call);
}

这句话设置IDT表,将0x80中断号和system_call函数进行绑定,调用int 0x80时,就会执行system_call函数。

include/asm/system.h

#define set_system_gate(n,addr) \
	_set_gate(&idt[n],15,3,addr)

#define _set_gate(gate_addr,type,dpl,addr) \
__asm__ ("movw %%dx,%%ax\n\t" \
	"movw %0,%%dx\n\t" \
	"movl %%eax,%1\n\t" \
	"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);

第一个参数表示描述符的地址,0x80号中断的描述符地址就是&idt[0x80] , 第二个参数描述符类型,第三个参数是描述符特权级dpl,第四个参数是偏移地址

再展开,变成了

__asm__ (
  "movw %%dx,%%ax\n\t"
  "movw %0,%%dx\n\t"
  "movl %%eax,%1\n\t"
  "movl %%edx,%2"
  :
  : "i" ((short) (0x8000+(3<<13)+(15<<8))), 
    "o" (*((char *) (&idt[0x80]))), 
    "o" (*(4+(char *) (&idt[0x80]))), 
    "d" ((char *) (&system_call)),
    "a" (0x00080000)
);

IDT表的表项

代码中两个”o” 分别是表项的低4个字节和高4个字节,序号是%1和%2

第一个输入  (short) (0x8000+(3<<13)+(15<<8)) 的二进制是

1110 1111 0000 0000

放到dx中,也就是edx的低2个字节,赋值给%2 ,也就是设置了

而edx的高2个字节是system_call偏移地址的高2个字节

dex的低2个字节是system_call偏移地址的低2个字节,复制给ax(eax的低2个字节)

eax的高2个字节是0x0008

表项赋值为:

system_call

kernel/system

system_call:
	cmpl $nr_system_calls-1,%eax
	ja bad_sys_call
	push %ds
	push %es
	push %fs
	pushl %edx
	pushl %ecx		# push %ebx,%ecx,%edx as parameters
	pushl %ebx		# to the system call
	movl $0x10,%edx		# set up ds,es to kernel space
	mov %dx,%ds
	mov %dx,%es
	movl $0x17,%edx		# fs points to local data space
	mov %dx,%fs
	call sys_call_table(,%eax,4)
	pushl %eax
	movl current,%eax
	cmpl $0,state(%eax)		# state
	jne reschedule
	cmpl $0,counter(%eax)		# counter
	je reschedule
ret_from_sys_call:
	movl current,%eax		# task[0] cannot have signals
	cmpl task,%eax
	je 3f
	cmpw $0x0f,CS(%esp)		# was old code segment supervisor ?
	jne 3f
	cmpw $0x17,OLDSS(%esp)		# was stack segment = 0x17 ?
	jne 3f
	movl signal(%eax),%ebx
	movl blocked(%eax),%ecx
	notl %ecx
	andl %ebx,%ecx
	bsfl %ecx,%ecx
	je 3f
	btrl %ecx,%ebx
	movl %ebx,signal(%eax)
	incl %ecx
	pushl %ecx
	call do_signal
	popl %eax
:	popl %eax
	popl %ebx
	popl %ecx
	popl %edx
	pop %fs
	pop %es
	pop %ds
	iret

通过 call sys_call_table(,%eax,4) ,call的是 sys_call_table + 4 * eax,4表示每个函数地址是4个字节。

sys_call_table是一个函数表,在 include/linux/sys.h中定义

fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,
sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link,
sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod,
sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount,
sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm,
sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access,
sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir,
sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid,
sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys,
sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit,
sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid,
sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask,
sys_setreuid,sys_setregid };

write 的 调用号是4,和这里对应的。

作者:雅乐网 » 雅乐网
发现互联网好东东
原文地址:linux0.11系统调用,_syscall, 感谢原作者分享。