armlinux 内核

74
1 ARMLinux 内内 内内内 [email protected] 内内内内内内内内内 2009 内 5 内

Upload: ellema

Post on 13-Jan-2016

125 views

Category:

Documents


0 download

DESCRIPTION

ARMLinux 内核. 陈文智 [email protected] 浙江大学计算机学院 2009 年 5 月. 提纲. 1. ARM 系统结构简介 2. ARM-Linux 内存管理 3. ARM-Linux 的中断响应和处理 4. ARM-Linux 系统调用 5. 系统的启动和初始化 6. ARM-Linux 进程管理和调度 7. Linux 的模块机制. 1. ARM 系统结构简介. ARM 有 7 种运行状态 : 用户状态( User ) 中断状态( IRQ, Imterrupt Request ) (0x18) - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: ARMLinux  内核

1

ARMLinux 内核

陈文智 [email protected]

浙江大学计算机学院2009 年 5 月

Page 2: ARMLinux  内核

2

提纲

1. ARM 系统结构简介 2. ARM-Linux 内存管理 3. ARM-Linux 的中断响应和处理 4. ARM-Linux 系统调用5. 系统的启动和初始化 6. ARM-Linux 进程管理和调度 7. Linux 的模块机制

Page 3: ARMLinux  内核

3

1. ARM 系统结构简介

ARM 有 7 种运行状态 : 用户状态( User ) 中断状态( IRQ, Imterrupt Request ) (0x18) 快中断状态( FIQ,Fast Imterrupt Request ) (0x1c) 监管状态( Supervisor ) 终止状态( Abort ) 无定义状态( Undefined ) 系统状态( System )

Page 4: ARMLinux  内核

4

ARM 系统结构中各个寄存器的使用方式 寄存器 使用方式程序计数器 pc( r15)

由所有运行状态共用

通用寄存器 r0-r7 由所有运行状态共用通用寄存器 r8-r12 除快中断以外所有其他运行状态共用(快中断状态有自

己专用的 r8-r12) 当前程序状态寄存

器 CPSR由所有运行状态共用

保存程序状态寄存器SPSR

除用户状态以外的 6种运行状态,各有自己的保存程序状态寄存器 SPSR

堆栈指针 sp( r13)和链接寄存器 lr( r14)

7种运行状态各有自己的 sp和 lr

Page 5: ARMLinux  内核

5

2 ARM-Linux 内存管理

存储管理是一个很大的范畴 地址映射、空间分配、保护机制

存储管理机制的实现和具体的 CPU 以及MMU 的结构关系非常紧密

操作系统内核的复杂性相当程度上来自内存管理,对整个系统的结构有着根本性的深远影响

Page 6: ARMLinux  内核

6

2.1 内存管理和 MMU

MMU ,也就是“内存管理单元”,其主要作用是两个方面:地址映射 对地址访问的保护和限制

MMU 就是提供一组寄存器MMU 可以做在芯片中,也可以作为协处

理器ARM 中的 CP15

Page 7: ARMLinux  内核

7

2.2 冯 · 诺依曼结构和哈佛结构

冯 · 诺依曼结构:程序只是一种数据,对程序也可以像对数据一样加以处理,并且可以和数据存储在同一个存储器中

嵌入式系统中往往采用程序和数据两个存储器、两条总线的系统结构,称为“哈佛结构”

Page 8: ARMLinux  内核

8

2.3 ARM 存储管理机制

ARM 系统结构中,地址映射可以是单层的按“段( section )”映射,也可以是二层的页面映射

采用单层的段映射的时候,内存中有个“段映射表” ,当 CPU 访问内存的时候: 其 32 位虚地址的高 12 位用作访问段映射表的下标,从表中找

到相应的表项 每个表项提供一个 12 位的物理段地址,以及对这个段的访问

许可标志,将这 12 位物理段地址和虚拟地址中的低 20 位拼接在一起,就得到了 32 位的物理地址

Page 9: ARMLinux  内核

9

如果采用页面映射,“段映射表”就成了“首层页面映射表”,映射的过程如下 ( 以页面大小= 4KB 为例 ) :

以 32 位虚地址的高 12 位( bit20-bit31 )作为访问首层映射表的下标,从表中找到相应的表项,每个表项指向一个二层映射表。

以虚拟地址中的次 8 位( bit12-bit19 )作为访问所得二层映射表的下标,进一步从相应表项中取得 20 位的物理页面地址。

最后,将 20 位的物理页面地址和虚拟地址中的最低 12 位拼接在一起,就得到了 32 位的物理地址。

Page 10: ARMLinux  内核

10

凡是支持虚存的 CPU 必须为有关的映射表提供高速缓存,使地址映射的过程在不访问内存的前提下完成,用于这个目的高速缓存称为 TLB

高速缓存 (I/O 的特殊性 )ARM 系统结构中配备了两个地址映射 TL

B 和两个高速缓存

Page 11: ARMLinux  内核

11

ARM 处理器中, MMU 是作为协处理器CP15 的一部分实现的

MMU 相关的最主要的寄存器有三个:控制寄存器,控制 MMU 的开关、高速缓存

的开关、写缓冲区的开关等地址转换表基地址寄存器 域访问控制寄存器

Page 12: ARMLinux  内核

12

控制寄存器中有 S 位(表示 System )和 R 位(表示 ROM ),用于决定了 CPU 在当前运行状态下对目标段或者页面的访问权限,如果段或者页面映射表项中的 2 位的“访问权限” AP 为 00 ,那么 S 位和 R 位所起的作用如表

S R CPU运行在特权状态

CPU运行在用户状态

0 0 不能访问 不能访问1 0 只读 不能访问0 1 只读 只读1 1 不确定 不确定

Page 13: ARMLinux  内核

13

如果 AP 为 01 ,则和 S 位 R 位无关,特权状态可读可写,用户状态不能访问。

如果 AP 为 10 ,则和 S 位 R 位无关,特权状态可读可写,用户状态只读。

如果 AP 为 11 ,则和 S 位 R 位无关,特权状态、用户状态都可读可写。

Page 14: ARMLinux  内核

14

2.4 ARM-Linux 存储机制的建立

ARM-Linux 内核也将这 4GB 虚拟地址空间分为两个部分 ,系统空间和用户空间

ARM 将 I/O 也放在内存地址空间中,所以系统空间的一部分虚拟地址不是映射到物理内存,而是映射到一些 I/O 设备的地址

Page 15: ARMLinux  内核

15

#define TASK_SIZE (0xc0000000UL)#define PAGE_OFFSET (0xc0000000UL)#define PHYS_OFFSET (0xa0000000UL)

#define __virt_to_phys(x) ((x) - PAGE_OFFSET + PHYS_OFFSET)#define __phys_to_virt(x) ((x) - PHYS_OFFSET + PAGE_OFFSET)

Page 16: ARMLinux  内核

16

Xsbase255 开发系统存储管理的描述数据结构: static struct map_desc xsbase255_io_desc[] __initdata = { /* virtual physical length domain r w c b */{ 0xE8000000, 0x00000000, 0x02000000, DOMAIN_IO, 0, 1, 0, 0 }, { 0xF0000000, 0x04000000, 0x00100000, DOMAIN_IO, 0, 1, 0, 0 }, { 0xf0110000, 0x08000000, 0x00010000, DOMAIN_IO, 0, 1, 0, 0 }, { 0xf0120000, 0x08100000, 0x00010000, DOMAIN_IO, 0, 1, 0, 0 }, { 0xf1000000, 0x0C700000, 0x00010000, DOMAIN_IO, 0, 1, 0, 0 }, };

//片选 0 CS0 : Intel Strata Flash 32M; // 片选 1CS1 : CS8900A;//片选 2 CS2 : Extend PORT0,扩充的 GPIO;// CS2 : Extend PORT1;// 片选 CS3 : USB Host(Ez-Host)

Page 17: ARMLinux  内核

17

ARM 处理器上的实现和 x86 的既相似又有很多不同: 在 ARM 处理器上,如果整个段( 1MB ,并且和 1MB 边界对齐)都有映射,就采用单层映射;而在 x86 上总是采用二层映射

ARM 处理器上所谓的“段( section )”是固定长度的,实质上就是超大型的页面;而 x86 上的“段( segment )”则是不定长的

Linux 在启动初始化的时候依次调用: start_kernel()>setup_arch()>pageing_init()>memtable_init()>create_mapping()

Page 18: ARMLinux  内核

18

2.5 ARM-Linux 进程的虚存空间

Linux 虚拟内存的实现需要 6 种机制的支持:地址映射机制内存分配回收机制缓存和刷新机制请求页机制交换机制内存共享机制

Page 19: ARMLinux  内核

19

系统中的每个进程都各有自己的首层映射表,这就是它的空间,没有独立的空间的就只是线程而不是进程

Linux 内核需要管理所有的虚拟内存地址,每个进程虚拟内存中的内容在其 task_struct 结构中指向的 vm_area_struct 结构中描叙

Page 20: ARMLinux  内核

20

task_struct 结构分析图 :

Page 21: ARMLinux  内核

21

由于那些虚拟内存区域来源各不相同, Linux 使用 vm_area_struct 中指向一组虚拟内存处理过程的指针来抽象此接口

为进程创建新的虚拟内存区域或处理页面不在物理内存中的情况下, Linux 内核重复使用进程的 vm_area_struct 数据结构集合。采用 AVL 树来减少查找时间。

当进程请求分配虚拟内存时, Linux 并不直接分配物理内存

Page 22: ARMLinux  内核

22

3 ARM-Linux 的中断响应和处理

中断是一个流程,一般来说要经过三个环节:中断响应中断处理中断返回

中断响应是第一个环节,主要是确定中断源,在整个中断机制中起着枢纽的作用

Page 23: ARMLinux  内核

23

使 CPU 在响应中断的时候能迅速的确定中断源,且尽量减少引脚数量,辅助手段主要有下列几种:中断源通过数据总线提供一个代表具体设备的数值,称为“中断向量”

在外部提供一个“集线器”,称为“中断控制器”

将中断控制器集成在 CPU 芯片中,但是设法“挪用”或“复制”原有的若干引线,而并不实际增加引线的数量

Page 24: ARMLinux  内核

24

ARM 是将中断控制器集成在 CPU 内部的,由外设产生的中断请求都由芯片上的中断控制器汇总成一个 IRQ 中断请求

中断控制器还向 CPU 提供一个中断请求寄存器和一个中断控制寄存器

GPIO 是一个通用的可编程的 I/O 接口,其接口寄存器中的每一位都可以分别在程序的控制下设置用于输入或者输出

Page 25: ARMLinux  内核

25

ARM Linux 将中断源分为三组:第一组是针对外部中断源;第二组中是针对内部中断源,它们都来自集

成在芯片内部的外围设备和控制器,比如 LCD 控制器、串行口、 DMA 控制器等等。

第三组中断源使用的是一个两层结构。

Page 26: ARMLinux  内核

26

在 Linux 中,每一个中断控制器都由 strcut hw_interrut_type 数据结构表示:

struct hw_interrupt_type {const char * typename;unsigned int (*startup)(unsigned int irq);void (*shutdown)(unsigned int irq);void (*enable)(unsigned int irq);void (*ack)(unsigned int irq);void (*end)(unsigned int irq);void (*set_affinity)(unsiged int irq,unsigned long mask);};

Page 27: ARMLinux  内核

27

每一个中断请求线都有一个 struct irqdesc 数据结构表示:

typedef struct {unsigned int status; /* IRQ status */hw_irq_controller *handler;struct irqaction *action; /*IRQ action list */unsigned int depth; /* nested irq disables */spinlock_t lock;}_cacheline_aligned irq_desc_t;

Page 28: ARMLinux  内核

28

具体中断处理程序则在数据结构 struct irqaction

三个数据结构的相互关系如图 :struct hw_interr

upt_type

Struct irqacton

指向具体的中断服务函数

irq_desc[NR_IRQS

Page 29: ARMLinux  内核

29

ARM Linux 的中断初始化。 在 ARM Linux 存储管理中,内核中 DRAM 区间的虚

拟地址和物理地址是相同的。系统加电引导以后, CPU 进入内核的总入口,即代码段的起点 stext , CPU 首先从自身读出 CPU 的型号以及其所在的开发板,把有关的信息保存在全局变量中;

然后就转入 start_kernel() 函数进行初始化; 接着是执行函数 trap_init ()

这个函数做的第一件事是将下列指令搬运到虚拟地址 0 处:

Page 30: ARMLinux  内核

30

第二件事是搬运底层中断响应程序的代码(如下所示)到 0x200 处 :

.LCvectors: swi SYS_ERROR0 b __real_stubs_start + (vector_undefinstr - __stubs_start) ldr pc, __real_stubs_start + (.LCvswi - __stubs_start) b __real_stubs_start + (vector_prefetch - __stubs_start) b __real_stubs_start + (vector_data - __stubs_start) b __real_stubs_start + (vector_addrexcptn - __stubs_start) b __real_stubs_start + (vector_IRQ - __stubs_start) b __real_stubs_start + (vector_FIQ - __stubs_start)

__stubs_start:vector_IRQ:

…vector_data:…vector_prefetch:

…vector_undefinestr:

…vector_FIQ:

…vector_addrexcptn:

….LCvswi: .word vector_swi.LCsirq: .word __temp_irq.LCsund: .word __temp_und.LCsabt: .word __temp_abt__stubs_end:

Page 31: ARMLinux  内核

31

trap_init() 函数执行完了以后,再执行 init_IRQ() 。通过函数 init_IRQ() 建立上面提及的 3个数据结构及其相互联系的框架。

Page 32: ARMLinux  内核

32

在进入中断响应之前, CPU 自动完成下列操作:将进入中断响应前的内容装入 r14_irq ,即中

断模式的 lr ,使其指向中断点。将 cpsr 原来的内容装入 spsr_irq ,即中断模

式的 spsr ;同时改变 cpsr 的内容使 CPU 运行于中断模式,并关闭中断。

将堆栈指针 sp 切换成中断模式的 sp_irq 。将 pc 指向 0x18 。

Page 33: ARMLinux  内核

33

中断流程图:

Page 34: ARMLinux  内核

34

4 ARM-Linux 系统调用

LIBC 和直接调用X86 有 INT 0x80arm 处理器有自陷指令 SWI cpu 遇到自陷指令后,跳转到内核态操作系统首先保存当前运行的信息,然后

根据系统调用号查找相应的函数去执行执行完了以后恢复原先保存的运行信息返回

Page 35: ARMLinux  内核

35

实验一创建和使用一个新的系统调用(1)

在 arch/arm/kernel/ 目录下创建一个新的文件 mysyscall.c

在 arch/arm/kernel/call.S 中添加新的系统调用,新的系统调用号 0x900000+226

void hello(void) {printk(“hello world\n”);

}

.long SYMBOL_NAME(sys_gettid) .long SYMBOL_NAME(sys_readahead) .long SYMBOL_NAME(hello)__syscall_end: .rept NR_syscalls - (__syscall_end - __syscall_start) / 4 .long SYMBOL_NAME(sys_ni_syscall) .endr

Page 36: ARMLinux  内核

36

实验一创建和使用一个新的系统调用(2)

修改 arch/arm/kernel/ 目录下的 Makefile文件,在 obj-y 后面添加 mysyscall.o

obj-y := arch.o compat.o dma.o $(ENTRY_OBJ) entry-common.o irq.o \ process.o ptrace.o semaphore.o setup.o signal.o sys_arm.o \ time.o traps.o $(O_OBJS_$(MACHINE)) mysyscall.o

Page 37: ARMLinux  内核

37

实验一创建和使用一个新的系统调用(3)

写一个测试程序来使用新的系统调用: test.h:#define sys_hello() {__asm__ __volatile__ ("swi 0x900000+226\n\t")}while(0)test.c:#include <stdio.h>#include “test.h”int main(void){

printf("start hello\n");sys_hello();

printf("end hello\n");}

Page 38: ARMLinux  内核

38

实验一创建和使用一个新的系统调用(4)

然后执行

启动开发板,将应用程序 test 通过 zmodem 协议下载到开发板的文件系统目录下,在板子上运行 test 程序所得结果如下:

注意,上面的例子是直接用汇编使用系统调用的,而不是使用 libc,因为 test应用程序使用的是新添加的系统调用,而 libc中并没有,所以只能直接用汇编。

# arm-linux-gcc test.c -o test

# ./teststart hellohello worldend hello

Page 39: ARMLinux  内核

39

思考:

如何增加一个带参数的系统调用?

Page 40: ARMLinux  内核

40

5. 系统的启动和初始化

使用 bootloader 将内核映像载入 内核数据结构初始化(内核引导第一部

分) :start_kernel() 中调用了一系列初始化函数,

以完成 kernel 本身的设置 调用 init() 过程,创建第一个内核线程。

Page 41: ARMLinux  内核

41

Start_kernel(): 输出 Linux 版本信息( printk(linux_banner) ) 设置与体系结构相关的环境( setup_arch() ) 页表结构初始化( paging_init() ) 设置系统自陷入口( trap_init() ) 初始化系统 IRQ ( init_IRQ() ) 核心进程调度器初始化(包括初始化几个缺省的 Bottom-half , sched_init

() ) 时间、定时器初始化(包括读取 CMOS 时钟、估测主频、初始化定时器

中断等, time_init() ) 提取并分析核心启动参数(从环境变量中读取参数,设置相应标志位等待

处理,( parse_options() ) 控制台初始化(为输出信息而先于 PCI 初始化, console_init() ) 剖析器数据结构初始化( prof_buffer 和 prof_len 变量) 核心 Cache 初始化(描述 Cache 信息的 Cache , kmem_cache_init() ) 延迟校准(获得时钟 jiffies 与 CPU 主频 ticks 的延迟, calibrate_delay

() )

Page 42: ARMLinux  内核

42

内存初始化(设置内存上下界和页表项初始值, mem_init() ) 创建和设置内部及通用 cache ( "slab_cache" , kmem_cache_sizes_init() ) 创建 uid taskcount SLAB cache ( "uid_cache" , uidcache_init() ) 创建文件 cache ( "files_cache" , filescache_init() ) 创建目录 cache ( "dentry_cache" , dcache_init() ) 创建与虚存相关的 cache ( "vm_area_struct" , "mm_struct" , vma_init() ) 块设备读写缓冲区初始化(同时创建 "buffer_head"cache 用户加速访问, buffer_in

it() ) 创建页 cache (内存页 hash 表初始化, page_cache_init() ) 创建信号队列 cache ( "signal_queue" , signals_init() ) 初始化内存 inode 表( inode_init() ) 创建内存文件描述符表( "filp_cache" , file_table_init() ) SMP 机器其余 CPU (除当前引导 CPU )初始化(对于没有配置 SMP 的内核,此

函数为空, smp_init() ) 启动 init 过程(创建第一个核心线程,调用 init() 函数,原执行序列调用 cpu_idle()

等待调度, init() ) 至此 start_kernel() 结束,基本的核心环境已经建立起来了。

Page 43: ARMLinux  内核

43

外设初始化 -- 内核引导第二部分: init() 函数作为内核线程,首先锁定内核,然

后调用 do_basic_setup() 完成外设及其驱动程序的加载和初始化

do_basic_setup() 返回后, init() 使用 execve() 系统调用加载执行 init 程序。

Page 44: ARMLinux  内核

44

Do_basic_setup(): 总线初始化(比如 pci_init() ) 网络初始化(初始化网络数据结构,包括 sk_init() 、 skb_init() 和

proto_init() 三部分,在 proto_init() 中,将调用 protocols 结构中包含的所有协议的初始化过程, sock_init() )

创建 bdflush 核心线程( bdflush() 过程常驻核心空间,由核心唤醒来清理被写过的内存缓冲区,当 bdflush() 由 kernel_thread() 启动后,它将自己命名为 kflushd )

创建 kupdate 核心线程( kupdate() 过程常驻核心空间,由核心按时调度执行,将内存缓冲区中的信息更新到磁盘中,更新的内容包括超级块和 inode 表)

设置并启动核心调页线程 kswapd (为了防止 kswapd 启动时将版本信息输出到其他信息中间,核心线调用 kswapd_setup() 设置 kswapd 运行所要求的环境,然后再创建 kswapd 核心线程)

Page 45: ARMLinux  内核

45

创建事件管理核心线程( start_context_thread() 函数启动 context_thread() 过程,并重命名为 keventd )

设备初始化(包括并口 parport_init() 、字符设备 chr_dev_init() 、块设备 blk_dev_init() 、 SCSI 设备 scsi_dev_init() 、网络设备 net_dev_init() 、磁盘初始化及分区检查等等, device_setup() )

执行文件格式设置( binfmt_setup() ) 启动任何使用 __initcall 标识的函数(方便核心开发者添加启动函数, do_initcalls() )

文件系统初始化( filesystem_setup() ) 安装 root 文件系统( mount_root() ) 加载 INIT 程序

Page 46: ARMLinux  内核

46

init() 函数到此结束,内核的引导部分也到此结束了,这个由 start_kernel() 创建的第一个线程已经成为一个用户模式下的进程了。此时系统中存在着六个运行实体: start_kernel() 本身所在的执行体,这其实是一个 " 手工 " 创建

的线程,它在创建了 init() 线程以后就进入 cpu_idle()循环了,它不会在进程(线程)列表中出现

init 线程,由 start_kernel() 创建,当前处于用户态,加载了 init程序

kflushd 核心线程,由 init 线程创建,在核心态运行 bdflush()函数

kupdate 核心线程,由 init 线程创建,在核心态运行 kupdate()函数

kswapd 核心线程,由 init 线程创建,在核心态运行 kswapd()函数

keventd 核心线程,由 init 线程创建,在核心态运行 context_thread() 函数

Page 47: ARMLinux  内核

47

init 进程和 inittab脚本init 进程是系统所有进程的起点,它的进

程号是 1 init 进程到底是什么可以通过内核参数

“ init=XXX” 设置 通常, init 进程是在根目录下的 linuxrc脚本文件。

Page 48: ARMLinux  内核

48

#!/bin/shecho "Setting up RAMFS, please wait ... "/bin/mount -n -t ramfs ramfs /etc/tmp/bin/mount -n -t ramfs ramfs /etc/var/bin/mount -n -t ramfs ramfs /root/bin/cp -a /mnt/var/* /etc/var#/bin/cp -a /mnt/root/* /rootecho "done and exiting"exec /sbin/init

Page 49: ARMLinux  内核

49

/sbin/init 程序需要读取 /etc/inittab 文件。inittab 是以行为单位的描述性(非执行

性)文本,每一个指令行都具有以下格式:

id:runlevel:action:process

Page 50: ARMLinux  内核

50

rc 启动脚本: rc.sysinit 中最常见的动作就是激活交换分区,检查磁盘,加载硬件模块

Shell 的启动

Page 51: ARMLinux  内核

51

6 ARM-Linux 进程管理和调度

Linux 进程有 5 种状态,分别是:TASK_RUNNING TASK_INTERRUPTIBLE TASK_UNINTERRUPTIBLE TASK_ZOMBIE TASK_STOPPED

Page 52: ARMLinux  内核

52

6.1 Linux 进程的创建、执行和消亡

1. Linux 进程的创建 系统的第一个真正的进程, init 内核线程

(或进程)的标志符为 1 新进程通过克隆老进程或当前进程来创建 ,

系统调用 fork 或 clone 可以创建新任务 复制完成后, Linux允许两个进程共享资源而不是复制各自的拷贝

Page 53: ARMLinux  内核

53

2. Linux 进程的执行 要让若干新进程按照需要处理不同的事情,

就必须通过系统调用 exec 函数 sys_execve 将可执行文件的名字从

用户空间取入内核空间以后就调用 do_execve( ) 执行具体的操作

Page 54: ARMLinux  内核

54

do_execve( ) 执行的流程: 打开可执行文件 , 获取该文件的 file 结构。 获取参数区长度 , 将存放参数的页面清零。 对 linux_binprm 结构的其它项作初始化 通过对参数和环境个数的计算来检查是否在这方面有错误 调用 prepare_binprm() 对数据结构 linux_binprm 作进一步准

备 把一些参数 ( 文件名、环境变量、文件参数 ) 从用户空间复制

到内核空间 调用 search_binary_handler() ,搜寻目标文件的处理模块并执

Page 55: ARMLinux  内核

55

3. Linux 进程的消亡 进程终止由可终止进程的系统调用通过调

用 do_exit ()实现 do_exit(long code) 带一个参数 code ,用

于传递终止进程的原因

Page 56: ARMLinux  内核

56

以下情况要调用 do_exit() 函数:具体对应的系统调用出错,不得不终止进程 ,

如:do_page_fault () sys_sigreturn () setup_frame ()save_v86_state ()

其他终止进程的情况,通过调用以下函数实现终止: sys_exit () sys_reboot() do_signal ()

Page 57: ARMLinux  内核

57

LINUX 系统进程的切换包括三个层次 :用户数据的保存:

正文段、数据段、栈段、共享内存段 寄存器数据的保存

PC 、 PSW 、 SP 、 PCBP 、 FP… 系统层次的保存

proc 、 u 、虚拟存储空间管理表格、中断处理栈

Page 58: ARMLinux  内核

58

6.2 ARM-Linux 进程的调度

Linux 进程调度由函数 schedule() 实现的,其基本流程可以概括为五步:清理当前运行中的进程选择下一个投入运行的进程 设置新进程的运行环境 执行进程上下文切换 后期整理

Linux 调度的时机有两种: 在内核应用中直接调用 schedule() 被动调用 schedule()

Page 59: ARMLinux  内核

59

7. Linux 的模块机制

Linux 中的可加载模块 (Module) 是 Linux内核支持的动态可加载模块 Insmod rmmod

Linux module 载入内核后,它就成为内核代码的一部分

若某个 module 空闲,用户便可将它卸载出内核

Page 60: ARMLinux  内核

60

与 module 相关的命令有: lsmod 把现在 kernel 中已经安装的 modules 列出

来 insmod 把某个 module 安装到 kernel 中 rmmod 把某个没在用的 module 从 kernel 中卸载 depmod 制造 module dependency file ,以告诉将

来的 insmod 要去哪儿找 modules 来安装

Page 61: ARMLinux  内核

61

module 相关的数据结构主要有 :module 的声明如下:

struct module {struct module *next;struct module_ref *ref; /* the list of modules that refer to me */struct symbol_table *symtab;const char *name;int size; /* size of module in pages */void* addr; /* address of module */int state;void (*cleanup)(void); /* cleanup routine */};

Page 62: ARMLinux  内核

62

symbol_table 的声明如下: struct symbol_table {int size; /* total, including string table!!! */int n_symbols;int n_refs;struct internal_symbol symbol[0]; /* actual size defined by n_symbols */struct module_ref ref[0]; /* actual size defined by n_refs */};

Page 63: ARMLinux  内核

63

和 module 相关的系统调用有: 系统调用 说明

Sys_create_module 为模块分配空间,将模块链入系统的模块链中

Sys_init_module 初始化模块,修正指针使模块正常工作

Sys_delete_module 从系统模块链中删除模块,释放内存空间

Sys_get_kernel_syms 将系统的所有符号表全部取出到用户空间

Page 64: ARMLinux  内核

64

7.1 Module 的使用

Module 的装入有两种方法 :通过 insmod命令手工将 module 载入内核 根据需要载入 module(demand loaded modu

le) 卸载 module 有两种方法

用户使用 rmmod命令卸载 modulekerneld 自动卸载

Page 65: ARMLinux  内核

65

2.4 系列内核的 insmod工作的主要流程是: insmod 先调用系统调用 sys_get_kernel_syms ,

将当前加到系统中的模块和内核的符号表全部输出到 kernel_sym 结构中,为后面使用。

将 Mymodule 目标文件读进 insmod 用户进程空间,成为一个映像。

根据第一步得到的信息,将 Mymodule 映像中的地址没有确定的函数和变量一一修正过来。

调用系统调用 sys_create_module 、 sys_init_module ,将 Mymodule链入到系统中去

Page 66: ARMLinux  内核

66

实验二 Linux2.6 内核移植

2.4.18 内核、 2.6 内核和 LynuxOS 4.0 在最好情况、平均情况下和最坏情况下任务的响应时间比较:

Page 67: ARMLinux  内核

67

实验二 Linux2.6 内核移植( 1 )

Linux 内核移植大致可以归纳成以下几个步骤: 准备工作,下载 Linux2.6 内核源代码和编译器源代码等

建立交叉编译环境 制作 Boot Loader 修改和编译内核 制作文件系统 编写相应的设备驱动 编写应用程序

Page 68: ARMLinux  内核

68

实验二 Linux2.6 内核移植( 2 )

1. 准备工作下载 Linux2.6.10 内核源代码,可以到 ftp://

ftp.kernel.org 下载 ARM Linux 是基于标准 Linux 内核为 ARM 做

的补丁,可以在 ftp://ftp.arm.linux.org.uk上下载

Page 69: ARMLinux  内核

69

实验二 Linux2.6 内核移植( 3 )

2. 编译交叉编译工具 编译 linux2.6 内核需要 gcc3.2 以上的版本 ,在这次实验中在主机平台上编译 arm-linux-gcc3.4.2

修改 t-linux 文件,在 TARGET_LIBGCC2_CFLAGS 加上 -D__gthr_posix_h 和 -Dinhibit_libc

编译安装

$cd gcc-3.4.2$./configure --target=arm-linux --prefix=/arm-linux --with-headers=/root/linux-2.6.10/include --enable-languages=c --disable-shared --disable-threads

$make$make install

Page 70: ARMLinux  内核

70

实验二 Linux2.6 内核移植( 4 )

3. 配置 Linux2.6.10 内核首先修改 Makefile ,把里面的 SUBARCH :=

(shell uname –m | sed –e s/i.86/i386/ -es/sun4u/sparc64/ -e s/arm. */arm/ -e s/sa110/arm/) 这一行去掉,改成 SUBARCH : =arm

修改编译器选项 :CROSS_COMPILE =arm-linux-

Page 71: ARMLinux  内核

71

实验二 Linux2.6 内核移植( 5 ) 4. 编译内核

使用 make menuconfig 配置内核 在 SYSTEM TYPE 目录中选择正确的 CPU ,在 In

tel Imlementations选项中选择 Intel DBPXA25X Development Platform 。

Page 72: ARMLinux  内核

72

在 Character Devices 下的选上 PXA Serial Port Support选项

在 General Setup 下的 Default Kernel command string选项中填入正确的串口名字 ttyS0 或 ttyS1 和波特率 115200

Page 73: ARMLinux  内核

73

实验二 Linux2.6 内核移植( 6 )

使用 make zImage命令来编译内核,编译好的二进制的文件 zImage 在 /linux-2.6.10/arch/arm/boot 下

把 zImage拷贝到 /tftpboot 目录下,并下载到 Xsbase255 板子中运行

这时应该可以在 minicom 中看到有系统启动信息输出

Page 74: ARMLinux  内核

74

可以看到 Linux2.6 内核在 XSBase255 开发系统跑起来: