Chinaunix首页 | 论坛 | 博客
  • 博客访问: 91271
  • 博文数量: 5
  • 博客积分: 678
  • 博客等级: 上士
  • 技术积分: 140
  • 用 户 组: 普通用户
  • 注册时间: 2007-01-31 12:41
文章分类

全部博文(5)

文章存档

2011年(3)

2010年(2)

分类: 虚拟化

2011-02-22 22:10:07

   上一次已经找到了对串口的操作方法。这样就可以考虑编译自己的内核了,因为在编译内核的过程中,串口的输出将作为调试的主要功能。
   先介绍一下我的工作环境:
1. 一台linux系统(或虚拟机),需要如下软件
 .新的内核,我是从上下载的2.6.37.
 .交叉编译工具,我使用的是CodeSourcery的mips版本,比较好用。而且带C库,可用于编译用户空间程序。安装完CodeSourcery之后,导出工具的路径。
 .交叉调试工具GDB。基本的制作方式为:下载一个GDB-7.0,解压,进入GDB,进行配置./configure –target=mipsel-unkown-linux-gnu –prefix=/cross,然后make,make install。这样调试工具就安装到了/cross目录下。
 注意:CodeSourcery自带的GDB不太好用,特别是远程调试有问题。所以用自己编译的更方便些。
2. 安装了simics-3.0.29的windows一台。参考上一篇。

   首先需要说明的是,simics的malta系统不支持yamon(bootloader),所以内核中与prom相关的代码可能会导致问题。这是第一个需要考虑修改的地方。
   另一个就是串口输出。上一篇已经提到了怎么让simics的串口输出字符。后面我们详细看下哪些地方需要修改。

第一步,我们先看看simics怎么和gdb连接起来使用。
   先把内核解压,进入内核目录,运行下面的命令:
Make ARCH=mips CROSS_COMPILE=mips-linux-gnu- allnoconfig
   这句的意思是,目标机器为mips,指定交叉编译工具为mips-linux-gnu-**, 然后将所有可以设为no的选项都禁掉,这是为了尽量不让内核其他模块影响我们的调试。然后开始配置内核,输入:
Make ARCH=mips CROSS_COMPILE=mips-linux-gnu- menuconfig
   在配置界面上,有以下几个选项值得注意
   Machine selection  --->
      System type (MIPS Malta board)  --->
   Endianess selection (Little endian)  --->
   CPU selection  --->
      CPU type (MIPS32 Release 1)  --->
      Kernel code model (32-bit kernel)  --->
   Device Drivers  --->
      Character devices  --->    
         Serial drivers  --->
            [*] 8250/16550 and compatible serial support  
   Kernel hacking  --->
      [*] Compile the kernel with debug info

   最后编译内核,输入
Make ARCH=mips CROSS_COMPILE=mips-linux-gnu-
   正常情况下会生成一个vmlinux文件,大概十几兆。将其传送到simics的workspace下的targets/malta下面(参见上一篇)。
   现在运行simics,从File->New session里打开malta-linux-common.simics文件,记得确保配置文件里指向的是新编译的内核(参见上一篇)。加载完后,在simics命令行输入:
Simics>new-gdb-remote回车,Simics会提示在9123端口打开了gdb server。
   然后在linux系统里linux-2.6.37目录下,输入命令
Root>/cross/bin/mipsel-unkown-linux-gnu-gdb回车,再输入
(gdb)symbol-file vmlinux (加载内核中编译进去的调试信息)
(gdb)target remote 192.168.1.100:9123回车
   192.168.1.100是我安装了simics的Windows系统的IP。回车后会看到提示gdb连接上了。屏幕输出为:
<
(gdb) symbol-file vmlinux
Reading symbols from /root/mips/code/linux-2.6.37/vmlinux...done.
(gdb) target remote 192.168.1.100:9123
Remote debugging using 192.168.1.100:9123
kernel_entry () at arch/mips/kernel/head.S:97
97    #else
Current language:  auto
The current source language is "auto; currently asm".
(gdb)

   然后就可以调试内核了,是不是很方便啊。而且你还可以同时在simics的命令行设置一些simics专有的断点,就像上一篇提到的那些。这样比单独用gdb调试内核更强了。
   此时如果你直接让simics运行,比如输入命令c,会发现串口上什么输出也没有。所以还需要修改一下相关的文件,才能让linux运行起来。
   具体调试过程就不再啰嗦了。下面总结一下内核里需要修改的地方。
1. 在arch/mips/mti-malta/malta-memory.c文件中
   函数prom_getmdesc()
   这个函数是计算机器的内存大小。由于需要yamon预先设置一些值,而我们此时没有yamon可用。所以这里当然会出错。想知道simics里面这个malta系统设置了多少内存。就在simics的命令行输入:
Simics>phys_mem.map回车。
   会看到一行:0x0000000000000000 ram  0 0x0                0x8000000
   这一行的意思就是设定了128M(0x8000000)的物理内存。
   于是我们把这个函数的开头改为如下,相当于把内存大小固定:
Unsigned int memsize;

Physical_memsize = 0x8000000;
Memsize = physical_memsize;
Memset(mdesc, 0, sizeof(mdesc));
/*函数后面部分就不用改了,保持原来的代码*/

2. 在arch/mips/mti-malta/malta-console.c中
   函数prom_putchar(char c)
   很显然,这个函数是早期printk所调用的输出函数。但是现在它工作不正常。由于此函数跟踪下去会很郁闷。特别是我本来对串口驱动也不熟悉。所以为了省事,我把这个函数改成下面这个样子
int prom_putchar(char c)
{
    asm __volatile__(
            ".set mips0"        "\t\t# __putc"    "\n\t"
            ".set noreorder"            "\n\t"
            "getstat:"                "\n\t"
            "lui    $8, 0xb800"            "\n\t"
            "ori    $8, $8, 0x3fd"            "\n\t"
            "lb    $9, ($8)"            "\n\t"
            "li    $10, 0x60"            "\n\t"
            "bne    $9, $10, getstat"        "\n\t"
            "lui    $8, 0xb800"            "\n\t"
            "ori    $8, $8, 0x3f8"            "\n\t"
            "sb    %0, ($8)"            "\n\t"
            :
            : "r" (c)
            : "$8", "$9", "$10");

    return 1;
}
   看过上一篇文章,就该知道这个函数的意思,就是往串口输出一个字符c.
   不过还有一点初始化的工作需要做。

3. 在arch/mips/kernel/early_printk.c中
   增加一个函数,用来初始化串口。
int simics_console_setup(struct console *con, char *name)
{
    asm __volatile__(
            ".set mips0"        "\t\t# __setup"    "\n\t"
            ".set noreorder"            "\n\t"
            "lui    $8, 0xb800"            "\n\t"
            "ori    $8, $8, 0x3fb"            "\n\t"
            "li    $9, 0x93"            "\n\t"
            "sb    $9, ($8)"            "\n\t"
            "lui    $8, 0xb800"            "\n\t"
            "ori    $8, $8, 0x3f8"            "\n\t"
            "li    $9, 0x3"            "\n\t"
            "sb    $9, ($8)"            "\n\t"
            "lui    $8, 0xb800"            "\n\t"
            "ori    $8, $8, 0x3f9"            "\n\t"
            "li    $9, 0"                "\n\t"
            "sb    $9, ($8)"            "\n\t"
            "lui    $8, 0xb800"            "\n\t"
            "ori    $8, $8, 0x3fb"            "\n\t"
            "li    $9, 0x13"            "\n\t"
            "sb    $9, ($8)"            "\n\t"
            "lui    $8, 0xb800"            "\n\t"
            "ori    $8, $8, 0x3f9"            "\n\t"
            "li    $9, 0"                "\n\t"
            "sb    $9, ($8)"            "\n\t"
            "lui    $8, 0xb800"            "\n\t"
            "ori    $8, $8, 0x3fc"            "\n\t"
            "li    $9, 0x03"            "\n\t"
            "sb    $9, ($8)"            "\n\t"
            :
            :
            : "$8", "$9");
    return 0;
}
   这段代码的含义在上一篇里也提到过了。现在在此文件的结构
static struct console early_console里增加一行,为:
    .setup    = simics_console_setup,
  
   这样simics_console_setup函数就会在早期的串口初始化函数里被调用。
   同时在drivers/serial/8250.c文件里的函数serial8250_console_putchar要参考prom_putc进行修改。否者后面可能看不到输出。

   这时候可以试着重新编译一下内核,再运行,应该可以看到串口有输入了。不过现在还是有问题,CPU的频率还没有得到。

4. 在arch/mips/mti-malta/malta-time.c中
   函数estimate_cpu_frequency(void)
   将此函数中的内容都删掉,改为:
Unsigned int count;
Count = 10000000;
Mips_hpt_frequency = count;
Return count;
  
   这是因为在配置文件c:\program files\virtutech\simics-3.0.29\targets\malta\malta-system.include里有这么一行:
if not defined freq_mhz      {$freq_mhz    = 10}
说明cpu频率被设为10MHZ,即10000000个时钟周期每秒。

   现在这个内核应该可以运行了。不过还没有文件系统。我们先用一个简单的方法来试试。
   在linux系统里,建立一个临时目录比如/test,进入此目录,然后生成一个hello.c文件,内容为:
#include

int main()
{
    printf("Hello World!\n");
    sleep(999999);
    return 0;
}
   编译这个文件
Mips-linux-gun-gcc –march=r4000 hello.c –o init
   会生成一个叫init的可执行文件。现在重新配置内核,增加下面的选项:
   General setup  --->  
[*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
(/test) Initramfs source file(s)
   让linux使用initramfs来做根文件系统。关于initramfs在网上有不少资料。
   编译内核。传送内核到simics的workspace相应目录下。打开simics并加载配置文件。然后运行系统。但是发现系统启动到最后还是会出问题。并不能按我们期望地来打印出Hellow World。
   根据几次调试,大致发现了是走到了执行init这个程序处出的错。现在我们演示一下如何找出这个错误:
   重启simics并加载配置文件。在simics命令行输入
Simics>new-gdb-remote回车
   在linux系统的内核目录linux-2.6.37下,输入
Root>/cross/bin/mipsel-unkown-linux-gnu-gdb回车
(gdb)symbol-file vmlinux回车
(gdb)target remote 192.168.1.100:9123回车
(gdb)break kernel_execve回车,在此函数设置断点,因为init程序由它来执行的。
(gdb)c回车,程序会在断点处停下,输出如下信息
Continuing.

Breakpoint 1, kernel_execve (filename=0x8028d3fc "/init", argv=0x802a18dc, envp=0x802a1850)
    at arch/mips/kernel/syscall.c:451
451        __asm__ volatile ("                    \n"
Current language:  auto
The current source language is "auto; currently c".
   先看下此处代码的样子
(gdb)l回车
446        register unsigned long __a1 asm("$5") = (unsigned long) argv;
447        register unsigned long __a2 asm("$6") = (unsigned long) envp;
448        register unsigned long __a3 asm("$7");
449        unsigned long __v0;
450    
451        __asm__ volatile ("                    \n"
452        "    .set    noreorder                \n"
453        "    li    $2, %5        # __NR_execve        \n"
454        "    syscall                        \n"
455        "    move    %0, $2                    \n"
   我们反编译一下此处的代码
(gdb)disassemble回车
Dump of assembler code for function kernel_execve:
0x801062c0 :    li    v0,4011
0x801062c4 :    syscall
0x801062c8 :    move    v1,v0
0x801062cc :    beqz    a3,0x801062dc
0x801062d0 :    nop
0x801062d4 :    jr    ra
0x801062d8 :    negu    v0,v1
0x801062dc :    jr    ra
0x801062e0 :    move    v0,v1
End of assembler dump.
   我们能看出来,在执行了syscall系统调用后,然后是判断返回值。我们再设置一个断点
(gdb)break *0x801062cc回车
   然后输入c命令执行,此时查看simics的主界面,会发现一个异常输出。
Tried to executed a YET UNIMPLEMENTED instruction:
  rdhwr at l:0x404b0c

   很显然,这个地址0x404b0c是用户空间的地址(参见see mips run里对地址空间的描述)。那就是说我们的init程序里出现了cpu不认识的指令。这说明两个问题:
1.    我们在编译hello.c时提供的选项不对。导致生成了r4kc不认识的指令。
2.    内核已经成功运行起来,因为执行init是内核所做的最后一件事。并且内核对于init程序也能识别并加载,否则不会进入到用户地址空间。
   此时我的屏幕是这个样子,图1:
 
   如果你对这个交叉编译工具比较熟悉,已经可以试试用busybox来做initramfs了。不管怎么样,内核运行起来了,根文件系统是下一步的事情了。

阅读(6074) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~