【源码剖析】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之间。
再来点专业的术语,不然一会用到的时候不理解;
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)
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时,没有任何代码影响到栈
asmlinkage这个宏asmlinkage int sys_fork(struct pt_regs regs);宏定义#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))asmlinkage意思:只让函数从栈上获取参数。
部分细节代码分析结束,感觉好混乱呀,一大堆代码来一张思路图,让代码走势有线路;
简单根据代码画出总思路图(代码走向图);
补充一张堆栈切换图;
系统调用大致就是这样子,有些地方还没有解析很仔细,会继续改进的。
- 【源码剖析】LINUX下的系统调用
- linux下read系统调用剖析
- Linux 系统调用 —— fork()内核源码剖析
- read 系统调用剖析 下
- linux下的系统调用
- fork系统调用底层实现源码剖析
- arm 体系架构下 linux kernel 的系统调用与返回 源码分析与总结
- Linux下的系统调用的原理
- linux: fork系统调用实现剖析
- linux下系统调用
- Linux下系统调用的实现
- Linux下系统调用的实现
- Linux下系统调用的实现
- Linux下C的exec系统调用
- Linux下系统调用的实现
- Linux下的exec系统调用详解
- Linux下系统调用的实现
- Linux下进程相关的系统调用
- Linux下配置SFTP服务器
- Git 三大区域的操作分析
- 嵌入式软件分层
- Hibernate——懒加载解析
- 自定义带倒影效果的TextView
- 【源码剖析】LINUX下的系统调用
- Vim常用指令整理
- 牛客《剑指Offer》 旋转数组的最小数字
- HDU 2476 String painter(区间DP)
- java中的编码规范
- [Terminal Game Center]弹球游戏
- xshell连接LINUX虚拟机失败
- ubuntu16.04安装matlab2016b
- HTC Vive VRTK SDK使用入门之集成(版本3.2.0)