Linux 内存绑定在局部存储器的实现

3.2.1 Linux 内存绑定在局部存储器的实现总体步骤

总体步骤:

l         采用方案三,在在原来分析的基础上,以及已知Linux系统内存的初始化的情况,对内核代码进行修改,主要包括确定新区的范围,建立新区,重新对分配内存的分配机制进行设置。

l         新区划分后,对新建的两个区进行一定程度上的延迟;

l         建立系统调用,系统调用将提供用户进行手动设置访问方式。

l         对内核进行配置,并进行相关调式。

l         用户程序进行最终的测试,并验证相关结论。

3.2.2 Linux 内核绑定在局部存储器代码实现

()、修改代码:

1)       init/main.c这个文件中,在这一段代码

#ifdef CONFIG_X86_LOCAL_APIC

#include <asm/smp.h>

#endif

的下面增加这段代码:

unsigned long max_normal_low_pfn=0;

EXPORT_SYMBOL(max_normal_low_pfn); 

这里使用EXPORT_SYMBOL是声明max_normal_low_pfn是全局变量所有的文件都可以使用它的用处是为了标记新区ZONE_NORMAL_LOW的最大可用的页框号。

 

2)       arch/x86/kernel/setup.c这个文件:

l         struct boot_params boot_params;

#endif之后添加,声明该变量已经在别的文件有定义了

extern unsigned long max_normal_low_pfn;

l         在setup_arch()这个函数中,在find_low_pfn_range()这个之后,增加这一句:

        max_normal_low_pfn = max_low_pfn/2;

        该句用于计算新区(ZONE_NORMAL_LOW)的最大可用页框号。

3)       arch/x86/mm/init_32.c文件

l         unsigned long max_low_pfn_mapped; unsigned long max_pfn_mapped;之后添加extern unsigned long max_normal_low_pfn;

l         在static void __init zone_sizes_init(void)这个函数中增加这句代码

        max_zone_pfns[ZONE_NORMAL_LOW]=max_normal_low_pfn;最好放在max_zone_pfns[ZONE_NORMAL]=max_low_pfn; max_zone_pfns是用于记录管理区的     开始和最后的页框号。

4)       include/linux/mmzone.h这个文件中

l         在enum zone_type这个枚举类型中,一定要在

#endif

ZONE_NORMAL,一定要在两个之间添加ZONE_NORMAL_LOW,这一句。

在这里添加,该文件中所有的关于管理区的定义类型是在zone_type这个枚举类型中,使用的是枚举类中是有关变量的默认值,如无定义,是从0开始的,如果有定义,就从有赋值的那个变量依次增加。

l         并找到这段代码,修改

    #if MAX_NR_ZONES < 2

#define ZONES_SHIFT 0

#elif MAX_NR_ZONES <= 2

#define ZONES_SHIFT 1

#elif MAX_NR_ZONES <= 4

#define ZONES_SHIFT 2

在这里增添这句代码:         /*这是在使用相关位进行管理区的标志*/

#elif MAX_NR_ZONES <= 8

#define ZONES_SHIFT  3

#else

#error ZONES_SHIFT -- too many zones configured adjust calculation

#endif 

l         在mm/page_alloc.c这个文件

    在#include "internal.h"的下面增加这几句:

        static int zone_flag=0;

static int task_mode=0;

        static unsigned long total_number=1000000;

        task_mode是用于标志在那种工作方式的如果为0则按照原来的策略分配管理     如果为1则是通过设置方式zone_flag=0分配到新区中的ZONE_NORMAL_LOW;如果zone_flag=1分配到新区中的ZONE_NORMALtotal_number是用于延迟的数据。

l         把sysctl_lowmem_reserve_ratio和const zone_names这两个数组修改成:

int sysctl_lowmem_reserve_ratio[MAX_NR_ZONES-1] = {

#ifdef CONFIG_ZONE_DMA

     256,

              #endif

#ifdef CONFIG_HIGHMEM

     32,

#endif

     32,

         32,

};

 

static char * const zone_names[MAX_NR_ZONES] = {

#ifdef CONFIG_ZONE_DMA

     "DMA",

#endif

#ifdef CONFIG_ZONE_DMA32

     "DMA32",

#endif

    "Normal_Low",

     "Normal",

#ifdef CONFIG_HIGHMEM

     "HighMem",

#endif

     "Movable",

};

    这里主要是对管理区的各个名称重新定义,但同时需要注意,给这些名称的定义与zone_type想对应,不然的话,在某些地方,它采用的是我们不知道的某种方式进行访问的时候,就会出错,结果在启动的时候,启动不了,显示出现在的堆栈段错误信息。

l         把__rmqueue_smallest函数修改成:

static struct page *__rmqueue_smallest(struct zone *zone, unsigned int order,

                        int migratetype)

{

    unsigned int current_order,i;

    struct free_area * area;

    struct page *page;

      

    /* Find a page of the appropriate size in the preferred list */

    for (current_order = order; current_order < MAX_ORDER; ++current_order) {

area = &(zone->free_area[current_order]);

        if (list_empty(&area->free_list[migratetype]))

            continue;

 

page = list_entry(area->free_list[migratetype].next,

                            struct page, lru);

        list_del(&page->lru);

        rmv_page_order(page);

        area->nr_free--;

 

__mod_zone_page_state(zone, NR_FREE_PAGES, - (1UL << order));

        expand(zone, page, order, current_order, area, migratetype);

if(task_mode==0)

               {

                printk("alloc page pfn=%lu with %lu pages base on %s,start_pfn=%lu task_mode=%d/n",(long unsigned int)page_to_pfn(page),(1UL<<(long unsigned int)order),zone->name,zone->zone_start_pfn,task_mode);

}

else if(task_mode==1)

               {

                 if(zone_flag==0)

                 {

                    for(i=0; i<total_number; i++) ;

                //    printk("This is in test memory!/n");

                 }

                 printk("alloc page pfn=%lu with %lu pages base on %s,start_pfn=%lu task_mode=1 zone_flag=%d/n",(long unsigned int)page_to_pfn(page),(1UL<<(long unsigned int)order),zone->name,zone->zone_start_pfn,zone_flag);

                }

return page;

}

return NULL;

}

    在这个函数中主要增加了对检查系统访问方式如果task_mode==1的话,就判断采用新的设置方式,否则,还是随机方式,设置后,再判断要在那个新区进行物理内存的分配,如果是ZONE_NORMAL_LOW,将进行延迟操作,以区分这两个新区,达到最后模拟的效果。

l         __alloc_pages_internal这个函数之前增加这几个函数用于系统调用访问本文件的变量。

void set_flag(int value1,int value2)

{

     task_mode = value1;

     zone_flag = value2;

}

void clean_flag(int value)

{

     task_mode = value;

}

void change_total_number(int value)

{

    total_number = value;

}

其中set_flag是用于设置访问管理区的工作方式而工作方式的选择在5中已做解释

同理clean_flag用于清除管理区的工作方式的标志。

change_total_number是用于修改total_number的值该值是用于延迟管理区。

l         修改__alloc_pages_internal()这个函数,在

If (should_fail_alloc_page(gfp_mask, order))

return NULL;  之后添加这段代码:

  if(task_mode==0)

       {

         if(high_zoneidx == ZONE_NORMAL&&zone_flag==0)

         {

               high_zoneidx = ZONE_NORMAL_LOW;

               zone_flag=1;

         }

         else if(high_zoneidx == ZONE_NORMAL&&zone_flag==1)

         {

              zone_flag=0;

         }

       }

       else if(task_mode==1)

       {

            printk("max_normal_low_pfn = %lu,max_low_pfn = %lu and max_pfn=%lu, task_mode=%d zone_flag=%d total_number=%ld/n",max_normal_low_pfn,max_low_pfn,max_pfn,task_mode,zone_flag,total_number);

         if(zone_flag==0)

         {

               high_zoneidx = ZONE_NORMAL_LOW;

               for(i=0; i<total_number; i++)

               {

                     if(i==(total_number-1))

                         printk("OK! i=%d/n",i);

        }

         }

         else if(zone_flag==1)

         {

             high_zoneidx = ZONE_NORMAL;

     }

       }

关于这段代码的功能在上面讲述task_modezone_flag中已经做了解释。这里就不重述。

 

 

3.2.3 新增系统调用 

(1)系统调用:

linux内核中设置了一组用于实现系统功能的子程序,叫做系统调用。

系统调用号:标识系统调用,在系统中,其值只能是唯一。

系统调用处理程序:接收用户态的信息,并调用其服务例程。

系统调用服务例程:内核态进程,实现系统调用的功能。

(2)系统调用的意义

    Linux系统调用函数与通常的库函数用法是非常接相似的。都是提供使用的接口,不过系统的调用是由操作系统核心提供的,是核心态进程,而通常的库函数是由函数库和用户自己构建的,运行于用户态。

       一般,用户进程是不可访问内核,读取内核数据或者修改内核数据的。如果那样的话,整个系统的设计是不可以取的。当应用程序要访问硬件设备和其他的操作系统的资源时,这个时候,对于用户进程是不能使用的,所以在操作系统中,内核提供了一些接口,该接口可以访问上述所需要的资源。这中接口专门提供给应用程序使用的,使之能够访问内核空间或调用相关函数,实现所需要的功能。

这其实也可以理解为在用户和硬件之间再加上一层中间层,该层向上提供用户空间进程的接口,向下发送信息到硬件。这样做的好处有以下几个方面:

l         提供系统的安全性和稳定性

l         避免恶意的应用程序对系统的攻击

l         提供应用程序的接口,但应用程序不知道系统调用对内核的处理,这个就形成很好的封装

(3)系统调用的执行过程

       当用户空间进程调用系统调用时,CPU会切换到内核态,并开始处理内核函数。

       80x86体系结构中,提供了两种方式进行调用系统调用。

l         一种是使用INT $0x80这个端口进入汇编语言的入口地址;

l         第二种使用的是system_call()这个函数。

       相对应的系统调用的退出方式有:

l         执行iret这条汇编指令返回到原来的状态(类似与return)

系统调用的执行过程:

l         在内核态的堆栈中保留寄存器的信息,实现入栈功能(中断中的保留现场)

l         调用系统服务例程的相关c函数来处理系统调用,实现其系统功能

l         退出系统调用的处理程序,并把原来保留在内核栈中的信息恢复(中断中的恢复现场),然后由内核态返回用户态

1.10 系统调用的执行过程

(4)建立系统调用

   建立新的系统调用,对系统调用的入口方式采用的是system_call()这个函数,这个函数接收系统调用号和参数,提供6种的参数方式(最多可以传入6个参数),然后它自动会根据你所传入的参数进行选择对应了6中参数方式的那种,并根据系统调用号,进入系统服务例程。

 

       下面是关于建立系统调用,该系统名称分别为set_memory_flagclean_memory_flag,主要有以下步骤:

l         修改arch/x86/include/asm/unistd_32.h分别增加了333334这个两个系统调用号,最后写成的系统调用名是set_memory_falgclean_memory_flag:

图:1.10 增加系统调用号

 

l         修改arch/86/kernel/syscall_table_32.S,增加sys_creat_syscall的中断向量处理函数入口,分别增加sys_set_memory_flagsys_clean_memroy_flag

1.11 中断向量处理函数入口

 

l         arch/x86/kernel/sys_i386_32.c中增添sys_set_memory_flagsys_clean_memrory_falg的系统服务例程。具体如下:

extern void set_flag(int value1,int value2);

asmlinkage int sys_set_memory_flag(int value1,int value2)

{

     set_flag(value1,value2);

     return 0;

}

 

extern void clean_flag(int value);

asmlinkage int sys_clean_memory_flag(int value)

{

   clean_flag(value);

   return 0;

}

 

extern void change_total_number(int value);

asmlinkage int sys_total_number(int value)

{

   change_total_number(value);

   return 0;

}

注意:在写系统服务例程时,如果不在是arch/x86/kernel/sys_i386_32.c中的函数或者是变量的话,要通过其他方式才能够访问。如果是函数或全局变量,只需在sys_i386_32.c标识函数或者全局变量已经在别的文件中了;如果是局部变量,别的文件是不能直接访问的,所以在那个文件中在增加对这些变量的处理,形成封装,然后在要对该变量访问的文件声明extern即可。

 

     如何定义整个系统的全局变量,而不是只对于当前文件的全局变量。只须定义新变量(不能是static变量),然后EXPORT_SYMBOL(变量)既可;

       上面的服务例程就采用了封装机制,在page_alloc.c中使用函数set_flagtask_modezone_flag进行封装,在arch/x86/kernel/sys_i386_32.c中要改变task_modezone_flag,对set_flag声明为extern即可。

 

3.2.4 Linux内核的配置

Linux内核的编译的主要步骤

1)      www.kernel.org这里找到新的内核版本,这里我们使用的是linux-2.6.28这个内核版本,找到linux-2.6.28.tar.gz这个文件,放到/usr/src/这个目录下面,并解压到当前目录,进入到/usr/src/linux-2.6.28/这个目录。

2)      生成Makefile文件,这个可以通过内核提供的功能去生成,主要有:

   make menuconfig: 纯文本模式,不用启动X Window,还可以远程登陆,进行内核参数的选择;

 make xconfig  利用X Window的功能来进行选择,是图形界面;

 make gconfig: 利用GDK函数库德图形界面进行内核参数的选择;

这里我使用的是make menuconfig进行内核参数的选择,/usr/src/linux-2.6.28/下执行该命令(make menuconfig),关于内核参数的选择,可以参考[15],这里不做说明。

3)      接下来进行编译,执行make命令即可;(会等很长的时间)

4)      执行make bzImage生成内核的主要文件;

5)      执行make modules命令,这个命令生成的内核所需要的内核模块;

6)      把生成的内核模块安装到/lib/modules/这个目录,执行make modules_install;

注意:如果你是重新编译已有的内核,这个时候你最好把/lib/modules/2.6.28这个目录删除或者进行重命名,否则会出错。

7)      命令:  mkinitrd –f /boot/initrd-2.6.28 2.6.28

生成initrd文件,这是启动时存在于内存的文件系统。Initrd的最初目的是为了把kernel的启动分成两个阶段:在内核中保留最少的最基本的启动代码,然后把对各种各样硬件设备的支持以模块的方式放在initrd,这样就在启动中从initrdmount的根文件系统中装载需要的模块。Mkinitrd –f是为了如果在/boot中已经存在上次的initrd,所要操作的是强制覆盖。

8)      把生成的bzImageSystem.map拷贝到/boot这个目录下,具体如下:

cp /usr/src/linux-2.6.28/arch/x86/boot/bzImage /boot/vmlinuz-2.6.28

(:x86是针对我自己的机器,对各个机器的路径不同,要看具体情况)

9)      修改/boot/grub/menu.lst,增添如下:

1.3 menu.lst添加新内核

              因为在本次设计中多次编译内核,所以把上述的步骤写成了shell脚本,具体可参考另外提供的shell文件。在usr/src/linux-2.6.28这个目录下创建名make.shshell脚本,具体代码如下:

make
make bzImage
make modules
rm -rf /lib/modules/2.6.28
make modules_install
mkinitrd -f /boot/vmlinuz-2.6.28 2.6.28
cp -f arch/x86/boot/bzImage /boot/vmlinuz-2.6.28
cp -f System.map /boot/System.map-2.6.28

注意:创建的make.sh的权限需要可执行的,不然执行chmod 777 make.sh即可。

 

3.2.5 仿真结果的性能测试

l         测试程序如下:

        #include<stdio.h>

#include<stdlib.h>

#include<linux/unistd.h>

#include<time.h>

 

#define __NR_set_memory_flag    333       /*set_memory_flag系统调用号*/

#define __NR_clean_memory_flag  334     /*clean_memory_flag系统调用号*/

 

 

/*set_memory_flag这个函数,接收两个参数,并调用系统调用,syscall(),里面的参数顺序是调用号,参数1,参数2*/

int set_memory_flag(int value1,int value2)

{

   printf("task_mode=%d and zone_flag=%d/n",value1,value2);

   return syscall(__NR_set_memory_flag,value1,value2);

}

/*clean_memory_flag将是对系统调用的封装,只是传入系统调用号,和参数1,用于清除管理区标志*/

int clean_memory_flag(int value)

{

   return syscall(__NR_clean_memory_flag,value);

}

/*接收命令终端的输入参数,只接收三个参数,如果不是的话,就输入错误信息*/

int main(int argc,char *argv[])

{

  

   int i,j;

                 int *p;

clock_t start,end;

   if(argc!=3)

   {

       printf("Input Error!");

       return 0;

   }

   start=clock();     /*开始时间*/

/*调用这个set_memrory_flag,这个函数中将使用系统调用进行有关内核参数的设置,改变管理区的分配方式。*/

   set_memory_flag(argv[1][0]-'0',argv[2][0]-'0'); 

   sleep(5);

/*申请分配空间*/

   for(i=0; i<2000; i++)

   {

      p=(int *)malloc(sizeof(int)*1024*1024*256);

      if(p==NULL)

      {

        printf("ERROR!/n");

        return -1;

       }

       free(p);

   }

/*清除管理中的标志,该标志的表示方式详见上述对task_modezone_flag的解释*/

   clean_memory_flag(0);

   end = clock();

/输入进行运行的时间*/

   printf("Execution time is %lf s./n",(double)(end-start)/CLOCKS_PER_SEC);

        return 0;

}

注明:查看管理区的状态,是通过查看/var/log/messages这个日志文件里面的信息,这是因为我们修改内核代码过程中,输出有关内核信息出来,而这些信息是写入到这个文件的。而查看管理页的状态这个过程是动态的,所以可以通过使用这个命令动态的查看:tail –f /var/log/messages。当然你也可以使用echo > /var/log/messages对这个文件进行清空,以防止文件过大。

l         运行前管理区的状态:

        May  9 14:05:57 localhost kernel: alloc page pfn=476042 with 1 pages        base on HighMem,start_pfn=24096 task_mode=0

May  9 14:05:57 localhost kernel: alloc page pfn=102357 with 1 pages    base on Normal_Low,start_pfn=4096 task_mode=0

May  9 14:05:58 localhost kernel: alloc page pfn=102384 with 1 pages    base on Normal_Low,start_pfn=4096 task_mode=0

May  9 14:05:58 localhost kernel: alloc page pfn=213676 with 1 pages    base on Normal,start_pfn=113151 task_mode=0

May  9 14:05:58 localhost kernel: alloc page pfn=213677 with 1 pages    base on Normal,start_pfn=113151 task_mode=0

对上面输出结果的解释:pfn=213677就是分配的物理页面的页框号为213677,而且只分配1个页面,这个页框号是在Normal这个管理区分配的,start_pfn就是代表每个管理区的起始页框号,task_mode=0代表的是工作方式,如果task_mode=0的话,就按照原来的分配方案进行管理区的分配,如果task_mode=1,分配管理区则按着本文设计中的方案进行那个分配,当zone_flag=0就在ZONE_NORMAL_LOW这个区中分配,当zone_flag=1时在ZONE_NORMAL中分配。

l         运行结果1:

        [root@localhost sys_call]# ./allocate 1 0

task_mode=1 and zone_flag=0

Execution time is 3.890000 s.

l         运行过程中管理区的状态:

May  9 18:15:32 localhost kernel: alloc page pfn=89597 with 1 pages base on Normal_Low,start_pfn=4096 task_mode=1 zone_flag=0

May  9 18:15:32 localhost kernel: alloc page pfn=89598 with 1 pages     base on Normal_Low,start_pfn=4096 task_mode=1 zone_flag=0

 

l         运行结果2:

        [root@localhost sys_call]# ./allocate 1 1

task_mode=1 and zone_flag=1

Execution time is 0.040000 s.

 

l         运行过程中管理区的状态:

        May  9 18:18:11 localhost kernel: alloc page pfn=216895 with 1 pages            base on Normal,start_pfn=113151 task_mode=1 zone_flag=1

        May  9 18:18:11 localhost kernel: alloc page pfn=203688 with 1 pages            base on Normal,start_pfn=113151 task_mode=1 zone_flag=1

 

多次数据记录情况                    1:

 

访问ZONE_NORMAL_LOW的时间 (s)

访问ZONE_NORMAL的时间(s

1

3.930000

0.050000

2

3.880000

0.040000

3

3.900000

0.050000

4

3.880000

0.050000

5

3.890000

0.040000

6

3.890000

0.040000

7

3.890000

0.040000

8

3.890000

0.040000

9

3.880000

0.040000

10

3.900000

0.040000

11

3.900000

0.040000

12

3.900000

0.040000

13

3.900000

0.050000

14

3.900000

0.040000

15

3.910000

0.050000

16

3.920000

0.050000

17

3.890000

0.040000

18

3.900000

0.040000

19

3.900000

0.050000

20

3.900000

0.040000

平均时间

3.897500

          0.043500

从上表中可知,访问的时间上有明显的差异,所以仿真过程中,访问远程(ZONE_NORMAL_LOW)的要比本地的(ZONE_NORMAL)的慢,该仿真的测试结果符合要求。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值