首页 > 代码库 > Linux系统

Linux系统

[Linux]
[1] 目录
    net                    网络协议栈及socket
    fs                     文件系统实现(vfs、具体文件系统)及编程接口(系统调用)
    init                   系统初始化核心代码,不能裁剪
    kernel                 系统核心代码, 进程管理,不能裁剪
    ipc                    进程间通信实现(消息队列、信号量和共享内存)
    mm                     内存管理
    arch(经常需要添加代码) 架构相关代码
      boot                 内启动前的准备(分离内核和ramdisk、自解压)代码
      kernel               cpu需要的核心代码,内核真正的启动代码在head.S
      mm                   cpu相关的内存管理代码
      lib                  cpu相关的标准库代码
      configs              支持的开发板的默认配置文件
      include              cpu相关的代码的头文件
      plat-VENDOR          某个厂家芯片需要用到的公共代码
        include
      plat-SOC系列         芯片厂家的一个系列SOC的公共代码
        include
      mach-SOC             某个SOC用到的源代码
        include
   drivers(经常需要添加代码)通用设备驱动
   include                 头文件
   scripts                 编译配置脚本
   Docutmentation          作者写的文档
   
[2] 文件
    源文件
    *.c/*.S/*.h            源代码
    Makefile               编译规则
    Kconfig                配置脚本
    
    目标文件
    .config                内核当前配置结果(选取哪些源代码)
    *.o                    目标文件
    System.map             符号表
    vmlinux                ELF格式的可执行程序(带调试信息)
    arch/arm/boot/Image    Binaray格式可执行程序
    arch/arm/boot/zImage   压缩的内核的二进制文件(带自解压算法)
    
[3] 特点
    1. 支持多种CPU
    2. 支持多种硬件驱动
    
    任何一款嵌入式产品,都不会用到所有的源代码,所以用源码生成目标文件的步骤:
    1. 选取需要的源代码 -- 配置
    2. 编译选取的源代码 -- 编译
    
[4] 编译
    1. 配置
       $ cp arch/arm/configs/s5pc100 .config
       $ make menuconfig
       
    2. 编译
       make 
  
[5] 编译原理  
    1. 过程
       见《内核编译流程》
       
    2. 结果
       见《[2] 文件》
       
    3. 模块的Makefile
       obj-y := 加入这个变量的*.o文件,被静态编译到内核(链接到zImage中)
       obj-m := 加入这个变量的*.o文件, 被动态编译到内核(链接到*.ko中)
    
[6] 配置原理
    1. 控制目录/文件是否编译
       (1) 目录
           例: CONFIG_SPI
           在.config里面查看CONFIG_SPI配置项值:
           CONFIG_SPI=y                        静态选中
           # CONFIG_SPI ...                    没有选中
           在drivers/Makefile中,查看控制目录编译的方法:
           obj-$(CONFIG_SPI) += spi/
           
       (2) 文件
           例: CONFIG_SPI_MASTER
           在.config里面查看CONFIG_SPI_MASTER配置项值:
           CONFIG_SPI_MASTER=y                 静态选中
           CONFIG_SPI_MASTER=m                 动态选中
           # CONFIG_SPI_MASTER ...             没有选中
           
           在drivers/spi/Makefile中,查看控制目录编译的方法:
           obj-$(CONFIG_SPI_MASTER) += spi.o


    2. 选中文件中的一部分代码(条件编译)
       例:
       #define CONFIG_DEBUG_LL (通过是否有这个宏定义来控制代码是否被编译到内核)
       #ifdef CONFIG_DEBUG_LL (arch/arm/kernel/head.S)
       ....
       #endif
       
       在.config里面查看CONFIG_DEBUG_LL配置项值:
       CONFIG_DEBUG_LL=y                   静态选中
       编译时,会被转义成:
       #define CONFIG_DEBUG_LL 1 (include/generated/autoconf.h)
       
       # CONFIG_DEBUG_LL=y                 不选中
       编译时,会被转义成:
       // #define CONFIG_DEBUG_LL 1 (include/generated/autoconf.h)
       
    3. 提供数据给源代码(宏替换)
       例: CONFIG_CMDLINE
           在.config中,查看CONFIG_CMDLINE配置项值:
           CONFIG_CMDLINE="root=/dev/mtdblock2 rootfstype=cramfs init=/linuxrc console=ttySAC2,115200"
           编译时,被转义成:
           #define CONFIG_CMDLINE "root=/dev/mtdblock2 rootfstype=cramfs init=/linuxrc console=ttySAC2,115200"
           
    4. Kconfig语法原理
       1. 配置项值
       <>     tristate
              y         静态选中
              m         动态选中
              n         不选中
       []     bool
              y         静态选中
              n         不选中
       ()     string
       
       2. 语法解释及例子
          见《Kconfig》


[7] 如何添加模块到工程中?(必须掌握)
    1. 添加源代码到对应目录
       例: drivers/hello/module_hello.c
       
    2. 给子目录添加Makefile(或者直接修改)
       obj-y += module_hello.o                         表示静态编译到内核
       obj-m += module_hello.o                         表示动态编译到*.ko
       obj-$(CONFIG_HELLO) += module_hello.o           根据图形菜单的配置决定静态/动态编译到内核
       
    3. 修改上一级目录下的Makefile
       obj-y       += hello/
       
    4. 添加Kconfig配置脚本
       config HELLO
            tristate "hello config"
            help
            hello config help

    5. 修改上一级目录下的Kconfig
       source "drivers/hello/Kconfig"
    
[9] 系统启动流程
    [kernel]
    1. 检查内核是否支持正在运行的CPU(A8)
       [方法]
       (1) 读取CPU内部的id
       (2) 调用__lookup_processor_type子程序检查内核是否支持该CPU
       (3) 如果内核不支持该CPU,打印‘P‘类型错误(Error: unrecognized/unsupported processor variant(当前运行的CPU ID))
       (4) 如果支持该CPU,继续运行
       错误原因: 操作系统不支持当前CPU所致,一般不会出现
       解决办法: 修改操作系统(ARM公司或操作系统厂家)
       
    2. 检查内核是否支持正在运行的主板
       (1) 主板id(arch number / mach type id)存储在R1(BootLoader放置)
       (2) 调用__lookup_mach_type子程序检查内核是否支持该主板
       (3) 如果内核不支持该CPU,打印‘A‘类型错误
          (Error: unrecognized/unsupported machine ID (r1 = BootLoader传输的主板id).
          Available machine support:\n\nID (hex)\tNAME
          Please check your kernel config and/or bootloader.)
          
       (4) 如果内核支持该主板,继续运行
       错误原因: 1. 操作系统确实不支持改主板,交给芯片厂家完成(概率非常小)
                 2. bootloader传输的id错误
       解决办法: 检查BootLoader传入内核的arch number是否正确
                 (1) 根据主板名字到include/generated/mach-types.h文件中查出当前主板ID
                 (2) 对比当前主板ID和A错误中打印的R1寄存器的值
                 
     3. 循环调用驱动模块(静态加载)的初始化函数
        错误原因: 驱动的初始化函数有问题,导致内核崩溃(OOPS), 崩溃驱动代码检测方法
        解决办法: 见《内核调试方法》
        
     4. 打开console终端,并且把它设置成内核的标准输入、标准输出和标准错误
        错误现象:  unable to open an intial console.
        错误原因: /dev/console不存在(文件系统制作时必须创建)
        
     5. 挂载根文件系统
        错误现象: 
         Root-NFS: No NFS server available, giving up.
         VFS: Unable to mount root fs via NFS, trying floppy.
         VFS: Cannot open root device "mtdblock0" or unknown-block(2,0)
         Please append a correct "root=" boot option; here are the available partitions:
         Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(2,0)
        错误原因: (1) root=参数指定的存放文件系统的设备不存在
                  (2) 设备上没有存储文件系统(或文件系统类型操作系统不支持 或文件系统错误)
        解决办法: (1) 检查root=参数设置的设备是否正确
                  (2) 设备上存储的文件系统是否正确
                  
     6. 启动init进程
        错误现象: (1) Failed to execute /xxxy.  Attempting defaults...
                  (2) NO init found. .......
                  
        错误原因: (1) init=参数指定的程序无法启动
                  (2) init=参数指定的程序有问题
                      && /sbin/init不存在
                      && /etc/init不存在
                      && /bin/init不存在
                      && /bin/sh不存在
        
        解决办法: (1) 检查init=参数指定的程序是正确
                  (2) 检查init=参数指定的程序是正确
        
    [rootfs]
     7. /etc/inittab(init进程解析执行)
        终端::动作:程序或脚本
        [终端]
        省略名字/dev/, 脚本之前不能有任何字符(包括空格):
例:
console::sysinit:/etc/init.d/rcS(console前面不能有任何字符,包括空格)
        
        [动作]
        sysinit                     指定系统初始化阶段执行的程序或脚本
        askfirst(respawn)           指定系统正常运行阶段,需要一直执行的程序或脚本
        ctrlaltdel                  指定在按Ctrl + Alt +Del组合键时运行的程序(只能在标准终端上起作用)
        restart                     指定系统重启前运行的脚本或脚本
        shutdown                    指定系统关闭前运行的程序或脚本
        
        [程序或脚本]
        应用程序或shell脚本
        
     8. /etc/init.d/rcS
        #!/bin/sh                   脚本解析器
  
        /bin/mount  -a              挂载/etc/fstab指定的文件系统
         
        告诉内核mdev程序的路径,系统有热插拔设备时,内核会调用mdev程序新建或删除对应设备文件节点
        echo /sbin/mdev > /proc/sys/kernel/hotplug


        启动mdev程序,扫描系统识别的设备,并且新建设备文件节点
        /sbin/mdev  -s
        
        /etc/fstab内容
        存放文件系统的设备       目录    文件系统该类型
        proc                     /proc   proc             defaults        0        0
        tmpfs                    /tmp    tmpfs            defaults        0        0
        sysfs                    /sys    sysfs            defaults        0        0
        tmpfs                    /dev    tmpfs            defaults        0        0
        tmpfs挂载时,文件系统为空,当向文件系统写入数据时,所有数据文件都存储到内存
        
    9. /etc/profile
        #!/bin/sh                                               脚本解析器
         export HOSTNAME=farsight                                机器名
         export USER=root                                        用户名
         export HOME=root                                        用户主目录
         export PS1="[$USER@$HOSTNAME \W]\# "                    命令提示符
         PATH=/bin:/sbin:/usr/bin:/usr/sbin                      PATH环境变量
         LD_LIBRARY_PATH=/lib:/usr/lib:$LD_LIBRARY_PATH          动态库的搜索路径
         export PATH  LD_LIBRARY_PATH
         注意:本脚本被登录程序调用,但是嵌入式设备中,很多没有配置登录程序,这时希望shell脚本直接执行
               本脚本(需要在inittab中,启动shell程序时,在前面加上‘-‘)
               
    10. 必须要的设备文件节点
        # sudo mknod /dev/console c(字符设备) 5(主设备号 Documentation/devices.txt) 1(次设备号)
        # sudo mknod /dev/null c 1 3
    
[10] 移植
     BSP?
     描述主板硬件配置的数据结构集合,也就是说它的主要功能告诉操作系统准备的硬件配置
     
[11] Linux应用程序调试
     1. gdb远程调试
        (1) 利用开发板的交叉编译器,编译要调试的程序(编译过程中要加-g参数,-O0)
            # arm-cortex_a8-linux-gnueabi-gcc hello.c -o hello -g -O0
            
        (2) 拷贝编译出来的可执行代码到开发板的root目录
            # sudo cp hello /source/rootfs/root
            
        (3) 在交叉编译器的目录下寻找gdbserver(在开发板上能运行的程序)程序
            # find ~/toolchain -depth -name gdbserver
            
        (4) 拷贝gdbserver到开发板
            # sudo cp ~/toolchain/arm-cortex_a8-linux-gnueabi/debug-root/usr/bin/gdbserver /source/rootfs/root/
            
        (5) 在开发板上运行gdbserver程序加载要调试的程序
            $ ./gdbserver 192.168.0.7(开发板ip地址):1234(端口号) hello(调试的可执行程序)
         
        (6) 在PC上,启动gdb(在PC上运行的gdb程序,也是有交叉编译器提供)并且加载要调试的可执行程序
            # arm-cortex_a8-linux-gnueabi-gdb hello(要调试的程序)
            
        (7) 在gdb程序中,远程连接开发板gdbserver:
            (gdb) target remote 192.168.0.7:1234
            
        (8) 下断点,然后让开发板调试程序继续运行
        
    2. 段错误调试(非常有用)
       (1) 利用开发板的交叉编译器,编译要调试的程序(编译过程中要加-g参数,-O0)
           # arm-cortex_a8-linux-gnueabi-gcc hello.c -o hello -g -O0  
       
       (2) 拷贝编译出来的可执行代码到开发板的root目录
            # sudo cp hello /source/rootfs/root  
             
       (3) 在开发板上打开core文件限制,然后运行程序
            $ ulimit -c unlimited
            $ ./hello
            出现段错误,产生core文件
            
       (4) 在交叉编译器的目录下寻找gdb(在开发板上能运行的程序)程序
           # find ~/toolchain -depth -name gdb
           # file gdb(确保是开发板上可以运行的gdb程序)
           
       (5) 拷贝gdb到开发板
            # sudo cp ~/toolchain/arm-cortex_a8-linux-gnueabi/debug-root/usr/bin/gdb /source/rootfs/root/
            
        (6) 在开发板上运行gdb程序解析core文件
            $ ./gdb hello(带调试信息的执行程序) core(产生的core文件)
            $ bt(查看详细的代码出错点) 
            
    3. 内存操作错误(有用)
       核心用法: memwatch.c监控工程中,内存操作错误,没有发生段错误退出
       见《实验十二》   
       
[12] 内核调试
     1. 内核汇编启动代码调试(汇编阶段)
        (1) 打开MMU之前
            led调试 -- 用led灯的亮灭来显示代码运行到哪个位置了
            直流蜂鸣器 -- 用蜂鸣器是否响,显示代码运行到哪个位置了
            UART调试(BootLoader使用的console) -- 通过打印字符来代码运行到哪个位置了
            
        (2) 打开MMU之后,进入C程序之前的汇编调试,只能用UART
            内核启动初期,只建立了1M的内存映射
        注意:汇编启动代码基本不会出错
           
     2. C代码部分
        [printk](必须掌握)
        1. 用法
           printk("<级别>""hello\n");
           [级别]
           0 - 7 数字越小级别越高
           [注意]
           (1) 不支持浮点
           (2) 打印到__log_buf
           
        2. 相关级别
           int console_printk[4] = {
              DEFAULT_CONSOLE_LOGLEVEL,          // console_printk[0] console级别
              DEFAULT_MESSAGE_LOGLEVEL,          // console_printk[1] 消息默认级别,printk没有指定级别时
              MINIMUM_CONSOLE_LOGLEVEL,          // console_printk[2] console最高级别
              DEFAULT_CONSOLE_LOGLEVEL,          // console_printk[3] 默认console级别
           };
           四个级别可以通过应用程序空间的/proc/sys/kernel/printk文件查看或者修改
           查看命令:cat /proc/sys/kernel/printk
           修改命令:echo "6 5" > /proc/sys/kernel/printk
           
        3. 消息的级别高于console级别,从console输出
           实验:
           1. printk_test动态编译到内核
              (1) 拷贝printk_test.c 到内核的drivers/char/
              (2) 在drivers/char/Makefile前面加入, obj-m += printk_test.o
              (3) # make
              (4) # cp arch/arm/zImage /tftpboot
              (5) # cp drivers/char/printk_test.ko /source/rootfs/lib/modules/2.6.35


           2. 重启开发板
           3. 加载printk_test.ko模块,观察哪些消息从console输出,然后卸载printk_test.ko
              加载命令:insmod /lib/modules/2.6.35/printk_test.ko
              卸载命令:rmmod printk_test


           4. 修改console口级别为6,然后再次加载printk_test.ko模块,观察哪些消息从console输出


        4.  消息从/proc/kmsg输出实验
            1. 关闭klogd程序,重启开发板
               在/etc/init.d/rcS中注释以下两行:
               # klogd&
               # syslogd&


            2. 加载printk_test.ko模块
               $ insmod /lib/modules/2.6.25/printk_test.ko


            3. cat /proc/kmsg 打印内核消息
            4. cat /proc/kmsg 再次打印内核消息,观察结果
            
        
         5.  消息被klogd和syslogd程序读到/var/log/messeges
             1. 在linux启动脚本(/etc/init.d/rcS)中,启动klogd和syslogd
                脚本如下:
                klogd&
                syslogd&
              
             2. 加载printk_test.ko模块
                $ insmod /lib/modules/2.6.25/printk_test.ko


             3. 查看/var/log/messeges内核消息
                cat /var/log/messeges
                 或
                tail /var/log/messeges
 
        
        [oops](内核发生段错误)(必须掌握)
        (1) 产生oops的方法,见《实验十三》
        (2) oops分析


// 原因
Unable to handle kernel NULL pointer dereference at virtual address 00000000


// 页目录表及其内容
pgd = c0004000
[00000000] *pgd=00000000


// oops内部编号
Internal error: Oops: 805 [#1]
last sysfs file:
Modules linked in:


// cpu现场(寄存器值)
CPU: 0    Not tainted  (2.6.35 #15)
PC is at dm9000_probe+0x20/0x8c8
LR is at platform_drv_probe+0x18/0x1c
pc : [<c020ef74>]    lr : [<c015f874>]    psr: a0000013
sp : cfc29ef0  ip : 00000064  fp : 00000000
r10: 00000000  r9 : 00000000  r8 : 00000000
r7 : c02c5fa8  r6 : c02c5ec8  r5 : c02c5ed0  r4 : c02c5ed0
r3 : 000000ff  r2 : 00000001  r1 : 00000001  r0 : 000000ec
Flags: NzCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment kernel
Control: 10c5387d  Table: 20004019  DAC: 00000017
Process swapper (pid: 1, stack limit = 0xcfc28268)


// kernel_init内核线程栈里面的数据
Stack: (0xcfc29ef0 to 0xcfc2a000)
9ee0:                                     00000000 cfc29f00 cfc472d0 00000000
9f00: c02c5f04 c02c5ed0 c02c5ed0 c02deeec c02deeec 00000000 00000000 00000000
9f20: 00000000 c015f874 c02c5ed0 c015e99c c02c5ed0 c02c5f04 c02deeec 00000000
9f40: 00000000 c015eaac c02deeec cfc29f58 c015ea4c c015e234 cfc01d08 cfc46bc0
9f60: c02deeec c02deeec cfce1200 c02deb60 00000000 c015db28 c02768b0 c02768b0
9f80: cfc24000 c02deeec c001f128 00000000 00000013 00000000 00000000 c015ed7c
9fa0: c001973c c001f128 00000000 00000013 00000000 c0023380 c001973c c00223a0
9fc0: c02e53c0 c02e53c0 c001f0ac c001f128 c0024e1c 00000000 00000000 00000000
9fe0: 00000000 c000857c 00000000 c00084e0 c0024e1c c0024e1c fadafb7d 7bffafdd


// 函数调用历史
[<c020ef74>] (dm9000_probe+0x20/0x8c8) from [<c015f874>] (platform_drv_probe+0x18/0x1c)
[<c015f874>] (platform_drv_probe+0x18/0x1c) from [<c015e99c>] (driver_probe_device+0xa8/0x158)
[<c015e99c>] (driver_probe_device+0xa8/0x158) from [<c015eaac>] (__driver_attach+0x60/0x84)
[<c015eaac>] (__driver_attach+0x60/0x84) from [<c015e234>] (bus_for_each_dev+0x48/0x84)
[<c015e234>] (bus_for_each_dev+0x48/0x84) from [<c015db28>] (bus_add_driver+0x98/0x214)
[<c015db28>] (bus_add_driver+0x98/0x214) from [<c015ed7c>] (driver_register+0xac/0x13c)
[<c015ed7c>] (driver_register+0xac/0x13c) from [<c0023380>] (do_one_initcall+0x58/0x1b0)
[<c0023380>] (do_one_initcall+0x58/0x1b0) from [<c000857c>] (kernel_init+0x9c/0x150)
[<c000857c>] (kernel_init+0x9c/0x150) from [<c0024e1c>] (kernel_thread_exit+0x0/0x8)


     (3) 根据错误信息,找到促发oops的源码
        arm-cortex_a8-linux-gnueabi-addr2line 0xc020ef74(PC值,地址) -e vmlinux(带调试信息的ELF可执行文件) -f
        
     (4) 分析错误
        (1) 促发oops的源代码不一定是产生错误的源代码
        (2) 如果不是,可以参考函数调用历史,回溯查找前面的函数看代码(特别是自己写的代码)是否有问题
        (3) 更改的代码和促发错误的代码的关系
        (4) 认真查看更改代码
        
      [panic]
      panic()函数打印消息,并挂死
          

Linux系统