linux系统学习一 系统调用详解

LINUX 源码解读:系统调用的设计与实现

总结:1. 内核不是进程(一定要理解,内核是通过硬件的能力来协调用户程序的)

2. 系统调用,可以理解为一个中断处理函数,系统调用的发生就是通过软件中断实现的

如果想了解其具体的实现过程,可以阅读下面的原文

介绍系统调用是操作系统内核提供的,为了和用户空间上运行的进程进行交互的一组接口,通过该接口,应用程序可以访问硬件设备和其他操作系统资源。系统调用主要有三个作用:a. 为用户空间提供一种硬件的抽象接口。b. 保证了系统的稳定与安全。c. 实现多任务和虚拟内存。对于用户空间的进程,在一般情况下是通过应用编程接口(API)而不是系统调用来进行编程,有些 API 往往直接封装了系统调用,但这并不意味着两者是一一对应的。当前最流行的 API 是基于 POSIX 标准的。原理本节通过对 getpid()的跟踪,来由外向内的了解 linux 系统调用的原理。库函数首先,用户进程调用 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 寄存器,后一句进行一个中断号为 0x80 的软中断,这里的中断处理程序正是系统调用处理程序。系统调用处理程序这个系统调用处理程序 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, %%eax\n\t" \&;int $0x80\n\t" \RESTOREARGS_##nr \: "=a" (resultvar) \: "i" (__NR_##name) ASMFMT_##nr(args) : "memory", "cc"); \(int) resultvar; })pid_t__getpid (void){#ifdef NOT_IN_libcINTERNAL_SYSCALL_DECL (err);pid_t result = INTERNAL_SYSCALL (getpid, err, 0);#elsepid_t result = THREAD_GETMEM (THREAD_SELF, pid);if (__builtin_expect (result <= 0, 0))result = really_getpid (result);#endifreturn result;}系统调用例程:#define CFI_STARTPROC .cfi_startproc//用在每个函数的开始,用于初始化一些内部数据结构#define CFI_ENDPROC .cfi_endproc//在函数结束的时候使用与.cfi_startproc 相配套使用#define CFI_DEF_CFA .cfi_def_cfa //定义计算 CFA 的规则#define CFI_DEF_CFA_REGISTER .cfi_def_cfa_register//xx reg ,offset reg 中的值保存在 offset 中,offset 是 CFA 的#define CFI_DEF_CFA_OFFSET .cfi_def_cfa_offset//xx offset 修改计算 CFA 的规则,reg 中的值不发生变化,之改变 offset#define CFI_ADJUST_CFA_OFFSET .cfi_adjust_cfa_offset//与上面相似但是修改前面一个 offset#define CFI_OFFSET .cfi_offset//xx reg ,offset reg 中的值保存在 offset 中,offset 是 CFA 的#define CFI_REL_OFFSET .cfi_rel_offset#define CFI_REGISTER .cfi_register#define CFI_RESTORE .cfi_restore#define CFI_REMEMBER_STATE .cfi_remember_state#define CFI_RESTORE_STATE .cfi_restore_state#define CFI_UNDEFINED .cfi_undefined系统调用结束例程:# system call handler stubENTRY(system_call)RING0_INT_FRAME. # can’t unwind into user space anywaypushl %eax # save orig_eax //保存原来的 eax 中的值CFI_ADJUST_CFA_OFFSET 4SAVE_ALL //保存系统寄存器信息GET_THREAD_INFO(%ebp) //获取 thread_info 结构的地址# system call tracing in operation / emulationtestl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)//检测 thread_info 中的相关标志看是否有系统跟踪jnz syscall_trace_entry//有系统跟踪则先执行系统跟踪的代码,然后转跳回来(这在 syscall_trace_entry 中有写的)cmpl $(nr_syscalls), %eax//比较请求的系统调用号和最大系统调用号(验证系统调用号是否有效)jae syscall_badsys//如果请求系统调用号无效则退出syscall_call:call *sys_call_table(,%eax,4)//跳转到系统调用表中,系统调用表是 4 字节对齐。movl %eax,PT_EAX(%esp) # store the return valuesyscall_exit:LOCKDEP_SYS_EXIT//用于调试使用,只有在开启调试的时候才会去检测系统调用深度DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don’t miss an interrupt# setting need_resched or sigpending# between sampling and the iret//#define ENABLE_INTERRUPTS(x) sti//#define DISABLE_INTERRUPTS(x) cliTRACE_IRQS_OFF //关闭中断跟踪movl TI_flags(%ebp), %ecx //testl $_TIF_ALLWORK_MASK, %ecx # current->work//检测是否可以返回用户空间jne syscall_exit_work在系统调用处理程序中,在进行了一些初始化之后(初始化内部数据结构,检查系统调用号是否在范围之内),便开始进行 syscall_call 例程,此例程主要便是根据存放在 eax 中的系统调用号到系统调用表 sys_call_table 找到相应的系统调用函数并执行,然后由于系统调用函数的返回值也将通过 eax 传回,所以需要将返回值保存下来。另外,在系统调用结束,返回用户空间的时候,系统应当检查当前进程的need_resched 标志,如果此标志设了,系统将调用 schedule()进行调度,这一部分便是在 resume_userspace 中做的。系统调用表# perform work that needs to be done immediately before resumptionALIGNRING0_PTREGS_FRAME # can’t unwind into user space anywaywork_pending:testb $_TIF_NEED_RESCHED, %cljz work_notifysigwork_resched:call scheduleLOCKDEP_SYS_EXITDISABLE_INTERRUPTS(CLBR_ANY) # make sure we don’t miss an interrupt# setting need_resched or sigpending# between sampling and the iretTRACE_IRQS_OFFmovl TI_flags(%ebp), %ecxandl $_TIF_WORK_MASK, %ecx # is there any work to be done other# than syscall tracing?jz restore_alltestb $_TIF_NEED_RESCHED, %cljnz work_reschedsyscall_exit_work:testl $_TIF_WORK_SYSCALL_EXIT, %ecxjz work_pendingTRACE_IRQS_ON //开启系统中断跟踪ENABLE_INTERRUPTS(CLBR_ANY) # could let syscall_trace_leave() call# schedule() instead//允许中断movl %esp, %eaxcall syscall_trace_leavejmp resume_userspaceEND(syscall_exit_work)在最新的 2.6.39 内核中,系统调用表是在/arch/x86/kernel/syscall_table_32.S中定义的。系统调用表规定了每个系统调用的调用函数,并且暗示了其系统调用号。同时在/arch/x86/include/asm/unistd_32.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 sys_restart_syscall /* 0 – old "setup()" system call, used forrestarting */.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 <linux/linkage.h>#include <linux/sys.h>#include <linux/cache.h>#include <asm/asm-offsets.h>#define __NO_STUBS#define __SYSCALL(nr, sym) extern asmlinkage void sym(void) ;#undef _ASM_X86_UNISTD_64_H#include <asm/unistd_64.h>#undef __SYSCALL#define __SYSCALL(nr, sym) [nr] = sym,#undef _ASM_X86_UNISTD_64_Htypedef void (*sys_call_ptr_t)(void);extern void sys_ni_syscall(void);const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {/**Smells like a like a compiler bug — it doesn’t work*when the & below is removed.*/[0 … __NR_syscall_max] = &sys_ni_syscall,#include <asm/unistd_64.h>};这样一来,我们若要改动系统调用的话就在 unistd_64.h 里面做就可以了。系统调用函数系统调用函数便是系统调用处理程序引导运行的函数,以 getpid()系统调用为例便是 sys_getpid,但是在 linux 源码中并不存在任何对 sys_getpid 这个函数的直接定义,事实上该函数的定义是通过宏 SYSCALL_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_read)#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_DEFINEx 系列(x=1..6),这是用来处理带参系统调用的。用 SYSCALL_DEFINE0 定义的 sys_getpid 位于/kernel/timer.c 中。实现在了解了系统调用的原理之后,实现一个系统调用就相对简单了。主要有以下几步a) 在内核源码树中实现系统调用函数。注意:函数定义的位置要选好,可以将其写入到/kernel/中的某个 c 文件中,也可以自己新建一个,但这样就要注意修改 Makefile。可以直接定义 sys_mysyscall(),也可以通过 SYSCALL_DEFINE0(mysyscall)宏来实现,若直接定义的话不要忘了加上 asmlinkage 限定词。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) 重新编译加载内核。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__((div("__syscalls_metadata"))) \__syscall_meta__##sname = { \.name = "sys_"#sname, \.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_return(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 $0x80" \: "=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;}想起那座山,那个城,那些人……

linux系统学习一     系统调用详解

相关文章:

你感兴趣的文章:

标签云: