《嵌入式Linux基础教程学习笔记一》

时间:2023-08-27 22:21:26

常用书目下载地址:http://www.cnblogs.com/pengdonglin137/p/3688029.html

第二章

1、进程上下文和中断上下文(Page20)

当应用程序执行系统调用,造成上下文的切换而进入内核时,内核会代表这个进程执行内核代码。你会经常听到,这种情况称为内核运行于进程上下文中。相反,处理IDE驱动器的中断处理程序(ISR)也是内核代码,但运行时并不代表任何特定的进程。这种情况通常被称为内核运行于中断上下文中。

2、独立处理器、配套芯片组和集成处理器 (Page26)

独立处理器是指那些专注于指令处理功能的处理器。与集成处理器相比,独立处理器需要额外的支持电路完成其基本操作。在大多数情况下,这意味着处理器周围需要配备一个芯片组或者一个定制的逻辑芯片,已实现一些增强功能,包括DRAM控制器、系统总线寻址配置以及外围设备(比如键盘控制器和串行端口)。独立处理器一般会提供最强的整体CPU性能。

单独处理器都需要连接支撑逻辑芯片才能访问外设,这些外设包括系统主内存(DRAM)、ROM或者闪存、系统总线(比如PCI)或者其他外设,比如键盘控制器、串行端口和IDE接口,诸如此类。执行逻辑芯片的的功能一般由配套的芯片组来完成,而这个芯片组很可能是专门为某个系列的处理器设计的。

虽然独立处理器的应用很广泛,包括一些高负载处理引擎,但是大多数的小型嵌入式系统都采用某种集成处理器或者片上系统(System On Chip,SOC)。

参考阅读:

http://pan.baidu.com/s/1eQGbs5c

http://www.cnblogs.com/pengdonglin137/p/3690410.html

3、查询本机的内核的发布信息

cat /proc/version

4、顶层源码目录

在本书中,会常常提到顶层源码目录。这时,我们指的是内核源码树的最高一层目录。顶层源码目录包含以下列出的子目录:

arch/         block/         crypto/        Documentation/ 
            drivers/     firmware/    fs/               include/   
            init/          ipc/             kernel/         lib/ 
            mm/         net/            samples/      scripts/   
            security/    sound/       usr/              virt/

5、编译内核是显示详细编译过程

如: make  ARCH=arm CROSS_COMPILE=arm-linux- V=1 uImage

   1: make V=0|1 [targets] 0 => quiet build (default), 1 => verbose build

   2: make V=2   [targets] 2 => give reason for rebuild of target

   3: make O=dir [targets] Locate all output files in "dir", including .config

   4: make C=1   [targets] Check all c source with $CHECK (sparse by default)

   5: make C=2   [targets] Force check of all c source with $CHECK

6、清理

有几个make命令会删除配置文件而不会给出任何警告。最常见的就是make distclean(此外还有make mrproper)。这个make目标的设计初衷是上内核代码树回到原始的、未配置的状态。这包括删除源码树中所有的配置数据,当然也会删除原先的.config文件。

   1: Cleaning targets:

   2:   clean          - Remove most generated files but keep the config and

   3:                     enough build support to build external modules

   4:   mrproper      - Remove all generated files + config + various backup files

   5:   distclean      - mrproper + remove editor backup and patch files

7、bzImage和zImage  (Page61)

很多架构和机器类型都需要一个二进制镜像目标,而这个目标与具体使用的架构和引导加载程序有关。比较常见的这类目标是zImage。对于很多架构来说,这就是默认的二进制镜像目标,可以被加载到目标嵌入式系统中并运行。新手常犯的一个错误就是将bzImage指定为make的目标。然而,bzImage目标是针对x86/PC架构的。有一个常见的错误观点,认为bzImage是指经过压缩工具bzip2压缩过的镜像,其实不然,bzImage是指大(big)的zImage。

8、make menuconfig

在顶层目录执行make menuconfig ,然后再顶层Makefile中的:

459 %config: scripts_basic outputmakefile FORCE

460         $(Q)mkdir -p include/linux include/config

461         $(Q)$(MAKE) $(build)=scripts/kconfig $@

%config可以匹配*config,然后进入script/kconfig中执行 make  menuconfig,

7 ifdef KBUILD_KCONFIG

8 Kconfig := $(KBUILD_KCONFIG)

9 else

0 Kconfig := arch/$(SRCARCH)/Kconfig

1 endif

2 

3 xconfig: $(obj)/qconf

4         $< $(Kconfig)

5 

6 gconfig: $(obj)/gconf

7         $< $(Kconfig)

8 

9 menuconfig: $(obj)/mconf                                                    

0         $< $(Kconfig)

1 

2 config: $(obj)/conf

3         $< $(Kconfig)

4 

5 oldconfig: $(obj)/conf

6         $< -o $(Kconfig)

其中Kconfig是arch/arm/Kconfig,对于menuconfig,配置工具是mconf,他可以解析Kconfig文件。

9、嵌入式Linux发行版所包含的的组件   (Page73)

Linux内核

引导加载程序,你需要将它移植到特定的硬件平台上,并做相应的配置;

适合于你所选架构的交叉编译器和相关的工具链

文件系统,其中包含很多软件包——主要是二进制可执行文件和程序库,而且他们是针对本地硬件架构和处理器而编译的;

设备驱动程序,内核通过它们访问硬件板卡上的定制硬件;

开发环境,包括主机上的工具和软件;

Linux内核源码树,并且适合于特定的处理器和硬件板卡;

10、单体内核

Linux采用单体(monolithic)内核结构。也就是说,这个内核是由代码编译并静态链接生成的,是一个单一的可执行文件。然而,也可以编译一组源码文件,并通过增量链接的方式生成一个对象模块,他可以动态加载到运行的内核中。

内核的构建过程,不管采用哪种架构,构建时都会生成一些通用文件,其中之一就是名为vmlinux的ELF二进制文件(处于顶层内核源码目录中)。这个二进制文件就是单体内核(monolithic kernel)本身,我们也称它为内核主体。

下面是tq2440在生成内核源码顶层目录下的vmlinux时的链接命令:

arm-linux-ld -EL  -p --no-undefined -X --build-id -o vmlinux  \

                                                              \

-T arch/arm/kernel/vmlinux.lds                                \

                                                              \

arch/arm/kernel/head.o                                        \

arch/arm/kernel/init_task.o                                   \

                                                              \

init/built-in.o                                               \

--start-group                                                 \

usr/built-in.o                                                \

                                                              \

arch/arm/kernel/built-in.o                                    \

arch/arm/mm/built-in.o                                        \

arch/arm/common/built-in.o                                    \

arch/arm/mach-s3c2410/built-in.o                              \

arch/arm/mach-s3c2400/built-in.o                              \

arch/arm/mach-s3c2412/built-in.o                              \

arch/arm/mach-s3c2440/built-in.o                              \

arch/arm/mach-s3c2442/built-in.o                              \

arch/arm/mach-s3c2443/built-in.o                              \

arch/arm/plat-s3c24xx/built-in.o                              \

arch/arm/plat-s3c/built-in.o                                  \

arch/arm/nwfpe/built-in.o                                     \

                                                              \

kernel/built-in.o                                             \

mm/built-in.o                                                 \

fs/built-in.o                                                 \

ipc/built-in.o                                                \

security/built-in.o                                           \

crypto/built-in.o                                             \

block/built-in.o                                              \

arch/arm/lib/lib.a                                            \

lib/lib.a                                                     \

arch/arm/lib/built-in.o                                       \

lib/built-in.o                                                \

drivers/built-in.o                                            \

sound/built-in.o                                              \

firmware/built-in.o                                           \

net/built-in.o                                                \

                                                              \

--end-group .tmp_kallsyms2.o

 

11、内核是如何合成的? (具体需要参见第五章的第一节P75)

《嵌入式Linux基础教程学习笔记一》

以arm为例,其中vmlinusx 在内核源码顶层目录,Image在arch/arm/boot/下,piggy.gz在arch/arm/boot/compressed/下,bootable kernel image在arch/arm/boot/下。

这里主要说明如下俩个概念的不同:启动加载程序(Bootstrap Loader)和引导加载程序(Bootloader)

引导加载程序简单理解为uboot或者Bootloader或者第1阶段的加载程序,可以将启动加载程序看做是第2阶段的加载程序(简单理解为:zImage头部的几个文件,负责初始化设置一些硬件以及解压内核镜像的部分)。

不要将启动加载程序和引导加载程序混淆,很多架构都使用启动加载程序(第2阶段加载程序)将Linux内核镜像加载到内存中。有些启动加载程序会对内核镜像进行校验和检查,而大多数启动加载程序会解压并重新部署内核镜像。引导加载程序和启动加载程序之间的区别也很简单:但硬件单板加电时,引导加载程序获得其控制权,根本不依赖内核。相反,启动加载程序的主要作用是作为裸机引导加载程序和Linux内核之间的粘合剂。启动加载程序负责提供合适的上下文让内核运行于其中,并且执行必要的步骤以解压和重新部署内核二进制镜像。这类似于PC架构中的主加载程序和次加载程序的概念。

启动加载程序和内核镜像拼接在一起,用于加载。

(具体参见:Page79)

《嵌入式Linux基础教程学习笔记一》

12、查看内核初始化细节  (page96)

initcall_debug是一个很有趣的内核命令行参数,它允许你观察启动过程中的函数调用。只需在启动内核是设置一下initcall_debug,就可以看到系统输出相关的诊断信息:

下面initcall最后的return x after XX usecs 表示函数的返回值以及函数调用的持续时间。

calling  spi_init+0x0/0x84 @ 1

initcall spi_init+0x0/0x84 returned 0 after 2936 usecs

calling  i2c_init+0x0/0x60 @ 1

initcall i2c_init+0x0/0x60 returned 0 after 4214 usecs

calling  customize_machine+0x0/0x24 @ 1

S3C Power Management, Copyright 2004 Simtec Electronics

initcall customize_machine+0x0/0x24 returned 0 after 22922 usecs

这是个查看内核初始化细节的好办法,特别是可以了解内核调用各个子系统和模块的顺序。更有趣的是函数调用的持续时间。如果你关心系统启动时间,通过这种方法可以确定启动时间是在哪些地方被消耗的。

13、根文件系统

根文件系统指的是挂在于文件系统层次结构根部的文件系统,简单表示为/。

简单来说,根文件系统是内核挂载的第一个文件系统,挂载位置是文件系统层次结构的顶端。

Linux系统对于根文件系统有一些特殊需求。Linux要求根文件系统中包含应用程序和工具软件,通过它们来引导系统、初始化系统服务(比如网络和系统控制台)、加载设备驱动程序和挂在额外的文件系统。

14、根文件系统中的glibc和Linux动态加载器

在嵌入式系统的根文件系统中一般都会有这两个动态库:glibc(如libc-XXX.so)和Linux动态加载器(如ld-XXX.so)。其中glibc包含标准C程序库的函数,比如printf()和很多其他大多数的应用程序都依赖的常用函数。

Linux动态加载器负责将二进制程序加载到内存中,并且,如果应用程序引用了共享库中的函数,它还需要执行动态链接。

15、定制用户空间初始化程序

在init/main.c中:

if (execute_command) {

    run_init_process(execute_command);

    printk(KERN_WARNING "Failed to execute %s.  Attempting "

                "defaults...\n", execute_command);

}

run_init_process("/sbin/init");

run_init_process("/etc/init");

run_init_process("/bin/init");

run_init_process("/bin/sh");

 

panic("No init found.  Try passing init= option to kernel.");

在上面的语句中,如果execute_command非空,它会指向一个运行在用户空间的字符串,而这个字符串中包含了一个定制的、由用户提供的命令。开发人员在内核命令行中指定这个命令,并且他会由我们前面所研究的__setup宏进行设置。

在init/main.c中:

static int __init init_setup(char *str)

{

    unsigned int i;

 

    execute_command = str;

    /*

     * In case LILO is going to boot us with default command line,

     * it prepends "auto" before the whole cmdline which makes

     * the shell think it should execute a script with such name.

     * So we ignore all arguments entered _before_ init=... [MJ]

     */

    for (i = 1; i < MAX_INIT_ARGS; i++)

        argv_init[i] = NULL;

    return 1;

}

__setup("init=", init_setup);

在include/linux/init.h中:

#define __setup_param(str, unique_id, fn, early)            \

    static char __setup_str_##unique_id[] __initdata __aligned(1) = str; \

    static struct obs_kernel_param __setup_##unique_id    \

        __used __section(.init.setup)            \

        __attribute__((aligned((sizeof(long)))))    \

        = { __setup_str_##unique_id, fn, early }

 

#define __setup(str, fn)                    \

    __setup_param(str, fn, fn, 0)

下面是一个内核命令行的例子:

initcall_debug init=/sbin/myinit console=ttyS1,115200 root=/dev/hda1

含义:

内核显示所有的初始化函数调用,配置初始的控制台设备/dev/ttyS1,其数据速率为115Kbit/s,并执行一个定制的、名为myinit的用户空间初始化进程,这个程序位于根文件系统的/sbin目录中。它还指导内核从设备/dev/hda1挂载器根文件系统,这个设备是第一个IDE硬盘。

注意一下,一般来说,内核命令行中各个参数的先后次序无关紧要。

16、应用程序依赖关系

大多数应用程序有两类依赖关系:

1、动态链接的应用程序对程序库的依赖,这种应用程序中包含未解决的引用,这需要由程序库提供;

2、应用程序可能需要的外部配置文件或者数据文件

对于前者,可以使用工具来确定,这里介绍两种方法(XXX代表可执行程序):

1、ldd xxx

2、objdump –x xxx | grep NEEDED

对于后者,至少要对相关的应用程序有个基本了解。

17、rmmod 和 modprobe –r

如:modprobe –r ext3 可以用于删除模块,包括某个模块所依赖的模块,而rmmod不会删除一个模块所依赖的模块。

18、 /proc 、sysfs以及tmpfs

他们俩个都是伪文件系统(Pseudo File Systems)。

/proc 文件系统的名称源于他的最初设计目的:它是一个接口,内核通过它可以获取一个Linux系统上所有进程的信息。随着时间的推移,它也不断发展壮大,可以提供更多方面的信息,而不仅限于进程。很多用户空间的应用程序都依靠/proc文件系统中的内容来完成它的工作。例如mount命令,如果在执行时不带任何参数,会列出系统中当前所有已挂载文件系统的信息,而它是从/proc/mounts文件中获取这些信息的。如果不存在/proc文件系统,mount命令直接返回。除了mount之外,其他一些与/proc文件系统交互的实用程序还有free、pkill、pmap以及uptime。请参考procfs软件包获取更多信息。

挂载/proc文件系统:  mount –t proc /proc /proc 或者 mount –t proc none /proc

sysfs是对具体的内核对象(比如物理设备)进行建模,并且提供一种将设备和设备驱动程序关联起来的方法。从sysfs中可以获取很多系统信息,很多实用工具都使用了这些信息,比如电源管理和热插拔能力,还有mtd-utils中的很多与Flash操作相关的工具。

tmpfs中的所有内容都是存储在内核的虚拟内存中的,断电或者重启后,这些内容都丢失了。tmpfs文件系统对于快速临时文件存储很有用。对于那些会使用很多小的临时文件的应用程序来说,这可以提高它们的性能。

挂载tmpfs文件系统:  mount –t tmpfs /tmpfs /tmp 或者 mount –t tmpfs none /tmp

嵌入式系统中自动挂载这三种文件系统一般在/etc/fstab中设置:

   1:  # cat /etc/fstab 
   2:  proc            /proc           proc    defaults        0       0
   3:  sysfs           /sys            sysfs   defaults        0       0
   4:  tmpfs           /dev            tmpfs   defaults        0       0
   5:  tmpfs           /tmp            tmpfs   defaults,size=120M      0       0
   6:  tmpfs           /var/run            tmpfs       defaults        0       0

19、MTD概述

内存技术设备(Memory Technology Device,MTD)子系统的目的是让内核支持种类繁多的类似内存的设备,比如闪存芯片。市面上有很多不同种类的闪存芯片,对它们进行编程的方法也多种多样,主要原因是它们要支持很多特殊和高效的模式。MTD子系统采用了层次化架构,将底层设备的复杂性和(使用这些内存和闪存设备的)高层的数据组织及存储格式分隔开。

简单来说,MTD是一个设备驱动程序层,它提供了一套访问原始闪存设备的通用API接口。MTD支持很多种闪存设备。然而,MTD不是块设备。MTD与设备打交道时是以擦除块(erase block)为单位的,其大小不一,而块设备是以固定大小的块(称为扇区)位操作单位的。块设备有两种主要操作——读取扇区和写入扇区,而MTD有3种:读、写和擦除。MTD设备的写寿命是有限的,所以MTD会包含内部逻辑将写操作分布开来已延长设备的寿命,这被称为损耗均衡(wear leveling)。

与通常的想法相反,SD/MMC卡、CompactFlash卡、USB闪存盘以及其他一些类似的设备都不属于MTD设备。这些设备的内部都包含了闪存转换层,用于完成类似MTD的功能,比如块擦除和损耗均衡。因此,对于系统来说,它们看上去就像是传统的块设备,不需要经过MTD的特殊处理。

Linux中的大多数设备属于字符设备或者块设备中一种。而MTD既不是字符设备,也不是块设备。虽然一些转换机制可以使MTD看起来像字符设备或块设备,但是在Linux驱动架构中,MTD有其独特之处。这是因为MTD驱动程序必须完成一些闪存特有的操作,比如块擦除操作和损耗均衡,而传统的块设备驱动程序是没有类似操作的。

20、软实时和硬实时

软实时

大多数人都同意软实时意味着操作有时间限制。如果超过了时间限制后操作还没有完成的话,体验的质量就会下降,但不会带来致命后果。桌面工作站就是一个需要软实时的绝好的例子。编辑文档时,你期望按键之后立刻在屏幕上看到结果。在播放mp3文件时,你期望听到没有任何杂音、爆音或者中断的高品质音乐。

一般而言,普通人无法分辨出小于几十毫秒的延时。当然音乐家能够听出比这更短的延时,并且告诉你它们影响了音乐的质量。如果这些所谓的软实时事件错过了时限,结果可能不尽如人意,并导致体验的质量有所下降,但这并不是灾难性的。

硬实时

硬实时的特点是错过时限会造成严重结果。在一个硬实时系统中,如果错过了时限,后果往往是灾难性的。当然,“灾难”是相对而言的。但如果你的嵌入式设备正在控制喷气式飞机引擎的燃料流,而它没能及时的响应飞行员输入的命令或者操作特性的变化,致命后果就不可避免了。

注意,时限的持续时间并不是硬实时的特征。原子钟中处理每个嘀嗒的服务程序就是这样的例子。只要在下一个嘀嗒到来之前的持续1秒的时间窗口内处理完成,数据就依然有效。但如果错过了某个嘀嗒,全球定位系统就可能会产生几英尺或甚至几英里的误差!

考虑到这一点,我们借鉴了一组常用的软实时和硬实时的定义。对于软实时系统,如果错过的了时限,系统的计算值或者结果会不太理想。然而,对于硬实时系统,如果错过了某个时限,系统就是失败的,而且可能会造成灾难性的后果。