高级操作系统实验报告

发布时间:2017-05-03 10:41:56

题目:实验五

组号:第五组 同组同学姓名:纪天啸 宋雅迪 印佳明 于洋 湛健 日期: 201747

实验环境:

内核版本:替换前4.8.0 替换后4.10.8

编译器:gcc 5.4.0

1. 问题描述

学习linux系统调用的过程,通过向系统内核中增加一个自己编写的新函数,实现对内核空间的读写。函数功能主要为向用户返回目前时间(书中要求返回xtime的值,在新版本中时间以由timekeeper结构体替代)。

由于编译一次内核时间较长,修改不易。所以主要实现方式为先通过编写内核模块,修改系统内存中的系统调用表,插入自己编写的内核函数,对功能进行验证。无误后,修改linux内核源代码,加入自己编写的系统调用函数,重新编译内核,对现有内核进行替换。并在用户空间对函数进行调用验证。

2. 流程图

插入内核模块方式:

重新编译内核方式:

系统调用函数功能的实现:

3. 设计实现

3.1程序源码

附上程序源码

要求每个函数都有文件头

/*******************************************************

函数名:

参数表说明:

返回值:

函数功能描述:

********************************************************/

每个关键步骤都有注释,最好英文

插入内核模块方式:

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#define my_syscall_num 134//占用的原表中的系统调用号

//模块信息说明

MODULE_LICENSE("GPL");

MODULE_AUTHOR("JTX");

MODULE_DESCRIPTION("my_syscall");

MODULE_VERSION("1.0");

void disable_write_protection(void);//清除写保护

void enable_write_protection(void);//恢复写保护

asmlinkage long sys_my_syscall(int flag, struct timeval __user *thetime);//自己实现的系统调用函数

unsigned long *sys_call_table = 0;//存储系统调用表的地址

static int (*anything_saved)(void);//存储原表项的值

/*******************************************************

函数名:unsigned long **get_sys_call_table(void)

参数表说明:无

返回值:系统调用表地址

函数功能描述:获得内存中系统调用表的首地址。赋予entry内核空间首地址,对每块地址进行遍历,当entry[__NR_close]==sys_close,时即可认为找到系统调用表的地址,__NR_closeclose的系统调用号。

********************************************************/

unsigned long **get_sys_call_table(void)

{

unsigned long **entry = (unsigned long **)PAGE_OFFSET;//赋予entry内核空间首地址

//对内核空间中每块地址进行遍历查找

for (;(unsigned long)entry < ULONG_MAX; entry += 1)

{

if (entry[__NR_close] == (unsigned long *)sys_close)

{

return entry;

}

}

return NULL;

}

/*******************************************************

函数名:void disable_write_protection(void)

参数表说明:无

返回值:无

函数功能描述:修改控制寄存器cr0,从而关闭写保护

********************************************************/

void disable_write_protection(void)

{

unsigned long cr0 = read_cr0();//读取cr0

clear_bit(16, &cr0);//清除第16

write_cr0(cr0);//写回控制寄存器

}

/*******************************************************

函数名:void enable_write_protection(void)

参数表说明:无

返回值:无

函数功能描述:修改控制寄存器cr0,恢复写保护

********************************************************/

void enable_write_protection(void)

{

unsigned long cr0 = read_cr0();

set_bit(16, &cr0);

write_cr0(cr0);

}

/*******************************************************

函数名:static int __init init_addsyscall(void)

参数表说明:无

返回值:整型,状态

函数功能描述:初始化内核模块。获取系统调用表地址,并修改插入自己编写的系统调用函数

********************************************************/

static int __init init_addsyscall(void)

{

printk(KERN_EMERG"insmod!\n");

sys_call_table = (unsigned long *)get_sys_call_table();//获取系统调用表首地址

anything_saved = (int(*)(void)) (sys_call_table[my_syscall_num]);//保存原始系统调用的地址

disable_write_protection();//关闭写保护

sys_call_table[my_syscall_num] = (unsigned long)&sys_my_syscall;//更改原始的系统调用地址

enable_write_protection();//恢复写保护

return 0;

}

/*******************************************************

函数名:asmlinkage long sys_my_syscall(int flag, struct timeval __user *thetime)

参数表说明:flag为标志位,thetime返回当前时间

返回值:长整型

函数功能描述:从内核变量中读取当前时间并返回给用户,当标志位为真是,打印时间

********************************************************/

asmlinkage long sys_my_syscall(int flag, struct timeval __user *thetime)

{

if (likely(thetime != NULL))

{

struct timeval ktv;

struct timespec64 now;

getnstimeofday64(&now);

ktv.tv_sec = now.tv_sec;

ktv.tv_usec = now.tv_nsec/1000;

if (copy_to_user(thetime, &ktv, sizeof(ktv)))

return -EFAULT;

if (flag)

printk(KERN_EMERG"flag is true!\ntv.tv_sec: %ld\ntv.tv_usec: %ld\n", ktv.tv_sec, ktv.tv_usec);

}

printk(KERN_EMERG"This is my_syscall!\n");

return 0;

}

/*******************************************************

函数名:static void __exit exit_addsyscall(void)

参数表说明:无

返回值:无

函数功能描述:恢复系统调用表,退出模块

********************************************************/

static void __exit exit_addsyscall(void)

{

disable_write_protection();//关闭写保护

//恢复原有的sys_call_table的函数指针的值。

sys_call_table[my_syscall_num] = (unsigned long)anything_saved;

//恢复写保护

enable_write_protection();

printk(KERN_EMERG"rmmod!\n");

}

module_init(init_addsyscall);

module_exit(exit_addsyscall);

重新编译内核方式:

1.include/linux/syscalls.h中加入函数声明:

asmlinkage long sys_my_syscall(int flag, struct timeval __user *thetime);

2.kernel/sys_ni.c加入系统调用,处理失败的情况:

cond_syscall(sys_my_syscall);

3.arch/x86/entry/syscalls/syscall_64.tbl(针对x86架构)中添加系统调用表项:

332 common my_syscall sys_my_syscall

4.time.c中添加函数定义

3.2测试结果

插入内核模块进行测试:

编译内核前内核版本:

编译替换后:

在用户空间对调用进行测试:

4. 总结

4.1 遇到的问题及解决办法

问题1:编译一次内核所需时间太长,修改调试不方便

问题1解决方法:采取先在内核模块中完成测试功能后,再写入内核代码。

问题2:在编译内核时提示无配置文件,无法编译

问题2解决办法:把系统中原有的内核配置文件.config拷贝到新内核的目录下即可

问题3:查找系统调用表时,网上的例子是使用命令查看/proc文件来获取,再硬编码到程序中,这种方法不可靠,当重新开机后地址会改变

问题3解决方法:遍历内核空间每个地址即可,具体说明可见代码中函数get_sys_call_table的说明

问题4:网上例子中采取内联汇编的方式修改寄存器,在32位中有效,在64位系统中报错

问题4解决方法:查阅资料可知,内核中提供了修改寄存器的接口

问题5:在内核代码kernel文件夹下系统调用表无效

问题5解决方法:查阅文档可知,x86架构有其指定的系统调用表,在arch/x86下,kernel文件夹中为其他架构通用的系统调用表

4.2 心得体会

本次实验涉及的是linux的系统调用。所谓系统调用,即操作系统为应用程序提供的与内核进行交互的一组接口。通过此接口,用户态下的应用程序可以转化为内核态,同时调用相应的内核函数运行,从而实现应用程序与内核的交互。

通过本次实验我们学会了如何向linux系统内核中添加自己编写的函数进行调用,明白了linux系统调用的过程,体会到了系统内核的重要性,计算机本身不具备文件写入和读出功能,这些功能由操作系统提供。操作系统是功能扩展机(虚拟机),它提供了许多系统调用,供程序员使用。要做好实验,需要按照实验教程上面的内容一步步落实,要边做边领悟相关原理及运行结果的出现的原因,这样我们才能在试验中学到更多、掌握更多。其次,也遇到问题我们自然是要先自己思考,通过不同的尝试来解决,之后不能解决的我们要多向老师同学请教,通过互相交流得来的知识也是会让我们难忘的。

高级操作系统实验报告

相关推荐