linux内核剖析(六)Linux系统调⽤详解(实现机制分析)
本⽂介绍了系统调⽤的⼀些实现细节。⾸先分析了系统调⽤的意义,它们与库函数和应⽤程序接⼝(API)有怎样的关系。然后,我们考察了Linux内核如何实现系统调⽤,以及执⾏系统调⽤的连锁反应:陷⼊内核,传递系统调⽤号和参数,执⾏正确的系统调⽤函数,并把返回值带回⽤户空间。最后讨论了如何增加系统调⽤,并提供了从⽤户空间访问系统调⽤的简单例⼦。
参考
系统调⽤概述
计算机系统的各种硬件资源是有限的,在现代多任务操作系统上同时运⾏的多个进程都需要访问这些资源,为了更好的管理这些资源进程是不允许直接操作的,所有对这些资源的访问都必须有操作系统控制。也就是说操作系统是使⽤这些资源的唯⼀⼊⼝,⽽这个⼊⼝就是操作系统提供的系统调⽤(System Call)。在linux 中系统调⽤是⽤户空间访问内核的唯⼀⼿段,除异常和陷⼊外,他们是内核唯⼀的合法⼊⼝。
⼀般情况下应⽤程序通过应⽤编程接⼝API,⽽不是直接通过系统调⽤来编程。在Unix世界,最流⾏的API是基于POSIX标准的。
操作系统⼀般是通过中断从⽤户态切换到内核态。中断就是⼀个硬件或软件请求,要求CPU暂停当前的⼯作,去处理更重要的事情。⽐如,在x86机器上可以通过int指令进⾏软件中断,⽽在磁盘完成读写操作后会向CPU发起硬件中断。
中断有两个重要的属性,中断号和中断处理程序。中断号⽤来标识不同的中断,不同的中断具有不同的中断处理程序。在操作系统内核中维护着⼀个中断向量表(Interrupt Vector Table),这个数组存储了所有中断处理程序的地址,⽽中断号就是相应中断在中断向量表中的偏移量。
⼀般地,系统调⽤都是通过软件中断实现的,x86系统上的软件中断由int $0x80指令产⽣,⽽128号异常处理程序就是系统调⽤处理程序system_call(),它与硬件体系有关,在entry.S中⽤汇编写。接下来就来看⼀下Linux下系统调⽤具体的实现过程。
为什么需要系统调⽤
linux内核中设置了⼀组⽤于实现系统功能的⼦程序,称为系统调⽤。系统调⽤和普通库函数调⽤⾮常相似,只是系统调⽤由操作系统核⼼提供,运⾏于内核态,⽽普通的函数调⽤由函数库或⽤户⾃⼰提供,运⾏于⽤户态。
⼀般的,进程是不能访问内核的。它不能访问内核所占内存空间也不能调⽤内核函数。CPU硬件决定了这些(这就是为什么它被称作“保护模式”(详细参见))。
为了和⽤户空间上运⾏的进程进⾏交互,内核提供了⼀组接⼝。透过该接⼝,应⽤程序可以访问硬件设备和其他操作系统资源。这组接⼝在应⽤程序和内核之间扮演了使者的⾓⾊,应⽤程序发送各种请求,⽽内核负责满⾜这些请求(或者让应⽤程序暂时搁置)。实际上提供这组接⼝主要是为了保证系统稳定可靠,避免应⽤程序肆意妄⾏,惹出⼤⿇烦。
系统调⽤在⽤户空间进程和硬件设备之间添加了⼀个中间层。该层主要作⽤有三个:
它为⽤户空间提供了⼀种统⼀的硬件的抽象接⼝。⽐如当需要读些⽂件的时候,应⽤程序就可以不去管磁盘类型和介质,甚⾄不⽤去管⽂件所在的⽂件系统到底是哪种类型。
系统调⽤保证了系统的稳定和安全。作为硬件设备和应⽤程序之间的中间⼈,内核可以基于权限和其他⼀些规则对需要进⾏的访问进⾏裁决。举例来说,这样可以避免应⽤程序不正确地使⽤硬件设备,窃取其他进程的资源,或做出其他什么危害系统的事情。
每个进程都运⾏在虚拟系统中,⽽在⽤户空间和系统的其余部分提供这样⼀层公共接⼝,也是出于这种考虑。如果应⽤程序可以随意访问硬件⽽内核⼜对此⼀⽆所知的话,⼏乎就没法实现多任务和虚拟内存,当然也不可能实现良好的稳定性和安全性。在Linux中,系统调⽤是⽤户空间访问内核的惟⼀⼿段;除异常和中断外,它们是内核惟⼀的合法⼊⼝。
API/POSIX/C库的区别与联系
⼀般情况下,应⽤程序通过应⽤编程接⼝(API)⽽不是直接通过系统调⽤来编程。这点很重要,因为应⽤程序使⽤的这种编程接⼝实际上并不需要和内核提供的系统调⽤⼀⼀对应。
⼀个API定义了⼀组应⽤程序使⽤的编程接⼝。它们可以实现成⼀个系统调⽤,也可以通过调⽤多个系统调⽤来实现,⽽完全不使⽤任何系统调⽤也不存在问题。实际上,API可以在各种不同的操作系统上实现,给应⽤程序提供完全相同的接⼝,⽽它们本⾝在这些系统上的实现却可能迥异。
在Unix世界中,最流⾏的应⽤编程接⼝是基于POSIX标准的,其⽬标是提供⼀套⼤体上基于Unix的可移植操作系统标准。POSIX是说明API和系统调⽤之间关系的⼀个极好例⼦。在⼤多数Unix系统上,根据POSIX⽽定义的API函数和系统调⽤之间有着直接关系。
Linux的系统调⽤像⼤多数Unix系统⼀样,作为C库的⼀部分提供如下图所⽰。C库实现了 Unix系统的主要API,包括标准C库函数和系统调⽤。所有的C程序都可以使⽤C库,⽽由于C语⾔本⾝的特点,其他语⾔也可以很⽅便地把它们封装起来使⽤。
从程序员的⾓度看,系统调⽤⽆关紧要,他们只需要跟API打交道就可以了。相反,内核只跟系统调⽤打交道;库函数及应⽤程序是怎么使⽤系统调⽤不是内核所关⼼的。
关于Unix的界⾯设计有⼀句通⽤的格⾔“提供机制⽽不是策略”。换句话说,Unix的系统调⽤抽象出了⽤
于完成某种确定⽬的的函数。⾄⼲这些函数怎么⽤完全不需要内核去关⼼。区别对待机制(mechanism)和策略(policy)是Unix设计中的⼀⼤亮点。⼤部分的编程问题都可以被切割成两个部分:“需要提供什么功能”(机制)和“怎样实现这些功能”(策略)。
区别
api是函数的定义,规定了这个函数的功能,跟内核⽆直接关系。⽽系统调⽤是通过中断向内核发请求,实现内核提供的某些服务。
联系
⼀个api可能会需要⼀个或多个系统调⽤来完成特定功能。通俗点说就是如果这个api需要跟内核打交道就需要系统调⽤,否则不需要。
程序员调⽤的是API(API函数),然后通过与系统调⽤共同完成函数的功能。
因此,API是⼀个提供给应⽤程序的接⼝,⼀组函数,是与程序员进⾏直接交互的。
系统调⽤则不与程序员进⾏交互的,它根据API函数,通过⼀个软中断机制向内核提交请求,以获取内核服务的接⼝。
并不是所有的API函数都⼀⼀对应⼀个系统调⽤,有时,⼀个API函数会需要⼏个系统调⽤来共同完成函数的功能,甚⾄还有⼀些API函数不需要调⽤相应的系统调⽤(因此它所完成的不是内核提供的服务)
系统调⽤的实现原理
基本机制
前⽂已经提到了Linux下的系统调⽤是通过0x80实现的,但是我们知道操作系统会有多个系统调⽤(Linux下有319个系统调⽤),⽽对于同⼀个中断号是如何处理多个不同的系统调⽤的?最简单的⽅式是对于不同的系统调⽤采⽤不同的中断号,但是中断号明显是⼀种稀缺资源,Linux显然不会这么做;还有⼀个问题就是系统调⽤是需要提供参数,并且具有返回值的,这些参数⼜是怎么传递的?也就是说,对于系统调⽤我们要搞清楚两点:
1. 系统调⽤的函数名称转换。
2. 系统调⽤的参数传递。
⾸先看第⼀个问题。实际上,Linux中每个系统调⽤都有相应的系统调⽤号作为唯⼀的标识,内核维护⼀张系统调⽤表,sys_call_table,表中的元素是系统调⽤函数的起始地址,⽽系统调⽤号就是系统调
⽤在调⽤表的偏移量。在x86上,系统调⽤号是通过eax寄存器传递给内核的。⽐如fork()的实现:
⽤户空间的程序⽆法直接执⾏内核代码。它们不能直接调⽤内核空间中的函数,因为内核驻留在受保护的地址空间上。如果进程可以直接在内核的地址空间上读写的话,系统安全就会失去控制。所以,应⽤程序应该以某种⽅式通知系统,告诉内核⾃⼰需要执⾏⼀个系统调⽤,希望系统切换到内核态,这样内核就可以代表应⽤程序来执⾏该系统调⽤了。
通知内核的机制是靠软件中断实现的。⾸先,⽤户程序为系统调⽤设置参数。其中⼀个参数是系统调⽤编号。参数设置完成后,程序执⾏“系统调⽤”指令。x86系统上的软中断由int产⽣。这个指令会导致⼀个异常:产⽣⼀个事件,这个事件会致使处理器切换到内核态并跳转到⼀个,并开始执⾏那⾥的异常处理程序。此时的异常处理程序实际上就是系统调⽤处理程序。它与硬件体系结构紧密相关。
新地址的指令会保存程序的状态,计算出应该调⽤哪个系统调⽤,调⽤内核中实现那个系统调⽤的函数,恢复⽤户程序状态,然后将控制权返还给⽤户程序。系统调⽤是设备驱动程序中定义的函数最终被调⽤的⼀种⽅式。
从系统分析的⾓度,linux的系统调⽤涉及4个⽅⾯的问题。
响应函数sys_xxx
响应函数名以“sys_”开头,后跟该系统调⽤的名字。
例如
系统调⽤fork()的响应函数是sys_fork()(见Kernel/fork.c),
exit()的响应函数是sys_exit()(见kernel/fork.)。
系统调⽤表与系统调⽤号-=>数组与下标
⽂件include/asm/unisted.h为每个系统调⽤规定了唯⼀的编号。
在我们系统中/usr/include/asm/unistd_32.h,可以通过find / -name unistd_32.h -print查)
⽽内核中的头⽂件路径不同的内核版本以及不同的发⾏版,⽂件的存储结构可能有所区别
假设⽤name表⽰系统调⽤的名称,那么系统调⽤号与系统调⽤响应函数的关系是:以系统调⽤号_NR_name作为下标,可出系统调⽤
表sys_call_table(见arch/i386/kernel/entry.S)中对应表项的内容,它正好是该系统调⽤的响应函数sys_name的⼊⼝地址。
linux内核设计与实现 pdf系统调⽤表sys_call_table记录了各sys_name函数在表中的位置,共190项。有了这张表,就很容易根据特定系统调⽤
在表中的偏移量,到对应的系统调⽤响应函数的⼊⼝地址。系统调⽤表共256项,余下的项是可供⽤户⾃⼰添加的系统调⽤空间。
在Linux中,每个系统调⽤被赋予⼀个系统调⽤号。这样,通过这个独⼀⽆⼆的号就可以关联系统调⽤。当⽤户空间的进程执⾏⼀个系统调⽤的时候,这个系统调⽤号就被⽤来指明到底是要执⾏哪个系统调⽤。进程不会提及系统调⽤的名称。
系统调⽤号相当关键,⼀旦分配就不能再有任何变更,否则编译好的应⽤程序就会崩溃。Linux有⼀个“未实现”系统调⽤sys_ni_syscall(),它除了返回⼀ENOSYS外不做任何其他⼯作,这个错误号就是专门针对⽆效的系统调⽤⽽设的。
因为所有的系统调⽤陷⼊内核的⽅式都⼀样,所以仅仅是陷⼊内核空间是不够的。因此必须把系统调
⽤号⼀并传给内核。在x86上,系统调⽤号是通过eax寄存器传递给内核的。在陷⼈内核之前,⽤户空间就把相应系统调⽤所对应的号放⼊eax中了。这样系统调⽤处理程序⼀旦运⾏,就可以从eax中得到数据。其他体系结构上的实现也都类似。
内核记录了系统调⽤表中的所有已注册过的系统调⽤的列表,存储在sys_call_table中。它与体系结构有关,⼀般在entry.s中定义。这个表中为每⼀个有效的系统调⽤指定了惟⼀的系统调⽤号。sys_call_table是⼀张由指向实现各种系统调⽤的内核函数的函数指针组成的表:
system_call()函数通过将给定的系统调⽤号与NR_syscalls做⽐较来检查其有效性。如果它⼤于或者等于NR syscalls,该函数就返回⼀ENOSYS。否则,就执⾏相应的系统调⽤。
call *sys_ call-table(,%eax, 4)
由于系统调⽤表中的表项是以32位(4字节)类型存放的,所以内核需要将给定的系统调⽤号乘以4,然后⽤所得的结果在该表中查询其位置
进程的系统调⽤命令转换为INT 0x80中断的过程
宏定义_syscallN()见include/asm/unisted.h)⽤于系统调⽤的格式转换和参数的传递。N取0~5之间的整数。
参数个数为N的系统调⽤由_syscallN()负责格式转换和参数传递。系统调⽤号放⼊EAX寄存器,启动INT 0x80后,规定返回值送EAX寄存器。
系统调⽤功能模块的初始化
对系统调⽤的初始化也就是对INT 0x80的初始化。
系统启动时,汇编⼦程序setup_idt(见arch/i386/kernel/head.S)准备了1张256项的idt表,由start_kernel()(见init/main.c),trap_init()(见arch/i386/kernel/traps.c)调⽤的C语⾔宏定
义set_system_gate(0x80,&system_call)(见include/asm/system.h)设置0x80号软中断的服务程序为 system_call(见arch/i386/kernel/entry.S), system.call就是所有系统调⽤的总⼊⼝。
内核如何为各种系统调⽤服务
当进程需要进⾏系统调⽤时,必须以C语⾔函数的形式写⼀句系统调⽤命令。该命令如果已在某个头⽂件中由相应的_syscallN()展开,则⽤户程序必须包含该⽂件。当进程执⾏到⽤户程序的系统调⽤命令时,实际上执⾏了由宏命令_syscallN()展开的函数。系统调⽤的参数由各通⽤寄存器传递,然后执⾏INT 0x80,以内核态进⼊⼊⼝地址system_call。
ret_from_sys_call
以ret_from_sys_call⼊⼝的汇编程序段在linux进程管理中起到了⼗分重要的作⽤。
所有系统调⽤结束前以及⼤部分中断服务返回前,都会跳转⾄此处⼊⼝地址。该段程序不仅仅为系统调⽤服务,它还处理中断嵌套、CPU调度、信号等事务。
内核如何为系统调⽤的参数传递参数
参数传递
除了系统调⽤号以外,⼤部分系统调⽤都还需要⼀些外部的参数输⼈。所以,在发⽣异常的时候,应该把这些参数从⽤户空间传给内核。最简单的办法就是像传递系统调⽤号⼀样把这些参数也存放在寄存器⾥。在x86系统上,ebx, ecx, edx, esi和edi按照顺序存放前五个参数。需要六个或六个以上参数的情况不多见,此时,应该⽤⼀个单独的寄存器存放指向所有这些参数在⽤户空间地址的指针。
给⽤户空间的返回值也通过寄存器传递。在x86系统上,它存放在eax寄存器中。接下来许多关于系统调⽤处理程序的描述都是针对x86版本的。但不⽤担⼼,所有体系结构的实现都很类似。
参数验证
系统调⽤必须仔细检查它们所有的参数是否合法有效。举例来说,与⽂件I/O相关的系统调⽤必须检查
⽂件描述符是否有效。与进程相关的函数必须检查提供的PID 是否有效。必须检查每个参数,保证它们不但合法有效,⽽且正确。
最重要的⼀种检查就是检查⽤户提供的指针是否有效。试想,如果⼀个进程可以给内核传递指针⽽⼜⽆须被检查,那么它就可以给出⼀个它根本就没有访问权限的指针,哄骗内核去为它拷贝本不允许它访问的数据,如原本属于其他进程的数据。在接收⼀个⽤户空间的指针之前,内核必须保证:指针指向的内存区域属于⽤户空间。进程决不能哄骗内核去读内核空间的数据。
指针指向的内存区域在进程的地址空间⾥。进程决不能哄骗内核去读其他进程的数据。
如果是读,该内存应被标记为可读。如果是写,该内存应被标记为可写。进程决不能绕过内存访问限制。
内核提供了两个⽅法来完成必须的检查和内核空间与⽤户空间之间数据的来回拷贝。注意,内核⽆论何时都不能轻率地接受来⾃⽤户空间的指针!这两个⽅法中必须有⼀个被调⽤。为了向⽤户空间写⼊数据,内核提供了copy_to_user(),它需要三个参数。第⼀个参数是进程空间中的⽬的内存地址。第⼆个是内核空间内的源地址。最后⼀个参数是需要拷贝的数据长度(字节数)。
为了从⽤户空间读取数据,内核提供了copy_from_ user(),它和copy-to-User()相似。该函数把第⼆个参数指定的位置上的数据拷贝到第⼀个参数指定的位置上,拷贝的数据长度由第三个参数决定。
如果执⾏失败,这两个函数返回的都是没能完成拷贝的数据的字节数。如果成功,返回0。当出现上述错误时,系统调⽤返回标准-EFAULT。
注意copy_to_user()和copy_from_user()都有可能引起阻塞。当包含⽤户数据的页被换出到硬盘上⽽不是在物理内存上的时候,这种情况就会发⽣。此时,进程就会休眠,直到缺页处理程序将该页从硬盘重新换回物理内存。
系统调⽤的返回值
系统调⽤(在Linux中常称作syscalls)通常通过函数进⾏调⽤。它们通常都需要定义⼀个或⼏个参数(输⼊)⽽且可能产⽣⼀些副作⽤,例如写某个⽂件或向给定的指针拷贝数据等等。为防⽌和正常的返回值混淆,系统调⽤并不直接返回错误码,⽽是将错误码放⼊⼀个名为errno的全局变量中。通常⽤⼀个负的返回值来表明错误。返回⼀个0值通常表明成功。如果⼀个系统调⽤失败,你可以读出errno的值来确定问题所在。通过调⽤perror()库函数,可以把该变量翻译成⽤户可以理解的错误字符串。
errno不同数值所代表的错误消息定义在errno.h中,你也可以通过命令”man 3 errno”来察看它们。需要注意的是,errno的值只在函数发⽣错误时设置,如果函数不发⽣错误,errno的值就⽆定义,并不会被置为0。另外,在处理errno前最好先把它的值存⼊另⼀个变量,因为在错误处理过程中,即使像printf()这样的函数出错时也会改变errno的值。
当然,系统调⽤最终具有⼀种明确的操作。举例来说,如getpid()系统调⽤,根据定义它会返回当前进程的PID。内核中它的实现⾮常简单:
asmlinkage long sys_ getpid(void)
{
return current-> tgid;
}
上述的系统调⽤尽管⾮常简单,但我们还是可以从中发现两个特别之处。⾸先,注意函数声明中的asmlinkage限定词,这是⼀个⼩戏法,⽤于通知编译器仅从栈中提取该函数的参数。所有的系统调⽤都需要这个限定词。其次,注意系统调⽤get_pid()在内核中被定义成sys_ getpid。这是Linux中所有系统调⽤都应该遵守的命名规则。
访问系统调⽤
系统调⽤上下⽂
内核在执⾏系统调⽤的时候处于进程上下⽂。current指针指向当前任务,即引发系统调⽤的那个进程。
在进程上下⽂中,内核可以休眠并且可以被抢占。这两点都很重要。⾸先,能够休眠说明系统调⽤可以使⽤内核提供的绝⼤部分功能。休眠的能⼒会给内核编程带来极⼤便利。在进程上下⽂中能够被抢占,其实表明,像⽤户空间内的进程⼀样,当前的进程同样可以被其他进程抢占。因为新的进程可以使⽤相同的系统调⽤,所以必须⼩⼼,保证该系统调⽤是可重⼈的。当然,这也是在对称多处理中必须同样关⼼的问题。
当系统调⽤返回的时候,控制权仍然在system_call()中,它最终会负责切换到⽤户空间并让⽤户进程继续执⾏下去。
系统调⽤访问⽰例
操作系统使⽤系统调⽤表将系统调⽤编号翻译为特定的系统调⽤。系统调⽤表包含有实现每个系统调⽤的函数的地址。例如,read()系统调⽤函数名
为sys_read。read()系统调⽤编号是 3,所以sys_read()位于系统调⽤表的第四个条⽬中(因为系统调⽤起始编号为0)。从地址sys_call_table + (3 * word_size)读取数据,得到sys_read()的地址。
到正确的系统调⽤地址后,它将控制权转交给那个系统调⽤。我们来看定义sys_read()的位置,即fs/read_write.c⽂件。这个函数会到关联到 fd 编号(传递给 read()函数的)的⽂件结构体。那个结构体包含指向⽤来读取特定类型⽂件数据的函数的指针。进⾏⼀些检查后,它调⽤与⽂件相关的 read() 函数,来真正从⽂件中读取数据并返回。与⽂件相关的函数是在其他地⽅定义的 —— ⽐如套接字代码、⽂件系统代码,或者设备驱动程序代码。这是特定内核⼦系统最终与内核其他部分协作的⼀个⽅⾯。
读取函数结束后,从sys_read()返回,它将控制权切换给ret_from_sys。它会去检查那些在切换回⽤户空间之前需要完成的任务。如果没有需要做的事情,那么就恢复⽤户进程的状态,并将控制权交还给⽤户程序。
从⽤户空间直接访问系统调⽤
通常,系统调⽤靠C库⽀持。⽤户程序通过包含标准头⽂件并和C库链接,就可以使⽤系统调⽤(或者调⽤库函数,再由库函数实际调⽤)。但如果你仅仅写出系统调⽤,glibc库恐怕并不提供⽀持。值得庆幸的是,Linux本⾝提供了⼀组宏,⽤于直接对系统调⽤进⾏访问。它会设置好寄存器并调⽤陷⼈指令。这些宏
是_syscalln(),其中n的范围从0到6。代表需要传递给系统调⽤的参数个数,这是由于该宏必须了解到
底有多少参数按照什么次序压⼊寄存器。举个例⼦,open()系统调⽤的定义是:
long open(const char *filename, int flags, int mode)
⽽不靠库⽀持,直接调⽤此系统调⽤的宏的形式为:
#define NR_ open 5
syscall3(long, open, const char*,filename, int, flags, int, mode)
这样,应⽤程序就可以直接使⽤open()
对于每个宏来说,都有2+ n个参数。
第⼀个参数对应着系统调⽤的返回值类型。
第⼆个参数是系统调⽤的名称。再以后是按照系统调⽤参数的顺序排列的每个参数的类型和名称。
_NR_ open在<asm/unistd.h>中定义,是系统调⽤号。该宏会被扩展成为内嵌汇编的C函数。由汇编语⾔执⾏前⼀节所讨论的步骤,将系统调⽤号和参数压⼊寄存器并触发软中断来陷⼊内核。调⽤open()系统调⽤直接把上⾯的宏放置在应⽤程序中就可以了。
让我们写⼀个宏来使⽤前⾯编写的foo()系统调⽤,然后再写出测试代码炫耀⼀下我们所做的努⼒。
#define NR foo 283
_sysca110(long, foo)
int main()
{
long stack size;
stack_ size=foo();
printf("The kernel stack
size is 81d/n",stack_ size);
return;
}
添加系统调⽤
通过修改内核源代码添加系统调⽤
linux-2.6.*
通过以上分析linux系统调⽤的过程,
将⾃⼰的系统调⽤加到内核中就是⼀件容易的事情。下⾯介绍⼀个实际的系统调⽤,
并把它加到内核中去。要增加的系统调⽤是:inttestsyscall(),其功能是在控制终端屏幕上显⽰hello world,
执⾏成功后返回0。
编写int testsyscall()系统调⽤–响应函数
编写⼀个系统调⽤意味着要给内核增加1个函数,将新函数放⼊⽂件kernel/sys.c中。新函数代码如下:
asmlingkage sys_testsyscall()
{
print("hello world\n");
return 0;
}
添加系统调⽤号
编写了新的系统调⽤过程后,下⼀项任务是使内核的其余部分知道这⼀程序的存在,然后重建包含新的系统调⽤的内核。为了把新的函数连接到已有的内核中去,需要编辑2个⽂件:
1).inculde/asm/unistd.h在这个⽂件中加⼊
#define_NR_testsyscall 191
系统调⽤表中添加对应项
2).are/i386/kernel/entry.s这个⽂件⽤来对指针数组初始化,在这个⽂件中增加⼀⾏:
.long SYMBOL_NAME(_sys_tsetsycall)
将.rept NR_syscalls-190改为NR_SYSCALLS-191,然后重新编译和运⾏新内核。
使⽤新的系统调⽤
在保证的C语⾔库中没有新的系统调⽤的程序段,必须⾃⼰建⽴其代码如下
#inculde
_syscall0(int,testsyscall)
main()
{
tsetsyscall();
}
在这⾥使⽤了_syscall0宏指令,宏指令本⾝在程序中将扩展成名为syscall()的函数,它在main()函数内部加以调⽤。
在testsyscall()函数中,预处理程序产⽣所有必要的机器指令代码,包括⽤系统调⽤参数值加载相应的cpu寄存器,然后执⾏int 0x80中断指令。
linux-3.*
在linux-3.8.4/kernel/sys.c ⽂件末尾添加新的系统调⽤函数如:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。