实验名称:实验6:编译内核和增加Linux系统调用
实验目的
- 1、熟悉Linux编译内核的过程和方法
- 2.熟悉系统调用的流程
实验内容
本实验由两部分组成。 第一部分仅仅要求编译一个干净的内核且加载成功,并不需要对内核修改。 第二部分是修改编译成功的核心,为用户提供新的系统调用,扩大系统服务。 实现系统调用psta,获取过程中的一些信息。原型如下:int psta(struct pinfo *buf); 参数buf用于存储过程信息的缓冲区。 pinfo定义如下:
struct pinfo {
int nice;/*进程的nice值*/ pid_t pid;/*进程ID*/ uid_t uid;/*用户的过程所有者ID*/ };
系统调用成功时返回值为0,系统调用失败时返回错误码EFAULT,表示 buf 指向非法地址空间。误码定义见核头文件asm-generic/errno
和asm-generic/errno-base.h。
实验环境
- VMware
- Fedora7
实验作业
一、Fedora下内核编译
第一步,下载内核
首先,我们应该决定编译哪个版本的核心。一般来说,待编译的核心版本(以下所称新核心)不低于当前正在运行的核心版本。如果两个版本之间的差距很大,则可能需要更新 gcc,binutils和 modutils编译工具等。我们只需要采用Linux发行版对应的内核版本可以使所有内核编译工具现成,无需更新,无疑减少了不必要的麻烦。首先查看当前环境下的核版本号:  可以看出,本机内核版本号为2.6.23.17,在http://www.kernel/org/pub/linux/kernel/v2.6
下载相应的内核压缩包。 本实验采用2.6.21.tar.gz,下载到桌面后,移动到/usr/src,并解压。   解压完成后,进入目录观察:  
第二步是生成内核配置文件.config
Linux.核心代码非常大,适用于许多系统结构,包括大量的驱动程序。用户在生成核心时应根据实际情况进行配置。所有配置都将保存在核心代码树的顶级目录中.config 在配置文件中。在核编译过程中,生成正确的配置文件是至关重要的一步。 配置文件可以从零开始生成,但没有必要。由于目前正在运行的核心已经有了相应的配置文件,该文件在/boot在目录中,使用它作为新内核配置文件的模板无疑是更好的方法。因此,我们将配置文件复制到/usr/src/linux-2.6.21.7目录下,命令如下:
make mrproper cp /boot/config-`uname -r` ./.config
介绍:`uname -r`
uname 可显示电脑以及操作系统的相关信息。 -r或–release 显示操作系统的发行编号。 方便我们进入相关目录。 第一个命令make mrproper用来保证内核树干净。如果内核树已经编译,则该命令有效。如果内核树是第一次编译,则可以省略该命令。  虽然现在有了模板,但是.config文件的配置不一定包括新内核的所有编译选项(因为新内核可能是更新版本,比如2.6.可使用以下命令:
make oldconfig
 该命令读取.config文件并根据新内核版本更新它。具体过程是这样的,该命令输出新内核所有的配置项,如果配置项已经在.config中有设置,则输出设置值;如果是新项,程序会停下来要求用户输入设置值,值的具体含义见附录C。用户输入值后程序继续运行直到所有配置项处理完毕。用户也可以使用如下命令:
make silentoldconfig
该命令的功能和“make oldconfig”相似,不过它不输出信息,除非是新选项需要用户输入的时候。到现在为止,生成的.config 文件就可以使用了,进入第3步。 
第三步、编译和安装新内核
在编译内核之前,还可以定义用户自己的内核版本号,这样做是为了便于识别。在内核代码树的根目录下有文件Makefile  在编译内核之前,还可以定义用户自己的内核版本号,这样做是为了便于识别。在内核代码树的根目录下有文件Makefile,它的前4行是:
VERSION =2
PATCHLEVEL=6
SUBLEVEL =21
EXTRAVERSION =
把第4行改成EXTRAVERSION = .7-ywl,这样新内核版本号就是2.6.21.7-ywl。 
然后执行下面三个命令:
make all
make modules_install
make install
make all
将生成期望的内核映像及模块    make modules_install
将安装模块到“默认目录/lib/modules/<内核版本号>”下面。  make install
最终将内核映像等几个文件复制到“/boot”目录,并修改引导程序的配置以启用该新内核。 
如果上述三个命令均执行成功,可以观察到引导程序grub的配置文件/boot/grub/menu.Ist的文件内容:  为了以后能直接操作菜单,把 hiddenmenu那一行注释掉(行的最前面加一个“#”字符即可)或删除,为了方便反应,我将菜单出现的时间调整为20。  重启,启动新内核 
发现新内核在其中,选择并启用   启动完毕,正常打开 
二、添加past系统调用
若系统调用名为xxx,则内核对应的实现函数名一般为sys_xxx,据此我们把 psta系统调用对应的内核函数命名为 sys_psta。下面假定当前工作目录是/usr/src/linux-2.6.21.7,给出添加系统调用psta的基本过程。
(1)调整arch/i386/kernel/syscall_table.S
在文件 arch/i386/kernel/syscall_table.S 的尾部加上要新增的系统调用函数名称,如阴影行所示,注释中320表示它的系统调用号。 进入目录:  打开文件:  进行修改

(2)在include/linux目录下添加头文件psta.h
#ifndef _LINUX_PSTA_H
#define _LINUX_PSTA_H
struct pinfo
{
int nice;
pid_t pid;
uid_t uid;
};
#endif

(3)在kernel目录下新建文件psta.c
在该文件中实现sys_psta函数
#include <linux/linkage.h>
#include <linux/psta.h>
#include <linux/unistd.h>
#include <linux/types.h>
#include <linux/kernel.h>
asmlinkage int sys_psta( struct pinfo* buf)
{
buf->pid=current->pid;
buf->uid=current->uid;
buf->nice=10;
return 0;
}
宏asmlinkage
定义在文件 linux/linkage.h中,表示函数的参数通过栈传递,而不是通过寄存器,所有系统调用都遵循这种参数传递方式。 sys_psta的实现非常简单,无非就是把task_struct结构中的几个成员复制到用户态空间。值得一提的是,nice
表示进程的优先级,取值范围为[-20,19],数值越低表示优先级越高。内核没有直接存储nice值,而是通过一个简单的变换后将它存放在 task_struct结构的static_prio成员中。两者之间的转换关系见下面几个宏(位于文件 kernel/sched.c中),其中MAX_RT_PRIO值为100。
#define NICE_TO_PRIO(nice) (MAX_RT_PRIO +(nice)+ 20)
#define PRIO_TO_NICE(prio) ((prio) - MAX_RT_PRIO - 20)
#define TASK_NICE(p) PRIO_TO_NICE((p)->static_prio)
(4)修改文件 kernel/Makefile
使psta.c能在内核编译时可见。Makefile 中有如下一行:
obj-y= sched.o fork.o exec_domain.o panic.o printk.o profile.o\
添加psta.o到该行中:
obj-y = psta.o sched.o fork.o exec_domain.o panic.o printk.o profile.o\
  值得说明的是,第2步中sys_psta的实现不一定要放在一个新文件中,例如文件kernel/sys.c也许就是添加sys_psta系统调用的合适位置,这样第3步就没有必要了。
(5)修改include/asm-i386/unistd.h
在include/asm-i386/unistd.h里面加上系统调用号的宏定义,在该文件中有如下几行:
#define_NR_epoll_pwait319
#ifdef _KERNEL__
#define NR_syscalls 320
可以看到,按照惯例系统调用号的宏名以“_NR_
”开头,而其后跟着的数值则是系统调用号。此外,NR_syscalls
表示的值应该是最大的系统调用号加一。所以修改后的内容如下:   
(6)修改include/linux/syscalls.h
加上函数sys_psta的声明。在该文件的首部添加一行: #include <linux/psta.h>
在该文件的最后一行“#endif”之前添加一行: asmlinkage int sys_psta(struct pinfo *buf);
  
(7)重新编译内核。
这里特别要提醒初学者, 因为我们已经有了正确的.config,最好备份一份到别的目录下以防被删除。上一次编译内核时已经在内核目录下生成了许多中间文件,所以本次编译内核之前要删除这些文件。这可以使用如下命令:
make mrproper
该命令连.config文件都会删除,所以等命令执行完后需要把备份的.config文件复制回来,然后执行前面的第3步就可以了。因为新内核包含了用户自己的代码,所以很可能会在“ makeall”时因编译出错而停止,根据错误提示信息修改错误后,可以重复本步骤。 清空上次编译的中间文件: 

三、简化实现
测试一
调用系统函数输出hello, 操作与之前一样:
测试程序:
#include<unistd.h>
#include<sys/syscall.h>
#include<assert.h>
#include<errno.h>
struct pinfo
{
int nice;
pid_t pid;
uid_t uid;
};
int main(void)
{
assert(6==syscall(__NR_write,1,"hello",6));
return 0;
}
运行图:  
测试二
添加的系统调用会输出hello world, 操作与之前一样: 头文件:
#ifndef _LINUX_PSTA_H
#define _LINUX_PSTA_H
struct pinfo
{
int nice;
pid_t pid;
uid_t uid;
};
#endif
.c文件
#include <linux/linkage.h>
#include <linux/psta.h>
#include <linux/unistd.h>
#include <linux/types.h>
#include <linux/kernel.h>
asmlinkage int psta( struct pinfo* buf)
{
//buf->pid=current->pid;
//buf->uid=current->uid;
//buf->nice=10;
printf("hello world");
return 0;
}
测试程序:
#include<unistd.h>
#include<sys/syscall.h>
#include<assert.h>
#include<errno.h>
struct pinfo
{
int nice;
pid_t pid;
uid_t uid;
};
int main(void)
{
struct pinfo info;
int ret;
ret=syscall(320,&info);
return 0;
}
运行图:

实验结果
实现系统调用psta,获取进程的若干信息。 实验代码: 头文件与之前相同 .c文件
#include <linux/linkage.h>
#include <linux/psta.h>
#include <linux/unistd.h>
#include <linux/types.h>
#include <linux/kernel.h>
asmlinkage int sys_psta( struct pinfo* buf)
{
buf->pid=current->pid;
buf->uid=current->uid;
buf->nice=10;
return 0;
}
测试程序:
#include<unistd.h>
#include<sys/syscall.h>
#include<assert.h>
#include<error.h>
#include<stdio.h>
struct pinfo{
int nice;
pid_t pid;
uid_t uid;
};
int main(void)
{
struct pinfo info;
int ret;
ret=syscall(320,&info);
printf("%d\n%d\n%d\n",info.pid,info.uid,info.nice);
return 0;
}
运行图:    测试结果如下:  至此实验结束.
实验总结
这是操作系统的最后一次必选实验了,通过这次实验课我对操作系统的了解更深刻一步,在前几次的基础上我掌握了Linux文件系统的基本原理、结构和实现方法,并且掌握了Linux文件系统中文件的建立、打开、读/写、执行、属性等系统调用的使用,学会设计简单的文件系统并实现一组操作。学会利用系统调用完自己需要的功能。但还有很多需要改进的地方,比如写入文件的大小限制,还有容错性检验等等,还有很多不足之处。