哈工大-操作系统-HitOSlab-李治军-实验2-系统调用

操作系统实验2:系统调用

实验内容请查看实验指导手册

  1. 实验需要掌握的知识点:
    如何使用用户函数调用系统函数、_syscall宏的含义以及作用、管理使用linux系统的基本知识
  2. 实验中添加和修改的文件
    (1)添加了:
    iam.c(路径:oslab/hdc/usr/root)
    whoami.c(路径:oslab/hdc/usr/root)
    who.c(路径:oslab/linux-0.11/kernel)
    (2)修改了:
    unistd.h(路径:oslab/hdc/include/unistd.h)
    system_call.s(路径:oslab/linux-0.11/kernel/system_call.s)
    sys.h(路径:oslab/linux-0.11/include/linux/sys.h)
    Makefile(路径:oslab/linux-0.11/kernel/Makefile)

绪论

我花了很长的时间,才搞清楚这个实验,干了一件什么事情:

  • 在用户层面写了两个程序iam.c和whoami.c,通过syscall这个宏开启调用系统函数的窗口,调用sys_iam以及sys_whoami系统函数。
  • 编写了两个函数sys_iam以及sys_whoami,以who.c的文件格式保存在了linux0.11系统的内核(kernel)中;
  • sys_iam调用了一个名为get_fs_byte()的系统函数实现数据读取输入,sys_whoami则调用了put_fs_byte()这个系统函数完成对数据的打印输出。
  • 整个实验就是作了一件这么简单的事情,至于修改了Makefile等文件的复杂操作,可以理解为让上述过程实现的辅助过程。

下面是具体的实验过程:

一、实验内容

1.编写iam以及whoami程序

iam.c的代码

#define __LIBRARY__
#include <unistd.h>
//_syscall1宏展开后是一个调用系统函数的函数
_syscall1(int, iam, const char*, name);
int main(int argc,char ** argv)
{
    
    
	int wlen = 0;
	if(argc<1)
	{
    
    
		printf("not enough arguments!\n");
		return -2;
	}
	wlen = iam(argv[1]);
	return wlen;
}

whoami.c的代码

#define __LIBRARY__
#include <unistd.h>
//_syscall2是宏,宏展开后是一个调用系统函数的函数
_syscall2(int, whoami,char*,name,unsigned int,size);

int main()
{
    
    
	char s[30];
	int rlen = 0;
	rlen = whoami(s,30);//这里调用了_syscall2写的whoami函数
	printf("%s\n",s);
	return rlen;
}

两个函数的编写参考了linux0.11中close.c函数。内核中编写完成后,保存在oslab/hdc/usr/root。通过ubuntu宿主机与虚拟机的文件交换,交换方式如下,才可将编写好的文件放到虚拟机中。

补充:宿主机ubuntu和虚拟机linux0.11的文件交换。
在linux0.11没有运行时,通过cd ~/oslab/,sudo ./mount-hdc。挂载内核的根文件系统镜像文件到ubuntu然后通过cd /oslab/hdc进入目录读写文件。
读写完毕,结束挂载通过cd ~/oslab/,sudo umount hdc。这时才可开启Bochs虚拟机

2.在内核的include/unistd.h添加系统调用号

为了实现自己编写的系统调用,我们需要手动实现 __NR_XXXXX 。就是系统调用的编号,别急着停止挂载,在 /home/shiyanlou/oslab/hdc/include/unistd.h中操作。操作结束后,可以短暂的停止挂载了。

#define __NR_close    6
/*
所以添加系统调用时需要修改include/unistd.h文件,
使其包含__NR_whoami和__NR_iam。
*/
//我的添加:
#define __NR_whoami   72
#define __NR_iam      73

3.修改系统调用表和调用总数

sys_call_table ,为系统调用表。在linux-0.11/kernel/system_call.s中,因为我们添加了两个系统调用,所以将nr_system_calls加2,修改系统调用总数。

# offsets within sigaction
sa_handler = 0
sa_mask = 4
sa_flags = 8
sa_restorer = 12

nr_system_calls = 74

sys_call_table是一个函数指针数组的起始地址,它定义在 include/linux/sys.h 中。增加实验要求的系统调用,需要在这个函数表中增加两个函数引用 ——sys_iamsys_whoami。当然该函数在 sys_call_table数组中的位置必须和__NR_xxxxxx的值对应上。
同时还要仿照此文件中前面各个系统调用的写法,加上extern int sys_whoami();以及extern int sys_iam();
在这里插入图片描述

4.实现内核函数

添加系统调用的最后一步,是在内核中实现函数 sys_iam()sys_whoami()
who.c代码如下:

#define __LIBRARY__
#include <asm/segment.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

char msg[24]; //23个字符 +'\0' = 24

int sys_iam(const char * name)
/***
function:将name的内容拷贝到msg,name的长度不超过23个字符
return:拷贝的字符数。如果name的字符个数超过了23,则返回“­-1”,并置errno为EINVAL。
****/
{
    
    
	int i;
	//临时存储 输入字符串 操作失败时不影响msg
	char tmp[30];
	for(i=0; i<30; i++)
	{
    
    
		//从用户态内存取得数据
		tmp[i] = get_fs_byte(name+i);
		if(tmp[i] == '\0') break;  //字符串结束
	}
	//printk(tmp);
	i=0;
	while(i<30&&tmp[i]!='\0') i++;
	int len = i;
	// int len = strlen(tmp);
	//字符长度大于23个
	if(len > 23)
	{
    
    
		// printk("String too long!\n");
		return -(EINVAL);  //置errno为EINVAL  返回“­-1”  具体见_syscalln宏展开
	}
	strcpy(msg,tmp);
	//printk(tmp);
	return i;
}

int sys_whoami(char* name, unsigned int size)
/***
function:将msg拷贝到name指向的用户地址空间中,确保不会对name越界访存(name的大小由size说明)
return: 拷贝的字符数。如果size小于需要的空间,则返回“­-1”,并置errno为EINVAL。
****/
{
    
     	
	//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;
}

who.c文件保存在oslab/linux-0.11/kernel中。

5.修改Makefile文件

要想让我们添加的 kernel/who.c 可以和其它 Linux 代码编译链接到一起,必须要修改 Makefile 文件。

Makefile 在代码树中有很多,分别负责不同模块的编译工作。我们要修改的是 kernel/Makefile。需要修改两处。
第一处:

OBJS  = sched.o system_call.o traps.o asm.o fork.o \
        panic.o printk.o vsprintf.o sys.o exit.o \
        signal.o mktime.o

改为:

OBJS  = sched.o system_call.o traps.o asm.o fork.o \
        panic.o printk.o vsprintf.o sys.o exit.o \
        signal.o mktime.o who.o

添加了 who.o

第二处:

### Dependencies:
exit.s exit.o: exit.c ../include/errno.h ../include/signal.h \
  ../include/sys/types.h ../include/sys/wait.h ../include/linux/sched.h \
  ../include/linux/head.h ../include/linux/fs.h ../include/linux/mm.h \
  ../include/linux/kernel.h ../include/linux/tty.h ../include/termios.h \
  ../include/asm/segment.h

改为:

### Dependencies:
who.s who.o: who.c ../include/linux/kernel.h ../include/unistd.h
exit.s exit.o: exit.c ../include/errno.h ../include/signal.h \
  ../include/sys/types.h ../include/sys/wait.h ../include/linux/sched.h \
  ../include/linux/head.h ../include/linux/fs.h ../include/linux/mm.h \
  ../include/linux/kernel.h ../include/linux/tty.h ../include/termios.h \
  ../include/asm/segment.h

添加了 who.s who.o: who.c ../include/linux/kernel.h ../include/unistd.h

修改了Makefile后,在终端中进入oslab/linux0.11,输入make all,进行编译。正确的编译最后一行内容为sync。如果你的编译后出现了错误信息,请根据错误信息进行修改,实在不行的话建议从头开始重做一遍。

6.编译运行程序

在终端中退回到oslab/,输入命令sudo umount hdc退出挂载。输入命令./run启动bochs。进入bochs后在输入框中依次输入gcc -o iam iam.c -Wallgcc -o whoami whoami.c -Wall

接着,在输入框中输入:

./iam f**kinglab2
./whoami

如果一切正常的话,将会得到如下图所示的结果:
在这里插入图片描述

至此完成对两个程序的编译。

二、程序测试

Linux 的一大特色是可以编写功能强大的 shell 脚本,提高工作效率。本实验的部分评分工作由testlab2.c和脚本 testlab2.sh 完成。它的功能是测试 iam.cwhoami.c

首先将 iam.cwhoami.c 在linux0.11下分别编译成 iamwhoami(如果实验1做过就不用了),然后将 testlab2.shtestlab2.c(在 /home/teacher 目录下) 拷贝到虚拟机目录oslab/hdc/usr/root
拷贝后需要卸载挂载,启动bochs

cd ~/oslab
//卸载挂载
sudo umount hdc
//启动bochs
./run

在bochs中输入如下命令

gcc -o testlab2 testlab2.c
sync
./testlab2

结果如图:
在这里插入图片描述

8个pass,这50分拿到了。

再执行testlab2.sh

在bochs中用下面命令为此脚本增加执行权限:

$ chmod +x testlab2.sh

然后运行之:

$ ./testlab2.sh

结果如图:
在这里插入图片描述30分又到手了

三、回答问题

  • Q1:从 Linux 0.11 现在的机制看,它的系统调用最多能传递几个参数?你能想出办法来扩大这个限制吗?

    • 最多传递3个参数。从_syscall3(type,name,atype,a,btype,b,ctype,c)可看出。Linux-0.11的系统调用通过寄存器ebx、ecx、edx传递参数,最多能传递3个参数。
  • Q2:用文字简要描述向 Linux 0.11 添加一个系统调用 foo() 的步骤

    • include/unistd.h中添加系统调用号
    • 在kernel/system_call.s修改总调用数
    • include/linux/sys.h添加调用
    • 修改kernel/Makefile
    • 在内核文件中实现foo.c
    • make all重新编译系统,使用gcc编译用户态程序

四、总结

系统调用的分析
1.初始化:
在这里插入图片描述2.调用流程(以iam()函数为例):
在这里插入图片描述

参考文献:
1.(浓缩+精华)哈工大-操作系统-MOOC-李治军教授-实验2-系统调用
2.蓝桥云课-操作系统原理与实践
3.哈工大操作系统实验手册
4.哈工大实验“官方”github仓库
5.哈工大操作系统实验课——系统调用(lab3)
6.操作系统实验报告-系统调用

猜你喜欢

转载自blog.csdn.net/qq_42518941/article/details/119037501