《linux系统调用详解.pdf》由会员分享,可在线阅读,更多相关《linux系统调用详解.pdf(10页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、LINUX源码解读:系统调用的设计与实现 5080309910 张至先 介绍 系统调用是操作系统内核提供的,为了和用户空间上运行的进程进行交互的一组接口,通过该接口,应用程序可以访问硬件设备和其他操作系统资源。系统调用主要有三个作用:a.为用户空间提供一种硬件的抽象接口。b.保证了系统的稳定与安全。c.实现多任务和虚拟内存。对于用户空间的进程,在一般情况下是通过应用编程接口(API)而不是系统调用来进行编程,有些 API 往往直接封装了系统调用,但这并不意味着两者是一一对应的。当前最流行的 API 是基于 POSIX 标准的。原理 本节通过对 getpid()的跟踪,来由外向内的了解 linu
2、x 系统调用的原理。库函数库函数 首先,用户进程调用glibc 中的 getpid()函数,这个函数在 include/unistd.h 中声明,其函数原型为:由于其实现是平台相关的,我们关注的实现在/glibc/nptl/sysdeps/unix/sysv/linux/getpid.c 中。extern _pid_t _getpid(void);其中引发系统调用的是 INTERNAL_SYSCALL 宏,定义在 /glibc/nptl/sysdeps/unix/sysv/linux/i386/sysdep.h 中。这一段主要是通过汇编来完成的,高亮的两句为重点,前一句将系统调用号放入eax
3、寄存器,后一句进行一个中断号为 0 x80 的软中断,这里的中断处理程序正是系统调用处理程序。系统调用处理程序系统调用处理程序 这个系统调用处理程序 system_call 定义在/arch/x86/kernel/entry_32.S 中。首先给出一些宏的说明。#define INTERNAL_SYSCALL(name,err,nr,args.)(register unsigned int resultvar;EXTRAVAR_#nr asm volatile(LOADARGS_#nr movl%1,%eaxnt int$0 x80nt RESTOREARGS_#nr :=a(resultva
4、r):i(_NR_#name)ASMFMT_#nr(args):memory,cc);(int)resultvar;)pid_t _getpid(void)#ifdef NOT_IN_libc INTERNAL_SYSCALL_DECL(err);pid_t result=INTERNAL_SYSCALL(getpid,err,0);#else pid_t result=THREAD_GETMEM(THREAD_SELF,pid);if(_builtin_expect(result work /检测是否可以返回用户空间 jne syscall_exit_work 在系统调用处理程序中,在进行了
5、一些初始化之后(初始化内部数据结构,检查系统调用号是否在范围之内),便开始进行 syscall_call 例程,此例程主要便是根据存放在 eax 中的系统调用号到系统调用表 sys_call_table 找到相应的系统调用函数并执行,然后由于系统调用函数的返回值也将通过 eax 传回,所以需要将返回值保存下来。另外,在系统调用结束,返回用户空间的时候,系统应当检查当前进程的need_resched 标志,如果此标志设了,系统将调用 schedule()进行调度,这一部分便是在 resume_userspace 中做的。系统调用表系统调用表#perform work that needs to
6、be done immediately before resumption ALIGN RING0_PTREGS_FRAME#cant unwind into user space anyway work_pending:testb$_TIF_NEED_RESCHED,%cl jz work_notifysig work_resched:call schedule LOCKDEP_SYS_EXIT DISABLE_INTERRUPTS(CLBR_ANY)#make sure we dont miss an interrupt#setting need_resched or sigpending
7、#between sampling and the iret TRACE_IRQS_OFF movl TI_flags(%ebp),%ecx andl$_TIF_WORK_MASK,%ecx#is there any work to be done other#than syscall tracing?jz restore_all testb$_TIF_NEED_RESCHED,%cl jnz work_resched syscall_exit_work:testl$_TIF_WORK_SYSCALL_EXIT,%ecx jz work_pending TRACE_IRQS_ON/开启系统中断
8、跟踪 ENABLE_INTERRUPTS(CLBR_ANY)#could let syscall_trace_leave()call#schedule()instead/允许中断 movl%esp,%eax call syscall_trace_leave jmp resume_userspace END(syscall_exit_work)在最新的 2.6.39 内核中,系统调用表是在/arch/x86/kernel/syscall_table_32.S中定义的。系统调用表规定了每个系统调用的调用函数,并且暗示了其系统调用号。同时在/arch/x86/include/asm/unistd_3
9、2.h 中定义了所有的系统调用号。值得一提的是,对于 64 位的机器,linux 提供了一个更好的实现。/arch/x86/kernel/syscall_64.c#ifndef _ASM_X86_UNISTD_32_H#define _ASM_X86_UNISTD_32_H /*This file contains the system call numbers.*/#define _NR_restart_syscall 0#define _NR_exit 1#define _NR_fork 2#define _NR_getgid 47 ENTRY(sys_call_table).long s
10、ys_restart_syscall /*0-old setup()system call,used for restarting*/.long sys_exit .long ptregs_fork .long sys_read .long sys_write .long sys_open /*5*/.long sys_close .long sys_waitpid.long sys_getpid /*20*/arch/x86/include/asm/unistd_64.h/*System call table for x86-64.*/#include#include#include#inc
11、lude#define _NO_STUBS#define _SYSCALL(nr,sym)extern asmlinkage void sym(void);#undef _ASM_X86_UNISTD_64_H#include#undef _SYSCALL#define _SYSCALL(nr,sym)nr=sym,#undef _ASM_X86_UNISTD_64_H typedef void(*sys_call_ptr_t)(void);extern void sys_ni_syscall(void);const sys_call_ptr_t sys_call_table_NR_sysca
12、ll_max+1=/*Smells like a like a compiler bug-it doesnt work *when the&below is removed.*/0._NR_syscall_max=&sys_ni_syscall,#include ;这样一来,我们若要改动系统调用的话就在 unistd_64.h 里面做就可以了。系统调用函数系统调用函数 系统调用函数便是系统调用处理程序引导运行的函数,以 getpid()系统调用为例便是 sys_getpid,但是在 linux 源码中并不存在任何对 sys_getpid 这个函数的直接定义,事实上该函数的定义是通过宏SYSCA
13、LL_DEFINE0来做的,该宏的定义位于/include/linux/syscalls.h 中。#ifndef _ASM_X86_UNISTD_64_H#define _ASM_X86_UNISTD_64_H#ifndef _SYSCALL#define _SYSCALL(a,b)#endif/*This file contains the system call numbers.*Note:holes are not allowed.*/*at least 8 syscall per cacheline*/#define _NR_read 0 _SYSCALL(_NR_read,sys_r
14、ead)#define _NR_write 1 _SYSCALL(_NR_write,sys_write)#define _NR_open 2 _SYSCALL(_NR_open,sys_open)#define _NR_close 3 _SYSCALL(_NR_close,sys_close)#define _NR_stat 4 _SYSCALL(_NR_stat,sys_newstat)#define _NR_getpid 39 _SYSCALL(_NR_getpid,sys_getpid)由此我们可以看出为什么系统调用函数同一的为“sys_“开头。在 syscall.h 中SYSCALL
15、_DEFINEx 系列(x=1.6),这是用来处理带参系统调用的。用 SYSCALL_DEFINE0 定义的 sys_getpid 位于/kernel/timer.c 中。实现 在了解了系统调用的原理之后,实现一个系统调用就相对简单了。主要有以下几步 a)在内核源码树中实现系统调用函数。注意:函数定义的位置要选好,可以将其写入到/kernel/中的某个 c 文件中,也可以自己新建一个,但这样就要注意修改 Makefile。可以直接定义 sys_mysyscall(),也可以通过 SYSCALL_DEFINE0(mysyscall)宏来实现,若直接定义的话不要忘了加上 asmlinkage 限定
16、词。b)在/include/linux/syscalls.h 中加入对 sys_mysyscall()的声明。c)在/arch/x86/include/asm/unistd_32.h 和/arch/x86/kernel/syscall_table_32.S 的末尾分别添加 mysyscall 的系统调用表项和系统调用号,并将 unistd_32.h 中 NR_syscalls 的值加一。注意:这是根据x86的32位机来说的,不同的平台可能稍有不同(比如x86 64位机上只需修改/arch/x86/include/asm/unistd_64.h)。系统调用表项和系统调用号要对应。d)重新编译加载
17、内核。SYSCALL_DEFINE0(getpid)return task_tgid_vnr(current);#define SYSCALL_DEFINE0(sname)SYSCALL_TRACE_ENTER_EVENT(_#sname);SYSCALL_TRACE_EXIT_EVENT(_#sname);static const struct syscall_metadata _used _attribute_(_aligned_(4)_attribute_(section(_syscalls_metadata)_syscall_meta_#sname=.name =sys_#sname,
18、.nb_args =0,.enter_event =&event_enter_#sname,.exit_event =&event_exit_#sname,;asmlinkage long sys_#sname(void)e)从用户空间访问系统调用。由于是“野生的“系统调用,所以 glibc 肯定不会提供 API 支持,但是我们可以直接对系统调用进行访问。老版本的 linux 中提供了一组宏_syscall(),其中 n从 0 到 6,代表需要传递给系统调用的参数个数。但在新版本中这个宏没有了,但是我们可以把这些宏原来的定义直接包括进程序中,效果还是一样的。#define _syscall_r
19、eturn(type,res)do if(unsigned long)(res)=(unsigned long)(-MAX_ERRNO)errno=-(res);res=-1;return(type)(res);while(0)/*XXX-_foo needs to be _foo,while _NR_bar could be _NR_bar.*/#define _syscall0(type,name)type name(void)long _res;_asm_ volatile(int$0 x80 :=a(_res):0(_NR_#name);_syscall_return(type,_res);#define _NR_mysyscall 250 _syscall0(int,mysyscall)Int main()mysyscall();return 0;#define _NR_mysyscall 250 _syscall0(int,mysyscall)Int main()mysyscall();return 0;
限制150内