上一次已经找到了对串口的操作方法。这样就可以考虑编译自己的内核了,因为在编译内核的过程中,串口的输出将作为调试的主要功能。
先介绍一下我的工作环境:
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了。不管怎么样,内核运行起来了,根文件系统是下一步的事情了。
阅读(6153) | 评论(0) | 转发(0) |