您好,欢迎访问三七文档
当前位置:首页 > 行业资料 > 冶金工业 > Linux系统调用讲义
Linux系统调用讲义Linux下系统调用的实现Linux中的系统调用Linux中怎样编译和定制内核Linux下系统调用的实现1.Unix/Linux操作系统的体系结构及系统调用介绍A.什么是操作系统和系统调用操作系统是从硬件抽象出来的虚拟机,在该虚拟机上用户可以运行应用程序。它负责直接与硬件交互,向用户程序提供公共服务,并使它们同硬件特性隔离。因为程序不应该依赖于下层的硬件,只有这样应用程序才能很方便的在各种不同的Unix系统之间移动。系统调用是Unix/Linux操作系统向用户程序提供支持的接口,通过这些接口应用程序向操作系统请求服务,控制转向操作系统,而操作系统在完成服务后,将控制和结果返回给用户程序。B.Unix/Linux系统体系结构一个Unix/Linux系统分为三个层次:用户、核心以及硬件。其中系统调用是用户程序与核心间的边界,通过系统调用进程可由用户模式转入核心模式,在核心模式下完成一定的服务请求后在返回用户模式。系统调用接口看起来和C程序中的普通函数调用很相似,它们通常是通过库把这些函数调用映射成进入操作系统所需要的原语。这些操作原语只是提供一个基本功能集,而通过库对这些操作的引用和封装,可以形成丰富而且强大的系统调用库。这里体现了机制与策略相分离的编程思想——系统调用只是提供访问核心的基本机制,而策略是通过系统调用库来体现。例:execv,execl,execlv,opendir,readdir...C.Unix/Linux运行模式,地址空间和上下文运行模式(运行态):一种计算机硬件要运行Unix/Linux系统,至少需要提供两种运行模式:高优先级的核心模式和低优先级的用户模式。实际上许多计算机都有两种以上的执行模式。如:intel80x86体系结构就有四层执行特权,内层特权最高。Unix只需要两层即可以了:核心运行在高优先级,称之为核心态;其它外围软件包括shell,编辑程序,Xwindow等等都是在低优先级运行,称之为用户态。之所以采取不同的执行模式主要原因时为了保护,由于用户进程在较低的特权级上运行,它们将不能意外或故意的破坏其它进程或内核。程序造成的破坏会被局部化而不影响系统中其它活动或者进程。当用户进程需要完成特权模式下才能完成的某些功能时,必须严格按照系统调用提供接口才能进入特权模式,然后执行调用所提供的有限功能。每种运行态都应该有自己的堆栈。在Linux中,分为用户栈和核心栈。用户栈包括在用户态执行时函数调用的参数、局部变量和其它数据结构。有些系统中专门为全局中断处理提供了中断栈,但是x86中并没有中断栈,中断在当前进程的核心栈中处理。地址空间:采用特权模式进行保护的根本目的是对地址空间的保护,用户进程不应该能够访问所有的地址空间:只有通过系统调用这种受严格限制的接口,进程才能进入核心态并访问到受保护的那一部分地址空间的数据,这一部分通常是留给操作系统使用。另外,进程与进程之间的地址空间也不应该随便互访。这样,就需要提供一种机制来在一片物理内存上实现同一进程不同地址空间上的保护,以及不同进程之间地址空间的保护。Unix/Linux中通过虚存管理机制很好的实现了这种保护,在虚存系统中,进程所使用的地址不直接对应物理的存储单元。每个进程都有自己的虚存空间,每个进程有自己的虚拟地址空间,对虚拟地址的引用通过地址转换机制转换成为物理地址的引用。正因为所有进程共享物理内存资源,所以必须通过一定的方法来保护这种共享资源,通过虚存系统很好的实现了这种保护:每个进程的地址空间通过地址转换机制映射到不同的物理存储页面上,这样就保证了进程只能访问自己的地址空间所对应的页面而不能访问或修改其它进程的地址空间对应的页面。虚拟地址空间分为两个部分:用户空间和系统空间。在用户模式下只能访问用户空间而在核心模式下可以访问系统空间和用户空间。系统空间在每个进程的虚拟地址空间中都是固定的,而且由于系统中只有一个内核实例在运行,因此所有进程都映射到单一内核地址空间。内核中维护全局数据结构和每个进程的一些对象信息,后者包括的信息使得内核可以访问任何进程的地址空间。通过地址转换机制进程可以直接访问当前进程的地址空间(通过MMU),而通过一些特殊的方法也可以访问到其它进程的地址空间。尽管所有进程都共享内核,但是系统空间是受保护的,进程在用户态无法访问。进程如果需要访问内核,则必须通过系统调用接口。进程调用一个系统调用时,通过执行一组特殊的指令(这个指令是与平台相关的,每种系统都提供了专门的trap命令,基于x86的Linux中是使用int指令)使系统进入内核态,并将控制权交给内核,由内核替代进程完成操作。当系统调用完成后,内核执行另一组特征指令将系统返回到用户态,控制权返回给进程。上下文:一个进程的上下文可以分为三个部分:用户级上下文、寄存器上下文以及系统级上下文。用户级上下文:正文、数据、用户栈以及共享存储区;寄存器上下文:程序寄存器(IP),即CPU将执行的下条指令地址,处理机状态寄存器(EFLAGS),栈指针,通用寄存器;系统级上下文:进程表项(proc结构)和U区,在Linux中这两个部分被合成task_struct,区表及页表(mm_struct,vm_area_struct,pgd,pmd,pte等),核心栈等。全部的上下文信息组成了一个进程的运行环境。当发生进程调度时,必须对全部上下文信息进行切换,新调度的进程才能运行。进程就是上下文的集合的一个抽象概念。D.系统调用的功能和分类操作系统核心在运行期间的活动可以分为两个部分:上半部分(tophalf)和下半部分(bottomhalf),其中上半部分为应用程序提供系统调用或自陷的服务,是同步服务,由当前执行的进程引起,在当前进程上下文中执行并允许直接访问当前进程的数据结构;而下半部分则是由处理硬件中断的子程序,是属于异步活动,这些子程序的调用和执行与当前进程无关。上半部分允许被阻塞,因为这样阻塞的是当前进程;下半部分不允许被阻塞,因为阻塞下半部分会引起阻塞一个无辜的进程甚至整个核心。系统调用可以看作是一个所有Unix/Linux进程共享的子程序库,但是它是在特权方式下运行,可以存取核心数据结构和它所支持的用户级数据。系统调用的主要功能是使用户可以使用操作系统提供的有关设备管理、文件系统、进程控制进程通讯以及存储管理方面的功能,而不必要了解操作系统的内部结构和有关硬件的细节问题,从而减轻用户负担和保护系统以及提高资源利用率。系统调用分为两个部分:与文件子系统交互的和进程子系统交互的两个部分。其中和文件子系统交互的部分进一步由可以包括与设备文件的交互和与普通文件的交互的系统调用(open,close,ioctl,create,unlink,...);与进程相关的系统调用又包括进程控制系统调用(fork,exit,getpid,...),进程间通讯,存储管理,进程调度等方面的系统调用。2.Linux下系统调用的实现(以i386为例说明)A.在Linux中系统调用是怎样陷入核心的?系统调用在使用时和一般的函数调用很相似,但是二者是有本质性区别的,函数调用不能引起从用户态到核心态的转换,而正如前面提到的,系统调用需要有一个状态转换。在每种平台上,都有特定的指令可以使进程的执行由用户态转换为核心态,这种指令称作操作系统陷入(operatingsystemtrap)。进程通过执行陷入指令后,便可以在核心态运行系统调用代码。在Linux中是通过软中断来实现这种陷入的,在x86平台上,这条指令是int0x80。也就是说在Linux中,系统调用的接口是一个中断处理函数的特例。具体怎样通过中断处理函数来实现系统调用的入口将在后面详细介绍。这样,就需要在系统启动时,对INT0x80进行一定的初始化,下面将描述其过程:1.使用汇编子程序setup_idt(linux/arch/i386/kernel/head.S)初始化idt表(中断描述符表),这时所有的入口函数偏移地址都被设为ignore_int(setup_idt:leaignore_int,%edxmovl$(__KERNEL_CS16),%eaxmovw%dx,%ax/*selector=0x0010=cs*/movw$0x8E00,%dx/*interruptgate-dpl=0,present*/leaSYMBOL_NAME(idt_table),%edimov$256,%ecxrp_sidt:movl%eax,(%edi)movl%edx,4(%edi)addl$8,%edidec%ecxjnerp_sidtretselector=__KERNEL_CS,DPL=0,TYPE=E,P=1);2.Start_kernel()(linux/init/main.c)调用trap_init()(linux/arch/i386/kernel/trap.c)函数设置中断描述符表。在该函数里,实际上是通过调用函数set_system_gate(SYSCALL_VECTOR,&system_call)来完成该项的设置的。其中的SYSCALL_VECTOR就是0x80,而system_call则是一个汇编子函数,它即是中断0x80的处理函数,主要完成两项工作:a.寄存器上下文的保存;b.跳转到系统调用处理函数。在后面会详细介绍这些内容。(补充说明:门描述符set_system_gate()是在linux/arch/i386/kernel/trap.S中定义的,在该文件中还定义了几个类似的函数set_intr_gate(),set_trap_gate,set_call_gate()。这些函数都调用了同一个汇编子函数__set_gate(),该函数的作用是设置门描述符。IDT中的每一项都是一个门描述符。#define_set_gate(gate_addr,type,dpl,addr)set_gate(idt_table+n,15,3,addr);门描述符的作用是用于控制转移,其中会包括选择子,这里总是为__KERNEL_CS(指向GDT中的一项段描述符)、入口函数偏移地址、门访问特权级(DPL)以及类型标识(TYPE)。Set_system_gate的DPL为3,表示从特权级3(最低特权级)也可以访问该门,type为15,表示为386中断门。)B.与系统调用相关的数据结构1.系统调用处理函数的函数名的约定函数名都以“sys_”开头,后面跟该系统调用的名字。例如,系统调用fork()的处理函数名是sys_fork()。asmlinkageintsys_fork(structpt_regsregs);(补充关于asmlinkage的说明)2.系统调用号(SystemCallNumber)核心中为每个系统调用定义了一个唯一的编号,这个编号的定义在linux/include/asm/unistd.h中,编号的定义方式如下所示:#define__NR_exit1#define__NR_fork2#define__NR_read3#define__NR_write4......用户在调用一个系统调用时,系统调用号号作为参数传递给中断0x80,而该标号实际上是后面将要提到的系统调用表(sys_call_table)的下标,通过该值可以找到相映系统调用的处理函数地址。3.系统调用表系统调用表的定义方式如下:(linux/arch/i386/kernel/entry.S)ENTRY(sys_call_table).longSYMBOL_NAME(sys_ni_syscall).longSYMBOL_NAME(sys_exit).longSYMBOL_NAME(sys_fork).longSYMBOL_NAME(sys_read).longSYMBOL_NAME(sys_write)......系统调用表记录了各个系统调用处理函数的入口地址,以系统调用号为偏移量很容易的能够在该表中找到对应处理函数地址。在linux/include/linux/sys.
本文标题:Linux系统调用讲义
链接地址:https://www.777doc.com/doc-3969031 .html