我是如何学习写一个操做系统(四):操做系统之系统调用

前言

最近有点事情,立刻要开学了,因此学习的脚步就慢下来了。这一篇主要是来讲操做系统的系统调用的,像C语言的printf深刻到内部就是一个有关屏幕输出的系统调用linux

什么是系统调用

以前提过操做系统是对硬件的抽象,也是软硬件之间的一层。以前好比若是咱们想要在屏幕上输出一些字符,就须要一些指令操做,而后把数据放到显存上。可是在有了操做系统后,就不须要这样作,也不能这样作了。这时候只要操做系统提供一个接口来让咱们完成这个任务git

由操做系统实现提供的全部系统调用所构成的集合即程序接口或应用编程接口(Application Programming Interface,API)。是应用程序同系统之间的接口。github

系统调用的实现

在硬件设计上,经过区份内核态和用户态来把内核程序和用户程序隔离开编程

CS寄存器最低的两位为0便是内核态,为3是用户态数组

可是系统调用的代码是处在内核态的,因此就须要提供一种方法来可以让用户程序进入内核态来实现系统调用缓存

在X86里,INT指令就是硬件用来提供由用户态进入内核态的方法,因此系统调用的实现就能够变为:函数

  • 由用户程序发起一个INT指令,指明要调用的服务
  • 在操做系统里写出相应的中断处理
  • 由操做系统根据用户指明要调用的服务取执行相应的代码

内联汇编

稍微说一下C里的内联汇编,以避免以后忘记。学习

gcc的内联汇编通常都是这个格式spa

asm ( 汇编指令
    : 输出操做数		// 非必需
    : 输入操做数		// 非必需
    : 其余被污染的寄存器	// 非必需
    );
复制代码
  1. 第一部分就是汇编指令操作系统

  2. 第二部分是输出操做数,都是 "=?"(var) 的形式, var能够是任意内存变量(输出结果会存到这个变量中), ?通常是下面这些标识符 (表示内联汇编中用什么来代理这个操做数):

    a,b,c,d,S,D 分别表明 eax,ebx,ecx,edx,esi,edi 寄存器 r 上面的寄存器的任意一个(谁闲着就用谁) m 内存 i 当即数(常量,只用于输入操做数) g 寄存器、内存、当即数 都行 在汇编中用%序号来表明这些输入/输出操做数,序号从0开始。为了与操做数区分开来,寄存器用两个%引出,如:%%eax

  3. 第三部分是是输入操做数,都是 "?"(var) 的形式, ? 除了能够是上面的那些标识符,还能够是输出操做数的序号,表示用 var 来初始化该输出操做数,上面的程序中 %0 和 %1 就是一个东西,初始化为 1(a的值)。

  4. 第四部分标出那些在汇编代码中修改了的、 又没有在输入/输出列表中列出的寄存器, 这样 gcc 就不会擅自使用这些"危险的"寄存器。 还能够用 "memory" 表示在内联汇编中修改了内存, 以前缓存在寄存器中的内存变量须要从新读取。

    参考连接

Linux0.11里对系统调用的代码实现

设置中断

  • 首先须要对IDT设置中断调用的处理函数
#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);
复制代码

实现中断函数

  • sys_call_table[]是一个指针数组,定义在include/linux/sys.h中,该指针数组中设置了全部72个系统调用C处理函数地址。
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
复制代码

提供接口

  • 在linux向应用程序提供系统调用接口write
  • _syscall3的本质上是一个宏
_syscall3(int,write,int,fd,const char *,buf,off_t,count)
复制代码
#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; \ }
复制代码

小结

这样对于一个系统调用就会变成

printf 用户调用

int 0x80 库函数的实现


进入内核


system_call 中断调用

sys_ 系统调用

相关文章
相关标签/搜索