mixin

http://www.linuxjournal.com/article/4540

什么是mixin

MakeMKV: 将 DVD 和蓝光光盘提取为 MKV

 http://linuxtoy.org/archives/makemkv.html

[转] 关于嵌入式系统的启动

http://hi.baidu.com/lihao102/blog/item/3f19a713b32b66856538db07.html 

嵌入式Linux启动分为两个部分,系统引导与Linux启动。系统引导将完成Linux装入内存前,初始化CPU和相关IO设备,并将Linux调入内存的工作。系统引导主要由BootLoader实现。在BootLoaderLinux内核调入内存之后,将权力交给LinuxKernel,进入Linux的启动部分。以下详细分析启动的过程与使用的文件。

一、系统引导与BootLoader
       BootLoader
因嵌入式系统的不同与PC机有很大不同,这里将以Hyper250(Inter Xscale GDPXA250)的启动为例来分析。由于没有BIOS驱动主板,EnbeddedOS必须由bootloader驱动所有的硬件,并完成硬件的初始化工作。
所有的初始化文件在hyper250/Bootloader目录下。
首先分析开机运行的分件:
            hyper250/Bootloader/X-Hyper250R1.1-Boot/src/start_xscale.S
      
文件包含两个库文件:
            hyper250/Bootloader/X-Hyper250R1.1-Boot/src/include/config.h
            hyper250/Bootloader/X-Hyper250R1.1-Boot/src/include/start_xscale.h
      
文件config.h主要完成系统各硬件的宏定义与设定,xscale.h主要完成对系统芯片的及系统操作的设定。
以下分析config.h文件:
       (1)
存储总线设备的宏定义:定义Flash的大小、字长等信息,定义SRAM的基址、大小和块大小。
       (2)
动态内存设定:定义DRAM的大小、基址。
       (3)
软件包信息:包名称、版本号。
       (4)
设定BOOT LOADER的位置:在DRAMSRAM的最大值、DRAM装入位置、栈的基址。
       (5)
设定kernel的位置:在DRAMSRAM的基址、KERNEL的最大值、KERNEL中块的数量。
       (6)
设定文件系统的位置:根目录在DRAMSRAM的基址、文件系统的最大值、文件系统中块的数量。
       (7)
设定LOADER程序:LOADER程序的静态内存基址、LOADER程序的最大值、块的数量。
       (8)
网络设定
以下分析start_xcalse.h文件:
       (1)
定义内存基址(A0000000)
       (2)
定义中断基址(40D00000)和中断保护栈的偏移量
       (3)
定义时钟管理基址(41300000)和寄存器偏移及其初始值
       (4)
定义GPIO接口寄存器基址(40E00000)及各寄存器的偏移
       (5)
定义GPIO接口各寄存器的初始值
       (6)
定义内存控制寄存器基址(48000000)和各寄存器的偏移
       (7)
定义内存控制寄存器的初始值
       (8)
定义电源管理寄存器的参数
       (9)
定义FFUART寄存器的基址(40100000)和各寄存器的偏移
       (10)
定义FFUART各寄存器的初始值
以下分析start_xcalse.S文件:
       (1)
设定中断基址(40D00000),完成中断保护栈的初始化
       (2)
初始化GPIO接口
       (3)
初始化内存SDRAM
       (4)
BootloaderFlash拷贝到SDRAM
       (5)
装入Linux内核镜像,将内核从Flash(000C 0000)装入SDRAM(A0008000).
       (6)
设定保护栈
       (7)
调用main.c的主函数c_main()       
      
      
以上start_xcalse.S通过APCS的编程标准书写的汇编文件初始化了系统相关的硬件,并且完成了BootLoader的装入内存和Linux内核的装入,最后将权力转交给main.c
以下将分析main.c文件:
            hyper250/Bootloader/X-Hyper250R1.1-Boot/src/main.c
      
以及两个库文件
            hyper250/Bootloader/X-Hyper250R1.1-Boot/src/include/main.h
            hyper250/Bootloader/X-Hyper250R1.1-Boot/src/include/scc.h 

 

二、Linux启动过程分析

       1.Makefile
分析:
在分析arch/arm/boot/compressed目录下的文件的时候,对于Makefile的分析是很重要的,因为内核将在这个目录相产生。这里主要工作是对内核的压缩和解压工作。本目录在编译完成后将产生vmlinuxhead.omisc.ohead-xscale.opiggy.o这几个文件。其中vmlinux(没有--lw:zImage是压缩过的内核)压缩过的内核。head.o是内核的头部文件,负责初始设置。misc.o将主要负责内核的解压工作,它在head.o之后。head-xscale.o文件主要针对Xscale的初始化,将在链接时与head.o合并。piggy.o是一个中间文件,其实是一个压缩的内核,只不过没有和初始化文件及解压文件链接而已。
      
       2.Decompress
分析:
BootLoader完成系统的引导以后并将Linux内核调入内存之后,调用bootLinux(),这个函数将跳转到kernel的起始位置。如果kernel没有压缩,就可以启动了。如果kernel压缩过,则要进行解压,在压缩过的kernel头部有解压程序。压缩过得kernel入口第一个文件源码位置在arch/arm/boot/compressed/head.S。它将调用函数decompress_kernel(),这个函数在文件arch/arm/boot/compressed/misc.c中,decompress_kernel()又调用proc_decomp_setup(),arch_decomp_setup()进行设置,然后使用在打印出信息“Uncompressing Linux...”后,调用gunzip()。将内核放于指定的位置。
启动首先运行的文件有:
            arch/arm/boot/compressed/head.S
            arch/arm/boot/compressed/head-xscale.S
            arch/arm/boot/compressed/misc.c
      
这些文件主要用于解压内核和以及启动内核映象。一旦内核启动,则这些文件所占内存空间将被释放。而且,一旦系统通过reset重起,当BootLoader将压缩过的内核放入内存中,首先执行的必然是这些代码。
以下分析head.S文件:
       (1)
对于各种Arm CPUDEBUG输出设定,通过定义宏来统一操作。
       (2)
设置kernel开始和结束地址,保存architecture ID
       (3)
如果在ARM2以上的CPU中,用的是普通用户模式,则升到超级用户模式,然后关中断。
       (4)
分析LC0结构delta offset,判断是否需要重载内核地址(r0存入偏移量,判断r0是否为零)
这里是否需要重载内核地址,我以为主要分析arch/arm/boot/Makefilearch/arm/boot/compressed/Makefilearch/arm/boot/compressed/vmlinux.lds.in三个文件,主要看vmlinux.lds.in链接文件的主要段的位置,LOAD_ADDR(_load_addr)0xA0008000,而对于TEXT_START(_text_start)的位置只设为0BSS_START(__bss_start)ALIGN(4)。对于这样的结果依赖于,对内核解压的运行方式,也就是说,内核解压前是在内存(RAM)中还是在FLASH上,因为这里,我们的BOOTLOADER将压缩内核(zImage)移到了RAM0xA0008000位置,我们的压缩内核是在内存(RAM)0xA0008000地址开始顺序排列,因此我们的r0获得的偏移量是载入地址(0xA0008000)。接下来的工作是要把内核镜像的相对地址转化为内存的物理地址,即重载内核地址。
       (5)
需要重载内核地址,将r0的偏移量加到BSS regionGOT table中。
       (6)
清空bss堆栈空间r2r3
       (7)
建立C程序运行需要的缓存,并赋于64K的栈空间。
       (8)
这时r2是缓存的结束地址,r4kernel的最后执行地址,r5kernel境象文件的开始地址。检查是否地址有冲突。
r5等于r2,使decompress后的kernel地址就在64K的栈之后。
       (9)
调用文件misc.c的函数decompress_kernel(),解压内核于缓存结束的地方(r2地址之后)。此时各寄存器值有如下变化:
            r0
为解压后kernel的大小
            r4
kernel执行时的地址
            r5
为解压后kernel的起始地址
            r6
CPU类型值(processor ID)
            r7
为系统类型值(architecture ID)
       (10)
reloc_start代码拷贝之kernel之后(r5+r0之后),首先清除缓存,而后执行reloc_start
       (11)reloc_start
r5开始的kernel重载于r4地址处。
       (12)
清除cache内容,关闭cache,将r7architecture ID赋于r1,执行r4开始的kernel代码。
关于head-xscale.S文件,它定义了xcale处理器的64kcache缓存的实现代码和关闭MMU及缓存的代码,这些代码将在链接过程中与head.S的合并。
关于misc.c文件,它引入了以下几个文件:
            include/linux/kernel.h
            include/asm-arm/arch-pxa/uncompress.h
            include/asm-arm/proc-armv/uncompress.h
            include/asm-arm/uaccess.h
            lib/inflate.c
      
以下分析misc.c文件的decompress_kernel()函数:
       (1)
首先传入参数:解压后内核地址,缓存开始地址,缓存结束地址,arch id。这些参数通过寄存器r0(r5),r1,r2,r3(r7)传入。
       (2)
接着执行proc_decomp_setup(),它在include/asm-arm/proc-armv/uncompress.h文件中。主要刷新并起用i cache,锁住交换缓存,这是一段嵌入的arm汇编代码。
       (3)
接着执行arch_decomp_setup(),它在include/asm-arm/arch-pxa/uncompress.h文件中,是一个空函数,用于扩展。
       (4)
然后执行makecrc(),它在lib/inflate.c中,主要将产生CRC-32 table,进行循环冗余校验。
       (5)
调用gunzip()解压kernel,它也在lib/inflate.c中。
       (6)
返回head.S,解压后kernel的长度传给r0,解压后的内核地址预先在r5中定义了。



         3.kernel进入文件分析:
随后系统将调入文件:arch/arm/kernel/head_armv.Sarch/arm/kernel/head_armo.S。对于armkernel而言,有两套.S文件:_armv.S  _armo.S. 选择_armv.S 还是_armo.S 依赖于处理器。ARMversion 1, version 2, 都只支持26位的地址空间。version 3开始支持32位的地址空间,同时还向后兼容26位的地址空间。version 4开始不再向后兼容26位的地址空间。这里由于Hyper250使用的是version7,故只涉及文件head_armv.S
head_armv.S是内核的入口点,在内核被解压到预定位置后,它将运行,这里简要说明其主要工作:
       (1)
首先,关中断并进入保护模式,这里将建立虚拟地址到物理地址的映射。(见第二章内存分析)

       (2)
调用lookup_processor_type,查询CUP和其ID是否在.proc.info表中,如果存在,则令r10指向此结构,在CPU的内核入口文件中。如果不是则提示errorp并挂起。关于r10指向的结构,他所属的内核入口文件,以Hyper250为例:arch/arm/mm/proc-xcale.S
这里要要注意的是,此处操作的对象是由vmlinux-armv.lds.in链接文件定位的段.proc.info中,这个段定义在proc-xcale.S文件末尾,这里要注意,上面并没有使系统进入保护模式,所以在这里对.proc.info寻址的时候,为了得到相对地址,做了一个相对寻址的变换。这里好象只用了这个结构的前3位:处理器类型值(value),处理器值掩码(mask)MMU标志值(mmuflags)。这3个值在分别放在寄存器r5(0x69052100)r6(0xfffff7f0)r8(0x00000c0e)中,r5r6只是用于和获得的处理器的ID相比较,而r8则有两个可能的值,分别表示MMU的状态:如果MMU开启,即CACHE_WRITE_THROUGH,则r8=0x00000c0a,否则r8=0x00000c0e。这里r8的值将会保持到初始页表时使用。
       r10
此时指向段.proc.info的开始地址。

       (3)
寄存器r1中的系统类型值(unique architecture number),这个系统类型值的定义,并且由bootloader传入。在文件arch/arm/tools/mach-types中:
       machine_is_xxx        CONFIG_xxxx                      MACH_TYPE_xxx             number
       xhyper250R1        ARCH_PXA_XHYPER250R1             PXA_XHYPER250R1             200

 

        (4)调用lookup_architecture_type,将以r1的值检查.arch.info表,这是个struct machine_desc由文件arch/arm/mach-pxa/xhyper250R1.c中的MACHINE_START()创建。假如没有此结构则提示errora并挂起。
这里要注意的是,段.arch.info的定位在vmlinux-armv.lds.in文件中紧接.proc.info,这个段定义在include/asm-arm/mach/arch.h文件中,使用了宏定义MACHINE_START()。文件首先定义了一个结构体machine_desc,段.arch.info主体部分使用了宏定义MACHINE_START()其中嵌入这个结构体。
通常来讲MACHINE_START()的实现应该在文件arch/arm/kernel/arch.c中,而这里hyper250的源码中,MACHINE_START()宏定义在arch/arm/mach-pxa/xhyper250R1.c中完成了定义,下面详细分析这个结构:
       (A)MACHINE_START
       MACHINE_START(_type,_name)
这宏开始处嵌入一个静态结构machine_desc,并且立即声明段.arch.info
       _type
MACH_TYPE(PXA_XHYPER250R1),用以赋值给machine_desc中的nr,这就是系统类型值number(200)
       _name
是描述系统类型的字符串,用以赋值给machine_desc中的namechar*
以下几个宏定义均在包含在machine_desc的赋值中,也在段.arch.info中。
       (B)MAINTAINER
       MAINTAINER(n)
,这个n并没有赋值给machine_desc结构,n"Hybus Co,. ltd."字符串,公司名字罢了。
       (C)BOOT_MEM
       BOOT_MEM(_pram,_pio,_vio)
,这里面很关键,又3个变量:
       _pram
,传值给phys_ram:物理内存的开始地址,程序中赋值为:0xa0000000
       _pio
,传值给phys_io:物理io的开始地址,程序中赋值为:0x40000000
       _vio
,传值给io_pg_offstio页表的偏移,程序中赋值为:_vio=0xfc000000,不过要进行转换:((_vio)>>18)&0xfffc=0x3f00
       (D)BOOT_PARAMS
       BOOT_PARAMS(_params)
这个宏定义了启动参数页表的偏移:param_offset,程序中赋值为:0xa0000100       
       (E)FIXUP(
接下来三个宏定义分别是三个函数指针:这些函数都在machine_desc结构中定义并且在xhyper250R1.c中实现。)
       FIXUP(fixup_xhyper250R1)
宏指向fixup_xhyper250R1函数,这个函数有4个参数:
       fixup_xhyper250R1(struct machine_desc *desc, struct param_struct *params, char **cmdline, struct meminfo *mi)       
       struct machine_desc
:这个结构体前面已经提过了。
       param_struct
:这个结构体定义在include/asm/setup.h中,这是一个向kernel传递参数的结构体。
       char **cmdline
:好像用于定义输出窗口行数。
       struct meminfo
:这个结构体定义在include/asm/setup.h中,这是一个对物理内存区间描述的结构体,它将整个地址空间分为8个区间,通常一个区必须是连续的地址并且是同一类型的设备,而用于特殊目的的地址将划分为一个独立的区。首先定义nr_banks:块号,然后是结构体bank[NR_BANKS]NR_BANKS8。结构体bank[NR_BANKS]中有:startsizenode
下面分析这个函数fixup_xhyper250R1的工作,
首先,调用宏SET_BANK并赋值为SET_BANK(0, 0xa0000000, 64*1024*1024),这个宏定义在arch/arm/mach-pxa/generic.h文件中。SET_BANK主要完成设置结构体meminfobank[_nr]startsizenode。以上为例,则完成了bank[0]区间中的start=0xa0000000size=64*1024*1024=64Mnode=(__start) - PHYS_OFFSET) >> 27=0
      
接着,使minr_banks=1,好象设定了这个结构只有一个区。要注意的是meminfo将在page_init()中用于初始化页面。       
       (F)MAPIO
       MAPIO(xhyper250R1_map_io)
宏指向xhyper250R1_map_io函数,这个函数没有参数,主要用于io地址从虚拟地址到物理地址的映射关系。
这个函数调用了pxa_map_io()iotable_init(xhyper250R1_io_desc)
       pxa_map_io()
函数定义在arch/arm/mach-pxa/generic.h文件中,实现在arch/arm/mach-pxa/generic.c中,主要调用了iotable_init()函数来进行io地址的区间映象。iotable_init(struct map_desc *)函数中,参数map_desc结构体定义在文件include/asm-arm/map.h中,主要有:virtualphysicallength和一些标志位:domainreadwritecachebuffer等。iotable_init()函数在文件arch/arm/mm/mm-armv.c中,循环调用create_mapping()来处理map_desc的映射关系。create_mapping函数主要工作就是将io的虚拟地址到物理地址的映射关系按照PAGE_SIZE(4K)的页来进行映射,同时还有段的映射关系。(关于内存的映射将在第2章中详细分析)       
       (G)INITIRQ
       INITIRQ(xhyper250R1_init_irq)
指向了xhyper250R1_init_irq函数,这个函数将主要完成中断的初始化,这里主要调用了函数 pxa_init_irq(),这个函数实现在arch/arm/mach-pxa/irq.c中。接着调用了set_GPIO_IRQ_edge()函数,这个函数也在irq.c中。(关于中断的分析将在以后进行)
我们以上通过分析宏MACHINE_START而分析了结构体machine_desc的一个实例的赋值,我们这里其实只用这个结构体很少一部分信息,主要有三个参数内存物理内存的开始地址、物理io的开始地址、io页表的偏移,分别存于寄存器r5(phys_ram=0xa0000000)r6(phys_io=0x40000000)r7(io_pg_offst=0x3f00)中,并返回。

       (5)
初始化页表,映射了4MRAM,以使内核运行。
这里值得注意的是:r5此时为物理内存开始地址(0xa0000000),程序利用宏定义pgtbl,将r4成为页表首地址0xC0004000。然后清空内核目录swapper_pg_dir开始的16K空间。
      
       (6)
设置lr为返回地址__ret,以使下面的程序得以跳转返回。
       (7)
使pc=[r10+12],也就是跳转到_xscale_proc_init结构中的b __xscale_setup位置,这个结构在arch/arm/mm/proc-xcale.S中。我们来看看这个结构:
       __pxa250_proc_info:             <--r10
指向这个地址
            .long 0x69052100
            .long 0xfffff7f0
       #if CACHE_WRITE_THROUGH
            .long 0x00000c0a
       #else
            .long 0x00000c0e        <--
这个参数传入了r8
       #endif
            b    __xscale_setup <--[r10+12]
            .long cpu_arch_name
            .long cpu_elf_name
            .long HWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_EDSP
            .long cpu_pxa250_info
            .long xscale_processor_functions
            .size __pxa250_proc_info, . - __pxa250_proc_info
      
       __xscale_setup
相关的程序多是对协处理器cp15的操作,之中用到了宏F_BIT|I_BIT|SVC_MODE
相关的宏定义在文件include/asm-arm/proc-armv/ptrace.h中。
       #define SVC_MODE        0x13
       #define T_BIT           0x20
       #define F_BIT           0x40
       #define I_BIT           0x80
      
       (8)
通过proc-xcale.S__xscale_setup设置MMU,并通过__ret返回head_armv.S
       (9)
__ret返回处设置lr通过__switch_data返回到__mmap_switched
       (10)
打开MMU,将pipeline清空,以使所有的内存得以正确的访问。并返回到__mmap_switched
       (11)__mmap_switched
通过__switch_data获得数据,并设置了stack pointer
       (12)
清空BSS,并保存CPU类型值(processor ID)以及系统类型(machine type)等。
       (13)
跳转到start_kernel

简历

 

ext3文件恢复--ext3grep

比如,我的/dev/sda1挂在在/home/david/oldhome/;在oldhome下面有个叫test的文件,被我误删了,那么如何回复呢?

恢复之前需要将该分区卸载:

umount /dev/sda1

然后:

ext3grep /dev/sda1 --restore-file oldhome/test

 

更多:

ext3grep /dev/sda1 --dump-names

ext3grep --help

Linux内核Makefile文件

 

http://blog.csdn.net/danforn/archive/2006/12/01/1424619.aspx

=== 目录
     === 1 概述
     === 2 角色分工
     === 3 内核编译文件
        --- 3.1 目标定义
        --- 3.2 内嵌对象 - obj-y
        --- 3.3 可加载模块 - obj-m
        --- 3.4 导出符号
        --- 3.5 库文件 - lib-y
        --- 3.6 目录递归
        --- 3.7 编译标记
        --- 3.8 命令依赖
        --- 3.9 依赖关系
        --- 3.10 特殊规则
     === 4 辅助程序
        --- 4.1 简单辅助程序
        --- 4.2 组合辅助程序
        --- 4.3 定义共享库 
        --- 4.4 C++语言使用方法
        --- 4.5 辅助程序编译控制选项
        --- 4.6 何时建立辅助程序
        --- 4.7 使用hostprogs-$(CONFIG_FOO)
     === 5 编译清除机制
     === 6 体系Makefile文件
        --- 6.1 变量设置
        --- 6.2 增加预设置项
        --- 6.3 目录表
        --- 6.4 引导映像
        --- 6.5 编译非内核目标
        --- 6.6 编译引导映像命令
        --- 6.7 定制编译命令
        --- 6.8 预处理连接脚本
        --- 6.9 $(CC)支持功能
     === 7 Kbuild变量
     === 8 Makefile语言
     === 9 Credits
     === 10 TODO

=== 1 概述
Makefile包括五部分:
     Makefile            顶层Makefile文件
     .config                  内核配置文件
     arch/$(ARCH)/Makefile      机器体系Makefile文件
     scripts/Makefile.*      所有内核Makefiles共用规则
     kbuild Makefiles      其它makefile文件
通过内核配置操作产生.config文件,顶层Makefile文件读取该文件的配置。顶层Makefile文件负责产生两个主要的程序:vmlinux (内核image)和模块。顶层Makefile文件根据内核配置,通过递归编译内核代码树子目录建立这两个文件。顶层Makefile文件文本一个名为arch/$(ARCH)/Makefile的机器体系makefile文件。机器体系Makefile文件为顶层makefile文件提供与机器相关的信息。每一个子目录有一个makefile文件,子目录makefile文件根据上级目录makefile文件命令启动编译。这些makefile使用.config文件配置数据构建各种文件列表,并使用这些文件列表编译内嵌或模块目标文件。scripts/Makefile.*包含了所有的定义和规则,与makefile文件一起编译出内核程序。

=== 2 角色分工
人们与内核makefile存在四种不同的关系:
*用户* 用户使用"make menuconfig"或"make"命令编译内核。他们通常不读或编辑内核makefile文件或其他源文件。
*普通开发者* 普通开发者维护设备驱动程序、文件系统和网络协议代码,他们维护相关子系统的makefile文件,因此他们需要内核makefile文件整体性的一般知识和关于kbuild公共接口的详细知识。
*体系开发者* 体系开发者关注一个整体的体系架构,比如sparc或者ia64。体系开发者既需要掌握关于体系的makefile文件,也要熟悉内核makefile文件。
*内核开发者* 内核开发者关注内核编译系统本身。他们需要清楚内核makefile文件的所有方面。
本文档的读者对象是普通开发者和系统开发者。

=== 3 内核编译文件
内核中大多数makefile文件是使用kbuild基础架构的makefile文件。本章介绍kbuild的makefile中的语法。
3.1节“目标定义”是一个快速导引,后面各章有详细介绍和实例。
--- 3.1 目标定义
     目标定义是makefile文件的主要部分(核心)。这些目标定义行定义了如何编译文件,特殊的兼容选项和递归子目录。
      最简单的makefile文件只包含一行:
     Example: obj-y += foo.o
    这行告诉kbuild在该目录下名为foo.o的目标文件(object),foo.o通过编译foo.c或者foo.S而得到。
    如果foo.o编译成一个模块,则使用obj-m变量,因此常见写法如下:
     Example: obj-$(CONFIG_FOO) += foo.o
     $(CONFIG_FOO)可以代表y(built-in对象)或m(module对象)。
      如果CONFIG_FOO不是y或m,那么这个文件不会被编译和链接。
--- 3.2 内嵌对象 - obj-y
    makefile文件将为编译vmlinux的目标文件放在$(obj-y)列表中,这些列表依赖于内核配置。
      Kbuild编译所有的$(obj-y)文件,然后调用"$(LD) -r"合并这些文件到一个built-in.o文件中。built-in.o经过父makefile文件链接到vmlinux。$(obj-y)中的文件顺序很重要。列表中文件允许重复,文件第一次出现将被链接到built-in.o,后续出现该文件将被忽略。
      链接顺序之所以重要是因为一些函数在内核引导时将按照他们出现的顺序被调用,如函数(module_init() / __initcall)。所以要牢记改变链接顺序意味着也要改变SCSI控制器的检测顺序和重数磁盘。
      例如: #drivers/isdn/i4l/Makefile
     # 内核ISDN子系统和设备驱动程序Makefile
     # 每个配置项是一个文件列表
     obj-$(CONFIG_ISDN)         += isdn.o
     obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o
--- 3.3 可加载模块 - obj-m
  $(obj-m)表示对象文件(object files)编译成可加载的内核模块。
  一个模块可以通过一个源文件或几个源文件编译而成。makefile只需简单地它们加到$(obj-m)。
      例如:#drivers/isdn/i4l/Makefile
        obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o
    注意:在这个例子中$(CONFIG_ISDN_PPP_BSDCOMP)含义是'm'。
      如果内核模块通过几个源文件编译而成,使用以上同样的方法。
      Kbuild需要知道通过哪些文件编译模块,因此需要设置一个$(<module_name>-objs)变量。
    例如:#drivers/isdn/i4l/Makefile
     obj-$(CONFIG_ISDN) += isdn.o
     isdn-objs := isdn_net_lib.o isdn_v110.o isdn_common.o
     在这个例子中,模块名isdn.o. Kbuild首先编译$(isdn-objs)中的object文件,然后运行"$(LD) -r"将列表中文件生成isdn.o.
  Kbuild使用后缀-objs、-y识别对象文件。这种方法允许makefile使用CONFIG_符号值确定一个object文件是否是另外一个object的组成部分。
     例如: #fs/ext2/Makefile 
        obj-$(CONFIG_EXT2_FS)     += ext2.o 
        ext2-y := balloc.o bitmap.o 
        ext2-$(CONFIG_EXT2_FS_XATTR) += xattr.o
     在这个例子中,如果$(CONFIG_EXT2_FS_XATTR)表示'y',则ext2.o只有xattr.o组成部分。
     注意: 当然,当你将对象文件编译到内核时,以上语法同样有效。因此,如果CONFIG_EXT2_FS=y,Kbuild将先编译ext2.o文件,然后链接到built-in.o。
--- 3.4 导出符号目标
      在makefile文件中没有特别导出符号的标记。
--- 3.5 库文件 - lib-y
      obj-*中的object文件用于模块或built-in.o编译。object文件也可能编译到库文件中--lib.a。
      所有罗列在lib-y中的object文件都将编译到该目录下的一个单一的库文件中。
      包含在0bj-y中的object文件如果也列举在lib-y中将不会包含到库文件中,因为他们不能被访问。但lib-m中的object文件将被编译进lib.a库文件。 
      注意在相同的makefile中可以列举文件到buit-in内核中也可以作为库文件的一个组成部分。因此在同一个目录下既可以有built-in.o也可以有lib.a文件。
      例如:#arch/i386/lib/Makefile
        lib-y   := checksum.o delay.o
     这样将基于checksum.o、delay.o创建一个lib.a文件。
      对于内核编译来说,lib.a文件被包含在libs-y中。将“6.3 目录表”。
      lib-y通常被限制使用在lib/和arch/*/lib目录中。
--- 3.6 目录递归
     makefile文件负责编译当前目录下的目标文件,子目录中的文件由子目录中的makefile文件负责编译。编译系统将使用obj-y和obj-m自动递归编译各个子目录中文件。
     如果ext2是一个子目录,fs目录下的makefile将使用以下赋值语句是编译系统编译ext2子目录。
     例如: #fs/Makefile
        obj-$(CONFIG_EXT2_FS) += ext2/
     如果CONFIG_EXT2_FS设置成'y(built-in)或'm'(modular),则对应的obj-变量也要设置,内核编译系统将进入ext2目录编译文件。
      内核编译系统只使用这些信息来决定是否需要编译这个目录,子目录中makefile文件规定那些文件编译为模块那些是内核内嵌对象。
      当指定目录名时使用CONFIG_变量是一种良好的做法。如果CONFIG_选项不为'y'或'm',内核编译系统就会跳过这个目录。
--- 3.7 编译标记
  EXTRA_CFLAGS, EXTRA_AFLAGS, EXTRA_LDFLAGS, EXTRA_ARFLAGS
  所有的EXTRA_变量只能使用在定义该变量后的makefile文件中。EXTRA_变量被makefile文件所有的执行命令语句所使用。
     $(EXTRA_CFLAGS)是使用$(CC)编译C文件的选项。
     例如: # drivers/sound/emu10k1/Makefile
           EXTRA_CFLAGS += -I$(obj)
           ifdef 
            DEBUG EXTRA_CFLAGS += -DEMU10K1_DEBUG 
            endif
     定义这个变量是必须的,因为顶层makefile定义了$(CFLAGS)变量并使用该变量编译整个代码树。
     $(EXTRA_AFLAGS)是每个目录编译汇编语言源文件的选项。
     例如: #arch/x86_64/kernel/Makefile
           EXTRA_AFLAGS := -traditional
     $(EXTRA_LDFLAGS)和$(EXTRA_ARFLAGS)用于每个目录的$(LD)和$(AR)选项。
     例如: #arch/m68k/fpsp040/Makefile
           EXTRA_LDFLAGS := -x
  CFLAGS_$@, AFLAGS_$@
     CFLAGS_$@和AFLAGS_$@只使用到当前makefile文件的命令中。
     $(CFLAGS_$@)定义了使用$(CC)的每个文件的选项。$@部分代表该文件。
     例如: # drivers/scsi/Makefile
           CFLAGS_aha152x.o =   -DAHA152X_STAT -DAUTOCONF
           CFLAGS_gdth.o   = # -DDEBUG_GDTH=2 -D__SERIAL__ -D__COM2__ \
  -DGDTH_STATISTICS CFLAGS_seagate.o =   -DARBITRATE -DPARITY -DSEAGATE_USE_ASM
     这三行定义了aha152x.o、gdth.o和seagate.o文件的编译选项。
     $(AFLAGS_$@)使用在汇编语言代码文件中,具有同上相同的含义。
     例如: # arch/arm/kernel/Makefile
           AFLAGS_head-armv.o := -DTEXTADDR=$(TEXTADDR) -traditional
           AFLAGS_head-armo.o := -DTEXTADDR=$(TEXTADDR) -traditional
--- 3.9 依赖关系
     内核编译记录如下依赖关系:
      1) 所有的前提文件(both *.c and *.h) 
      2) CONFIG_ 选项影响到的所有文件
      3) 编译目标文件使用的命令行
     因此,假如改变$(CC)的一个选项,所有相关的文件都要重新编译。
--- 3.10 特殊规则
      特殊规则使用在内核编译需要规则定义而没有相应定义的时候。典型的例子如编译时头文件的产生规则。其他例子有体系makefile编译引导映像的特殊规则。特殊规则写法同普通的Make规则。
     Kbuild(应该是编译程序)在makefile所在的目录不能被执行,因此所有的特殊规则需要提供前提文件和目标文件的相对路径。
     定义特殊规则时将使用到两个变量:
  $(src): $(src)是对于makefile文件目录的相对路径,当使用代码树中的文件时使用该变量$(src)。
  $(obj): $(obj)是目标文件目录的相对路径。生成文件使用$(obj)变量。
     例如: #drivers/scsi/Makefile
     $(obj)/53c8xx_d.h: $(src)/53c7,8xx.scr $(src)/script_asm.pl 
        $(CPP) -DCHIP=810 - < $< | ... $(src)/script_asm.pl
     这就是使用普通语法的特殊编译规则。
     目标文件依赖于两个前提文件。目标文件的前缀是$(obj), 前提文件的前缀是$(src)(因为它们不是生成文件)。

=== 4 辅助程序
    内核编译系统支持在编译(compliation)阶段编译主机可执行程序。为了使用主机程序需要两个步骤:第一个步骤使用hostprogs-y变量告诉内核编译系统有主机程序可用。第二步给主机程序添加潜在的依赖关系。有两种方法,在规则中增加依赖关系或使用$(always)变量。具体描述如下。
--- 4.1 简单辅助程序
     在一些情况下需要在主机上编译和运行主机程序。下面这行告诉kbuild在主机上建立bin2hex程序。
     例如: hostprogs-y := bin2hex
     Kbuild假定使用makefile相同目录下的单一C代码文件bin2hex.c编译bin2hex。
--- 4.2 组合辅助程序
     主机程序也可以由多个object文件组成。定义组合辅助程序的语法同内核对象的定义方法。
     $(<executeable>-objs)包含了所有的用于链接最终可执行程序的对象。
     例如: #scripts/lxdialog/Makefile
        hostprogs-y   := lxdialog 
        lxdialog-objs := checklist.o lxdialog.o
     扩展名.o文件都编译自对应的.c文件。在上面的例子中checklist.c编译成checklist.o,lxdialog.c编译为lxdialog.o。最后两个.o文件链接成可执行文件lxdialog。
     注意:语法<executable>-y不能用于定义主机程序。
--- 4.3 定义共享库 
     扩展名为.so的对象是共享库文件,并且是位置无关的object文件。内核编译系统提供共享库使用支持,但使用方法有限制。在下面例子中libkconfig.so库文件被链接到可执行文件conf中。
     例如: #scripts/kconfig/Makefile
           hostprogs-y   := conf
           conf-objs     := conf.o libkconfig.so
           libkconfig-objs := expr.o type.o
     共享库文件需要对应的-objs定义, 在上面例子中库libkconfig由两个对象组成:expr.o和type.o。expr.o和type.o将被编译为位置无关代码并被链接如libkconfig.so。共享库不支持C++语言。
--- 4.4 C++语言使用方法
     内核编译系统提供了对C++主机程序的支持以用于内核配置,但不主张其它方面使用这种方法。
     例如: #scripts/kconfig/Makefile
           hostprogs-y   := qconf
           qconf-cxxobjs := qconf.o
     在上面例子中可执行文件由C++文件qconf.cc组成 - 通过$(qconf-cxxobjs)标识。
     如果qconf由.c和.cc文件混合组成,附加行表示这种情况。
     例如: #scripts/kconfig/Makefile
           hostprogs-y   := qconf
           qconf-cxxobjs := qconf.o
           qconf-objs   := check.o
--- 4.5 辅助程序编译控制选项
     当编译主机程序时仍然可以使用$(HOSTCFLAGS)设置编译选项传递给$(HOSTCC)。这些选项将影响所有使用变量HOST_EXTRACFLAG的makefile创建的主机程序。
     例如: #scripts/lxdialog/Makefile
           HOST_EXTRACFLAGS += -I/usr/include/ncurses
     为单个文件设置选项使用下面方式:
     例如: #arch/ppc64/boot/Makefile
     HOSTCFLAGS_piggyback.o := -DKERNELBASE=$(KERNELBASE)
     也可以使用附加链接选项:
     例如: #scripts/kconfig/Makefile
           HOSTLOADLIBES_qconf := -L$(QTDIR)/lib
     当链接qconf时将使用外部选项"-L$(QTDIR)/lib"。
--- 4.6 何时建立辅助程序
     只有当需要时内核编译系统才会编译主机程序。有两种方式:
     (1) 在特殊规则中作为隐式的前提需求
     例如: #drivers/pci/Makefile
     hostprogs-y := gen-devlist
     $(obj)/devlist.h: $(src)/pci.ids $(obj)/gen-devlist 
              ( cd $(obj); ./gen-devlist ) < $<
     编译目标文件$(obj)/devlist.h需要先建立$(obj)/gen-devlist。注意在特殊规则中使用主机程序必须加前缀$(obj)。
     (2) 使用$(always)
     当没有合适的特殊规则可以使用,并且在进入makefile文件时就要建立主机程序,可以使用变量$(always)。
     例如: #scripts/lxdialog/Makefile
           hostprogs-y   := lxdialog
           always     := $(hostprogs-y)
     这样就告诉内核编译系统即使没有任何规则使用lxdialog也要编译它。
--- 4.7 使用hostprogs-$(CONFIG_FOO)
     在Kbuild文件中典型模式如下:
     例如: #scripts/Makefile
           hostprogs-$(CONFIG_KALLSYMS) += kallsyms
     对Kbuild来说'y'用于内嵌对象'm'用于模块。
     因此如果config符号是'm',编译系统也将创建该程序。换句话说内核编译系统等同看待hostprogs-m和hostprogs-y。但如果不涉及到CONFIG符号仅建议使用hostprogs-y。

=== 5 编译清除机制
  "make clean"命令删除在编译内核生成的大部分文件,例如主机程序,列举在 $(hostprogs-y)、$(hostprogs-m)、$(always)、$(extra-y)和$(targets)中目标文件都将被删除。代码目录数中的"*.[oas]"、"*.ko"文件和一些由编译系统产生的附加文件也将被删除。
  附加文件可以使用$(clean-files)进行定义。
     例如: #drivers/pci/Makefile
           clean-files := devlist.h classlist.h
  当执行"make clean"命令时, "devlist.h classlist.h"两个文件将被删除。内核编译系统默认这些文件与makefile具有相同的相对路径,否则需要设置以'/'开头的绝对路径。
  删除整个目录使用以下方式:
  例如: #scripts/package/Makefile
     clean-dirs := $(objtree)/debian/
  这样就将删除包括子目录在内的整个debian目录。如果不使用以'/'开头的绝对路径内核编译系统见默认使用相对路径。
  通常内核编译系统根据"obj-* := dir/"进入子目录,但是在体系makefile中需要显式使用如下方式:
     例如: #arch/i386/boot/Makefile
           subdir- := compressed/
  上面赋值语句指示编译系统执行"make clean"命令时进入compressed/目录。
  在编译最终的引导映像文件的makefile中有一个可选的目标对象名称是archclean。
     例如: #arch/i386/Makefile
           archclean: 
              $(Q)$(MAKE) $(clean)=arch/i386/boot
  当执行"make clean"时编译器进入arch/i386/boot并象通常一样工作。arch/i386/boot中的makefile文件可以使用subdir-标识进入更下层的目录。
  注意1: arch/$(ARCH)/Makefile不能使用"subdir-",因为它被包含在顶层makefile文件中,在这个位置编译机制是不起作用的。
  注意2: 所有列举在core-y、libs-y、drivers-y和net-y中的目录将被"make clean"命令清除。

=== 6 体系Makefile文件
  在开始进入各个目录编译之前,顶层makefile文件设置编译环境和做些准备工作。顶层makefile文件包含通用部分,arch/$(ARCH)/Makefile包含该体系架构所需的设置。因此arch/$(ARCH)/Makefile会设置一些变量和少量的目标。
  当编译时将按照以下大概步骤执行: 
1) 配置内核 => 产生 .config文件 
2) 保存内核版本到include/linux/version.h文件中 
3) 符号链接include/asm to include/asm-$(ARCH) 
4) 更新所有目标对象的其它前提文件
  - 附加前提文件定义在arch/$(ARCH)/Makefile文件中 
5) 递归进入init-* core* drivers-* net-* libs-*中的所有子目录和编译所有的目标对象
  - 上面变量值都引用到arch/$(ARCH)/Makefile文件。 
6) 链接所有的object文件生成vmlinux文件,vmlinux文件放在代码树根目录下。
  最开始链接的几个object文件列举在arch/$(ARCH)/Makefile文件的head-y变量中。
7) 最后体系makefile文件定义编译后期处理规则和建立最终的引导映像bootimage。
  - 包括创建引导记录
  - 准备initrd映像和相关处理
--- 6.1 变量设置
  LDFLAGS      $(LD)一般选项
     选项使用于链接器的所有调用中。通常定义emulation就可以了。
     例如: #arch/s390/Makefile
           LDFLAGS   := -m elf_s390 
      注意: EXTRA_LDFLAGS和LDFLAGS_$@可以进一步订制使用选项,将第7章。
  LDFLAGS_MODULE       $(LD)链接模块的选项
     LDFLAGS_MODULE通常设置$(LD)链接模块的.ko选项。
     默认为"-r"即可重定位输出文件。
  LDFLAGS_vmlinux   $(LD)链接vmlinux选项
     LDFLAGS_vmlinux定义链接最终vmlinux时链接器的选项。
     LDFLAGS_vmlinux支持使用LDFLAGS_$@。
     例如: #arch/i386/Makefile
           LDFLAGS_vmlinux := -e stext
  OBJCOPYFLAGS      objcopy选项
     当使用$(call if_changed,objcopy)转化a .o文件时,OBJCOPYFLAGS中的选项将被使用。
     $(call if_changed,objcopy)经常被用作为vmlinux产生原始的二进制文件。
     例如: #arch/s390/Makefile
           OBJCOPYFLAGS := -O binary
          #arch/s390/boot/Makefile
           $(obj)/image: vmlinux FORCE $(call if_changed,objcopy)
     在上面例子中$(obj)/image是vmlinux的二进制版本文件。$(call if_changed,xxx)
的使用方法见后。
  AFLAGS   $(AS)汇编选项
     默认值见顶层Makefile文件
     针对每个体系需要另外添加和修改它。
     例如: #arch/sparc64/Makefile
           AFLAGS += -m64 -mcpu=ultrasparc
  CFLAGS      $(CC)编译器选项
     默认值见顶层Makefile文件
     针对每个体系需要另外添加和修改它。
     通常CFLAGS变量值取决于内核配置。
     例如: #arch/i386/Makefile
           cflags-$(CONFIG_M386) += -march=i386
           CFLAGS += $(cflags-y)
     许多体系Makefiles文件动态启动市场目标机器上的C编译器检测支持的选项:
           #arch/i386/Makefile
           ...
           cflags-$(CONFIG_MPENTIUMII)   += $(call cc-option,\
                 -march=pentium2,-march=i686) ...
           # Disable unit-at-a-time mode ...
           CFLAGS += $(call cc-option,-fno-unit-at-a-time)
           ...
     第一个例子当config选项是'y'时将被选中。
  CFLAGS_KERNEL      $(CC)编译built-in对象的选项
     $(CFLAGS_KERNEL)包含外部C编译器选项编译本地内核代码。
  CFLAGS_MODULE      $(CC)编译模块选项
     $(CFLAGS_MODULE)包含外部C编译器选项编译可加载内核代码。
--- 6.2 增加预设置项
     prepare: 这个规则用于列举开始进入子目录编译前需要的前提文件。通常是些包含汇编常量的头文件。
     例如:
     #arch/s390/Makefile
     prepare: include/asm-$(ARCH)/offsets.h
     在这个例子中include/asm-$(ARCH)/offsets.h将在进入子目录前编译。
      详见XXX-TODO文件描述了kbuild如何产生offset头文件。
--- 6.3 目录表
     体系makefile文件和顶层makefile文件共同定义了如何建立vmlinux文件的变量。注意没有体系相关的模块对象定义部分:所有的模块对象都是体系无关的。
  head-y, init-y, core-y, libs-y, drivers-y, net-y
     $(head-y) 列举首先链接到vmlinux的对象文件。
     $(libs-y) 列举了能够找到lib.a文件的目录。
     其余的变量列举了能够找到内嵌对象文件的目录。
     $(init-y) 列举的对象位于$(head-y)对象之后。
     然后是如下位置秩序:
     $(core-y), $(libs-y), $(drivers-y) 和 $(net-y)。
     顶层makefile定义了所有同用目录,arch/$(ARCH)/Makefile文件只需增加体系相关的目录。
     例如: #arch/sparc64/Makefile
           core-y += arch/sparc64/kernel/
           libs-y += arch/sparc64/prom/ arch/sparc64/lib/
           drivers-$(CONFIG_OPROFILE) += arch/sparc64/oprofile/
--- 6.4 引导映像
     体系makefile文件定义了编译vmlinux文件的目标对象,将它们压缩和封装成引导代码,并复制到合适的位置。这包括各种安装命令。如何定义实际的目标对象无法为所有的体系结构提供标准化的方法。
     附加处理过程常位于arch/$(ARCH)/下的boot/目录。
     内核编译系统无法在boot/目录下提供一种便捷的方法创建目标系统文件。因此arch/$(ARCH)/Makefile要调用make命令在boot/目录下建立目标系统文件。建议使用的方法是在arch/$(ARCH)/Makefile中设置调用,并且使用完整路径引用arch/$(ARCH)/boot/Makefile。
     例如: #arch/i386/Makefile
           boot := arch/i386/boot
           bzImage: vmlinux 
              $(Q)$(MAKE) $(build)=$(boot) $(boot)/$@
     建议使用"$(Q)$(MAKE) $(build)=<dir>"方式在子目录中调用make命令。
     没有定义体系目标系统文件的规则,但执行"make help"命令要列出所有目标系统文件,因此必须定义$(archhelp)变量。
     例如: #arch/i386/Makefile
     define 
        archhelp echo '* bzImage     - Image (arch/$(ARCH)/boot/bzImage)' 
      endef
     当执行不带参数的make命令时,将首先编译第一个目标对象。在顶层makefile中第一个目标对象是all:。
     一个体系结构需要定义一个默认的可引导映像。
     "make help"命令的默认目标是以*开头的对象。
     增加新的前提文件给all目标可以设置不同于vmlinux的默认目标对象。
     例如: #arch/i386/Makefile
           all: bzImage 
     当执行不带参数的"make"命令时,bzImage文件将被编译。
--- 6.5 编译非内核目标
  extra-y
     extra-y定义了在当前目录下创建没有在obj-*定义的附加的目标文件。
     在extra-y中列举目标是处于两个目的: 
      1) 是内核编译系统在命令行中检查变动情况
        - 当使用$(call if_changed,xxx)时 
      2) 内核编译系统知道执行"make clean"命令时删除哪些文件
     例如: #arch/i386/kernel/Makefile
           extra-y := head.o init_task.o
     上面例子extra-y中的对象文件将被编译但不会练接到built-in.o中。
--- 6.6 编译引导映像命令
     Kbuild提供了一些编译引导映像有用的宏。
  if_changed
     if_changed是后面命令使用的基础。
     用法: 
        target: source(s) 
            FORCE $(call if_changed,ld/objcopy/gzip)
     当这条规则被使用时它将检查哪些文件需要更新,或命令行被改变。后面这种情况将迫使重新编译编译选项被改变的执行文件。使用if_changed的目标对象必须列举在$(targets)中,否则命令行检查将失败,目标一直会编译。
     赋值给$(targets)的对象没有$(obj)/前缀。
     if_changed也可以和定制命令配合使用,见6.7"kbuild定制命令"。
     注意: 一个常见错误是忘记了FORCE前导词。
  ld 
      链接目标。常使用LDFLAGS_$@作为ld的选项。
  objcopy 
      复制二进制文件。常用于arch/$(ARCH)/Makefile中和使用OBJCOPYFLAGS作为选项。
     也可以用OBJCOPYFLAGS_$@设置附加选项。
  gzip 
      压缩目标文件。使用最大压缩算法压缩目标文件。
     例如: #arch/i386/boot/Makefile
     LDFLAGS_bootsect := -Ttext 0x0 -s --oformat binary
     LDFLAGS_setup   := -Ttext 0x0 -s --oformat binary -e begtext

     targets += setup setup.o bootsect bootsect.o
     $(obj)/setup $(obj)/bootsect: %: %.o FORCE 
            $(call if_changed,ld)
      在上面例子中有两个可能的目标对象,分别需要不同的链接选项。使用LDFLAGS_$@语法为每个目标对象设置不同的链接选项。
     $(targets)包含所有的目标对象,因此内核编译系统知道所有的目标对象并且将:
      1) 检查命令行的改变情况 
      2) 执行make clean命令时删除目标对象
     ": %: %.o"是简写方法,减写setup.o和bootsect.o文件。
     注意: 常犯错误是忘记"target :="语句,导致没有明显的原因目标文件被重新编译。
--- 6.7 定制编译命令
     当执行带KBUILD_VERBOSE=0参数的编译命令时命令的简短信息会被显示。要让定制命令具有这种功能需要设置两个变量:
     quiet_cmd_<command> - 将被显示的内容 
      cmd_<command>      - 被执行的命令
     例如: #
           quiet_cmd_image = BUILD   $@ 
            cmd_image = $(obj)/tools/build $(BUILDFLAGS) \ 
                    $(obj)/vmlinux.bin > $@
           targets += bzImage
           $(obj)/bzImage: $(obj)/vmlinux.bin $(obj)/tools/build FORCE 
                $(call if_changed,image)
                @echo 'Kernel: $@ is ready'
     执行"make KBUILD_VERBOSE=0"命令编译$(obj)/bzImage目标时将显示:
     BUILD   arch/i386/boot/bzImage
--- 6.8 预处理连接脚本
     当编译vmlinux映像时将使用arch/$(ARCH)/kernel/vmlinux.lds链接脚本。
     相同目录下的vmlinux.lds.S文件是这个脚本的预处理的变体。内核编译系统知晓.lds文件并使用规则*lds.S -> *lds。
     例如: #arch/i386/kernel/Makefile
           always := vmlinux.lds
           #Makefile
           export CPPFLAGS_vmlinux.lds += -P -C -U$(ARCH)
     $(always)赋值语句告诉编译系统编译目标是vmlinux.lds。$(CPPFLAGS_vmlinux.lds)赋值语句告诉编译系统编译vmlinux.lds目标的编译选项。
     编译*.lds时将使用到下面这些变量:
     CPPFLAGS      : 定义在顶层Makefile
     EXTRA_CPPFLAGS      : 可以设置在编译的makefile文件中
     CPPFLAGS_$(@F) : 目标编译选项。注意要使用文件全名。
--- 6.9 $(CC)支持功能
     内核可能会用不同版本的$(CC)进行编译,每个版本有不同的性能和选项,内核编译系统提供基本的支持用于验证$(CC)选项。$(CC)通常是gcc编译器,但其它编译器也是可以。
  cc-option cc-option 用于检测$(CC)是否支持给定的选项,如果不支持就使用第二个可选项。
     例如: #arch/i386/Makefile
           cflags-y += $(call cc-option,-march=pentium-mmx,-march=i586)
     在上面例子中如果$(CC)支持-march=pentium-mmx则cflags-y等于该值,否则等于-march-i586。如果没有第二个可选项且第一项不支持则cflags-y没有被赋值。
  cc-option-yn cc-option-yn用于检测gcc是否支持给定的选项,支持返回'y'否则'n'。
     例如: #arch/ppc/Makefile
           biarch := $(call cc-option-yn, -m32)
           aflags-$(biarch) += -a32
           cflags-$(biarch) += -m32
     在上面例子中如果$(CC)支持-m32选项则$(biarch)设置为y。当$(biarch)等于y时,变量$(aflags-y)和$(cflags-y)将分别等于-a32和-m32。
  cc-option-align gcc版本>= 3.0用于定义functions、loops等边界对齐选项。
     gcc < 3.00 
        cc-option-align = -malign 
      gcc >= 3.00 
        cc-option-align = -falign
     例如: 
        CFLAGS += $(cc-option-align)-functions=4
     在上面例子中对于gcc >= 3.00来说-falign-functions=4,gcc < 3.00版本使用-malign-functions=4。
  cc-version cc-version返回$(CC)编译器数字版本号。
     版本格式是<major><minor>,均为两位数字。例如gcc 3.41将返回0341。
     当一个特定$(CC)版本在某个方面有缺陷时cc-version是很有用的。例如-mregparm=3在一些gcc版本会失败尽管gcc接受这个选项。
     例如: #arch/i386/Makefile
           GCC_VERSION := $(call cc-version)
           cflags-y += $(shell \
           if [ $(GCC_VERSION) -ge 0300 ] ; then echo "-mregparm=3"; fi ;)
     在上面例子中-mregparm=3只使用在版本大于等于3.0的gcc中。
=== 7 Kbuild变量
  顶层Makefile文件导出下面这些变量:
  VERSION, PATCHLEVEL, SUBLEVEL, EXTRAVERSION
     这几个变量定义了当前内核版本号。很少体系体系Makefiles文件直接使用他们,常用$(KERNELRELEASE)代替。
     $(VERSION)、$(PATCHLEVEL)和$(SUBLEVEL)定义了三个基本部分版本号,例如"2", "4",和"0"。这三个变量一直使用数值表示。
     $(EXTRAVERSION)定义了更细的补钉号,通常是短横跟一些非数值字符串,例如"-pre4"。
  KERNELRELEASE
     $(KERNELRELEASE)是一个单一字符如"2.4.0-pre4",适合用于构造安装目录和显示版本字符串。一些体系文件使用它用于以上目的。
  ARCH
     这个变量定义了目标系统体系结构,例如"i386"、“arm"、"sparc". 一些内核编译文件测试$(ARCH)用于确定编译哪个文件。默认情况下顶层Makefile文件设置$(ARCH)为主机相同的系统体系。当交叉编译编译时,用户可以使用命令行改变$(ARCH)值:
        make ARCH=m68k ...
  INSTALL_PATH
     这个变量定义了体系Makefiles文件安装内核映项和System.map文件的路径。
  INSTALL_MOD_PATH, MODLIB
     $(INSTALL_MOD_PATH)定义了模块安装变量$(MODLIB)的前缀。这个变量通常不在Makefile文件中定义,如果需要可以由用户添加。
     $(MODLIB)定义了模块安装目录。
     顶层Makefile定义$(MODLIB)为$(INSTALL_MOD_PATH)/lib/modules/$(KERNELRELEASE)。用户可以使用命令行修改这个值。
=== 8 Makefile语言
  内核Makefiles设计目标用于运行GNU Make程序。Makefiles仅使用GNU Make提到的特性,但使用了较多的GNU扩展部分。
  GNU Make程序支持基本的列表处理功能。内核Makefiles文件结合"if"语句使用了简单的列表建立和维护功能。
  GNU Make程序有两种赋值操作符:":="和"="。 ":="执行时立即计算右值并赋值给左值。"="类似公式定义,当每次使用左值要被使用时计算右值并赋给它。
  一些情况中使用"="合适,而一些情况中使用":="才是正确选择。
=== 9 Credits
  Original version made by Michael Elizabeth Chastain, <mailto:mec@shout.net> Updates 
by Kai Germaschewski <
kai@tp1.ruhr-uni-bochum.de> Updates by Sam Ravnborg <sam@ravnborg.org>
=== 10 TODO
- Describe how kbuild support shipped files with _shipped.
- Generating offset header files.
- Add more variables to section 7?

 

MACHINE_START宏

转自:http://blog.chinaunix.net/u2/60011/showart_1010489.html

 linux2.6.18内核,在Mach-s3c2410.c文件中,有如下的宏定义:

 

 

MACHINE_START(SMDK2410, "SMDK2410") /* @TODO: request a new identifier and switch to SMDK2410 */
 /* Maintainer: Jonas Dietsche */
 .phys_io = S3C2410_PA_UART,
 .io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
 .boot_params = S3C2410_SDRAM_PA + 0x100,
 .map_io = smdk2410_map_io,
 .init_irq = s3c24xx_init_irq,
 .init_machine = smdk_machine_init,
 .timer = &s3c24xx_timer,
MACHINE_END

 
MACHINE_START定义在include/asm-arm/mach/arch.h中

#define MACHINE_START(_type,_name) \
static const struct machine_desc __mach_desc_##_type \
 __attribute_used__ \
 __attribute__((__section__(".arch.info.init"))) = { \
 .nr = MACH_TYPE_##_type, \
 .name = _name,
#define MACHINE_END \
};

 
将前面定义的MACHINE_START展开后得到,

static const struct machine_desc __mach_desc_SMDK2410
 __attribute_used__
 __attribute__((__section__(".arch.info.init"))) = {
 .nr = MACH_TYPE_SMDK2410, /* architecture number */
 .name = "SMDK2410", /* architecture name */
 /* Maintainer: Jonas Dietsche */
 .phys_io = S3C2410_PA_UART, /* start of physical io */
 .io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
 .boot_params = S3C2410_SDRAM_PA + 0x100, /* tagged list */
 .map_io = smdk2410_map_io, /* IO mapping function */
 .init_irq = s3c24xx_init_irq,
 .init_machine = smdk_machine_init,
 .timer = &s3c24xx_timer,
}

 
MACH_TYPE_SMDK2410定义在arch/include/asm-arm/mach-types.h内,值为193.
/* arch/include/asm-arm/mach-types.h */
#define MACH_TYPE_SMDK2410             193
这个值是机器的类型值,编译时由arch/arm/tool/mach-types里面定义的数据生成的。
/* arch/arm/tool/mach-types */
smdk2410  ARCH_SMDK2410  SMDK2410  193

由上发现,MACHINE_START主要是定义了"struct machine_desc"的类型,放在 section(".arch.info.init"),是初始化数据,Kernel 起来之后将被丢弃。

各个成员函数在不同时期被调用:
1. .init_machine 在 arch/arm/kernel/setup.c 中被 customize_machine 调用,放在 arch_initcall() 段里面,会自动按顺序被调用。
2. init_irq在start_kernel() --> init_IRQ() --> init_arch_irq() 被调用
3. map_io 在 setup_arch() --> paging_init() --> devicemaps_init()被调用
其他主要都在 setup_arch() 中用到。

 

[转]win32 API窗口句柄的获得--我的经验

 

转自:博主见了请勿见怪,是从别处转来的,上面没有出处,所以... 

初次写文档,文笔不通畅的地方,以及理解错误之处望各位朋友多多指正!

这篇文章是关于如何获取窗口句柄,以及有哪些函数可供使用的简单讨论!可适用于vc、bcb(其他的我没有试,估计可以),本人在bcb环境下试 验。

首先我会罗列出一些获取句柄的win32 api 函数,然后简单说说他们的用途!最后说说我是怎么理解和应用的。见笑了!

可用的win32 api函数:

1.HWND FindWindow(LPCTSTR lpClassName, LPCTSTR lpWindowName)

   HWND FindWindowEx(HWND hwndParent, HWND hwndChildAfter,LPCTSTR lpClassName, LPCTSTR lpWindowName)

2.HWND WindowFromPoint(POINT& Point)

3.BOOL CALLBACK EnumChildProc(HWND hwnd,LPARAM lParam)

   BOOL CALLBACK EnumChildWindows(HWND hWndParent, WNDENUMPROC lpEnumFunc,LPARAM lParam)

   BOOL CALLBACK EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam)

   BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)

一般用途:

       对于第一种,大家都很熟悉,是捕捉句柄的常规武器,FindWindow这两兄弟,可以接受捕捉对象的类名或者窗口标题之一,作为参数,返回一个 HWND。可是对于一般群众,不一定知道所有的窗口(包括标题栏、按钮等等)的类名啊!--可以简单举例,请问你知道桌面图标的窗口的类名吗?而对于窗口 标题,有可能会出现相同的标题,有两个窗口--指一个程序的两个进程,这又是个麻烦吧!好了,这个问题先放放,继续下一组。

       第二组,通过win32定义的POINT结构(typedef struct tagPOINT {   LONG x;
  LONG y;} POINT),来获得当前鼠标光标位置的窗口HWND,这是最直观的武器!常规操作如下:先得到Cursor的POINT(BOOL GetCursorPos(LPPOINT)函数),再用WindowFromPoint。这样,我们几乎可以获得任何打开的有窗口的函数的HWND了! 然后通过获取类名的win32 api函数(int GetClassName(  HWND hWnd,   LPTSTR lpClassName,   int nMaxCount ))得到类名--这里的lpClassName最好用字符数组地址,nMaxCount就是数组的size了,同时,这种方法解决了第一个问题的麻 烦!--我可以把鼠标放在任何地方!*^_^*

       第三组,这些是用来列举和处理任何窗口的超级武器!通过组合运用EnumWindows和EnumWindowsProc,EnumChildWindows与 EnumChildProc,可以扫描桌面所有窗口并对之处理!

我的理解:(这部分用任务驱动式教学方法--谁让小弟是老师呢!xi xi)

任务:得到所有的窗口的类名。

解决办法1:我们会先想到第三组,可以自桌面窗口开始(它是所有窗口的祖先),依次扫描,获取类名并存之。有点儿像Visual Stdio的Spy++,或者Borland 的WinSight32,具体办法如下:(bcb中)

在主程序中,调用EnumWindows,传入YouEnumProc的函数地址作第一个参数,别忘了转换成 WNDENUMPROC类型。第二参可NULL。::EnumWindows(reinterpret_cast<WNDENUMPROC> YouEnumProc,NULL);

在YouEnumProc函数中,如果第一参HWND = = NULL,就跳离(return FALSE;),可以结束啦!

然后,把类名数组准备好,得到类名,存之。

返回真值,继续下一次扫描。

看起来并不复杂,是一种函数递归。但是我可会解释!面啊!: p

第二种解决方法:简单、直观--自己想出来的,颇得意

首先准备一个时钟,一种存类名方法(我用TMemo)

在定时器处理函数中:

1、得到当前cursor的点位置

2、再用WindowFromPoint,

3、然后得到类名,放到TMemo里

这样可以用鼠标获得你想要的窗口(包括按钮等),只要鼠标在窗口放一会儿。。。哈哈

第三种方法:其实利用FindWindow和循环结构也应该可以

总结:其实得到HWND的方法很多,比如知道了窗口层次,依次向下扫。。。在说第三种呢!但我觉得,我的方法最直接有效,你说呢?

欢迎大家与我联系,并讨论这个问题!有关这个问题我还有许多疑问,比如HWND与ID的转换,在如IE页面中的表单控件的HWND或ID,还是其他 的东东,总之是能识别他的东西。。。这个我很困惑,没办法!

不清楚地方,大家要参照MSDN啊

 

codelite中设置wxWidgets开发环境

 转自: http://www.mindward.net/docs/codelite.html

CodeLite

codelite.PNG

CodeLite采用跨平台的wxWidgets来作为GUI界面图形库,集成了cscope, tags 和 sqlite 的一款C++跨平台的集成开发环境,小而精悍,不但已经具备了大家熟悉的商业化软件Source Insight的超强代码分析和阅读功能,同时也具备项目文件管理(可以转换来自Visual Studio的项目文件),集成的调试器,以及代码自动补全等高端商业化集成开发环境中才能够见到的高端编辑功能,请参考更多特性。因为是用C++写的,所以对系统的要求不高,在低配制的机器上运行的不慢,而且还是开源和免费的,是一款值得推荐的好软件。

Setup CodeLite on Windows

首先,我们需要搭建起基本的编译环境。wxWidgets的官方网站上虽然也提供了针对不同平台编译好的安装包,但是缺乏编译器,界面设计工具和Visual Studio整合工具,以及一些并不在wxWidgets核心中的有用的第三方图形控件,虽然可以自己动手编译和整合,但是,这无疑是一个费时费力的工作,需要很多的经验和耐心,并不是适合初次接触wxWidgetsCodeLite的人。好在已经有人帮我们准备好了方便使用的安装包,我们只需要知道在那里下载,如何安装和配制就好了。

Download wxSDK Packages and Installation

在Windows下做软件开发,编译器的首选当然是Visual Studio,目前微软也有免费的Microsoft Visual C++ 2008 Express版本可供下载和使用。但是,如果考虑做跨平台的软件,选择gcc作为编译器无疑是明智的,无论Linux,MacOSX还是其他的Unix变体,大多选用gcc作为编译器,所以,选择gcc作为编译器能够让你的软件提前通过编译器的验证,能够更容易的在不同平台上通过编译。TDM GCC项目已经帮助大家测试并整合了Windows平台下的gcc安装工具TDM GCC On-Demand Installer,大家可以根据需要选择下载并安装那些工具包。安装完成后,需要修改环境变量,将安装目录加入PATH搜索路径中。

安装完编译器后,该下载安装wxWidgets的安装包了。wxPack项目为大家准备好了这样的安装包,目前最新的版本是wxPack v2.8.9.02 Download,安装包中除了包括预先编译好的wxWidgets2.8.9版本以外,还包括了界面设计工具[wxFormBuilder][],Visual Studio整合工具wxVC和一些附加的图形控件wxAdditions,详细的信息请通过wxPack的文档来进一步了解。

好了,到了下载安装CodeLite的时间了,在SourceForge上找到并下载最新版本的安装包,就可以了。

CodeLite Setting

启动了CodeLite之后,点击菜单[Settings]->[Environment Variables],回出现如下图所示的对话框:

codelite-env.PNG

其中要设置两个环境变量,[WXWIN]环境变量告诉CodeLite本机wxWidgets的安装目录,这个例子中,我的wxWidgets的安装目录是[D:\wx\wxWidgets2.8];另外一个环境变量[WXCFG]指定在该安装目录下你选择的wxWidgets的编译配置,在[%WXWIN%\lib]目录下会找到对应的目录。比如,选择gcc编译器编译成Unicode版的静态链接库调试版本,那么,[WXCFG]的值就设为[gcclib\mswud],而选择用VC编译器编译成Unicode版的动态链接库版本则将[WXCFG]的值设为[vcdll\mswud]。

另外一个需要设置的是编译程序的时候的头文件搜索路径和库文件搜索路径,请点击[Settings]->[Build Settings],按照下图所示填入对应的路径信息:

根据需要配置好环境变量以后,下一步我们需要配置CodeLite的预先解析好的C++语言符号库的路径,以便能够容易的浏览代码,选择菜单项[tags]->[Manage external tags database],出现如下图所示的对话框:

codelite-tags.PNG

这里,需要指定C++库程序在本机的路径,以便CodeLite能够找到这些源程序,包括编译器的C++头文件路径和wxWidgets的头文件路径。当然,你也可以创建自己的预解析符号库,并让CodeLite使用你自己创建的符号库。

CodeLite VideoTutorials

CodeLite的视频教程做的很不错,可以省去不少阅读文档的时间。可是,因为中国伟大的防火墙的阻隔,CodeLite的官方网站没办法正常访问,造成一些很好的入门文档没办法得到,阻碍了大家对它的学习和掌握,这里,我借助SSH Client as SOCKS Server中介绍的方法,下载了这些视频教程,期望大家能喜欢。

按照上面视频教程的操作步骤,自己实践一下,有任何问题,欢迎留言或到Forum中讨论。

[转]Linux USB gadget设备驱动解析(2)---驱动调试

 http://bbs.dzsc.com/space/?uid=18331

作者:刘洪涛, 华清远见嵌入式学院金牌讲师。

这一节主要把在实现“linux模拟U盘功能”过程中的一些调试过程记录下来,并加以解析。

一、背景知识
    1、USB Mass Storage类规范概述
       USB 组织在universal Serial Bus Mass Storage Class Spaceification 1.1版本中定义了海量存储设备类(Mass Storage Class)的规范,这个类规范包括四个
        独立的子类规范,即:
       1. USB Mass Storage Class Control/Bulk/Interrupt (CBI) Transport
       2.USB Mass Storage Class Bulk-Only Transport
       3.USB Mass Storage Class ATA Command Block
       4.USB Mass Storage Class UFI Command Specification
       前 两个子规范定义了数据/命令/状态在USB 上的传输方法。Bulk- Only 传输规范仅仅使用Bulk 端点传送数据/命令/状态,CBI 传输规范则使用Control/Bulk/Interrupt 三种类型的端点进行数据/命令/状态传送。后两个子规范则定义了存储介质的操作命令。ATA 命令规范用于硬盘,UFI 命令规范是针对USB 移动存储。
       Microsoft Windows 中提供对Mass Storage 协议的支持,因此USB 移动设备只需要遵循 Mass Storage 协议来组织数据和处理命令,即可实现与PC 机交换数据。而Flash 的存储单元组织形式采用FAT16 文件系统,这样,就可以直接在Windows的浏览器中通过可移动磁盘来交换数据了,Windows 负责对FAT16 文件系统的管理,USB 设备不需要干预FAT16 文件系统操作的具体细节。
       USB(Host)唯一通过描述符了解设备的有关信息,根据这些信息,建立起通信,在这 些描述符中,规定了设备所使用的协议、端点情况等。因此,正确地提供描述符,是USB 设备正常工作的先决条件。
       Linux- 2.6.26内核中在利用USB gadget驱动实现模拟U盘时主要涉及到file_storage.c、s3c2410_udc.c等驱动文件(这些文件的具体结构,将在下一篇文章中 描述)。此时我们想先从这些代码中找到USB描述描述符,从中确定使用的存储类规范,从而确定协议。确定通讯协议是我们调试的基础。
       存储类规范是由接口描述符决定的。接口描述符各项的定义义如下:

点击看大图

   其中,bInterfaceClass、bInterfaceSubClass、bInterfaceProtocol可以判断出设备是否是存储类,以及属于哪种存储子类和存储介质的操作命令。
       在file_storage.c文件中,

   /* USB protocol value = the transport method */
       #define USB_PR_CBI     0x00         // Control/Bulk/Interrupt
       #define USB_PR_CB      0x01         // Control/Bulk w/o interrupt
       #define USB_PR_BULK        0x50           // Bulk-only

   /* USB subclass value = the protocol encapsulation */
       #define USB_SC_RBC   0x01           // Reduced Block Commands (flash)
       #define USB_SC_8020  0x02           // SFF-8020i, MMC-2, ATAPI (CD-ROM)
       #define USB_SC_QIC   0x03           // QIC-157 (tape)
       #define USB_SC_UFI   0x04           // UFI (floppy)
       #define USB_SC_8070  0x05           // SFF-8070i (removable)
       #define USB_SC_SCSI  0x06           // Transparent SCSI

      默认的情况是:
               mod_data = {                                    // Default values
                         .transport_parm                      = "BBB",
                         .protocol_parm                        = "SCSI",
                         ……

    默认的赋值如下:
        bInterfaceClass=08 表示:存储类
        bInterfaceSubClass=0x06 表示:透明的SCSI指令
        bInterfaceProtocol=0x50 表示:bulk-only 传输

    2、Bulk-Only 传输协议
        下面看看Bulk-Only 传输协议:(详细的规范请阅读《Universal Serial BusMass Storage ClassBulk-Only Transport》)
        设 备插入到USB 后,USB 即对设备进行搜索,并要求设备提供相应的描述符。在USBHost 得到上述描述符后,即完成了设备的配置,识别出为Bulk-Only 的Mass Storage 设备, 然后即进入Bulk-Only 传输方式。在此方式下,USB 与设备间的所有数据均通过Bulk-In和Bulk-Out 来进行传输,不再通过控制端点传输任何数据。
        在 这种传输方式下,有三种类型的数据在USB 和设备之间传送,CBW、CSW 和普通数据。CBW(Command Block Wrapper,即命令块包)是从USB Host 发送到设备的命令, 命令格式遵从接口中的bInterfaceSubClass 所指定的命令块,这里为SCSI 传输命令集。USB设备需要将SCSI 命令从CBW 中提取出来,执行相应的命令,完成以后,向Host 发出反映 当前命令执行状态的CSW(Command Status Wrapper),Host 根据CSW 来决定是否继续发 送下一个CBW 或是数据。Host 要求USB 设备执行的命令可能为发送数据,则此时需要将 特定数据传送出去,完毕后发出CSW,以使Host 进行下一步的操作。USB 设备所执行的操

 作可用下图描述:

CBW的格式如下:

点击看大图

dCBWSignature:
        CBW的标识,固定值:43425355h (little endian)。
    dCBWTag:
        主机发送的一个命令块标识,设备需要原样作为dCSWTag(CSW中的一部分)再发送给Host;主要用于关联CSW到对应的CBW。
    dCBWDataTransferLength:
        本次CBW命令要求在命令与回应之间传输的字节数。如果为0,则不传输数据。
    bmCBWFlags:
        反映数据传输的方向,0 表示来自Host,1 表示发至Host;
    bCBWLUN:
        对于有多个LUN逻辑单元的设备,用来选择具体目标。如果没有多个LUN,则写0。
    bCBWCBLength:
        命令的长度,范围在0~16.

CBWCB:
        传输的具体命令,符合bInterfaceSubClass.中定义的命令规范,此处是SCSI
    CSW命令格式如下:

点击看大图
    dCSWSignature:
        CSW的标识,固定值:53425355h (little endian)
    dCSWTag:
        设置这个标识和CBW中的dCBWTag一致,参照上面关于dCBWTag的解释
    dCSWDataResidue:
        还需要传送的数据,此数据根据dCBWDataTransferLength-本次已经传送的数据得到
    bCSWStatus:
        指示命令的执行状态。如果命令正确执行,bCSWStatus 返回0 即可。

3、SCSI指令集

Bulk-Only 的CBW 中的CBWCB 中的内容即为如下格式的命令块描述符(Command Block Descriptor)。SCSI-2 有三种字长的命令,6 字节、10字节和12字节,Microsoft Windows 环境下支持12 字节长的命令。

点击看大图
    Operation Code:
        操作代码,表示特定的命令。高3 位为Group Code,共有8 种组合,
    即8 个组,低5 五位为Command Code,可以有32 种命令。
    Logicol unit Number:
        为了兼容SCSI-1 而设的,此处可以不必关心。
    Logical block address:
        为高位在前,低位在后的逻辑块地址,即扇区地址。第2 位为高位,第3、4、5 依次为低位。
    Transfer length:
        为需要从逻辑块地址处开始传输的扇区数(比如在Write 命令中)。
    Parameter list length:
        为需要传输的数据长度(比如在Mode Sense 命令中);
    Allocation length:
        为初始程序为返回数据所分配的最大字节数,此值可以为零,表示不需要传送数据。
        SCSI指令集的Direct Accesss 类型存储介质的传输命令有许多, Mass Storage协议只用到了其中的一些。更多的SCSI指令参见:http://en.wikipedia.org/wiki/SCSI_command
    指令代码      指令名称            说明
    04h          Format Unit      格式化存储单元
    12h          Inquiry          索取器件信息
    1Bh          Start/Stop        load/unload
    55h          Mode select     允许Host对外部设备设置参数。
    5Ah          Mode  Sense      向host传输参数
    Eh  Prevent/Allow Medium Removal    写保护
    >28h         Read(10)          Host读存储介质中的二进制数据
    A8h         Read(12)          同上,不过比较详细一点
    25h         Read Capacity       要求设备返回当前容量
    23h         Read Format Capacity   查询当前容量及可用空间  
    03h         Request  Sense        请求设备向主机返回执行结果,及状态数据
    01h        Rexero Unit          返回零轨道
    2Bh         Seek(10)          为设备分配到特定地址
    1Dh         Send  Diagnostic      执行固件复位并执行诊断
    00h        Test Unit Ready       请求设备报告是否处于Ready状态
    2Fh         Verify               在存储中验证数据
    2Ah         Write(10)          从主机向介质写二进制数据
    AAh         Write(12)          同上,不过比较详细    
    2Eh        Write and Verify      写二进制数据并验证

对于不同的命令,其命令块描述符略有不同,其要求的返回内容也有所不同,根据相 应的文档,可以对每种请求作出适当的回应。比如,下面是INQUIRY 请求的命令块描述符和其返回内容的数据格式:如:INQUIRY
    命令描述符:

点击看大图

返回数据格式

点击看大图
        Host 会依次发出INQUIRY、Read Capacity、UFI Mode Sense 请求,如果上述请求的返回结果都正确,则Host 会发出READ 命令,读取文件系统0 簇0 扇区的MBR 数据,进入文件系统识别阶段。

4、利用USB View观察结果
       可通过USB View软件查看到USB设置阶段获取到的信息。

点击看大图


        二出现的主要问题
            在调试过程中遇到了一个问题。现象是:在目标板加载完驱动后,即执行完:
            # insmod g_file_storage.ko file="/dev/mtdblock2" stall="0" removable="1"
         后,接好USB线。此时在windows端设备出有usb storage设备加入,但出现不了盘符。
         下面记录下调试过程。

调试过程
      根据规范,当完成SCSI指令集中Inquiry 命令时,可以出现盘符。所以可以通过bushound软件查看通讯过程,找出原因。
      下面是利用bushound工具在出现问题时采集到的数据。
      Dev     Phase    Data                                                                   Info             Time       Cmd.Phase. Ofs

  --- ----- --------------------------------- ---------- ----- -----------
      26      CTL      80 06 00 01 - 00 00 12 00                                          GET DESCRIPTR         0us           1.1.0
      26      DI       12 01 10 01 - 00 00 00 10 - 25 05 a5 a4 - 12 03 01 02 ........%....... 4.8ms                           1.2.0
                       03 01 ..                                                                                               1.2.16
      26      CTL      80 06 00 02 - 00 00 09 00                                          GET DESCRIPTR         14us          2.1.0
      26      DI       09 02 20 00 - 01 01 04 c0 - 01                                     .. ......             3.9ms         2.2.0
      26      CTL      80 06 00 02 - 00 00 20 00                                          GET DESCRIPTR         16us          3.1.0
      26      DI       09 02 20 00 - 01 01 04 c0 - 01 09 04 00 - 00 02 08 06   .. ............. 4.9ms           3.2.0
                       50 05 07 05 - 81 02 40 00 - 00 07 05 02 - 02 40 00 00 P.....@......@..                  3.2.16  
      26      CTL      80 06 00 03 - 00 00 02 00                                          GET DESCRIPTR         60us          4.1.0
      26      DI       09 02 20 00 - 01 01 04 c0 - 01                                     .. ......             3.9ms         2.2.0
      26      DI       04 03                                                                 ..                 3.9ms         3.1.0
      26      CTL      80 06 00 03 - 00 00 04 00                                          GET DESCRIPTR         15us          5.1.0
      26      DI       04 03 09 04                                                             ....             3.9ms         6.1.0
      26     CTL      80 06 03 03 - 09 04 02 00                                          GET DESCRIPTR        10us            1.2.16
      26      DI       1a 03                                                                   ....            4.0ms           6.2.0
      26     CTL      80 06 03 03 - 09 04 1a 00                                           GET DESCRIPTR        18us            7.1.0
      26      DI      1a 03 33 00 - 37 00 32 00 - 30 00 34 00 - 31 00 37 00 ..3.7.2.0.4.1.7. 4.9ms             7.2.0
                      35 00 36 00 - 37 00 37 00 - 35 00                                   5.6.7.7.5. 7.2.16
      26     CTL      00 09 01 00 - 00 00 00 00                                           SET CONFIG         16us              8.1.0
      26     CTL      01 0b 00 00 - 00 00 00 00                                           SET INTERFACE      60ms              9.1.0
      26     CTL      a1 fe 00 00 - 00 00 01 00                                           CLASS               62ms              10.1.0
      26     DI             00 .                                                                              3.9ms           10.2.0
      26     DO        55 53 42 43 - 08 60 e0 86 - 24 00 00 00 - 80 00 06 12 USBC.`..$....... 985us           11.1.0
                      00 00 00 24 - 00 00 00 00 - 00 00 00 00 - 00 00 00       ...$...........                11.1.16
      26      DI      00 80 02 02 - 1f 00 00 00 - 4c 69 6e 75 - 78 20 20 20      ........Linux 1.0ms             12.1.0
                      46 69 6c 65 - 2d 53 74 6f - 72 20 47 61 - 64 67 65 74           File-Stor Gadget        12.1.16
                      30 33 31 32                                                       0312                  12.1.32
      26     CTL      80 06 00 02 - 00 00 20 00                                        GET DESCRIPTR           893ms           13.1.0

      26     DI       09 02 20 00 - 01 01 04 c0 - 01 09 04 00 - 00 02 08 06 .. ............. 4.1ms           13.2.0
                      50 05 07 05 - 81 02 40 00 - 00 07 05 02 - 02 40 00 00 P.....@......@..                13.2.16
      26      CTL     80 06 00 02 - 00 00 20 00                                             GET DESCRIPTR         2.7sc        14.1.0
      26      DI      09 02 20 00 - 01 01 04 c0 - 01 09 04 00 - 00 02 08 06 .. .............  4.4ms           14.2.0
                      50 05 07 05 - 81 02 40 00 - 00 07 05 02 - 02 40 00 00 P.....@......@..                   14.2.16
      26      USTS 05 00 00 c0                                                              no response       2.8sc             15.1.0

注意上面红色部分的代码,DO发出了55 53 42 43开 始的CBW命令块,命令码是12,即Inquiry命令。要求目标返回Inquiry命令要求的数据,长度是0x24。接下来设备端通过DI返回了设备信 息。按照规范,在返回完了数据后,设备端还应该通过DI向系统返回CSW的值。但实际的捕获内容并没有。所以导致不能正确出现盘符。
    在file_storage.c发送数据时都会调用到start_transfer() 函数。在此函数中加入printk调试语句,观察现象。发现只要加入的调试语句,windows端就能够正常设别设备了。于是,可以猜测是因为需要在连续 两次发送之间加上一些延时。在函数中加入udelay(800)后,windows系统可以正常发现设备了。具体的代码架构,将在下一遍文章中解析。
    下面是程序正常后,用bushound捕获到的数据。


    红色部分,可以看出设备正确的按照规范在发送完数据后,返回CSW信息。

点击看大图
      四、总结做好USB gadget驱动、或者USB host驱动调试需要:
        ·掌握一定的知识基础
        包括:USB协议、具体的类设备规范、USB驱动程序架构、USB设备端控制器操作等。
        ·合理利用调试工具。
        包括:USB view 、bushound 、及一些硬件USB信号分析仪。