写在前面:参考了很多人写的实验流程,自己在做的时候路径给我干蒙了,很多前人的文件路径写的很不清晰,尤其是挂载是用来做什么的,两个文件要先挂载才能在hdc目录里找到子路径,这点东西让我忙乎了一下午,留一个正确的火炬
一. 实验流程
1. 编写iam.c以及whoami.c代码(保存路径oslab/hdc/usr/root,需挂载后进入目录路径编写)
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;
}
挂载:oslab/路径下执行:sudo ./mount-hdc
解除挂载:sudo umount hdc
注:挂载状态才能进入oslab/hdc/usr/root路径,解除挂载后才能开启bochs虚拟机。
2. 在内核的include/unistd.h添加系统调用号
在oslab/hdc/usr/include/unistd.h文件中添加两行:(新建两个系统调用)
#define __NR_whoami 72
#define __NR_iam 73
3. 修改系统调用表和调用总数
在oslab/linux-0.11/kernel/system_call.s中将nr_system_calls加2,nr_system_calls表示系统调用总数。
nr_system_calls = 72
nr_system_calls = 74
sys_call_table是一个函数指针数组的起始地址,所在地址为oslab/linux-0.11/include/linux/sys.h,需要在这个函数表中增加我们定义的两个函数引用:sys_iam、sys_whoami。方式如下:
4. 实现内核函数
在oslab/linux-0.11/kernel编写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”
}
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;
}
5. 修改Makefile文件
修改oslab/linux-0.11/kernel/Makefile
第一处:
第二处:
6. 至此完成全部配置,在oslab/linux0.11路径下make all
7. 编译运行程序
输入sudo umount hdc
进行卸载挂载,然后输入./dbg-bochs
打开bochs页面,依次输入下列命令进行编译:
gcc -o iam iam.c -Wall
gcc -o whoami whoami.c -Wall
然后分别输入
./iam mc
./whoami
./iam 12345678901234567890123456789
./whoami
两次均显示mc,说明第一次运行成功;第二次输入超过最长字符长度,故没有更改储存值,仍返回mc。
依次输入对两个测试集分别进行编译、增加执行权限。
gcc -o testlab2 testlab2.c -Wall
chmod +x testlab2.sh
最后输入以下命令运行:
./testlab2
./testlab2.sh
运行结果如下图所示,全部通过!
二. 回答以下问题
1. 从 Linux 0.11 现在的机制看,它的系统调用最多能传递几个参数?
最多传递3个参数。从_syscall3(type,name,atype,a,btype,b,ctype,c)可看出。Linux-0.11的系统调用通过寄存器ebx、ecx、edx传递参数,最多能传递3个参数。
2. 你能想出办法来扩大这个限制吗?
- 使用堆栈传递参数:可以使用堆栈来传递更多的参数。在参数传递方式中将参数放入堆栈中,以便在系统调用期间访问更多的参数。
- 使用特殊寄存器或数据结构:引入额外的寄存器或数据结构(如struct)来传递更多的参数,其中包含多个字段,每个字段对应一个系统调用的参数。
- 寄存器组合:通过寄存器组合传递更多的数据,这种方式利用寄存器中的部分位来表示更多的参数
- 寄存器拆分:将寄存器拆分为高位和低位传递一直比较小的参数。
- 寄存器重用:如果系统调用的实现允许,可以重新利用一些寄存器来传递更多的参数。
3. 用文字简要描述向 Linux 0.11 添加一个系统调用 foo() 的步骤。
- 编写系统调用函数:首先要编写实现新系统调用功能的函数。命名为 sys_foo() 或类似的命名,这个函数将执行所需的操作,确保函数按照内核规范编写,并正确处理参数、返回值等。
- 更新系统调用表:在 sys_call_table 中添加一个新条目,用于指向新系统调用函数。sys_call_table 是一个数组,存放了系统调用函数指针的地址。
- 更新系统调用号:在 include/linux/sys.h 文件中,给系统调用分配一个唯一的系统调用号,需要为新系统调用分配一个未被使用的数字。
- 更新系统调用数量:在 kernel/sys.c 文件中的 NR_SYSCALLS 宏中增加系统调用的数量,确保将新系统调用的数目添加到系统调用总数中。
- 更新系统调用的汇编文件:如果新系统调用需要不同的寄存器传递参数或者与现有的系统调用不同,则需要更新相关的汇编文件,确保参数正确传递给你的新系统调用函数。
- 重新编译内核:修改内核后,需要重新编译内核源代码,并生成新的内核映像文件。
- 测试新系统调用:将新的内核映像文件引导到系统中,并测试新添加的系统调用功能。确保它能够按照预期工作,并与现有的系统调用协同运行。