重新编译Linux内核踩的坑

来源:互联网 发布:知乎日报的rss 编辑:程序博客网 时间:2024/06/11 22:07

比较有意义的一次实验,坑都写在讨论与心得里。

实验目的

学习重建Linux内核。

学习Linux内核的系统调用,理解、掌握Linux系统调用的实现框架、用户界面、参数传递、进入/返回过程。阅读Linux内核源代码,通过添加一个简单的系统调用实验,进一步理解Linux操作系统处理系统调用的统一流程。了解Linux操作系统缺页处理,进一步掌握task_struct结构的作用。

实验内容

在现有的系统中添加一个不用传递参数的系统调用。这个系统调用的功能是实现统计操作系统缺页总次数、当前进程的缺页次数及每个进程的"脏"页面数,严格来说这里讲的"缺页次数"实际上是页错误次数,即调用do_page_fault函数的次数。实验主要内容:

  • 在linux操作系统环境下重建内核
  • 添加系统调用的名字
  • 利用标准C库进行包装
  • 添加系统调用号
  • 在系统调用表中添加相应表项
  • 修改统计缺页次数相关的内核结构和函数
  • sys_mysyscall的实现
  • 编写用户态测试程序

实验指导

完成实验后回答问题:

  1. 多次运行test程序,每次运行test后记录下系统缺页次数和当前进程缺页次数,给出这些数据。test程序打印的缺页次数是否就是操作系统原理上的缺页次数?有什么区别?
  2. 除了通过修改内核来添加一个系统调用外,还有其他的添加或修改一个系统调用的方法吗?如果有,请论述。
  3. 对于一个操作系统而言,你认为修改系统调用的方法安全吗?请发表你的观点。

 

 

 

 

实验报告

 

一、实验环境

 

二、实验内容和结果及分析

1、实验设计思路

    本实验的前半部分按照老师的实验指导来就可以了,只要不发生误操作,就没有什么太大的问题。因此,这里主要对统计程序的设计思路做详细说明:

    在实验指导中给出了pf和pfcount,所以实现缺页次数并不难,直接打印即可。那么怎么去统计脏页数呢?经过查阅资料发现task_struct中有一个数据成员叫做nr_dirtied,遍历所有进程,循环打印这个数据成员即可。

 

2、实验步骤及截图

 

3、测试程序运行结果截图

    首先我们可以查看当前的内核版本:

    再对比之前的内核版本:

    说明成功吧4.10.0的内核换成了4.8.0。

    然后去var/log/kern.log中查看程序输出结果:

    可以看见,总的缺页次数是489725,当前进程的缺页次数是72,脏页数的部分截图如上(比较多,这里只截取了一部分)。

4、结果分析

 

5、源程序

    首先是统计总的缺页次数、当前进程缺页次数以及各个进程脏页数的程序,这个程序比较特别的地方是要用task_struct中的nr_dirtied,如下所示:

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

*Project Name:实验2统计程序

*Author:

*Student ID:

*Last Modified: 2017/12.23

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

externunsignedlong pfcount;//必须先申明其他地方定义的pfcount

asmlinkageint sys_mysyscall(void){

struct task_struct*p=NULL;//用来遍历进程

printk("<1>OSLab2 total page fault: %lu times\n",pfcount);//所有缺页次数

printk("<1>OSLab2 current process's page fault: %lu times\n",current->pf);//当前进程缺页次数

for(p=&init_task;(p=next_task(p))!=&init_task;){//循环打印各个进程脏页数

printk("<1>OSLab2 dirty page: %d\n pages",p->nr_dirtied);

}

return0;

}

    然后是用户态程序这个程序比较简单,我没有输出到终端,而是直接/var/log/kern.log查看的,所以只需要调用223号系统调用即可:

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

*Project Name:实验2用户程序

*Author:

*Student ID:

*Last Modified: 2017/12.23

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

#include <unistd.h>

#include <sys/syscall.h>

#define __NR_ mysyscall 223

int main(){

syscall(223);

}

 

三、讨论、心得(20分)

    相比之下这个实验比实验1简单了不少,但是比较麻烦,需要很小心地操作,因为如果中间出了什么问题,再编译一遍内核可不是闹着玩的。为了顺利做出这个实验,在实验过程中我每做一步都比较小心,但还是出了很多问题。最后成功完成这个实验是在一个周五的晚上,我熬到了凌晨4点钟,守着机器把内核编译完,然后又解决了"客户机禁用CPU"的错误,才做成功的,重启成功的那一刻我简直都不想睡觉了,很激动。

    下面是做这个实验的一些收获:

    (1)libncurses5-dev是用来干嘛的?

    在make menuconfig的时候会出现终端下的字符菜单,这个软件包就是为此提供支持作用的。

    (2)怎么在启动时加载编译好的内核?

    因为我客户机的内核是4.10,但是编译的却是4.8,而Ubuntu每次重启默认会启动最新的内核,也就是说每次机器都会加载4.10,而不是我编译好的4.8,这个问题开始很困扰我,我在网上搜到的做法是去改启动项,改grub文件等等。这样导致了很多错误,但后来发现其实只需要在虚拟机启动的时候按住Esc键就可以到启动菜单里去了,然后就能选择需要加载的内核版本了。如下图:

    (3)重启后出现"客户机禁用CPU"的情况怎么办?

    内核加载正常,本以为大功告成,但是VMWare却告诉我"客户机禁用CPU",经过查资料,发现这主要是配置文件的问题,需要手动进行修改,打开真机上安装虚拟机时留下的vmx文件,在里面加入这句话:

    再重启就正常了。

    (4)pf初始化的正确位置。这个实验中tsk->pf=0;这句代码的位置十分关键,如果加入的位置不当,做出来的缺页次数会很离谱(比如当前进程缺页数会有好几万)。

    正确的位置如下图所示:

    这是因为arch_dup_task_struct(tsk,orig)函数的关系,如果看一下这个函数怎么实现的话就会知道:

    它直接把orig赋给了tsk,所以如果pf在上面初始化,子进程的pf就还是父进程的,不满足要求。

    (5)最后是一个小经验:由于这个实验失败率比较高,所以make的时候如果追求速度的话可以用make -j2或者make -j4(甚至更高的CPU核心数,如果机器性能允许的话),虽然这样做在makefile的依赖关系写得不够好的情况下可能有风险,但是linux内核的makefile肯定是写得比较完美的,所以为了顺利完成实验,尽情地用多核编译吧!

 

最后回答几个问题:

    1.多次运行test程序,每次运行test后记录下系统缺页次数和当前进程缺页次数,给出这些数据。test程序打印的缺页次数是否就是操作系统原理上的缺页次数?有什么区别?

    答:我运行了10次test程序,结果记录如下:

次数

系统缺页数

当前进程缺页数

1

462445

73

2

464264

74

3

505757

73

4

506182

73

5

506698

73

6

512899

73

7

513841

73

8

514260

73

9

517156

73

10

518152

73

    可以看到,每运行一次,系统缺页次数就会增加,而当前进程缺页次数则(几乎)保持不变。

    test打印的缺页数不是操作系统原理上的缺页数,区别在于:...

 

    2.除了通过修改内核来添加一个系统调用外,还有其他的添加或修改一个系统调用的方法吗?如果有,请论述。

    答:...

 

    3.对于一个操作系统而言,你认为修改系统调用的方法安全吗?请发表你的观点。

    答:...