2018-2019-1 20189213《Linux内核原理与分析》第五周作业

本文深入解析Linux系统调用的三层机制,包括用户态函数(API)、system_call及sys_xyz()封装例程。通过getpid、getuid和rename系统调用的实际应用,对比C语言与内嵌汇编代码调用方式,详细介绍参数传递过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第四章:系统调用的三层机制(上)

系统调用的"三层皮"

分别指的是:用户态函数(API)、system_call(中断服务程序入口)以及sys_xyz()系统调用处理函数封装例程。它们各自的作用如下:

API

第一层是指Libc中定义的API,这些API封装了系统调用,使用int0x80触发一个系统调用中断;
当然,并非所有的API都使用了系统调用,如完成数学加减运算的API就没有使用系统调用;
也有可能某个API使用了多个系统调用;
这一层的作用就是为应用程序员提供易于使用的API来调用系统调用;

system_call

它运行于内核态。system_call是所有系统调用在内核的入口点,在其中的开始处保护用户态程序执行上下文,结束处恢复用户态程序执行上下文,在中间根据传入的系统调用号对应的中断服务程序;

sys_xyz()系统调用封装例程

执行具体的系统调用操作,完成用户的系统调用请求;每个系统调用都对应一个封装例程。
由上面的分析我们可以知道,理论上要请求一个系统调用,我们既可以使用Libc提供的API,也可以直接在C中内嵌汇编代码触发0x80中断来完成。

下面实验,我们就用实际的例子来演示这两种方法使用同一个系统的调用:

getpid和getuid

首先我们选择的是比较简单的系统调用sys_pid(进程ID)(),sys_uid(用户ID),通过查阅系统调用列表发现它对应的系统调用号分别是20,24;
利用这个系统调用可以在屏幕上输出进程id和用户id;而这两个系统调用函数对应的API就是getpid和getuid;
第一步我们首先使用C语言代码实现:
C语言代码如下图所示:
1508946-20181108110734214-1604533481.png
然后运行后我们发现进程id与用户id已输出:
1508946-20181108110747577-810187397.png
接下来我们运用C语言中内嵌汇编代码来实现调用getpid和getuid系统调用:
下图是内嵌汇编代码的具体代码:
1508946-20181108114722373-68659657.png
然后是编译成功界面:
1508946-20181108114732405-1013791073.png
可以看出两种方法都输出了进程id和用户id;
参数传递的过程:
以getpid系统调用为例,首先是系统调用传递第一个参数使EBX寄存器的值为0,然后使用eax寄存器传递系统调用号20,然后用int 0x80触发系统调用,最后通过eax寄存器返回系统调用值。

rename

后面我们又选择了系统调用sys_rename(),它含有两个参数,通过查阅系统调用列表发现它对应的系统调用号是38;
利用这个系统调用可以给一个文件重命名;而这个系统调用函数对应的API就是rename。
第一步我们使用C语言代码调用rename系统调用:
C语言代码如下图所示:
1508946-20181108100913283-2095049789.png
然后运行后我们发现原有文件夹hello.c改变成了newhello.c:
1508946-20181108101138025-1311927750.png
接下来我们运用C语言中内嵌汇编代码来实现调用rename系统调用:
下图是内嵌汇编代码的具体代码:
1508946-20181108102348930-1939212633.png
然后是编译成功执行界面:
1508946-20181108102411075-67681120.png
可以看出hello.c也成功改为了newhello.c。
两种方法都实现了给文件夹重命名的功能,但C语言中内嵌汇编涉及了很多参数。
下面分析rename系统调用参数的传递:
首先是将oldname存入EBX寄存器,将newname存入ECX寄存器,这是由多个参数需要存入寄存器时按照EBX、ECX、EDX、ESI、EDI、EBP的顺序存储的;
然后将系统调用号38存入EAX寄存器,接着执行int 0X80来执行系统调用陷入内核态。其中system_call根据传入的系统调用号查找对应系统调用内核函数,根据EBX和ECX寄存器传入的参数调用内核函数sys_rename,执行完后将执行结果存放到EAX寄存器中,并同时将EAX的值传给ret。

总结:

即便是最简单的程序,也难免要用到诸如输入、输出以及退出等操作,而要进行这些操作则需要调用操作系统所提供的服务,也就是系统调用。
Linux下的系统调用是通过中断(int 0x80)来实现的。在执行int 0x80 指令时,寄存器eax 中存放的是系统调用的功能号,而传给系统调用的参数则必须按顺序放到寄存器ebx,ecx,edx,esi,edi中,当系统调用完成之后,返回值可以在寄存器 eax中获得。
所有的系统调用功能号都可以在相关网站中找到,为了便于使用,它们是用 SYS_这样的宏来定义的,如 SYS_write、SYS_exit等。
当一个系统调用所需的参数个数大于6时,执行int 0x80 指令时仍需将系统调用功能号保存在寄存器eax 中,所不同的只是全部参数应该依次放在一块连续的内存区域里,同时在寄存器ebx 中保存指向该内存区域的指针。系统调用完成之后,返回值仍将保存在寄存器eax 中。
由于只是需要一块连续的内存区域来保存系统调用的参数,因此完全可以像普通的函数调用一样使用栈(stack)来传递系统调用所需的参数。如果采用栈来传递系统调用所需的参数,在执行int 0x80指令时还应该将栈指针的当前值复制到寄存器 ebx中。

转载于:https://www.cnblogs.com/aiYY/p/9927943.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值