本文始作于2012年1月28日,刊登于人人网,于2013年2月13日迁移至此
昨天晚上一直不明白为什么sys_call_table[] = {}里面写上那几个函数名之后,这个数组里面存的就是函数地址了,今天下午,我又看了好几遍unistd.h还有《剖析》的解释,也没什么进展,我想既然书里面没提到,那肯定是理所当然的事情,什么才是理所当然的事呢,那应该就是语言本身的事吧。于是我就开始百度
“函数指针”,果然有了眉目,百科上说,函数指针的声明方法为:“函数类型 (标志符 指针变量名) (形参列表);”例如:
1 #include<stdio.h> 2 3 int max(int x,int y){ return(x>y?x:y); } 4 5 void main() 6 { 7 int (*ptr)(int, int); 8 int a,b,c; 9 ptr=max; 10 scanf("%d%d",&a,&b); 11 c=(*ptr)(a,b); 12 printf("a=%d,b=%d,max=%d",a,b,c); 13 }
原来函数指针的引用方法就是用函数名。怪不得sys_call_table[] = {}里面写上那几个函数名之后,这个数组里面存的就是函数地址了,因为函数名max里面就是函数的地址值。
在unistd.h编译的时候要跟linux-0.11/kernel下面的所有源文件一起编译,它现在上半部分把kernel里要用的函数都extern过来,然后再把他们写进sys_call_table[],这样里面储存的就是kernel里面编译的函数的真实地址了。当然还有最后一个问题就是传参的问题,不过我先把这块调好再说。一共要修改的是那么几个文件:
1.kernel/who.c,这个文件是我要自己写的。
2.include/unistd.h,把刚才写的两个函数在这里注册以一下,就是给他俩分配好号码。
3.include/sys.h,把这两个函数extern过来,然后在下面把他们两个填进指针索引数组sys_call_table[]。
4.Makefile,编译的时候别忘了编译我写的who.c,要不然全白玩。在OBJS后面加上who.o;在Makefile的### Dependencies中加入一行:who.s who.o: who.c ../include/linux/kernel.h ../include/unistd.h。
1 who.c: 2 /* 3 * linux/kernel/who.c 4 * 5 * Copyleft 2012 BlackSun2012 6 */ 7 8 #include <stdio.h> 9 10 int sys_iam(void); 11 int sys_whoiam(void); 12 13 int sys_iam(void) 14 { 15 printf("This is function sys_iam.\n"); 16 return 0; 17 } 18 19 int sys_whoiam(void) 20 { 21 printf("This is function sys_whoiam.\n"); 22 return 0; 23 }
make报错:
who.c:7:19: stdio.h: 没有那个文件或目录
who.c: In function `sys_iam':
who.c:14: warning: implicit declaration of function `printf'
make[1]: *** [who.o] 错误 1
make[1]:正在离开目录 `/home/spiderhunt2011/oslab/linux-0.11/kernel'
make: *** [kernel/kernel.o] 错误 2
我擦勒,这该不会是说我不能用C语言标准函数库吧...,我上网一百度,果然是这样的。他们说:
内核调用的函数是编译进内核的;
应用层调用的是应用层的库;
两者对应的头文件目录不一致,不能搞混了,不然即使函数同名,运行也会出错。
原因是 内核是第一个运行的,也就是说在内核运行时,没办法调入lib,
另外,内核一般大小要求,不可能把一个2M的C库给链接在一起。
<stdio.h> 不是内核的库啊。一个内核态,一个用户态的啊。
这是坑爹阿,还不能用标准库,那怎么输出信息阿,不让我亲自编写printf的汇编代码吧,不能都我写吧。还好,我往下一看实验报告,上面提到了这个问题,它说:
要知道到,printf()是一个只能在用户模式下执行的函数,而系统调用是在内核模式中运行,所以printf()不可用,要用printk()。那然后参照别的kernel/*.c代码,可以推断出,这时代码开头包含的文件应该是#include <linux/kernel.h>。这样代码就改成这样了:
/* * linux/kernel/who.c * * Copyleft 2012 BlackSun2012 */ #include <linux/kernel.h> int sys_iam(void); int sys_whoiam(void); int sys_iam(void) { printk("This is function sys_iam.\n"); return 0; } int sys_whoiam(void) { printk("This is function sys_whoiam.\n"); return 0; }
这样make就通过了。
但是运行起来根本运行不了,就好像什么都没改过一样,他一直提示__NR_iam没有定义,但是我在unistd.h里面明明写过了啊,于是到处检查,检查了能有3个小时,还是没有检查出来。我都又在服务器上下载另一个oslab了,擦算重头在做一份了,但是当我在重头做的时候我发现了问题在哪,还是实验报告没有看仔细,实验报告上面说:
该目录下的unistd.h是标准头文件(它和0.11源码树中的unistd.h并不是同一个文件,虽然内容可能相同),没有__NR_whoami和__NR_iam两个宏,需要手工加上它们,也可以直接从修改过的0.11源码树中拷贝新的unistd.h过来。
第一次看实验报告的时候每太注意这句话,因为也不怎么懂,第二遍写的时候突然领悟了,这个意思是说,我确实要改unistd.h的那两个宏,但是这个文件要在虚拟机里面改,在oslab的源代码里改是没有用的,另外还知道了,虚拟机外面的代码叫做“源码树”,看来实验报告一个字都不能落。另外还有,我刚才说就改4个文件也不对,第二次看实验指导书的时候发现还应该改一个文件,就是kernel/system_call.s,在这里面有个常量值nr_system_calls,指定了系统调用的总个数,原先是72,我要把它改成74才行,这个指示是在代码的注释中看到的,指导书果然写的很隐秘,看来以后指导书要一个字一个字扣才行啊,要读很多遍才能做出效果,总是有地方藏的很隐蔽,不仔细看真看不出来...。好了现在中间结果已经出来了,今天也不用干别的了,昨天还说今天要做两个实验呢,现在是11:30,抓紧时间把实验二最后一关过去再睡觉。不过看到努力了半天结果终于出来了,心理还是很高兴的,而且经历这次检查功力提升了不少,起码对linux的源码树熟悉了许多。
现在过实验二的最后一关 -- 跨界传参
测试宏展开通过:

初次检测通过,这次过得很顺利,连代码都是第一次编译就通过了的,之前都没有编译过,字符统计也输出正确,罕见的顺利,希望能持续下去:

先貼一下代码,不过还得改,指定错误类型的功能还没写,以注释代替:
1 /* 2 * linux/kernel/who.c 3 * 4 * Copyleft 2012 BlackSun2012 5 */ 6 #include <errno.h> 7 #include <linux/kernel.h> 8 #include <asm/segment.h> 9 10 #define MAX 23 11 12 char str[MAX + 1]; 13 14 int sys_iam(const char *name) 15 { 16 int i; 17 18 for (i = 0; i <= MAX; ++i) { 19 str[i] = get_fs_byte(name + i); 20 if (str[i] == '\0') 21 return i; 22 } 23 /* errno = EIVNAL; */ 24 return -1; 25 } 26 27 int sys_whoiam(char *name, unsigned int size) 28 { 29 int i; 30 31 for (i = 0; i < size; ++i) { 32 put_fs_byte(str[i], name + i); 33 if (str[i] == '\0') 34 return i; 35 } 36 /* errno = EIVNAL; */ 37 return -1; 38 }
不过接下来首先要解决的问题是怎么把外面的文件导入虚拟机的目录里面,我记得实验指导书上给过方法,我找找。
评判级测试,满分50%,首次测试得分40%,问题应该是在字符超限的判断上,代码的问题,操作系统上的应该已经通过了,现在好好检查一下程序设计,fix bug:

浙公网安备 33010602011771号