linux0.11怎么实现的系统调用

此篇文章主要为了解决哈工大操作系统实验3——系统调用,从一点都不了解一步步深入探究,最终完成实验的整个过程。所以篇幅较长,可耐心看,会有收获。

linux0.11怎么实现的系统调用

Linux0.11中,include/unistd.h 中定义了72个系统调用号(太多此处不贴出)以及几个宏函数(如下):

#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函数可以展开成为系统调用函数,后面的数字表示函数有几个参数。所以可以知道_syscall宏展开的系统调用最多3个参数。

_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参数格式填上去

write系统调用函数的实现
type name atype a btype b ctype c
函数返回类型 函数名 第一个参数类型 第一个参数 第二个参数类型 第二个参数 第三个参数类型 第三个参数
int write int fd const char * buf off_t 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的三个参数。

int 0x80

int 0x80是一个中断号,在boot/head.s中创建了中段描述符表(IDT):

idt_descr:
       .word 256*8-1              # idt contains 256 entries
       .long _idt
_idt: .fill 256,8,0            # idt is uninitialized

 lidt指令为6字节操作数,它将_idt的地址加载进idtr寄存器,IDT被设置为包含256个8字节表项的描述符表。

  中断描述符表的初始化工作主要通过宏_set_get来完成,它定义于include/asm/system.h中,如下:

#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))
/*设置中断门函数,特权级0,类型386中断门*/
#define set_intr_gate(n,addr) /
       _set_gate(&idt[n],14,0,addr)
/*设置陷阱门函数,特权级0,类型386陷阱门*/
#define set_trap_gate(n,addr) /
       _set_gate(&idt[n],15,0,addr)
/*设置系统调用函数,特权级3,类型386陷阱门*/
#define set_system_gate(n,addr) /
       _set_gate(&idt[n],15,3,addr)

内核用这些宏初始化IDT表:

//在初始化的main函数中的sched_init中,有下面的代码
void sched_init(void)
{
	...
	set_system_gate(0x80,&system_call);
}

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

在include/asm/system.h 有两个宏(__asm__是一个GCC的内联汇编,后面讲解这段代码的含义)

//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)
);

首先,对这段内联汇编进行解释:

__asm__是GCC关键字asm的宏定义:

#define __asm__ asm

它用来声明一个内联汇编表达式,所以任何一个内联汇编表达式都是以他开头的,是必不可少的。

movw,movl 后面的w l 表示操作数的长度, b(byte,8-bit),w(word,16-bit),l(long,32-bit)。例如movb %al,%bl 就是移动8bit的数据,从al寄存器到bl寄存器。(这个语法上面寄存器都要加个百分号)。而%%是因为GCC在编译时会将%视为特殊字符,拥有特殊意义,%%仅仅是为了汇编的不被GCC全部转译掉。

\n\t是嵌入式汇编一种书写格式

%0 ,%1,%2,%3:0,1,2,3可以看做变量, 这些变量在程序的 ':' 之后,程序的两个 ':' 是定义输入、输出的。针对这段程序这些变量的前面都加了明确的限定,例如"i"(输入项)、"o"(输出项),剩下的"d"(edx的初始值),"a"(eax的初始值)。而0、1、2、3的概念就是指第几个变量,这里输入项、输出向、寄存器初始混合编号;相应的0("i"((short)(0x8000+(dpl<<13)+(type<<8)))));1((*((char *)(gate_addr))));2((*(4+(char *)(gate_addr))));3("d"((char *)(addr)));4("a"(0x00080000))。

经过解释,现在就能看懂这段代码了,我们针对IDT的表项,对其进行计算:

IDT表的表项

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

i:第一个输入(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

注意从pushl %edx开始的三句代码,是前面第3点提到的三个参数依次从右向左入栈。重点是call _sys_call_table(,%eax,4)这句代码,翻译过来就是call [eax*4 + _sys_call_table],4表示每个函数地址是4个字节,根据第3点,eax存的是_NR_write的值也就是4,因为_sys_call_table是sys.h中的一个int (*)()类型的数组,里面存的是所有的系统调用函数地址,所以再翻译一下就是访问sys_call_table[4]也就是sys_write函数:

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,和这里对应的。

sys_write在fs下的read_write.c:

int
sys_write (unsigned int fd, char *buf, int count)
{
  struct file *file;
  struct m_inode *inode;
...
}

到这里为止才明白调用的就是这个sys_write函数。

总结一下:linux0.11系统调用的执行过程:

(1)当执行系统调用函数时,系统调用函数会执行int 0x80中断命令,同时将系统调用号放入eax寄存器中,并将要传递给系统的参数放入ebx,ecx,edx中。中断处理程序会执行system_call()函数。

(2)  system_call()函数首先保存原段寄存器,在将调用参数压入栈中。然后将ds(在保护模式下,ds装的是段选择符)、es指向内核数据段,cs段会在中断产生时由中断门的段选择符赋值为内核代码段,并将原段选择符保存到栈中。然后调用对应的功能函数。当从功能函数返回时,内核会查看当前任务运行状态,如果不在就绪态就去执行调度程序。如果在就绪态,但其时间片用完,则也去执行调度程序。当任务继续执行时则继续对信号进行处理,然后退回到系统调用函数。

参考资料:

http://ju.outofmemory.cn/entry/374099 Linux0.11 _syscall分析

http://www.cnblogs.com/hongzg1982/articles/2120993.html  关于嵌入式汇编的解释

发布了5 篇原创文章 · 获赞 3 · 访问量 1000

猜你喜欢

转载自blog.csdn.net/guaizaiguaizai/article/details/84841973