实现 sys_iam() 和 sys_whoami()
添加系统调用的最后一步,是在内核中实现函数 sys_iam() 和 sys_whoami()。
每个系统调用都有一个 sys_xxxxxx() 与之对应,它们都是我们学习和模仿的好对象。
比如在 fs/open.c 中的 sys_close(int fd):
int sys_close(unsigned int fd) { // …… return (0); }
它没有什么特别的,都是实实在在地做 close() 该做的事情。
所以只要自己创建一个文件:kernel/who.c,然后实现两个函数就万事大吉了。
按照上述逻辑修改相应文件
通过上文描述,我们已经理清楚了要修改的地方在哪里
1.添加iam和whoami系统调用编号的宏定义(_NR_xxxxxx),文件:include/unistd.h
2.修改系统调用总数, 文件:kernel/system_call.s
3.为新增的系统调用添加系统调用名并维护系统调用表,文件:include/linux/sys.h
4.为新增的系统调用编写代码实现,在linux-0.11/kernel目录下,创建一个文件 who.c
#include <asm/segment.h> #include <errno.h> #include <string.h> char _myname[24]; int sys_iam(const char *name) { char str[25]; int i = 0; do { // get char from user input str[i] = get_fs_byte(name + i); } while (i <= 25 && str[i++] != '\0'); if (i > 24) { errno = EINVAL; i = -1; } else { // copy from user mode to kernel mode strcpy(_myname, str); } return i; } int sys_whoami(char *name, unsigned int size) { int length = strlen(_myname); printk("%s\n", _myname); if (size < length) { errno = EINVAL; length = -1; } else { int i = 0; for (i = 0; i < length; i++) { // copy from kernel mode to user mode put_fs_byte(_myname[i], name + i); } } return length; }
修改 Makefile
要想让我们添加的 kernel/who.c 可以和其它 Linux 代码编译链接到一起,必须要修改 Makefile 文件。
Makefile 里记录的是所有源程序文件的编译、链接规则,《注释》3.6 节有简略介绍。我们之所以简单地运行 make 就可以编译整个代码树,是因为 make 完全按照 Makefile 里的指示工作。
Makefile 在代码树中有很多,分别负责不同模块的编译工作。我们要修改的是 kernel/Makefile。需要修改两处。
(1)第一处
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。
(2)第二处
### 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 修改后,和往常一样 make all 就能自动把 who.c 加入到内核中了。
编写测试程序
到此为止,内核中需要修改的部分已经完成,接下来需要编写测试程序来验证新增的系统调用是否已经被编译到linux-0.11内核可供调用。首先在oslab目录下编写iam.c,whoami.c
/* iam.c */ #define __LIBRARY__ #include <unistd.h> #include <errno.h> #include <asm/segment.h> #include <linux/kernel.h> _syscall1(int, iam, const char*, name); int main(int argc, char *argv[]) { /*调用系统调用iam()*/ iam(argv[1]); return 0; }
/* whoami.c */ #define __LIBRARY__ #include <unistd.h> #include <errno.h> #include <asm/segment.h> #include <linux/kernel.h> #include <stdio.h> _syscall2(int, whoami,char *,name,unsigned int,size); int main(int argc, char *argv[]) { char username[64] = {0}; /*调用系统调用whoami()*/ whoami(username, 24); printf("%s\n", username); return 0; }
以上两个文件需要放到启动后的linux-0.11操作系统上运行,验证新增的系统调用是否有效,那如何才能将这两个文件从宿主机转到稍后虚拟机中启动的linux-0.11操作系统上呢?这里我们采用挂载方式实现宿主机与虚拟机操作系统的文件共享,在 oslab 目录下执行以下命令挂载hdc目录到虚拟机操作系统上。
sudo ./mount-hdc
再通过以下命令将上述两个文件拷贝到虚拟机linux-0.11操作系统/usr/root/目录下,命令在oslab/目录下执行:
cp iam.c whoami.c hdc/usr/root
如果目标目录下存在对应的两个文件则可启动虚拟机进行测试了。
编译
[/usr/root]# gcc -o iam iam.c [/usr/root]# gcc -o whoami whoami.c
运行测试
[/usr/root]# ./iam wcf [/usr/root]# ./whoami
命令执行后,很可能会报以下错误:
这代表虚拟机操作系统中/usr/include/unistd.h文件中没有新增的系统调用调用号
为新增系统调用设置调用号
#define __NR_whoami 72 #define __NR_iam 73
再次执行:
实验成功
为什么这里会打印2次?
因为在系统内核中执行了 printk() 函数,在用户模式下又执行了一次 printf() 函数。
要知道到,printf() 是一个只能在用户模式下执行的函数,而系统调用是在内核模式中运行,所以 printf() 不可用,要用 printk()。
printk() 和 printf() 的接口和功能基本相同,只是代码上有一点点不同。printk() 需要特别处理一下 fs 寄存器,它是专用于用户模式的段寄存器。
天道酬勤
实验三总共花费7小时,看的不是特别仔细,没有特别深入的学习宏展开和内联汇编。但基本理解了系统调用的目的和方式,Linus永远的神!