linux syscall和int 80的区别

前言

syscallint 80是中断指令,Linux通过对这两个指令的封装为开发者们提供的一种用户态切换至内核态的方法,因为在处理器中用户态没有权限向更高的权限空间切换的,以x86为例,它只允许高权限向低权限切换或同等权限切换,不允许低权限向高权限切换。但是处理器保留了一个机制,就是当产生中断时(无论是任何中断)都会让处理器切换权限并跳转至中断处理函数里,而中断处理函数又由内核注册所以就完成了用户态到内核态的切换

Tips
本文以X86架构为例

中断与普通函数的区别

中断与普通的函数调用是有区别的,一般最大的区别是返回值上面,一般普通函数调用是存在返回值的,相比之下函数调用的开销一般比中断函数要大,普通函数在调用时遵循的是函数调用约定(Calling Convention),这些约定定义了哪些寄存器需要由调用者(Caller)处理,哪些寄存器需要由被调用者(Callee)处理,例如调用者需要使用参数寄存器来传递参数然后通过call发起调用,而被调用者需要接收这些参数并执行代码体同时还需要使用返回寄存器来存储返回值和跳转回之前的代码段并进行现场恢复,这些约定都是由ISO或处理器厂商定义的。
一般的情况下函数调用使用的寄存器是通用寄存器,并且函数调用不涉及到切换堆栈,只涉及到堆栈增长
函数调用通常使用下面这些寄存器:
通用寄存器:EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP等。
浮点寄存器:ST0-ST7(在使用浮点运算时)。
SIMD寄存器:XMM0-XMM7(在使用SSE指令集时)。
而中断是为了快速响应而涉及的,它没有参数没有返回值,也不会有堆栈切换,它切换到内核态之后由内核态做堆栈现场保护堆栈切换,在返回时也由内核来恢复堆栈
同时它们的调用也不一样,函数调用使用的是call指令,中断是int,并且中断通常是由硬件自动触发的,返回的指令也有所不同,函数调用是RET,中断是IRET
并且RET指令是不会清理堆栈的也不会恢复堆栈寄存器,而IRET指令执行之后CPU自动清理堆栈恢复堆栈寄存器(ESP/SS)

int 80

0x80属于处理器的一个中断号,它是IDT表的第0x80号中断,int是处理器中触发中断的一个指令,当产生中断时CPU会以0x80为索引到IDT表中取到中断函数入口地址并进行跳转,在跳转时CPU会进行权限切换,在x86里内核态的权限为ring 0,用户态为最低级别的ring 3,为方便内核处理中断CPU会在产生中断时先将当前上下文进行保存,然后切换到中断函数里去执行,由于x86架构权限设计的限制,所以想要切换到内核态只能由中断来进入,所以Linux依靠这一点为用户态提供了调用内核功能的能力,因为用户态是不能访问实际物理设备的所以只能通过内核来访问,例如我们平时所使用的readwrite内部其实都是使用了int 80来调用内核态帮我们完成工作。
以下是printf调用write输出到屏幕的汇编代码,write内部会调用sys_write:

sys_write(unsigned int fd, const char * buf, size_t count)
[section .data]
strHello db “Hello, world!,0Ah
STRLEN equ $ - strHello
[section .text]
global _start
_start:
mov edx,STRLEN			;将count保存到eda寄存器
mov ecx,strHello		;将buf保存到ecx寄存器
mov ebx,1				;将fd参数保存到ebx寄存器,这里fd=1,对应的是stdout
mov eax,4				;调用sys_write, 系统调用号为4
int 0x80				;产生中断

当执行int 0x80之后就会进入到IDT索引为0x80的中断函数里,这个中断函数会读取eax寄存器里的值然后去调用sys_call_table数组里存放的函数,可以在linux/kernel/system_call.s代码里找到调用过程:

movl $0x10,%edx       			# set up ds, es to kernel space
mov %dx,%ds
mov %dx,%es
movl $0x17,%edx       			# fs points to local data space
mov %dx,%fs
call _sys_call_table(,%eax,4)   # %eax contains the index (NR_write)
pushl %eax

这里linux设计的非常精明,_sys_call_table里的元素是按系统调用顺序来排列的,这样就可以把eax作为偏移来调用,4是偏移量,作为乘数,在32位操作系统里地址是以4字节偏移的,下面是sys_call_table的定义。

fn_ptr sys_call_table[] = {
    sys_setup, sys_exit, sys_fork, sys_read, sys_write,
    sys_open, sys_close, sys_waitpid, sys_creat, sys_link,
    sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod,
    sys_chmod, sys_chown, sys_break, sys_stat, sys_lseek,
    sys_getpid, sys_mount, sys_umount, sys_setuid, sys_getuid,
    sys_stime, sys_ptrace, sys_alarm, sys_fstat, sys_pause,
    sys_utime, sys_stty, sys_gtty, sys_access, sys_nice,
    sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir,
    sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof,
    sys_brk, sys_setgid, sys_getgid, sys_signal, sys_geteuid,
    sys_getegid, sys_acct, sys_phys, sys_lock, sys_ioctl,
    sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit, sys_uname,
    sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid,
    sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask,
    sys_setreuid, sys_setregid
};

syscall

既然已经知道了int 80的意义,那么syscall与它一样,都是产生中断的方式,它是处理器为提升系统调用效率而开发的新的指令,目前linux已经使用syscall来替代int 80了,在x86架构上它只有IA-64上面引入的,IA-32是依然使用的是int 80syscall的系统调用地址由IA32_LSTAR寄存器记录,这个寄存器里会存放系统调用的入口函数地址,当执行syscall指令时CPU会去读取IA32_LSTAR寄存器里的地址并跳转,一个完整的syscall由三个寄存器组成:IA32_LSTARIA32_STARIA32_FMASK, 它们的作用分别如下:
IA32_LSTAR: 配置内核态代码段选择子。
**IA32_STAR: ** 配置用户态代码段选择子。
**IA32_FMASK: ** 指定系统调用过程中需要屏蔽的标志位。
IA32_LSTARIA32_STAR分别对应高32位低32位,可以把它俩看成一个寄存器IA32_STAR存储的是返回用户态时的地址,当执行syscall时会跳转IA32_LSTAR的地址,当处理完成时需要调用sysret来返回用户态,则跳转的就是IA32_STAR的地址IA32_FMASK用来控制在执行系统调用时需要屏蔽的特权标志位,确保系统调用执行时的状态是安全的,例如屏蔽中断(IF)陷阱中断(TF)方向标志位(DF),屏蔽这些标志位以保证系统能够正确稳定的执行系统调用。

Tips
中断: 计算机系统中的机制,用于处理异步事件或外部信号。它允许计算机在处理当前任务时,立即响应高优先级的事件,从而提高系统的响应性和效率,中断分为硬件中断和软件中断,硬件中断是由硬件产生中断信号给CPU由CPU转入IDT表中处理,软件中断是由软件调用CPU特定指令来产生中断让CPU进入中断函数进行处理
陷阱中断: 跟踪标志位主要作用是控制处理器进入单步操作方式。‌ 当陷阱标志位被设置为1时,‌处理器会以单步执行的方式运行指令,‌即处理器在每条指令执行结束后,‌都会产生一个编号为1的内部中断,‌这种内部中断被称为单步中断
方向标志位: 用于控制字符串操作指令的处理方向。这个标志位用于决定在执行字符串操作指令(如 MOVS, CMPS, SCAS, LODS, STOS)时,处理的数据是向内存地址增加还是减少

syscall与int 80的区别在哪里?

最大的区别就是效率上,以x86为例,intel推出syscall就是为了替代int 80中断效率的,int 80是通用中断,它产生中断的流程与硬件中断一致,在进行中断切换时会进行现场保护其次会去查找IDTIDT是存放在内存当中的,CPU首先需要去IDTR里将IDT地址读取出来,然后在根据索引到IDT表里找到对应入口地址然后在进行跳转,同时还需要保存所有寄存器的值如 EAX, EBX, ECX, EDX, ESI, EDI, EBP 等,而syscall是特殊设计,它只保存必要的恢复寄存器,例如EIP,返回地址是存储在IA32_STAR里的,它所需要的操作极少,同时在切换堆栈时也会进行堆栈保存,但是它是从MSR寄存器中获取内核堆栈,而通用中断则是从TSS中获取,TSS存在于内存当中,TSS的基地址存放在TR寄存器中,产生中断时需要先从TR寄存器获取到TSS任务状态段然后去里面查找内核的堆栈在进行切换。
至于堆栈的保护这些一般是由操作系统来使用汇编代码保存,CPU的设计时只会规定寄存器的使用,但它不是一定的,是灵活的,操作系统可以自由决定使用什么寄存器或内存来保存那些想要保存的数据信息。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

17岁boy想当攻城狮

感谢打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值