《linux系统调用接口.ppt》由会员分享,可在线阅读,更多相关《linux系统调用接口.ppt(37页珍藏版)》请在金锄头文库上搜索。
1、 第第10章章 系统调用接口系统调用接口系统调用接口的功能系统调用接口的功能内核内核为用用户与硬件与硬件设备(例如:例如:CPU,磁磁盘,打印机等,打印机等)交互提供了一个接口。交互提供了一个接口。该接口被称接口被称为系系统调用接口。用接口。它的功能是:它的功能是:v使用户编程更加容易,把用户从学习硬件使用户编程更加容易,把用户从学习硬件设备的低级编程特性中解放出来。设备的低级编程特性中解放出来。v可以极大提高系统的安全性,因为内核接可以极大提高系统的安全性,因为内核接收用户请求之前,可以检查其合法性。收用户请求之前,可以检查其合法性。v使用系统调用接口使得程序具有良好的可使用系统调用接口使得
2、程序具有良好的可移植性。移植性。10.1 API和系和系统调用用UNIX操作系操作系统为编程程员提供了提供了应用用编程程接口接口(API)。在在API提供的提供的libc标准函数准函数库中,一部分中,一部分是用是用户态的的库函数,另一部分是系函数,另一部分是系统调用。用。库函数和系函数和系统调用的用的区区别是是:库函数是一个函数定义,说明如何获得一个给库函数是一个函数定义,说明如何获得一个给定的服务,库函数代码不属于内核。定的服务,库函数代码不属于内核。系统调用是通过软件中断系统调用是通过软件中断(int指令指令)向内核发向内核发出的一个明确服务请求,提供服务的代码属于出的一个明确服务请求,提
3、供服务的代码属于内核代码。内核代码。为了区了区别库函数和系函数和系统调用,用,libc标准准C库中,每个系中,每个系统调用都有一个封装例程用都有一个封装例程(wrapperroutine)。应用程序通用程序通过这个封装例程来引用个封装例程来引用API函数函数库中的系中的系统调用。用。用用户执行一个系行一个系统调用用时,内核通,内核通过(int0x80)软件中断或件中断或调用用门从用从用户空空间进入内入内核空核空间,这就是所就是所谓的的模式模式转换。CPU切切换到内核到内核态开始开始执行与系行与系统调用相用相对应内核函数。内核函数。执行行结束后内核将束后内核将执行行结果和控制果和控制权还给用用户
4、进程。程。图10-6给出出调用系用系统调用的示意用的示意图。调用一个系用一个系统调用示意用示意图 printf() 在应用程序中在应用程序中调用系统调用调用系统调用printf() int 0x80 在在libclibc库中库中的封装例程的封装例程用户态用户态system_call: sys_printf() ret_from_sys_call iretsys_printf() 内核态内核态系统调用处理系统调用处理机制机制系统调用服系统调用服务例程务例程图图10-6系统调用示意图系统调用示意图10.2 模式模式转换的硬件的硬件处理理在在i386中,完成系中,完成系统调用接口模式用接口模式转换的
5、的硬件是陷阱硬件是陷阱门和和调用用门,软件是件是int0x80指令或指令或调用指令用指令下面分析陷阱门技术。下面分析陷阱门技术。注:调用门技术请读者见教材注:调用门技术请读者见教材P227(因因为通常情况下,在为通常情况下,在Linux内核没有使用调内核没有使用调用门)这里不做介绍。用门)这里不做介绍。10.2.1 陷阱陷阱门模式模式转换系系统调用属于用属于软件中断件中断。i386保保护模式下的模式下的软件中断件中断使用使用陷阱陷阱门描述描述符符。使用陷阱门执行软件中断,不会影响使用陷阱门执行软件中断,不会影响硬件中断请求。硬件中断请求。Linux为系统调用设置的陷阱门向量号是为系统调用设置的
6、陷阱门向量号是128,即,即16进制的进制的“80H”。i386使用的陷阱使用的陷阱(trap)指令是指令是int$0x80。图图10-1给出陷阱门进行模式转换的示意图。给出陷阱门进行模式转换的示意图。系统调用的服务例程系统调用的服务例程 system_call()处理程序处理程序 中断描述符表中断描述符表 offset dpl 01111 selector offset GATE 0baseaddr limitdpl bseaddr baseaddr limit IDT的基址的基址 限长限长selector base addr limit中断描述符寄存器中断描述符寄存器IDTRCS(代码段寄
7、存器代码段寄存器)代码段描述符高速缓存代码段描述符高速缓存 system_call()起始地址起始地址 物理存储器物理存储器一个门描一个门描述符占述符占8B。在在GDT或或LDT中的中的代码段描代码段描述符。述符。陷阱门陷阱门 80H图图10-1陷阱门模式转换图陷阱门模式转换图10.3 系系统调用接口用接口i386保保护模式下,系模式下,系统调用接口由用接口由软件和件和硬件共同硬件共同协作完成。作完成。下面下面讲解系解系统调用用处理程序理程序system_call的工作原理。的工作原理。10.3.1 初始化系初始化系统调用用系系统启启动内核初始化期内核初始化期间调用用trap_init函函数建
8、立数建立IDT表中向量号表中向量号为128对应的表的表项(8个字个字节的陷阱的陷阱门描述符描述符)。代。代码如下:如下:set_system_gate(SYSCALL_VECTOR,&system_call);#defineSYSCALL_VECTOR0x80上面两行代码等效于下面的语句:上面两行代码等效于下面的语句:set_system_gate(0x80,&system_call);set_system_gate()初始化初始化80H开始的开始的8个字个字节的陷阱的陷阱门描述符。步描述符。步骤如下:如下:1.将内核代码段选择符将内核代码段选择符_KERNEL_CS装入装入80H陷阱门的陷阱
9、门的2、3两个字节。两个字节。2.将将system_call()可执行代码的第一条指令可执行代码的第一条指令偏移量装入偏移量装入80H陷阱门的陷阱门的0、1、6和和7共共4个字节中。个字节中。3.将将15添入添入80H陷阱门类型号字段,说明这陷阱门类型号字段,说明这是一个陷阱门。是一个陷阱门。4.将门描述符将门描述符DPL字段设置为字段设置为3,允许用户,允许用户进程调用进程调用system_call()程序。程序。10.3.2 系系统调用用执行流程行流程 在封装例程中含有在封装例程中含有int $0x80汇编指令代码汇编指令代码 int $0x80使该调用进入内核:使该调用进入内核:syst
10、em_call() 该系统调用被执行,并返回执行结果该系统调用被执行,并返回执行结果 该系统调用由标准该系统调用由标准C库的封装例程(宏)来引导库的封装例程(宏)来引导 用户发出一个系统调用的请求用户发出一个系统调用的请求 由由system_call找到指定的系统调用函数找到指定的系统调用函数图图10-4 系统调用的执行流程系统调用的执行流程10.3.3 封装例程封装例程system_call()是系是系统调用入口点。用入口点。系系统调用主要供用用主要供用户编程使用,但也可程使用,但也可以被内核以被内核态线程程调用。用。为了了简化系化系统调用的用的调用用过程,程,Linux提提供的封装例程是一
11、供的封装例程是一组预处理宏。共定理宏。共定义了了6个宏:个宏:v从从_syscall0到到_syscall5#define_syscallN(type,name,type1,arg1,type2,arg2,type3,arg3,.)typename(type1arg1,type2arg2,type3arg3,.)long_res;_asm_volatile(int$0x80:=a(_res):0(_NR_#name),b(long)(arg1),c(long)(arg2),d(long)(arg3);._syscall_return(type,_res);程序清单程序清单10-1封装例程:封装
12、例程:程序清单程序清单10-1给出封装例程的定义给出封装例程的定义:封装例程的解封装例程的解释_syscallN中的中的“N”是系是系统调用的参数个用的参数个数。数。前两个字符串指明系统调用的前两个字符串指明系统调用的返回值类型和返回值类型和名字名字;紧随其后的每一对参数指明该系统调用所需紧随其后的每一对参数指明该系统调用所需要的其他参数的类型和名字。要的其他参数的类型和名字。fork()系统调用无参数,它的封装例程是:系统调用无参数,它的封装例程是:系统调用名字系统调用名字调用参数个调用参数个数数返回类型值返回类型值_syscall0(int,fork)write()系统调用有系统调用有3个
13、参数,其封装例程个参数,其封装例程宏指令格式是:宏指令格式是:可以按照程序清可以按照程序清单10-1把把_syscall3(int,write, )宏展开成下面的代宏展开成下面的代码:_syscall3(int,write,int,fd,constchar*,buf,unsignedint,count)参数个数参数个数其中,兰色字符串是其中,兰色字符串是write()系统调用需系统调用需要的要的3对参数。对参数。intwrite(intfd,constchar*buf,unsignedintcount)long_res;_asm_volatile(int$0x80:=a(_res):0(_NR
14、_write),b(long)fd),c(long)buf),d(long)count);if(unsignedlong)_res=(unsignedlong)-125)errno=-_res;_res=-1;return(int)_res;它是系统调用号,来自它是系统调用号,来自_syscall3()的第的第2个参数个参数对这个函数个函数进行行编译,生成的,生成的汇编代代码如下:如下:write:pushl%ebx;将将ebx内容进栈内容进栈movl8(%esp),%ebx;将第一个参数放入将第一个参数放入ebxmovl12(%esp),%ecx;将第二个参数放入将第二个参数放入ecxmov
15、l16(%esp),%edx;将第三个参数放入将第三个参数放入edxmovl$4,%eax;将将_NR_write放入放入eaxint$0x80;执行系统调用执行系统调用cmpl$-125,%eax;检测返回码检测返回码jbe.L1;如无错跳转如无错跳转negl%eax;求求eax的补码的补码movl%eax,errno;将结果放入将结果放入errnomovl$-1,%eax;将将eax置为置为-1.L1:popl%ebx;从堆栈弹出从堆栈弹出ebxret;返回调用程序返回调用程序从汇编代码中可知传递给从汇编代码中可知传递给write()的参数的参数是在执行是在执行int0x80指令之前就被装
16、入到指令之前就被装入到CPU各个寄存器中。各个寄存器中。如果如果eax中的返回值为中的返回值为-1到到-125之间,将之间,将被解释为错误码。否则返回被解释为错误码。否则返回eax中的值,中的值,表明调用成功。表明调用成功。10.3.4系系统调用号与系用号与系统调用用表表system_call()函数是内核中所有系函数是内核中所有系统调用用的唯一入口点,因此内核要的唯一入口点,因此内核要为每个系每个系统调用用编一个序号,一个序号,这个序号叫做个序号叫做系系统调用号。用号。从上一从上一节的封装例程代的封装例程代码中可以看到,中可以看到,执行行int0x80之前,系之前,系统调用号已被放在用号已被
17、放在eax中。中。程序清程序清单10-2给出了部分系出了部分系统调用号。用号。#define_NR_exit1#define_NR_fork2#define_NR_read3#define_NR_write4#define_NR_open5#define_NR_close6 #define_NR_removexattr235#define_NR_lremovexattr236#define_NR_fremovexattr237程序清单程序清单10-2系统调用号系统调用号系系统调用表用表系系统调用表是指向各系用表是指向各系统调用函数指用函数指针组成的表。成的表。系系统调用号是系用号是系统调用表中
18、各表用表中各表项的相的相对偏移量。偏移量。执行行system_call()时,根据,根据eax中的系中的系统调用号,定位用号,定位该系系统调用函数在系用函数在系统调用表用表(sys_call_table)中的准确位置。中的准确位置。程序清程序清单10-3给出了部分系出了部分系统调用表。用表。 .dataENTRY(sys_call_table).longSYMBOL_NAME(sys_ni_syscall)/0-oldsetup()/systemcall.longSYMBOL_NAME(sys_exit).longSYMBOL_NAME(sys_fork).longSYMBOL_NAME(sy
19、s_read).longSYMBOL_NAME(sys_write).longSYMBOL_NAME(sys_open)/5 程序清单程序清单10-3系统调用表系统调用表10.3.5system_call 的的执行行过程程核心栈核心栈 old SS old ESP old EFLAGS old CS old EIP 031SS:ESP(从从TSS中获取核心栈堆栈指针中获取核心栈堆栈指针)栈的扩展方向栈的扩展方向SS:ESP(最新核心栈的栈指针)最新核心栈的栈指针)图图10.5使用陷阱门有特权级改变上下文切换后的堆栈使用陷阱门有特权级改变上下文切换后的堆栈 系系统执行行int$0x80指令指令进
20、行模式行模式转换时,核心核心栈的布局如的布局如图10-5所示。所示。ENTRY(system_call)pushl%eax/saveorig_eax,首先保存首先保存EAX寄存器的内容。寄存器的内容。SAVE_ALL/保存各通用寄存器的内容。保存各通用寄存器的内容。GET_CURRENT(%ebx)/获取调用进程的获取调用进程的task_struct结构的指针。结构的指针。 cmpl$(NR_syscalls),%eax/检测系统调用是否合法系统调用。检测系统调用是否合法系统调用。jaebadsys/如果该系统调用号越界则跳转到标号如果该系统调用号越界则跳转到标号badsys处。处。testb
21、$0x20,flags(%ebx) /检测是否设置了检测是否设置了PF_TRACESYS标志。标志。jnetracesys/如果设置了如果设置了PF_TRACESYS标志则跳转到标号标志则跳转到标号tracesys处。处。call*SYMBOL_NAME(sys_call_table)(,%eax,4)/这里是调用并执行该这里是调用并执行该/系统调用函数的地方系统调用函数的地方movl%eax,EAX(%esp) /savethereturnvalue,系统调用返回,系统调用返回,/返回值进栈保存。返回值进栈保存。ret_from_sys_call:函数函数system_call()的源代的源
22、代码如下:如下:函数函数system_call()是所有系是所有系统调用的入口点。用的入口点。在源代在源代码中的中的调用用语句是:句是:call*SYMBOL_NAME(sys_call_table)(0,%eax,4)该语句调用与系统调用号该语句调用与系统调用号(在在eax中中)相对应的相对应的服务例程。服务例程。系统调用表的每一个表项占系统调用表的每一个表项占4个字节,因此个字节,因此把系统调用号乘以把系统调用号乘以4,再加上系统调用表的,再加上系统调用表的基址,就找到了指向该服务例程的地址,也基址,就找到了指向该服务例程的地址,也就找到了要执行的系统调用函数。就找到了要执行的系统调用函数
23、。里边是系统调用里边是系统调用号号当服当服务例程例程执行行结束束时,可以从,可以从eax中中获得返回得返回值,并把,并把这个返回个返回值进栈保存。保存。当当进程恢复到自己的用程恢复到自己的用户态执行行时,就,就可以在可以在eax中找到中找到该系系统调用的返回用的返回值。注:由于注:由于lcall7代码很少使用,所以不在代码很少使用,所以不在PPT中讲解。中讲解。10.5 添加新系添加新系统调用用实例例通通过学学习向内核添加一个新系向内核添加一个新系统调用的用的过程,程,进一步理解系一步理解系统调用的基本原理。用的基本原理。实例:新系统调用的功能是获取系统当前实例:新系统调用的功能是获取系统当前
24、系统时间并显示在屏幕上。步骤如下:系统时间并显示在屏幕上。步骤如下:1.编写新系统调用源程序,新系统调用的名编写新系统调用源程序,新系统调用的名称为:称为:2.intptimeofday(structtimeval*tv,3.structtimezone*tz);4.新系统调用的源程序如下:新系统调用的源程序如下:#includeexternstructtimezonesys_tz;asmlinkageintsys_ptimeofday(structtimeval*tv,structtimezone*tz)if(tv)structtimevalktv;do_gettimeofday(&ktv)
25、;if(copy_to_user(tv,&ktv,sizeof(ktv)return-EFAULT;printk(Date:%09d%09d,ktv.tv_sec,ktv.tv_usec);if(tz)if(copy_to_user(tz,&sys_tz,sizeof(sys_tz)return-EFAULT;printk(Thetimeis:%09d%09d,sys_tz.tz_minuteswest,sys_tz.tz_dsttime);return0;ptimeofday.c程序程序 2.连接新的系接新的系统调用,将用,将ptimeofday.c代代码添加到添加到/usr/linux/k
26、ernel/sys.c文件文件中,同中,同时需要重新需要重新编辑2个文件个文件:在文件在文件/usr/src/linux/include/asm/unistd.h中中为新系统调用分配一个系统调用号为为新系统调用分配一个系统调用号为239。即在表即在表10-2中的最后再添加一行:中的最后再添加一行:#define_NR_ptimeofday239在文件在文件/usr/src/linux/arch/asm/kernel/entry.S中,即在表中,即在表10-3中的最后再添加一行:中的最后再添加一行:.longSYMBOL_NAME(sys_ptimeofday)3.编译新的新的Linux内核。内
27、核。编译内核要以超内核要以超级用用户身份登身份登录,命令行如下:,命令行如下:#makexconfig#makedep#makebzImage编译结束后,系统将产生一个新压缩的内编译结束后,系统将产生一个新压缩的内核映像文件:核映像文件:#usr/src/linux/arch/boot/bzImage为了安装新系统内核,应该将上面产生的为了安装新系统内核,应该将上面产生的新内核映像文件拷贝到新内核映像文件拷贝到/boot/目录下:目录下:#cpusr/src/linux/arch/boot/bzImage/boot/bzImage-new4.启启动新的内核操作系新的内核操作系统:如果你的机器使
28、用如果你的机器使用grub加载加载OS,可参照可参照教材表教材表8-2内容修改内容修改/etc目录下的目录下的grub.conf文件。文件。如果使用如果使用lilo加载加载OS,需要在已有的系统需要在已有的系统中修改中修改/etc/lilo.conf文件。可参照表文件。可参照表10-1原有的原有的/etc/lilo.conf文件文件修改后的修改后的/etc/lilo.conf文件文件promptprompttimeout=50timeout=50default=linuxdefault=linuxboot=/dev/had7boot=/dev/had7map=/boot/mapmap=/boo
29、t/mapinstall=/boot/boot.binstall=/boot/boot.bmassage=/boot/messagemassage=/boot/messagelba32lba32image=/boot/vmlinuz-2.4.18image=/boot/bzImage-newlabel=linuxlabel=linux-newinitrd=/boot/initrd-2.4.18.imgimage=/boot/vmlinuz-2.4.18read-onlylabel=linuxroot=/dev/hdb8initrd=/boot/initrd-2.4.18.imgother=/d
30、ev/hda1read_onlyoptionalroot=/dev/hdb8label=DOSother=/dev/hda1optionallabel=DOS表表10.1修改前后修改前后lilo.conf文件的内容文件的内容 5.5.尝试使用新的系使用新的系统调用。用。因为在已有标准因为在已有标准C库中没有新系统调用的封装例程,库中没有新系统调用的封装例程,所以要由自己来完成。所以要由自己来完成。下面是为新系统调用编写的封装例程下面是为新系统调用编写的封装例程(ptime.c)。#include#include#define_NR_ptimeofday239intptimeofday(stru
31、cttimeval*tv,structtimezone*tz);_syscall2(int,ptimeofday,structtimeval*,tv,structtimezone*,tz)main()structtimevaltv;structtimezonetz;ptimeofday(&tv,&tz);printf(sec=%ld,sec=%ld,WG=%d,corr=%dn,tv.tv_sec,tv.tv_usec,tz.tz_minuteswest,tz.tz_dsttime);printf(time:%sn,ctime(&(tv.tv_sec);ptime.c#gccoptimepti
32、me.c#./ptimeDate:1039330329301970Thetimeis:-4800sec=1039330329sec=301970WG=-480corr=0time:SunDec814:52:092002$6.运行新的系统调用。执行结果如下:运行新的系统调用。执行结果如下:10.6 小小结本章重点本章重点讨论和分析了内核向用和分析了内核向用户程序提程序提供的供的编程接口:程接口:系系统调用接口用接口。为了加深理解,本章最后了加深理解,本章最后给出了一个出了一个编写写新系新系统调用的用的编程程实例。例。本章的基本原理:本章的基本原理: 系统调用的基本原理系统调用的基本原理本章的关键技术:本章的关键技术:使用陷阱门的调用技术使用陷阱门的调用技术使用调用门的调用技术使用调用门的调用技术本章的基本概念:本章的基本概念:调用门调用门 陷阱门陷阱门 系统调用接口系统调用接口 封装例程封装例程