oslab操作系统实验2:系统调用
作者:互联网
系列文章目录
文章目录
一 理论基础
操作系统接口
消息处理机制
系统调用的实现
系统调用的过程如下:
-
应用程序调用库函数(API);
-
API 将系统调用号存入 EAX,然后通过中断调用使系统进入内核态;
-
内核中的中断处理函数根据系统调用号,调用对应的内核函数(系统调用);
-
系统调用完成相应功能,将返回值存入 EAX,返回到中断处理函数;
-
中断处理函数返回到 API 中;
-
API 将 EAX 返回给应用程序。
以close函数的实现为例看看系统调用的实现
lib/close.c文件中的内容:
#define __LIBRARY__
#include <unistd.h>
/*这是一个宏 在上面那个头文件中有定义*/
_syscall1(int, close, int, fd)
宏展开之后,内容如下
int close(int fd)
{
long __res;
__asm__ volatile ("int $0x80"
: "=a" (__res)
: "0" (__NR_close),"b" ((long)(fd)));
/*将__NR_close存入EAX,将参数fd存入EBX,然后进行0x80中断调用*/
/*调用返回后,从EAX取出返回值,存入__res,在通过对返回值的判断决定返回API怎样的返回值*/
if (__res >= 0)
return (int) __res;
errno = -__res;
return -1;
}
__NR_close就是系统调用的编号,在include/unistd.h中定义。
所以,在添加我们的系统调用时,一定要修改unistd.h这个文件。在用户空间,也要添加对应的系统调用。
内核的中断处理
在内核初始化时,主函数(在 init/main.c 中,Linux 实验环境下是 main())调用了sched_init()初始化函数。
void sched_init(void)
{
// ……
set_system_gate(0x80,&system_call);
}
这个函数的作用就是填写 IDT(中断描述符表),将 system_call 函数地址写到 0x80 对应的中断描述符中,也就是在中断 0x80 发生后,自动调用函数 system_call。
system_call.s是一个纯汇编函数。
!……
! # 这是系统调用总数。如果增删了系统调用,必须做相应修改
nr_system_calls = 72
!……
.globl system_call
.align 2
system_call:
! # 检查系统调用编号是否在合法范围内
cmpl \$nr_system_calls-1,%eax
ja bad_sys_call
push %ds
push %es
push %fs
pushl %edx
pushl %ecx
! # push %ebx,%ecx,%edx,是传递给系统调用的参数
pushl %ebx
! # 让ds, es指向GDT,内核地址空间
movl $0x10,%edx
mov %dx,%ds
mov %dx,%es
movl $0x17,%edx
! # 让fs指向LDT,用户地址空间
mov %dx,%fs
call sys_call_table(,%eax,4)
pushl %eax
movl current,%eax
cmpl $0,state(%eax)
jne reschedule
cmpl $0,counter(%eax)
je reschedule
关键是call sys_call_table(,%eax,4)这一句。
sys_call_table是一个函数指针数组的起始地址,定义如下:
在内核中实现我们的sys函数
每个系统调用都有一个sys_xxxx与其对应,在kernel中添加对应的函数定义即可。
在用户态和核心态之间传递数据
在#include/asm/segment.h中,有以下两个函数:
分别能从用户空间获得一字节的数据到内核空间,和从内核空间中拷贝一字节数组到用户空间。
Ubuntu和linux0.11之间的文件交换
oslab 下的 hdc-0.11-new.img 是 0.11 内核启动后的根文件系统镜像文件,相当于在 bochs 虚拟机里装载的硬盘。在 Ubuntu 上访问其内容的方法是:
cd ~/oslab/
sudo ./mount-hdc
读写完毕,不要忘了卸载这个文件系统:
sudo umount hdc
经过 sudo ./mount-hdc 这样处理以后,我们可以在 Ubuntu 的 hdc 目录下创建一个 xxx.c 文件,然后利用 Ubuntu 上的编辑工具(如 gedit 等)实现对 xxx.c 文件的编辑工作,在编辑保存以后。
执行 sudo umount hdc 后,再进入 Linux 0.11(即 run 启动 bochs 以后)就会看到这个 xxx.c(即如下图所示),这样就避免了在 Linux 0.11 上进行编辑 xxx.c 的麻烦,因为 Linux 0.11 作为一个很小的操作系统,其上的编辑工具只有 vi,使用起来非常不便。
二 实验过程
实验任务
1.第一个系统调用是 iam(),其原型为:
int iam(const char * name);
完成的功能是将字符串参数 name 的内容拷贝到内核中保存下来。要求 name 的长度不能超过 23 个字符。返回值是拷贝的字符数。如果 name 的字符个数超过了 23,则返回 “-1”,并置 errno 为 EINVAL。
在 kernal/who.c 中实现此系统调用。
2.第二个系统调用是 whoami(),其原型为:
int whoami(char* name, unsigned int size);
它将内核中由 iam() 保存的名字拷贝到 name 指向的用户地址空间中,同时确保不会对 name 越界访存(name 的大小由 size 说明)。返回值是拷贝的字符数。如果 size 小于需要的空间,则返回“-1”,并置 errno 为 EINVAL。
也是在 kernal/who.c 中实现。
实验步骤
创建kernel/who.c
修改Makefile
who.c文件的实现
#include <string.h>
#include <errno.h>
#include <asm/segment.h>
char msg[24]; /*存放字符串参数name*/
int iam(const char *name)
{
int i;
char tmp[30];
for (i = 0; i < 30; i++)
{
tmp[i] = get_fs_byte(name + i);
if (tmp[i] == '\0')
{
break;
}
}
int len = strlen(tmp);
if (len > 23)
{
return -(EINVAL);
}
strcpy(msg, tmp);
return len;
}
int whoami(char *name, unsigned int size)
{
//msg的长度大于 size
int len = 0;
for (; msg[len] != '\0'; len++)
;
if (len > size)
{
return -(EINVAL);
}
int i = 0;
//把msg 输出至 name
for (i = 0; i < size; i++)
{
put_fs_byte(msg[i], name + i);
if (msg[i] == '\0')
break; //字符串结束
}
return i;
},
修改kernel的Makefile,将who.c与内核其它代码编译链接到一起。最后在linux-0.11 目录下make
之后启动linux系统,编写iam.c 和 whoami.c 测试 ,满分, 结束
#include <errno.h>
#define __LIBRARY__
#include <unistd.h>
#include <stdio.h>
_syscall1(int , iam, const char *, name);
int main(int argc, char ** argv)
{
iam(argv[1]);
return 0;
}
#include <errno.h>
#define __LIBRARY__
#include <unistd.h>
_syscall2(int, whoami,char*,name,unsigned int,size);
int main()
{
char s[30];
whoami(s,30);
printf("%s",s);
return 0;
}
总结
拖了好几天,终于做完了这一个lab,感到心力交瘁的同时也有着说不出的满足感。
通过这次实验,可算了解啥是系统调用了,真是有点佩服linus了,能写出来这么复杂的东西。
下一个实验是进程和线程系列,冲!
标签:__,调用,操作系统,int,oslab,call,内核,name 来源: https://blog.csdn.net/weixin_43410946/article/details/118254748