Linux系统调用(2.哈工大OS实验二)

Linux系统调用与哈工大实验二

实验要求

此次实验的基本内容是:在Linux 0.11上添加两个系统调用,并编写两个简单的应用程序测试它们。
具体实验细节可以参考蓝桥云:

https://www.lanqiao.cn/courses/115/learning/?id=374&compatibility=false

  • 系统调用
    1. iam()
      第一个系统调用是iam(),其原型为:
    int iam(const char *name);
    
    完成的功能是将字符串参数name的内容拷贝到内核中保存下来。要求name的长度不能超过23个字符。返回值是拷贝的字符数。如果name的字符个数超过了23,则返回“-1”,并置errnoEINVAL
    kernel/who.c中实现此系统调用。
    2. whoami()
    第二个系统调用是whoami(),其原型为:
    int whoami(char *name, unsigned int size);
    
    它将内核中由iam()保存的名字拷贝到name指向的用户地址空间中,同时确保不会对name越界访存(name的大小由size说明)。返回值是拷贝的字符数。如果size小于需要的空间,则返回“-1”,并置errnoEINVAL
    也是在kernel/who.c中实现。
  • 应用程序
    1. iam.c
      应用程序ima.cmain()函数中调用系统调用iam(),其接收命令行参数作为名字传递给int iam(const char *name)中的参数name
    2. whoami.c
      应用程序whoami.cmain()函数调用系统调用whoami(),将iam()保存在内核空间的name变量读出保存到函数参数name中,并打印输出。

前置内容

1.什么是系统调用

系统调用是操作系统实现硬件解耦与封装,为上层软件提供接口调用的一种途径。应用程序通过系统调用请求操作系统的服务。系统中的各种共享资源都由操作系统统一掌管,因此在用户程序中,凡是与资源有关的操作(如存储分配、I/O操作、文件管理等),都必须通过系统调用的方式向操作系统提出服务请求,由操作系统代为完成。简单来说就是操作系统提供统一的封装函数,用户想要实现对底层硬件资源的使用,就只能通过操作系统提供的API来完成。这样就能确保程序的相对安全与稳定。
在这里插入图片描述
所以系统调用便提供了从用户模式能够访问内核模式的途径。在Linux中实现系统调用是利用了软件中断的模式来实现,将系统调用设置成了一种特殊的中断模式。
其中int $0x80便是实现的唯一的汇编指令,其余代码均是指定输入输出以及指令执行过程中的寄存器及变量的使用。
int $0x80指令属于软中断(software interrupt)。软中断又叫做编程异常(programmed exception),是异常的一种。该指令的作用是以0x80作为索引值,用于在中断描述符表IDT中查找存储了中断处理程序信息的描述符。
之前我们也分析过IDT中断描述符表的结构,中断描述符表(Interrupt Descriptor Table,IDT)是用来告诉处理器在遇到异常或 “INT”操作码(汇编中)时所应调用的中断服务例程 (Interrupt Service Routine,ISR)。简单来说就是会有一段内核空间专门用来存储中断程序的地址,索引值以及优先级。其中set_system_gate(0x80,&system_call)便是用来设置系统调用函数位置的函数。这里具体的设置细节内容就不细讲了。
当设置好了系统调用中断,当触发0x80中断,操作系统便会根据idt中记录的入口函数地址从而调用system_call函数

system_call:cmpl $nr_system_calls-1,%eaxja bad_sys_callpush %dspush %espush %fspushl %edxpushl %ecx		# push %ebx,%ecx,%edx as parameterspushl %ebx		# to the system callmovl $0x10,%edx		# set up ds,es to kernel spacemov %dx,%dsmov %dx,%esmovl $0x17,%edx		# fs points to local data spacemov %dx,%fscall sys_call_table(,%eax,4)pushl %eaxmovl current,%eaxcmpl $0,state(%eax)		# statejne reschedulecmpl $0,counter(%eax)		# counterje rescheduleret_from_sys_call:movl current,%eax		# task[0] cannot have signalscmpl task,%eaxje 3f

system_call会模拟函数调用过程。可以看到system_call函数首先将DS,ES,FS这些数据段寄存器压栈,然后将保存在EBX,ECX,EDX的库函数API的参数逆序压栈。接着通过 call sys_call_table(,%eax,4)调用sys_call_table(%eax存储的系统调用号)从而运行sys_call_table中对应的各种系统调用函数。当系统调用函数结束模拟正常的函数返回过程,从而实现内核态到用户态的切换。
在这里插入图片描述
大致过程如下图:
在这里插入图片描述
因此本次的实验两个系统调用函数只需要在sys_call_table中存入相应的系统调用号和函数入口地址,然后编写sys_iam(),sys_whoami()两个函数即可。系统调用和内核态到用户态的切换过程sys_call()函数都已经帮我们封装好了。

2.通过sys_fork()函数举例

当我们运行一个内核态的fork()函数,fork通过0x80中断(eax=2)进行系统调用,通过system_call()运行了syscall_table中系统调用号为2的sys_fork()系统调用函数,从而实现了fork过程。
在这里插入图片描述

fork()函数

void main(void) {...    move_to_user_mode();if (!fork()) {init();}for(;;) pause();
}

move_to_user_mode已经让我们目前正在运行的程序模式转入用户态,处于受限状态,如果需要进行特殊操作,需要通过中断陷入内核态才可以。因此此时的fork()已经运行在了用户态,属于进程代码,他的代码段属于进程代码段
在这里插入图片描述
在这里插入图片描述
会根据局部LDT和TSS存储在对应的进行虚拟内存中。而内核态的代码与资源存储在全局描述符表的数据段与代码段中。因此此时的fork()函数需要在函数内部调用系统调用函数,进入内核态,从而能够实现进程空间中代码段数据段与全局描述符表中的代码段与数据段空间进行交互访问与运算。
fork ()函数

static _inline _syscall0(int,fork)#define _syscall0(type,name) \
type name(void) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \: "=a" (__res) \: "0" (__NR_##name)); \
if (__res >= 0) \return (type) __res; \
errno = -__res; \
return -1; \
}

所以,把宏定义都展开,其实就相当于定义了一个函数。

int fork(void) {volatile long __res;_asm {_asm mov eax,__NR_fork_asm int 80h_asm mov __res,eax}if (__res >= 0)return (void) __res;errno = -__res;return -1;
}

关键指令就是一个 0x80 号软件中断的触发,int 80h。其中还有一个 eax 寄存器里的参数是 __NR_fork,这也是个宏定义,值是 2。根据上文的介绍,当执行了int 80h时,便会触发system_call()函数并在模拟正常的函数调用,并在system_call()中调用system_call_table[eax对应的系统调用号(这里是__NR_fork)]跳转到sys_fork()的函数入口。

sys_call_table()

那我们接着看 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
};

这里的fn_ptr类型是int (*)(),表示一个返回类型为int的函数指针,简单说就是fn_ptr存储的就是对应函数的入口地址。

extern int sys_setup();
extern int sys_exit();
extern int sys_fork();
extern int sys_read();
extern int sys_write();
extern int sys_open();
extern int sys_close();
......
extern int sys_setsid();
extern int sys_sigaction();
extern int sys_sgetmask();
extern int sys_ssetmask();
extern int sys_setreuid();
extern int sys_setregid();

在include/linux/sys.h中已经定义了对应的函数。因此加入我们要设计一个对应的系统调用函数,我们只需要在sys.h中添加一个extern (*int)()函数,并将函数入口地址添加到table中,接下来我们就只需要编写sys_call()的函数功能即可。例如sys_fork()函数

sys_fork()

_sys_fork:call _find_empty_processtestl %eax,%eaxjs 1fpush %gspushl %esipushl %edipushl %ebppushl %eaxcall _copy_processaddl $20,%esp
1:  ret

这个函数功能就不具体讲了。从这里的探索我们也可以看出,操作系统通过系统调用,提供给用户态可用的功能,都暴露在 sys_call_table 里了。系统调用统一通过 int 0x80 中断来进入,具体调用这个表里的哪个功能函数,就由 eax 寄存器传过来,这里的值是个数组索引的下标,通过这个下标就可以找到在 sys_call_table 这个数组里的具体函数。
同时也可以看出,用户进程调用内核的功能,可以直接通过写一句 int 0x80 汇编指令,并且给 eax 赋值,当然这样就比较麻烦。
所以也可以直接调用 fork 这样的包装好的方法,而这个方法里本质也是 int 0x80 以及 eax 赋值而已。
这里再放一下刚才的图:
在这里插入图片描述

3.GCC内联汇编

可以参考如下文章:

https://blog.csdn.net/myprogram_player/article/details/121372941?ops_request_misc=&request_id=&biz_id=102&utm_term=内联汇编&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-1-121372941.142v87insert_down28,239v2insert_chatgpt&spm=1018.2226.3001.4187

大致格式如下:

asm volatile 
(           这里写指令 : 输出操作数                   /* 可选 */: 输入操作数                   /* 可选 */: 可能被破坏的寄存器列表         /* 可选 */);
或者
__asm__ __volatile__ 
(            这里写指令 : 输出操作数                   /* 可选 */: 输入操作数                   /* 可选 */: 可能被破坏的寄存器列表         /* 可选 */);

输入输出操作数格式:

[ [asmSymbolicName] ] constraint (cexpression)例如:
[a_val]"r"(a), [b_val]"r"(b)
"r"(a), "r"(b)

插入到C代码中的汇编语句是以:分隔的四个部分,第一部分是汇编代码本身,通常成为指令部。指令部是必须的,而其它部分可以根据实际情况而省略。 GCC采用如下方法来解决汇编代码中操作数如何与C代码中的变量相结合的问题:对寄存器的使用只需给出**“样板”和约束条件**,具体如何将寄存器与变量结合起来完全由GCC和GAS负责。具体而言就是:在指令部,加上前缀%的数字(如%0,%1)就是需要使用寄存器的**“样板”操作数。指令部中使用几个样板操作数,就表明有几个变量需要与寄存器相结合,这样GCC和GAS在编译和汇编时会根据后面给定的约束条件**进行恰当的处理。由于样板操作数也使用%作为前缀,因此寄存器名前面应该加上两个%,以免产生混淆。 紧跟在指令部后面的是输出部,是规定输出变量如何与样板操作数进行结合的条件,每个条件称为一个“约束”,必要时可以包含多个约束,相互之间用逗号分隔开就可以。每个输出约束都以’=‘号开始,然后紧跟一个对操作数类型进行说明的字后,最后是如何与变量相结合的约束。凡是与输出部中说明的操作数相结合的寄存器或操作数本身,在执行完嵌入的汇编代码后均不保留执行之前的内容,这是GCC在调度寄存器时所使用的依据。 输出部后面是输入部,输入约束与输出约束相似,但不带’='号。如果一个输入约束要求使用寄存器,则GCC在预处理时就会为之分配一个寄存器,并插入必要的指令将操作数装入该寄存器。与输入部中说明的操作数结合的寄存器或操作数本身,在执行完嵌入的汇编代码后也不保留执行之前的内容。 在内联汇编中用到的操作数从输出部的第一个约束开始编号,序号从0开始,每个约束计数一次。需要注意的是,内联汇编语句的指令部在引用一个操作数时总是将其作为32位的长字使用,但实际情况可能需要的是字或者字节,因此应该在约束中指明正确的限定符:

约束限定字符	含义
“a”	将输入变量放入eax
“b”	将输入变量放入ebx
“c”	将输入变量放入ecx
“d”	将输入变量放入edx
“S”	将输入变量放入esi
“D”	将输入变量放入edi
“q”	将输入变量放入eax,ebx ,ecx ,edx中的一个
“r”	将输入变量放入通用寄存器,也就是eax ,ebx,ecx,edx,esi,edi中的一个
“A”	放入eax和edx,把eax和edx,合成一个64位的寄存器(uselong longs)
“m”	内存变量
“o”	操作数为内存变量,但是其寻址方式是偏移量类型,也即是基址寻址,或者是基址加变址寻址
“V”	操作数为内存变量,但寻址方式不是偏移量类型
“,”	操作数为内存变量,但寻址方式为自动增量
“p”	操作数是一个合法的内存地址(指针)
“g”	将输入变量放入eax,ebx,ecx ,edx中的一个或者作为内存变量
“X”	操作数可以是任何类型
“I”	0-31 之间的立即数(用于32位移位指令)
“J”	0-63 之间的立即数(用于64 位移位指令)
“N”	0-255 ,之间的立即数(用于out 指令)
“i”	立即数
“n”	立即数,有些系统不支持除字以外的立即数,这些系统应该使用“n”而不是“i”
“=”	操作数在指令中是只写的(输出操作数)
“+”	操作数在指令中是读写类型的(输入输出操作数)
“f”	浮点数
“t”	第一个浮点寄存器
“u”	第二个浮点寄存器
“G”	标准的80387
%	该操作数可以和下一个操作数交换位置

实验具体流程

这里参考如下链接以及蓝桥云手册即可:

https://blog.csdn.net/leoabcd12/article/details/122268321?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168639094316800225524154%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=168639094316800225524154&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-1-122268321-null-null.142v88control,239v2insert_chatgpt&utm_term=%E5%93%88%E5%B7%A5%E5%A4%A7%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%AE%9E%E9%AA%8C&spm=1018.2226.3001.4187

有一些细节问题注意即可:
在这里插入图片描述
当出现这种问题时,需要进入bochs虚拟机(注意不是你的Linux虚拟机)中修改/usr/include/unistd.h中的调用设置调用号

#define __NR_whoami	   	72
#define __NR_iam	   	73     

很多时候出现 xxx.c:EMOENT 一般是你的文件并不存在导致的。
整体实现过程比较简单,这里就不具体讲了。

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

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

相关文章

排好队,一个一个来:宫本武藏教你学队列(附各种队列源码)

文章目录 前言:理解“队列”的正确姿势一个关于队列的小思考——请求处理队列的两大“护法”————顺序队列和链式队列数组实现的队列链表实现的队列 循环队列关于开篇,你明白了吗?最后说一句 前言: 哈喽!欢迎来到黑…

分享几个线上副业!!

线上副业 有哪些可以在线上运作就能赚取生活费的方式? 我这个暑假没有去打暑假工,因为疫情原因,一直待在家里,没有收入就不能出去玩,不能买漂亮衣服,就开始了一系列的线上兼职寻找,到处碰壁&…

学会Python如何去变现?副业月收入10000+了解一下

自学 Python 之后如果不去公司上班,自己一个人可以通过此技能挣什么钱? 逆天的Python,只要你掌握了相关技术,就可以靠它赚钱,具体怎么赚,我们来看看一位小哥哥的回答。 以我差不多四年的 Python 使用经验…

悟空问答赚钱副业项目,操作的好可月入10000+

我不知道你是否做过这种项目。这也是自媒体。如果你没有,你可以试试。收入不错。 有人可能会说悟空问答已经过时了。事实上,悟空问答每天仍能挣300元。好吧,我们已经取得了经验,在这里我们也与大家分享。 基本上,每个…

ChatGPT:编写一个带UI界面的计算器

代码: import tkinter as tkclass Calculator:def __init__(self, master):self.master mastermaster.title("Calculator")self.total tk.StringVar()self.entered_number tk.StringVar()self.entered_number.set(0)self.total.set(0)self.entry tk.…

使用Java进行编曲

一、编曲部分 1.1一丢丢乐理知识 简单普及下乐理哈,这样便于读谱 钢琴谱一行分两个部分 上面一行用右手弹(主奏); 下面一行用左手弹奏(伴奏)。 1.2 关于节奏 (1)、主奏与伴奏中支持输入的35个音符: 倍低音&#…

小米系列手机(包括红米,黑鲨)开启调试模式

1. 点击我的设备 进入设置主页面,点击我的设备,点击全部参数。 2. 点击MIUI版本 连续点击MIUI版本直到出现提示,开发者权限已开启。 3. 点击更多设置 返回设置,点击更多设置。 4. 查看信息 在更多设置中就能看到开发者选项。 …

小米手机超越苹果,成欧洲第二;马斯克特斯拉内部邮件:痛恨开会,少讲黑话;Spring 6.0 发布|极客头条...

「极客头条」—— 技术人员的新闻圈! CSDN 的读者朋友们早上好哇,「极客头条」来啦,快来看今天都有哪些值得我们技术人关注的重要新闻吧。 整理 | 梦依丹 出品 | CSDN(ID:CSDNnews) 一分钟速览新闻点&#…

MiPush四种推送对象

文档中心 推送对象目前支持四种:RegID、别名、userAccount、标签。 RegID:针对单一设备推送消息。应用调用MiPushClient类的静态方法registerPush注册小米推送服务,注册的结果将通过PushMessageReceiver继承类的onCommandResult方法和onRec…

小米正式宣布:这种手机以后买不到了…

开头先问大家一个问题,你的手机屏幕尺寸是多少? 还记得当初乔老爷子发布 iPhone 时,称 3.5 英寸是人手握持的最佳尺寸。 不过,当时苹果显然没有考虑到奥尼尔这样体格魁梧的人的使用感受... 3.5 英寸,4.0 英寸&#xf…

MIUI金凡回应用户反馈小米手机发热情况

本文转载自IT之家 IT之家 6 月 17 日消息 小米产品总监、MIUI 体验总负责人金凡近期称,已正式成立了“MIUI 先锋小组”,集中解决大家反馈的各类体验问题,做好首席客服小组。接下来会以报告的形式将工作进度发在小米社区中,请大家…

原来这样可以优雅地解决小米手机后台弹窗权限问题

/ 今日科技快讯 / 7月23日,据外媒报道,微软宣布将向总部位于美国旧金山的人工智能研究公司OpenAI投资10亿美元,为其云计算平台开发AI技术。 / 作者简介 / 本篇文章转载自nodzhang的博客,分享了他对于小米手机后台弹出界面…

时薪15美元的ChatGPT外包工人,干的都是苦力活

整理 | 朱珂欣 出品 | CSDN程序人生(ID:coder_life) 自 ChatGPT 去年 11 月发布以来,让不少打工人陷入担心失业的恐慌中,也解决了部分人的“就业问题”。 34 岁的 Alexej Savreux ,就是其中之一。 作为 …

AutoGPT:全自动的人工智能助手

让 GPT-4 为你实现一切! 随着人工智能技术的飞速发展,GPT-4 作为强大的人工智能语言模型成为了众多应用场景的核心。今天,我们将为你揭秘一款具有革命性意义的 GPT-4 应用——AutoGPT!一款让你轻松操控 GPT-4,实现各种…

文心一言、GPT3.5及GPT4的应用测评对比

省时查报告-专业、及时、全面的行研报告库 省时查方案-专业、及时、全面的营销策划方案库 【免费下载】2023年2月份热门报告合集 最新亲测国内可用ChatGPT使用教程(3分钟搞定) ChatGPT团队背景研究报告 ChatGPT的发展历程、原理、技术架构及未来方向 Cha…

看New Bing回答世纪难题:女友和妈妈掉水里先救谁

1.女友和妈妈掉水里先救谁 今天好奇想看看New Bing怎么回答这种世纪难题 结果New Bing非常聪明,反手建议我不要直接回答这个问题,而是换个角度哄女朋友,带着点不甘心,我继续追问它 New Bing还是耍起了滑头,我开始怀疑…

“一天宕机三次”,为什么高并发这么难?

受访者 | 陈皓 作者 | 屠敏 出品 | 《新程序员》编辑部 高并发,并不是一个新鲜的话题,全互联网公司为之“费尽心思”多年,仍然无法完全逃脱卡顿、崩溃乃至宕机的宿命。 这不近日,一款名为“羊了个羊”的小程序游戏突然爆火&am…

美图终于等来AIGC的春天

作者|陈 妍 编辑|大 风 人类历史上,经历过三次科技颠覆时刻。 第一次是上世纪90年代,PC互联网的兴起,开始把全世界连接到一起。1995年,以雅虎为代表的企业,开创免费门户网站的互联网行业商业…

UML建模都有那些图(架构师必刷)

分析&回答 统一建模语言(Unified Modeling Language,UML)又称标准建模语言。 UML从考虑系统的不同角度出发,定义了用例图、类图、对象图、包图、状态图、活动图、序列图、通信图、构件图、部署图等10种图。下图中红字部分为…

SWAT 建模与案例应用

SWAT 建模与案例应用 一、模型简介 SWAT模型是美国农业部(USDA)的农业研究中心开发的分布式水文模型。 主要目的是为了预测在大流域复杂多变的土壤类型、土地利用方式和管理措施条件下,土地管理对水分、泥沙和化学物质的长期影响。 近年来…