操作系统课程设计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系统调用的理解。

设计一: 设计任务:模拟Linux文件系统 在任一OS下,建立一个大文件,把它假象成一张盘,在其中实现一个简单的模拟Linux文件系统。 1. 在现有机器硬盘上开辟100M的硬盘空间,作为设定的硬盘空间。 2. 编写一管理程序simdisk对此空间进行管理,以模拟Linux文件系统,要求: (1) 盘块大小1k (2) 空闲盘块的管理:Linux位图法 (3) 结构:超级块, i结点区, 根目录区 3. 该simdisk管理程序的功能要求如下: (1) info: 显示整个系统信息(参考Linux文件系统系统信息),文件可以根据用户进行读写保护。目录名和文件名支持全路径名和相对路径名,路径名各分量间用“/”隔开。 (2) cd …: 改变目录:改变当前工作目录,目录不存在时给出出错信息。 (3) dir …: 显示目录:显示指定目录下或当前目录下的信息,包括文件名、物理地址、保护码、文件长度、子目录等(带/s参数的dir命令,显示所有子目录)。 (4) md …: 创建目录:在指定路径或当前路径下创建指定目录。重名时给出错信息。 (5) rd …: 删除目录:删除指定目录下所有文件和子目录。要删目录不空时,要给出提示是否要删除。 (6) newfile …: 建立文件。 (7) cat …: 打开文件。 (8) copy …: 拷贝文件,除支持模拟Linux文件系统内部的文件拷贝外,还支持host文件系统与模拟Linux文件系统间的文件拷贝,host文件系统的文件命名为<host>…,如:将windows下D:盘的文件\data\sample\test.txt文件拷贝到模拟Linux文件系统中的/test/data目录,windows下D:盘的当前目录为D:\data,则使用命令: simdisk copy <host>D:\data\sample\test.txt /test/data 或者:simdisk copy <host>D:sample\test.txt /test/data (9) del …: 删除文件:删除指定文件,不存在时给出出错信息。 (10) check: 检测并恢复文件系统:对文件系统中的数据一致性进行检测,并自动根据文件系统的结构和信息进行数据再整理。 4. 程序的总体流程为: (1) 初始化文件目录; (2) 输出提示符,等待接受命令,分析键入的命令; (3) 对合法的命令,执行相应的处理程序,否则输出错误信息,继续等待新命令,直到键入EXIT退出为止。 设计二: 设计任务:模拟文件系统的前端操作shell 实现一个简单的shell(命令行解释器)。 将设计一的管理程序simdisk作为后台进程运行,利用本设计任务的shell操作simdisk。 本设计任务在于学会如何实现在前端的shell进程和后端的simdisk进程之间利用共享内存进行进程间通信(IPC)。 设计三: 设计任务:模拟文件系统的操作管理 实现多个进程同时对模拟文件系统进行操作。设计管理程序simdisk的用户访问权限管理。访问模拟文件系统的每个进程都属于某个用户,管理程序simdisk根据其访问权限决定其对模拟文件系统的操作。 对模拟文件系统的操作要求做到:共享读,互斥写。 本设计任务在于学会如何实现信息的安全管理和进程同步。 注:要求从课程设计的整体来考虑设计任务一、二、三,并分阶段实现。
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页