【源码剖析】LINUX下的系统调用

来源:互联网 发布:蜂窝移动数据只有两个 编辑:程序博客网 时间:2024/06/18 07:00

                                                                    LINUX下的系统调用

注: 我这里看到源码是linux 2.6.11版本的源码;

系统调用是什么东西,这个才能开始后续工作。


系统调用举个例子更容易理解,假如你想找个“铁饭碗”,你是不是得托人给领导送礼呀。你个平头百姓怎么会平时接触到那些大领导呢,你肯定是费尽心机地找到了一个“铁关系”,这个“铁关系”作为中间人,将你的意思传达给大领导,大领导帮你开个后门,你是不是就有了一个”铁饭碗“。


这个”铁关系“就是这里的系统调用,然后这里的你就是用户模式,而这里的大领导肯定就是内核模式。


系统调用的定义就很简单了,系统调用时发生在用户进程通过调用特殊函数,以请求内核提供服务的时候。此时用户态被暂时挂起(我替你办事 ,你肯定得等呀),内核检验用户请求(看你的要求过不过分),尝试执行。并把结果返回给用户(将好消息带回去),接着你就可以带着你的好消息启动了。


解决完概念问题,那么来研究一下,这个内核是怎么给你帮忙的;


系统调用的分类:

1.处理I/O请求:open、close、read、write、poll

2.进程fork、execve、kill

3.时间time、settimeofday

4.内存mmap、brk



开始之前先说点简单的,带回来的消息;

带回来的消息就一定是好消息吗?系统调用必须返回类型是int,你想一下你平时使用的系统函数,不是int就是int封装的类型。那么返回值0或者正数就是好消息,如果返回来的是负数就是坏消息。其实在最新的内核版本里面返回负数不一定代表错误,lseek函数即使结果正确也会返回一个很大的负数,而错误的返回码一般在-1到-4095之间。


再来点专业的术语,不然一会用到的时候不理解;

1.中断有两种类型:软中断,硬中断
硬件中断:来自硬盘的异常和其他事件的发生,如电源断电、键盘按下。
软件中断:一个带有中断号的指令,可以手动的触发中断处理程序(LInux下,使用int  Ox80来触发所有的系统调用)。

2.LINUX下是通过int 0x80来触发所有的系统调用


3.系统调用号,是通过寄存器eax传入内核的。


万事俱备只欠东风;


先来个系统调用的大致图解吧(看图简单)fork为例:


根据内核代码分析:

中断向量表------->sys_call函数

内核代码:Linux/arch/i386/kernel/traps.c

初始化中断向量表void __init trap_init(void){#ifdef CONFIG_EISAvoid __iomem *p = ioremap(0x0FFFD9, 4);if (readl(p) == 'E'+('I'<<8)+('S'<<16)+('A'<<24)) {EISA_bus = 1;}iounmap(p);#endif#ifdef CONFIG_X86_LOCAL_APICinit_apic_mappings();#endifset_trap_gate(0,÷_error);set_intr_gate(1,&debug);set_intr_gate(2,&nmi);set_system_intr_gate(3, &int3); /* int3-5 can be called from all */set_system_gate(4,&overflow);set_system_gate(5,&bounds);set_trap_gate(6,&invalid_op);set_trap_gate(7,&device_not_available);set_task_gate(8,GDT_ENTRY_DOUBLEFAULT_TSS);set_trap_gate(9,&coprocessor_segment_overrun);set_trap_gate(10,&invalid_TSS);set_trap_gate(11,&segment_not_present);set_trap_gate(12,&stack_segment);set_trap_gate(13,&general_protection);set_intr_gate(14,&page_fault);set_trap_gate(15,&spurious_interrupt_bug);set_trap_gate(16,&coprocessor_error);set_trap_gate(17,&alignment_check);#ifdef CONFIG_X86_MCEset_trap_gate(18,&machine_check);#endifset_trap_gate(19,&simd_coprocessor_error);set_system_gate(SYSCALL_VECTOR,&system_call); //SYSCALL_VECTOR是一个宏定义,//#define SYSCALL_VECTOR 0x80 ,对应的是系统调用中断号是0x80,最终执行的sys_call函数。/* * Should be a barrier for any external CPU state. */cpu_init();trap_init_hook();

sys_call函数的实现:

sys_call---->sys_call_table

内核代码:Linux/arch/i386/kernel/unistd.h

#define _syscall1(type,name) \type name(type1 arg1) \{ \long __res; \__asm__ volatile ("int $0x80" \  //__asm__ 是一个GCC的额关键字,表示接下来要嵌入汇编代码//_asm_ 函数的第一个参数是一个字符串,表示汇编代码                                   //volatile 告诉编译器不要对代码进行优化。: "=a" (__res) \      //将eax寄存器的数据,存在_res上。“a”表示用eax寄存器: "0" (__NR_##name)); \  //_NR_##name 拼接字符串,“0”表示编译器选择输入、输出用相同的寄存器来传递参数。__syscall_return(type,__res); \}检查系统调用的返回值,并把它转化成相应的errno错误码。#define __syscall_return(type, res) \do { \if ((unsigned long)(res) >= (unsigned long)(-(128 + 1))) { \errno = -(res); \res = -1; \} \return (type) (res); \} while (0)有一个参数时,参数存在ebx中。linux下支持6个参数的传递,分别用寄存器ebx、ecx、edx、esi、edi、ebp#define _syscall1(type,name,type1,arg1) \type name(type1 arg1) \{ \long __res; \__asm__ volatile ("int $0x80" \: "=a" (__res) \: "0" (__NR_##name),"b" ((long)(arg1))); \   //"b"表示EBX寄存器,__syscall_return(type,__res); \}

sys_call内部调用sys_call_table的实现:

内核代码:Linux/arch/i386/kernel/entry.s

sys_call函数的实现:# system call handler stubENTRY(system_call)pushl %eax# save orig_eaxSAVE_ALL            //将各种寄存器的值压入栈,以避免后续执行的代码的覆盖。GET_THREAD_INFO(%ebp)# system call tracing in operationtestb $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT),TI_flags(%ebp)jnz syscall_trace_entrycmpl $(nr_syscalls), %eax  //nr_syscalls表示最大的系统调用号加1.。eax存放的是用户传进来的系统调用号。jae syscall_badsys//系统调用号确定有效后syscall_call:call *sys_call_table(,%eax,4)  //sys_call_table是查找中断服务程序并执行。movl %eax,EAX(%esp)# store the return valuesyscall_exit:cli# make sure we don't miss an interrupt# setting need_resched or sigpending# between sampling and the iretmovl TI_flags(%ebp), %ecxtestw $_TIF_ALLWORK_MASK, %cx# current->workjne syscall_exit_workrestore_all:RESTORE_ALL     //RESTORE_ALL系统恢复# perform work that needs to be done immediately before resumptionALIGN

sys_call_table调用sys_fork实现fork函数

内核代码:Linux/arch/i386/kernel/traps.c

中断服务程序sys_call_table(0,%eax,4) ;/*1.函数的第一个参数:系统调用函数的地址;给出的例子:指的是syscall_table上偏移量为0+eax * 4 的值指向的函数  ;也是eax寄存器里面存的系统调用号所对应的系统函数;*/.dataENTRY(sys_call_table).long sys_restart_syscall/* 0 - old "setup()" system call, used for restarting */.long sys_exit.long sys_fork.long sys_read.long sys_write.long sys_open/* 5 */.long sys_close.long sys_waitpid.long sys_creat.long sys_link.long sys_unlink/* 10 */.long sys_execve.long sys_chdir.long sys_time     .........long sys_request_key.long sys_keyctlsyscall_table_size=(.-sys_call_table)



在进入sys_call函数时,有一个宏SAVE_ALL

SAVE_ALL保存寄存器#define SAVE_ALL \cld; \pushl %es; \pushl %ds; \pushl %eax; \pushl %ebp; \pushl %edi; \pushl %esi; \pushl %edx; \pushl %ecx; \pushl %ebx; \movl $(__USER_DS), %edx; \    //后面这三条mov指令,都是设置内核的数据段movl %edx, %ds; \movl %edx, %es;//函数倒数6个 寄存器,刚好是系统调用传递参数的6个寄存器。//所以系统调用的参数是通过SAVE_ALL保存在栈上的。并且在调用sys_call_table时,没有任何代码影响到栈



sys_call函数的实现时,最前面的关键字asmlinkage;


asmlinkage这个宏asmlinkage int sys_fork(struct pt_regs regs);宏定义#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))asmlinkage意思:只让函数从栈上获取参数。



部分细节代码分析结束,感觉好混乱呀,一大堆代码来一张思路图,让代码走势有线路;




简单根据代码画出总思路图(代码走向图);






 补充一张堆栈切换图;




系统调用大致就是这样子,有些地方还没有解析很仔细,会继续改进的。


原创粉丝点击