360 likes | 617 Views
SE3208 uCLinux Porting. B.H.Lee. 目录. uClinux 构成以及编译器 BOOTLOAD 的构成 Arch/ 和 include/ 目录的构成 uClinux Porting. 目录的构成. uClinux-2.4.6/ => uClinux 2.4. 6 版本 Linux Kernel uClibc/ => uClinux 优化过的 libc 库 forJupiter/ => Jupiter chip 的监控程序 (gdb 用 ) 和 Bootloader
E N D
SE3208 uCLinux Porting B.H.Lee
目录 • uClinux 构成以及编译器 • BOOTLOAD的构成 • Arch/和include/目录的构成 • uClinux Porting
目录的构成 uClinux-2.4.6/ => uClinux 2.4.6版本Linux Kernel uClibc/ => uClinux优化过的libc库 forJupiter/ => Jupiter chip的监控程序(gdb用)和Bootloader forVirgine/ => virgine chip的监控程序和Bootloader forAmazon/ => amazon chip的监控程序和Bootloader app/ => Shell以及测试程序 sash/ => 嵌入式Shell程序 busybox/ => 嵌入式应用程序(ls等) se3208-elf2flt/=> 文件格式变换程序, 把elf格式转换成flat格式
编译方法 > make mrproper > make jupiter_config #或make amazon_config > make oldconfig > make dep > make 最终romimage.bin文件生成以后结构如下 seloader.bin (Bootloader Image) linux.bin (Linux Kernel Image) rdgz.bin (压缩的Ramdisk Image)
Kernel的运行 seloader> reload kernel # 把位于ROM的Kernel程序下载到RAM seloader> reload ramdisk # 把位于ROM的Ramdisk下载到RAM seloader> boot Booting之前可以用memcmp命令检查下载是否正确完成
制作应用程序 1) se3208-elf2flt (elf执行文件) => 生成.bflt文件 2) 把RamdiskImage连接到设备以后安装 ############################### gunzip rd.gz losetup /dev/loop1 rd mount /dev/loop1 ./ramdisk_root ############################### 在ramdisk_root目录生成Ramdisk的内容 3)把bflt文件复制到ramdisk_root/bin目录 4)解除文件系统移除设备,生成rdgz.bin文件 ############################## unmount ./ramdisk_root losetup -d /dev/loop1 gzip rd cp rd.gz rdgz.bin ##############################
Make的执行过程 make mrproper : 删除所有的目的文,件相关文件和设置文件等 make jupiter_config: /arch/eiscnommu/def-configs/Jupiter 把Jupiter用defconfig设置 make oldconfig : 用/arch/eiscnommu/defconfig进行设置 把Include/asm-eiscnommu连接到asm Make dep : 确认构成Kernel源的文件之间的相互关系 把Include/asm/arch连接到arch-jupiter ( 设置板的相关参数)
Menuconfig 设置Arch/eiscnommu/config.in文件 Defconfig 文件里存储基本的设置参数 CONFIG_SE3208=y CONFIG_UID16=y CONFIG_RWSEM_GENERIC_SPINLOCK=y CONFIG_UCLINUX=y CONFIG_EXPERIMENTAL=y CONFIG_ARCH_JUPITER=y CONFIG_NO_PGT_CACHE=y CONFIG_CPU_32=y CONFIG_NO_MMU_LARGE_ALLOCS=y DRAM_BASE=0C200000 DRAM_SIZE=00E00000 CONFIG_JUPITER_DEBUG=y CONFIG_NET=y CONFIG_KCORE_ELF=y CONFIG_BINFMT_FLAT=y CONFIG_KERNEL_ELF=y …. CONFIG_BLK_DEV_RAM_SIZE=4096 CONFIG_BLK_DEV_INITRD=y
Mach-jupiter目录的构成 Arch.c : 设置Ramdisk起始地址和大小,IO data r/w 函数(peek,poke),初始化中断控制器 Irq.c : 与中断控制相关的函数( mask_irq, unmask_irq ..) Timer.c : Jupiter_timer_interrupt的定义
Arch-jupiter目录的构成 Hardware.h : 设置和宏定义各peripheral的control register Irq.h: 定义中断控制初始化inline函数 Irqs.h : 定义中断号码 Time.h : 登录TIMER中断处理者以及设置TIMER初始化
Bootloader流程 设置PLL、RAM,复制DATA SECTION 初始化cache、serial以及输入命令 连接bootp(获得IP地址) 利用以太网从HOST复制Kernel和Ramdisk image到指定的板上 利用serial从HOST复制Kernel和Ramdisk image到指定的板上 把位于内存的Kernel和Ramdisk Image移到Flash领域 Bootloader的命令的Debugging操作
Bootloader的构成图 Bootp packet生成和通信。CMD_RBL的桥接函数 CMD_RBL的登录和命令执行所需函数 Flash erase,write,lock,unlock 编译时,为了管理Section而设置的script 在tb loader驱动别的函数时使用的基本函数 用C语言编写的main函数。寻找和运行CMD_TLB里要执行的函数 初始化ethernet以及传送接收函数 连接mac_in.c和boot.c,tftp.c的桥接函数 Serial输入输出。驱动其他函数时实现输入输出的基本函数 运行tb loader所需的基本设备的初始化 Tftp packet的生成和传送接收 连接CMD_TBL和istring.c的桥接函数
/Arch的构成 ꊱ kernel/ : 代表如下核心代码 - Kernel的初始化代码 - 中断、Exception Handler代码 - Processor相关代码 - Signal相关代码 - 基于Processor的系统呼叫代码 ꊲ mm/ : 与虚拟存储器管理相关的代码,主要由控制MMU的代码构成 - 存储器初始化代码 - Page FaultHandler代码 - tlb, cache相关代码 - exception table code
/Arch的构成2 ꊳ lib/ : 为了提高速度把Kernel经常使用的函数用汇编语言编写的代码 - User mode 存储器复制代码 - string 处理代码 - TCP/IP checksum 代码 ꊴ boot/ : 装载Kernel用的代码。Kernel的解压以及移动Kernel到指定的位置
Include/asm-eiscnommu的构成 . Kernel使用的头文件中的Processor相关的文件构成 . Kernel使用的宏和Inline函数中,由Processor相关的代码构成 代码内容 ꊱ 用Inline汇编语言编写的代码(比如Semaphore函数,TCP/IP checksum函数等) ꊲ 与CPU以及板资源相关的宏(Cache / TLB的大小, 主存储器大小等) ꊳ SoC以及目标板的固有信息相关的宏(memory map, 周遍设备控制以及状态寄存器等)
Makefile以及Config.in文件 Linux Kernel通过Makefile来设置环境和进行编译 mainmenu_name "Linux Kernel Configuration" define_bool CONFIG_SE3208 y define_bool CONFIG_SBUS n define_bool CONFIG_UID16 y define_bool CONFIG_RWSEM_GENERIC_SPINLOCK y # Begin uclinux additions ----------------------------------------------------- define_bool CONFIG_UCLINUX y define_bool CONFIG_FULLDEBUG n # End uclinux additions ------------------------------------------------------- ...... (略) ...... mainmenu_option next_comment comment 'System Type'
Makefile以及Config.in文件 choice 'EISC system type'\ "JupiterCONFIG_ARCH_JUPITER \ Amazon CONFIG_ARCH_AMAZON \ VirgineG2CONFIG_ARCH_VIRGINE" Jupiter if [ "$CONFIG_ARCH_JUPITER" = "y" ]; then define_bool CONFIG_NO_PGT_CACHE y define_bool CONFIG_CPU_32 y define_bool CONFIG_NO_MMU_LARGE_ALLOCS y hex 'jupiter DRAM Base address' DRAM_BASE 0x0C000000 hex 'jupiter DRAM Size' DRAM_SIZE 0x01000000 # define_hex FLASH_MEM_BASE 0x08400000 # define_hex FLASH_SIZE 0x00200000 define_bool CONFIG_JUPITER_DEBUG y fi ...... (略) ...... <List 1> arch/se3208/config.in 文件的一部分
uClinux的Porting arch/eiscnommu/目录 include/asm-eiscnommu)/目录的内容 SE3208的特点 * SE3208核的CPU模式只有一个, 所以SP(堆栈指针) 寄存器只有一个 * 系统Boot设备为ROM, CPU的exception table必须位在ROM的0x0地址
arch/se3208/目录和include/asm-se3208/目录的构成 1) arch/se3208/kernel/head.S : kernel最初运行的代码 - 初始化堆栈指针 - 初始化存储器控制器 - 初始化临时serial设备驱动 - 呼叫start_kernel() 函数 2) arch/se3208/kernel/debug-se3208asm.S, debug-se3208.c : 临时serial设备驱动 Linux Kernel在Booting的时候利用printk()函数传递信息,但printk()实际上是在console_init()函数运行以后才能工作,为了确认console_init()以前的代码工作与否,制作了向serial设备输送文字队列的代码 3) 为了Kernel同步化而设计的代码 - 中断禁止/允许代码:cli(),sti(),save_flags(),save_flags_cli() restore_flags() - 与semaphore相关的代码: down() down_interruptible(), up()
setup_arch()函数 1) Processor特有初始化代码 2) 获得物理存储器大小和位置信息以后, 根据得到的信息初始化Paging系统 * 得到系统信息中存储器大小和位置信息 * 初始化bootmem分配器 * bootmem分配器是为了在Paging系统工作之前有效使用物理存储器而设置的,起分配存储器的功能 * 一般在初始化Paging系统时使用,实际上调用init_bootmem()函数即可 * 初始化Paging系统。具体是初始化zone分配器,调用paging_init()函数,在这里调用free_area_init_node()函数来初始化系统的所有Page
Setup_arch() void __init setup_arch(char **cmdline_p) { ...... (略) ...... if (meminfo.nr_banks == 0) { meminfo.nr_banks = 1; meminfo.bank[0].start = PAGE_OFFSET;//PHYS_OFFSET; meminfo.bank[0].size = MEM_SIZE; } ...... (略) ...... bootmap_size= init_bootmem_node( NODE_DATA(0), memory_start >> PAGE_SHIFT, PAGE_OFFSET >> PAGE_SHIFT, END_MEM >> PAGE_SHIFT);
Setup_arch() free_bootmem(memory_start, END_MEM - memory_start); reserve_bootmem(memory_start, bootmap_size); paging_init(&meminfo, mdesc); ...... (略) ...... if (mdesc->fixup) mdesc->fixup(mdesc, (struct param_struct *) tag, &from, &meminfo); }
trap_init()函数 * 组成exception table。 ENTRY(trap_init) push %r1 - %r4 ldi HANDLER_START, %r3 ldi irq_start, %r1 ldi irq_end, %r2 .L1: ldb (%r1), %r4 stb %r4, (%r3) add %r1, 1, %r1 add %r3, 1, %r3 cmp %r2, %r1 jnz .L1 pop %r1 - %r4, %pc
init_IRQ()函数 * 起初始化和驱动中断控制器的作用 void jupiter_init_aic() { __DISABLE_INT();// 设置sr寄存器来禁止中断 __SET_VECTORED();// 用向量方式处理中断 __SET_INTVGR(0x01); __SET_INTEN(0x0);// 设置INTEN寄存器来禁止中断 __ENABLE_INT();// 设置sr寄存器来允许中断 }
time_init()函数 • * 初始化系统Timer,初始化Timer Device Driver • 在time_init()函数中调用setup_timer()函数 • 把jupiter_timer_interrupt()函数设置为Timer中断处理者(Handler),初始化及驱动JUPITER SoC 内部的Timer控制器 • extern __inline__ void setup_timer (void) • { • int olden; • gettimeoffset = jupiter_gettimeoffset; • timer_irq.handler = jupiter_timer_interrupt;//设置Timer中断处理者函数 • setup_eisc_irq(IRQ_TIMER0, &timer_irq); • __SET_TIMCNT0( TIMER_CNT );// 初始化Timer控制器 • __SET_TIMCON0( (TIMER_PDC<<8) | (TIMER_MD<<1) | TIMER_EN ); • __GET_INTEN(olden); • __SET_INTEN( olden | TIMER0_BIT );// 驱动Timer }
console_init() • 是初始化终端设备驱动以及初始化CONSOLE设备(VGA、serial等)的函数 • 编写Serial设备驱动程序后,在此函数的下段增加初始化系统Serial设备的驱动 • 在console_init()调用se3208_console_init()函数 • 用叫做se3208_console的console结构体的地址为Factor来调用register_console()函数=> 以后可以使用printk
calibrate_delay() 取得处理器BogoMIPS (简单的MIPS)的函数 如果函数正常终止,可以确认entry.S中中断处理者的正常动作与否
kernel_thread()宏 pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags) { pid_t __ret; __asm__ __volatile__( " mov%1, %%r0 or%%r0, %2, %%r0# 在sys_clone()的flagsfactor里设置CLONE_VM ldi0x0, %%r1 ldi%5, %%r6# 把sys_clone()代号作为factor来传送 swi0x2# 调用系统呼叫 mov %%r6, %%r0 mov%%r0, %0# 调查母或子 cmp%%r0, 0x0 jnz1f# 如果是母终止kernel_thread
kernel_thread()宏 mov%4, %%r1#如果是子复归地址设置为2f ldi2f, %%r0# 跳转到fn push%%r0, %%r1 jr%3 2:jmp_sys_exit 1:" : "=r" (__ret) : "Ir" (flags), "I" (CLONE_VM), "r" (fn), "r" (arg), "I" (__NR_clone) : "%r0", "%r1", "%r6"); return __ret; }
switch_to() • 在Linux中,Kernel thread除了共享Kernel和存储器空间以外跟Process视为相同,成为Scheduling对象 • 运行kernel_thread(init, ...)以后, init thread不会马上执行而是先运行0号Process(start_kernel() 运行中生成的kernel thread) • 0号Process在kernel_thread结束以后执行cpu_idle()函数,此时运行schedule()函数来选择和执行init thread • 在schedule()函数内起Task Swiching功能的代码就是switch_to()
Switch_to()函数 /* * r0 = previous, r1 = next, return previous. *previous and next are guaranteed not to be the same. */ ENTRY(_switch_to) push%r2-%r7, %er, %sr# 把prev process的寄存器值储存到自己的Kernel堆栈里 lea(%sp),%r2 mov%r0,%r3 st%r2,(%r3, TSS_SAVE) ld(%r1, TSS_SAVE),%r2 lea(%r2),%sp pop%r2-%r7, %er, %sr, %pc# 把next process的寄存器值从自己的Kernel堆栈中还原 <List 6> switch_to 函数
Process执行环境相关的代码 ꊱ 程序装载代码: 在uClinux,应用程序的形式采用“flat”(在Linux里是“elf”, “coff”等) 根据应用程序类型的不同提供不同的装载代码,flat形式采用load_flat_binary()函数 ꊲ start_thread()宏: load_(文件形式)_binary()函数中最后使用的宏。设置User Mode的堆栈指针和程序Counter,设置argc, argv, envp等地址
Start_thread() #define start_thread(regs,pc,sp)\ ({\ unsigned long *stack = (unsigned long *)sp;\ set_fs(USER_DS);\ memzero(regs, sizeof(struct pt_regs));\ regs->SE_sp = sp;/* user sp */\ regs->SE_pc = pc;/* user pc */\ regs->SE_sr = 0x3000;/* user sr */\ regs->SE_er = 0x0;/* user er */\ regs->SE_r2 = stack[2];/* r2 (envp) */\ regs->SE_r1 = stack[1];/* r1 (argv) */\ regs->SE_r0 = stack[0];/* r0 (argc) */\ }) <List7> start_thread() 宏的实现
copy_thread()函数 * 和execve一样在Linux被认为Process操作中非常重要的系统呼叫之一fork()函数中调用此函数 * 在Linux中fork()的类似函数共有三个 - fork(), vfork(), clone() 三个函数都调用Kernel内部的do_fork()函数,函数的最后部分调用copy_thread() int copy_thread(int nr, unsigned long clone_flags, unsigned long esp, unsigned long unused, struct task_struct * p, struct pt_regs * regs) { struct pt_regs * childregs; struct context_save_struct * save; atomic_set(&p->thread.refcount, 0x1); childregs = ((struct pt_regs *)((unsigned long)p + 8192)) - 1;
Copy_thread()函数 /*下面if-else语句用软件方式实现了CPU mode */ if (regs->SE_prev_mode == KERNEL_MODE) { childregs = (struct pt_regs *) ((unsigned long)childregs - 12); *childregs = *regs; childregs->SE_sp = ((unsigned long)p + 8192) ; childregs->SE_r6 = 0; } else { /* regs->SE_prev_mode == USER_MODE时. */ *childregs = *regs; childregs->SE_r6 = 0; if (esp) { childregs->SE_sp = esp; } } save = ((struct context_save_struct *)(childregs)) - 1; *save = INIT_CSS; save->pc |= (unsigned long)__ret_from_fork; p->thread.save = save; return 0; } <List 8> copy_thread 函数的实现