操作系统课程设计3_系统调用

一.实验目的

(1)学习怎样重新编译 Linux 内核

(2)理解、掌握 Linux 标准内核和发行版本内核的区别

二.实验内容

1、通过重新编译Linux来实现系统调用

2、通过增加模块来实现系统调用

三.实验步骤和结果

(一)通过重新编译内核进行系统调用

1.获取root权限(即管理员权限)

在终端输入下列指令:sudo su

注意:获取root权限需要用户输入密码(登陆密码),密码是看不见的,用户只需正确的输入密码即可,回车键结束

示意图如下:

 

2.准备工作

1)安装相关编译程序

sudo apt-get install build-essential kernel-package libncurses5-dev

选择第一项:

 

2)从外面将linux-5.5.8.tar拖入到了虚拟机桌面,将该压缩文件移入到/usr/src/文件夹中

 

3.进入/usr/src解压文件

cd /usr/src

sudo tar -xvf /usr/src/linux-5.5.8.tar.xz

解压完成!

 

4.进入解压出的文件目录

cd /usr/src/linux-5.5.8/kernel

 

5.安装vim

sudo apt-get install vim

 

6.打开sys.c加入函数

vim sys.c

在vim中, i进入编辑, esc退出编辑状态. G跳到末尾, gg进入开头。 :wq保存退出, :q不保存退出

在末尾加入函数

asmlinkage long sys_helloworld(void){

printk( "helloworld!");

    return 1;

}

 

7.添加声明

cd /usr/src/linux-5.5.8/arch/x86/include/asm/

vim syscalls.h

(插入asmlinkage long sys_helloworld(void);)

 

8.加一个系统调用的id

a、使用命令cd /usr/src/linux-5.5.8/arch/x86/entry/syscalls,进入/usr/src/linux-5.5.8/arch/x86/syscalls目录

b、使用命令vim syscall_64.tbl,打开文件syscall_64.tbl(该文件有一个系统调用列表,最前面的属性是id)

c、在里面添加自己的系统调用号(335  64  helloworld           sys_helloworld)

d、使用esc +:wq命令保存退出

 

9.配置内核

cd /usr/src/linux-5.5.8

编译内核和安装内核。

依次输入这四条语句

sudo make mrproper

sudo make clean

sudo make menuconfig  (并且在make menuconfig时,将那个General setup内的localversion修改成新的名称,比如我这里的myKernel)

根据自己处理器的最大线程数目来编译。

sudo make -j4 (我的电脑是4核4线程),线程越多编译越快!

配置内核界面:

 

开始编译,过程耗时两小时左右。

编译成功!

 

10、安装内核

编译后安装内核到系统中

sudo make modules_install  //安装模块

sudo make install  // 安装内核

模块安装完成!

内核安装完成!

 

11、重启虚拟机

将之前的工作保存后直接重启,重启后点击鼠标进入ubuntu并且迅速按住shift,长按!选择新内核。

 

 

12、验证系统调用是否成功

a、登陆虚拟机

b、用文本编辑器编辑一段代码hello.c,并保存至根目录下。

c、输入下列指令:

(1)gcc hello.c

(2)./a.out

返回值为1,系统调用实现成功!

 

(二)通过模块修改系统调用

1、在/usr/include/x86_64-linux-gnu/asm/unistd_64.h文件中查看系统调用序号

发现333号系统调用是空的,因此选取333作为新的系统调用号。

2、查看系统调用表的内存地址:为0xffffffffa6c002a0

3、编写模块文件:

module5.c

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/unistd.h>
#include <linux/sched.h>



MODULE_LICENSE("Dual BSD/GPL");

#define SYS_CALL_TABLE_ADDRESS 0xffffffffb18002a0  //sys_call_table对应的地址
#define NUM 333  //系统调用号为333
int orig_cr0;  //用来存储cr0寄存器原来的值
unsigned long *sys_call_table_my=0;

static int(*anything_saved)(void);  //定义一个函数指针,用来保存一个系统调用

static int clear_cr0(void) //使cr0寄存器的第17位设置为0(内核空间可写)
{
    unsigned int cr0=0;
    unsigned int ret;
    asm volatile("movq %%cr0,%%eax":"=a"(cr0));//将cr0寄存器的值移动到eax寄存器中,同时输出到cr0变量中
    ret=cr0;
    cr0&=0xfffeffff;//将cr0变量值中的第17位清0,将修改后的值写入cr0寄存器
    asm volatile("movq %%eax,%%cr0"::"a"(cr0));//将cr0变量的值作为输入,输入到寄存器eax中,同时移动到寄存器cr0中
    return ret;
}

static void setback_cr0(int val) //使cr0寄存器设置为内核不可写
{
    asm volatile("movq %%eax,%%cr0"::"a"(val));
}

asmlinkage long sys_mycall(void) //定义自己的系统调用
{   
    printk("模块系统调用-当前pid:%d,当前comm:%s\n",current->pid,current->comm);
    return current->pid;    
}
static int __init call_init(void)
{
    sys_call_table_my=(unsigned long*)(SYS_CALL_TABLE_ADDRESS);
    printk("call_init......\n");
    anything_saved=(int(*)(void))(sys_call_table_my[NUM]);//保存系统调用表中的NUM位置上的系统调用
    orig_cr0=clear_cr0();//使内核地址空间可写
    sys_call_table_my[NUM]=(unsigned long) &sys_mycall;//用自己的系统调用替换NUM位置上的系统调用
    setback_cr0(orig_cr0);//使内核地址空间不可写
    return 0;
}

static void __exit call_exit(void)
{
    printk("call_exit......\n");
    orig_cr0=clear_cr0();
    sys_call_table_my[NUM]=(unsigned long)anything_saved;//将系统调用恢复
    setback_cr0(orig_cr0);
}

module_init(call_init);
module_exit(call_exit);

MODULE_AUTHOR("25");
MODULE_VERSION("BETA 1.0");
MODULE_DESCRIPTION("a module for replace a syscall");

 

Makefile

4、执行make命令

5、载入模块,并用lsmod命令查看模块是否被加载

6、编写测试程序:

test.c

7、编译test.c,执行a.out,输出Hello,3011,系统调用成功!

四.实验问题分析

1、第一次编译就报了下面的错误,后来我想了想,可能是在添加系统调用id的时候出了错。因为当时添加系统调用的id的时候,我看到syscall_64.tbl里面的函数名都是__x64_sys_开头,所以误以为所有的函数名都要以这个格式来命名,现在弄明白了,不用管系统里的其他函数是什么样的,这里的函数名必须与sys.c里添加的函数以及syscall.h里的函数声明保持严格的一致,否则会报错。

2、第二次重新编译,前面的过程很顺利,最后一步进行系统调用的时候又出现了问题,返回值为-1,我通过上网查阅相关资料推测应该是系统调用找不到的原因。我又重新检查了一遍,才发现自己犯了粗心大意的错误,原来,最后的hello.c程序我在写的时候误将syscall(335)写成了syscall(333),系统调用的id错了,自然就找不到了。改过来之后,返回值为1,系统调用成功。

3、第二个小实验其实也并没有多难,但是做的过程并非一帆风顺,前前后后重做了十几次,因为每次加载模块的时候,都显示被杀死,而我想卸载的时候,又显示模块正在被使用,卸载不了,我通过lsmod查看时发现系统中有module5的存在,因此我推测,这个模块应该可以理解为被杀死的模块的尸体。想要卸载掉这个死模块,只能重启。

4、其中第一个问题是,查看系统调用序号的时候,我一开始是在unistd_32.h里查看的,然后找了个223来用,但后来发现,应该在unistd_64.h里找。

5、第二个问题也是实验成功之前一直困扰我一个小细节。就是每次重启之后,我一直都用的第一次查出来的系统调用表的内存地址,以为这个地址是恒定不变的。后来通过上网查资料,问同学才最终发现内存地址每次重启都会更新,这个才是导致我的模块一直被杀死的重要原因。

 

 

五.实验总结

本次实验,我学会了通过重新编译内核和修改模块两种方法来实现Linux系统调用,通过修改模块的方法来修改系统调用,更省时,也更能体现Linux内核的模块作用。在实验过程中我遇到了很多困难,但是在一步步的解决这些问题的同时,也在进一步的加深我对Linux系统调用的理解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值