一步步编写操作系统 67 系统调用的实现1-2 68

接上文:

系统调用的子功能要用eax寄存器来指定,所以咱们要看看有哪些系统调用啦,在linux系统中,系统调用是定义在/usr/include/asm/unistd.h文件中,该文件只是个统一的入口,指向了32位和64位两种版本。在asm目录下提供了这两个版本,文件名分别是unistd_32.h 和unistd_64.h,这里给大家摘录了部分32位x86平台下的unistd_32.h文件,见图

 

在/usr/include/asm/unistd_32.h文件中共定义了348个系统调用,哦,给大家说一下,我用的linux版本是CentOS release 6.3 (Final),不知道新版本内核中是否增加了新的系统调用功能。

我们要用的系统调用是第4号调用,即__NR_write。不要被它前面的两个下滑线吓到,就是个命名而已,它代表就是我们所说的write系统调用。

如果不知道某个系统调用的用法,可以用man命令来查看,方法是man 2 系统调用名。咱们执行man 2 write看看,见图

 

man后面的数字2是表示查看System Calls方面的帮助,对于man自己的帮助信息,man命令也可以man自己,可以用man man来查看。上图只是部分帮助信息,咱们了解这些就够用了。write的功能是把buf指向的缓冲区中的count个字节写入fd指向的文件描述符,执行成功后返回写入的字节数,失败则返回-1。

如果在c语言中调用write的话,直接代入实参就行了,这是最简单的方式,如代码c_syscall.c:

#include <unistd.h>
int main(){write(1,"hello,world\n",4);return 0;
}

为了使用c标准库中的write函数,文件开头包含了标准头文件unistd.h,通过该函数可以使用系统的write系统调用,该文件在磁盘上的路径是/usr/include/unistd.h。不过在本机上测试发现不包含unistd.h,其编译、运行都没问题,也许这和隐式声明有关,这里不再深究。

调用“系统调用”有两种方式:

  1. 将系统调用指令封装为c库函数,通过库函数进行系统调用,操作简单。
  2. 不依赖任何库函数,直接通过汇编指令int与操作系统通信。

以上的c代码就是用的第一种方式,不知道您是否对write函数的内部实现感兴趣,其实我也没研究过,不过万变不离其宗,核心思想是必须与进行内核沟通才能获得内核提供的功能。所以,write内部封装的一定是系统调用指令,按照这种设想,下次咱们会模拟一下它的实现。

 

调用“系统调用”有两种方式:

  1. 将系统调用指令封装为c库函数,通过库函数进行系统调用,操作简单。
  2. 不依赖任何库函数,直接通过汇编指令int与操作系统通信。

以上的c代码就是用的第一种方式,不知道您是否对write函数的内部实现感兴趣,其实我也没研究过,不过万变不离其宗,核心思想是必须与进行内核沟通才能获得内核提供的功能。所以,write内部封装的一定是系统调用指令,按照这种设想,一会咱们会模拟一下它的实现。

我们这里要介绍下第二种:跨过库函数直接与系统内核通信,这样最终的程序是不需要与任何库文件链接,这是获得系统功能效率最高的方式。

我相信,如果曾经学过汇编语言,老师都给咱们演示过第二种方式,但大多数同学还是觉得云里雾里,即使照葫芦画瓢完成了打印字符串的工作,也有部分同学不清楚自己在做什么,所以我在这里尽量多说一点。

前面我们已经知道了write系统调用函数的c语言使用方式,我们要用汇编代码直接与内核通信该怎么做?我们要看看系统调用输入参数的传递方式。

当输入的参数小于等于5个时,linux是用寄存器传递参数。当参数个数大于5个时,把参数按照顺序放入连续的内存区域,并将该区域的首地址放到ebx寄存器。这里我们只演示参数小于等于5个的情况。

eax寄存器用来存储子功能号(寄存器eip、ebp、esp是不能使用的)。5个参数是存放在以下寄存器中,传送参数的顺序是:

  1. ebx存储第1个参数
  2. ecx存储第2个参数
  3. edx存储第3个参数
  4. esi存储第4个参数
  5. edi存储第5个参数

好啦,理论知识够用啦,现在赶紧实践一把,见以下代码syscall_write.S

 1 section .data2 str_c_lib: db "c library says: hello world!", 0xa ;0xa为LF ascii码3 str_c_lib_len equ $-str_c_lib45 str_syscall: db "syscall says: hello world!", 0xa6 str_syscall_len equ $-str_syscall78 section .text9 global _start10 _start:11 ;;;;;;;;;;;;; 方式1: 模拟c语言中系统调用库函数write ;;;;;;;;;;;;;12 push str_c_lib_len ;按照c调用约定压入参数13 push str_c_lib14 push 11516 call simu_write ;调用下面定义的simu_write17 add esp,12 ;回收栈空间1819 ;;;;;;;;;;;;; 方式2: 跨过库函数,直接进行系统调用 ;;;;;;;;;;;;;20 mov eax,4 ;第4号子功能是write系统调用(不是c库函数write)21 mov ebx, 122 mov ecx, str_syscall23 mov edx, str_syscall_len24 int 0x80 ;发起中断,通知linux完成请求的功能。2526 ;;;;;;;;;;;;; 退出程序 ;;;;;;;;;;;27 mov eax,1 ;第1号子功能是exit28 int 0x80 ;发起中断,通知linux完成请求的功能。2930 ;;;;;;;下面自定义的simu_write用来模拟c库中系统调用函数write,;;;;;;这里模拟它的实现原理31 simu_write:32 push ebp ;备份ebp33 mov ebp,esp34 mov eax,4 ;第4号子功能是write系统调用(不是c库函数write) 35 mov ebx, [ebp+8] ;第1个参数36 mov ecx, [ebp+12] ;第2个参数37 mov edx, [ebp+16] ;第3个参数38 int 0x80 ;发起中断,通知linux完成请求的功能39 pop ebp ;恢复ebp40 ret

代码syscall_write.S中,我们演示了系统调用的两种方式。程序开头定义了两种方式下打印的字符串,其中0xa为LF(LineFeed)ascii码,这样就会输出一个换行符。

第11~17行是在演示方式1,模拟调用c库函数write的方式。因为write是c库函数,按一般的做法是,汇编程序需要与c代码生成的目标文件链接才能调用c的代码。在这个例子中我们并没有这样帮做,因为我想让大家了解write函数的本质,所以,在这里为大家定义了simu_write来代替c库函数write,用它来简单解释write的原理,它定义在31~40行。这里是按照c调用约定将参数从右到左依次入栈,随后调用simu_write实现字符串打印功能。

第19~24行是在演示第2种系统调用的方式,这是最简单直接可依赖的方式。20~24行是在eax中赋予子功能号、将参数按照顺序依次写入对应的寄存器。

第31~40行是simu_write的实现,它内部在本质上是和第2种方式一样,都是在内部调用int指令直接和系统通信实现系统调用。此函数只是为了试图揭开c库函数的实现原理,良苦用心您懂的。

好啦,编译链接过程如下:

nasm -f elf -o syscall_write.o syscall_write.S

其中-f参数是用来指定编译输出的文件格式,这里需要指定为elf,目的是将来要和gcc编译的elf格式的目标文件链接,所以格式必须相同。nasm输出为目标文件,已经用-o指定文件名为syscall_write.o。

最后用ld程序将syscall_write.o链接成elf格式的二进制可执行文件。

ld -o syscall_write.bin syscall_write.o

程序执行后的效果如图

 

顺便说一句,syscall_write.bin如果因为权限不足而无法执行时,可以用以下指令增加执行权限:

chmod u+x syscall_write.bin

本文摘自《操作系统真象还原》,请大家支持正版,多谢。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/439540.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【HDU - 6662】Acesrc and Travel(树形dp,博弈dp)

题干&#xff1a; Acesrc is a famous tourist at Nanjing University second to none. During this summer holiday, he, along with Zhang and Liu, is going to travel to Hong Kong. There are nnspots in Hong Kong, and n−1n−1 bidirectional sightseeing bus routes …

一步步编写操作系统 69 汇编语言和c语言共同协作 70

由于有了上一节的铺垫&#xff0c;本节的内容相对较少&#xff0c;这里给大家准备了两个小文件来实例演示汇编语言和c语言相互调用。 会两种不同语言的人&#xff0c;只是掌握了同一件事物的两种表达方式。人在学习一种新语言时&#xff0c;潜意识里是建立了语言符号与事物形象…

一步步编写操作系统 71 直接操作显卡,编写自己的打印函数71-74

一直以来&#xff0c;我们在往屏幕上输出文本时&#xff0c;要么利用bios中断&#xff0c;要么利用系统调用&#xff0c;这些都是依赖别人的方法。咱们还用过一个稍微有点独立的方法&#xff0c;就是直接写显存&#xff0c;但这貌似又没什么含量。如今我们要写一个打印函数了&a…

【CodeForces - 208C 】Police Station(单源最短路条数,起点终点建图,枚举顶点)

题干&#xff1a; The Berland road network consists of n cities and of m bidirectional roads. The cities are numbered from 1 to n, where the main capital city has number n, and the culture capital — number 1. The road network is set up so that it is possi…

一步步编写操作系统 75 从显卡读取光标位置1

我们在打印字符时&#xff0c;通常都不用指定字符显示的坐标位置&#xff0c;大家也没觉得有什么奇怪&#xff0c;原因是字符是在当前光标的位置处显示的&#xff0c;而且光标的位置会一直更新顺延&#xff0c;我们的字符一直跟着光标走&#xff0c;似乎光标就是字符的导航一样…

【Chrome浏览器】常用快捷键整理

标签页和窗口快捷键 1. Ctrl n 打开新窗口 2. Ctrl t 打开新的标签页&#xff0c;并跳转到该标签页 3. Ctrl Shift t 重新打开最后关闭的标签页&#xff0c;并跳转到该标签页 4. Ctrl Tab 跳转到下一个打开的标签页 5. Ctrl Shift Tab 跳转到上一个打开的标签页 6.…

一步步编写操作系统 76 用汇编语言编写字符打印函数

之前咱们介绍显卡上那么多的寄存器终于发挥用处了&#xff0c;我们看看前文中介绍的表CRT Controller Data Registers中索引为0Eh的 Cursor Location High Register寄存器和索引为0Fh的Cursor Location Low Register寄存器&#xff0c;这两个寄存器都是8位长度。分别用来存储光…

【人工智能课程实验】 - 利用贝叶斯分类器实现手写数字 的识别

读入数据与预处理 因为老师给的文件无法直接读取&#xff0c;故从官网导入数据&#xff1a; 官网链接&#xff1a;http://www.cs.nyu.edu/~roweis/data.html 导入数据之后要对MATLAB文件进行读入&#xff1a; datasio.loadmat(trainfile) 对文件type一下&#xff1a; ty…

一步步编写操作系统 77 内联汇编与ATT语法简介

内联汇编 之前和大家介绍过了一种汇编方法&#xff0c;就是C代码和汇编代码分别编译&#xff0c;最后通过链接的方式结合在一起形成可执行文件。 另一种方式就是在C代码中直接嵌入汇编语言&#xff0c;强大的GCC无所不能&#xff0c;咱们本节要学习的就是这一种&#xff0c;它…

【Python学习】内置函数(不断更新)

关于常用在for循环中的range函数 python range() 函数可创建一个整数列表&#xff0c;一般用在 for 循环中。 函数语法 range(start, stop[, step]) 参数说明&#xff1a; start: 计数从 start 开始。默认是从 0 开始。例如range&#xff08;5&#xff09;等价于range&#…

【Python学习】 简单语法与常见错误(持续更新)

关于单引号和双引号 当输出的字符串内部没有单引号的时候&#xff0c;外面可以用单引号&#xff0c; 但是如果内部有了单引号&#xff0c;那么外部只能用双引号。 dict {Name: Zara, Age: 7, Class: First} print(dict) print (dict[Name]: , dict[Name]) print ("dic…

一步步编写操作系统 78 intel汇编与ATT汇编语法区别

本节咱们介绍下intel汇编语法和at&t汇编语法的区别。 以上表中未列出这两种语法在内存寻址方面的差异&#xff0c;个人觉得区别还是很大的&#xff0c;下面单独说说。 在Intel语法中&#xff0c;立即数就是普通的数字&#xff0c;如果让立即数成为内存地址&#xff0c;需要…