day01

来源:互联网 发布:js null的valueof 编辑:程序博客网 时间:2024/05/17 05:55
回顾:
面试题:谈谈对嵌入式linux系统的认识
1.直接给出场景:一块开发板,一台电脑,如果在开发板上运行linux系统
2.谈谈上位机要做的工作
  安装系统
  安装软件
  部署交叉编译器
3.谈谈下位机要做的工作
  掌控硬件信息
          粗看
                  三大件
                  外围
                      产品研发阶段
                      产品发布阶段
          细看
                原理图
                芯片手册
                具体硬件外设具体分析
                举例子:mma8653,重点谈I2C总线
   部署软件
           uboot
           linux内核
           根文件系统
   粗谈软件各自特点        
   三部分软件启动流程
   谈谈对uboot的认识
           吹牛
           三大功能
                   硬件初始化
                       ....
                 加载启动
                 传递参数
           下位机部署uboot
                 获取源码
                 获取编译器
                 验证源码
                 掌握软件的执行路程
                         看链接ld的命令
                         确定链接脚本
                         确定运行的起始地址
                         确定运行的第一个文件
                         确定运行的入口函数
                         顺藤摸瓜
                 根据硬件差异进行代码修改
                       举例子:MX25处理器
                         重点在硬件信息相关的头文件
                 uboot的调试
                         打印
                         点灯
                         仪器
       谈谈linux内核
                   吹牛
                   七大功能
                   下位机部署linux内核
                           获取源码
                           获取编译器
                           验证源码
                           根据硬件差异修改配置源码
                                   两个角度
                                           配置的角度
                                                   各种坑
                                           平台源文件角度device.c    
       谈谈根文件系统rootfs
                   特点和包含的内容
                   必要目录和可选目录
                   利用busybox获取各种命令
                   大谈特谈busybox特点
                           吹牛
                           源码操作
                           创建各种目录
                           添加动态库
                                   标准的库一律放lib
                                   库来自于交叉编译器
                                   需要哪些库就拷贝哪些库即可
                                   arm...readelf -d 应用程序
                                   注意:加载器
                           添加系统启动的必要配置文件和脚本文件
                                   inittab
                                   rcS
                                   fstab
                           用strip精简体积
                           利用NFS网络服务进行测试
                               产品研发阶段用NFS
                           产品阶段将rootfs制作成镜像烧写到闪存上
                               做镜像
                                   dd
                                   mkfs.ext4
                                   mount
                                   cp
                                   umount
                             分区规划
                                 fdisk分区
                             利用tftp下载,利用mmc write烧写
                             设置启动参数
                             至此系统部署完毕!
*************************************************
嵌入式课程第三阶段:嵌入式linux设备驱动开发
1.linux系统的两种状态或者两个空间(了解)
  两种状态:用户态和内核态
  两个空间:用户空间和内核空间
  两种状态=两个空间
  用户空间特点:
        用户空间包含的软件就是一堆的命令,一堆的各种应用程序,一堆的各种库,一堆的各种配置文件。 
        用户空间的软件在运行的时候,CPU的工作模式为USER模式 。
        用户空间的软件不允许直接访问硬件外设的物理地址,如果用户空间的软件要想访问硬件外设的物理地址必须首先将外设的物理地址映射到用户的虚拟地址空间中,一旦完成这种映射,用户空间软件即可访问这个映射的用户虚拟地址就是在访问外设的物理地址,用户虚拟地址空间的大小为3G,地址空间范围为:0x00000000~0xBFFFFFFF。 
        用户空间的软件不允许直接访问内核空间的代码,地址和数据,如果用户空间的软件要想访问内核空间的代码,地址和数据必须利用系统调用(唯一途径)!
        用户空间的软件如果进行了非法的内存访问,不会造成操作系统的崩溃,仅仅造成应用程序的崩溃(例如:*(int *)0 = 0)
         
  内核空间特点:
          内核空间包含的软件就是uImage(七大子系统)
          内核空间的软件在运行的时候,CPU的工作模式为SVC管理模式
          内核空间的软件同样不允许直接访问外设的物理地址,内核程序
          要想访问外设的物理地址必须首先将外设的物理地址映射到
          内核空间的虚拟地址上,一旦完成这种映射,将来内核软件访问
          映射的内核虚拟地址就是在访问对应的物理地址
          内核虚拟地址空间的大小为1G,地址空间范围:0xC0000000~0xFFFFFFFFF
          内核软件在运行时,如果进行了非法的内存访存,系统直接崩溃(吐核)
          例如(*(int *)0 = 0)
          
2.回顾:编写用户空间软件的框架
  vim helloworld.c
  #include <stdio.h> //标准C的头文件
  //main函数为程序的入口函数
  //argc:参数的个数
  //argv:参数的内容
  int main(int argc, char *argv[])
  {
      //printk函数为标准C的库函数
      printf("hello,world\n");
      //程序的出口
      return 0;
  }
   
3.linux内核程序编程框架
  参考代码:
  上位机执行:
  mkdir /opt/drivers/day01/1.0 -p
  cd /opt/drivers/day01/1.0
  vim helloworld.c 添加如下内容
  #include <linux/init.h>
  #include <linux/module.h>  
  int helloworld_init(void)
  {
      printk("%s\n", __func__);
      return 0;
  }  
  void helloworld_exit(void)
  {
      printk("%s\n", __func__);
  }
  module_init(helloworld_init);
  module_exit(helloword_exit);
  MODULE_LICENSE("GPL");
  保存退出
  说明:
  1.内核程序一律用C语言实现
  2.内核程序使用的头文件不是标准C库的头文件,内核
    程序使用的头文件都位于内核源码中(/opt/kernel)  
  3.用module_init宏修饰的函数就是内核程序的入口函数(类似main函数) 。
    当向内核uImage安装(insmod)内核程序时,内核uImage会自动调用module_init修饰的函数。 
    入口函数的返回值数据类型为int型 。
    入口函数的形参为void 。
    入口函数执行成功,返回0,并且表示此内核程序安装完毕,生命周期正式开始,存于内存中。 
    入口函数执行失败,返回负值,表示内核程序安装失败,同样内存中也不会存在安装的内核程序。 
   
  4.用module_exit宏修饰的函数就是内核程序的出口函数(类似return 0)
    当向内核uImage卸载(rmmod)内核程序时,内核uImage会自动调用module_exit修饰的函数。 
    一旦调用完毕,内存中也不会存在这个内核程序了。 
    出口函数的返回值和形参的数据类型都是void。 
   
  5.内核程序打印信息不用printf而是用printk。 
    此函数的定义实现同样存在于内核源码中(/opt/kernel),不是在标准C库中。 
     
 6.切记:任何内核程序(.c结尾)必须添加MODULE_LICENSE("GPL")告诉内核此内核程序同样遵循GPL协议,否则内核会骂街, 
        更严重的是内核中的某些函数不允许调用!
   
4.linux内核程序的编译
  回顾之前内核程序的编译过程:
  1.拷贝内核程序到内核源码的某个目录下
  2.修改这个目录下的Kconfig,添加配置菜单
  3.修改这个目录下的Makefile,添加编译支持
  4.make menuconfig要不选*要不选M
  5.重新编译内核make uImage
    单独编译内核程序make modules,结果.c->.ko(insmod/rmmod)
   
  内核编译还可以使用另一种简便的方法来编译,这种方法
  只需一个小小的Makefile即可搞定:
  cd /opt/drivers/day01/1.0
  vim Makefile 添加如下内容:
  obj-m += helloworld.o  #helloworld.c->helloworld.ko
  #当执行make或者make all时调用执行
  #make -C /opt/kernel modules:等价于cd /opt/kernel然后make modules
  #-C:表示到某个目录下
  #SUBDIRS=$(PWD)=/opt/drivers/day01/1.0
  #make  -C /opt/kernel SUBDIRS=$(PWD) modules   
  #到/opt/kernel目录下执行make modules进行单独编译
  #并且告诉内核在内核源码之外还有一个目录/opt/drivers/day01/1.0
  #把这个目录下的.c编译生成.ko
  all:
  (TAB键)make  -C /opt/kernel SUBDIRS=$(PWD) modules  
   
  #当执行make clean时,将当前目录下生成的目标文件进行清理删除
  clean:
  (TAB键)make -C /opt/kernel SUBDIRS=$(PWD) clean  
   
  保存退出
  make //编译
  ls
          helloworld.c ... helloworld.ko
  mkdir /opt/rootfs/home/drivers //单独存放.ko文件
  cp helloworld.ko /opt/rootfs/home/drivers    
  make clean //将生成的目标文件进行清理
  ls   
    helloworld.c  Makefile
   
  下位机测试:
  首先启动下位机的linux系统(注意采用NFS网络启动)
  下位机的linux系统启动完毕执行:
  cd /home/drivers/
  ls
       helloworld.ko
  echo 8 > /proc/sys/kernel/printk //降低默认的打印阈值(级别)
  insmod helloworld.ko //安装内核程序到uImage中
                                           //此时helloworld的入口函数被调用
  lsmod  //查看安装的helloworld
  rmmod  helloworld //从内核uImage中卸载内核程序
                                      //此时helloworld的出口函数被调用
                                                                                
5.linux内核命令行传参
  1.回顾应用程序的命令行传参实现过程
    mkdir /opt/drivers/day01/2.0 -p
    cd /opt/drivers/day01/2.0
    vim helloworld.c
    #include <stdio.h>
    int main(int argc, char *argv[])
    {
         int a;
         int b;
          
         if(argc != 3) {
              printf("用法:%s a b\n", argv[0]);
              return -1;
         }
          
         //将字符串转成对应的整形数
         //"100"->100
         //"0x100"->0x100
         a = strtoul(argv[1], NULL, 0);
         b = strtoul(argv[2], NULL, 0);
          
         printf("a = %d, b = %d\n",a ,b);
         return 0;
    }
    保存退出
    gcc -o helloworld helloworld.c
    ./helloworld  100 200
    //argc=3
    //argv[0]="./helloworld"
    //argv[1]="100"
    //argv[2]="200"
        a = 100, b = 200     
     
  2.linux内核命令行传参
    linux内核程序接收参数的变量务必是全局变量  
    全局变量的数据类型为以下标准数据类型,无结构体
    bool,invbool
    char,uchar
    short,ushort
    int,uint
    long,ulong
    charp(就是char *)    
    切记:linux内核中不允许操作float,double数据类型
    float,double数据类型一般放在用户空间来完成数据处理
    将来接收参数的全局变量一定要进行传参声明,用以下宏
    即可进行传参声明,声明完毕,将来即可接收参数信息:
    module_param(name, type, perm);
    name:接收参数的全局变量名
    type:全局变量的数据类型
    perm:全局变量的访问权限,一般用8进制数表示即可,0664
              注意:不允许有可执行权限
     
    案例:编写内核程序,掌握linux内核命令行传参
    实施步骤:
    上位机执行:
    mkdir /opt/drivers/day01/3.0  
    cd /opt/drivers/day01/3.0
    vim helloworld.c  
    vim Makefile
    make
    cp helloworld.ko /opt/rootfs/home/drivers
     
    下位机执行:
    cd /home/drivers/
    echo 8 > /proc/sys/kernel/printk
    //不传递参数
    insmod helloworld.ko
    lsmod
    rmmod helloworld
     
     
    //安装内核程序时传递参数
    insmod helloworld.ko irq=100 pstring=china
    lsmod
    rmmod helloworld
     
    //安装内核程序以后传递参数
    insmod helloworld.ko irq=100 pstring=china
    lsmod
    cat /sys/module/helloworld/parameters/irq //查看irq文件的内容
    echo 20000 > /sys/module/helloworld/parameters/irq //向文件irq写入新值
    rmmod helloworld
    结论:如果传参声明时,指定的权限为非0,那么将来在
          /sys/module/内核程序名/parameters目录下
          会生成一个跟变量名同名的文件,文件的内容就是
          变量的值,将来修改文件的内容就是间接修改变量的值
          如果权限为0,就不会有同名的文件,只能在安装内核程序时
          给这个权限为0的全局变量传递参数!          
    切记:如果没有安装内核程序以后传递参数的需求,传参
          声明时的权限一律给0,目的是节省内存资源!
 
6.linux内核符号导出(实现多文件之间的互相访问)
  6.1.回忆应用程序多文件之间的调用
  mkdir /opt/drivers/day01/4.0  
  cd /opt/drivers/day01/4.0
  vim test.h //声明
  vim test.c //定义
  vim main.c //调用
  arm...gcc -shared -fpic -o libtest.so test.c
  arm...gcc -o main main.c -L. -ltest
  cp libtest.so /opt/rootfs/home/applib/
  cp main /opt/rootfs/home/applib/
  下位机测试:
  cd /home/applib
  export LD_LIBRARY_PATH=/home/applib:$LD_LIBRARY_PATH
  ./main
   
  6.2.linux内核多文件之间的互相访问
  linux内核要求将来被别的文件访问的全局变量或者函数  
  要显示的进行导出操作(简称符号导出),导出的方法用以下宏即可:
  EXPORT_SYMBOL(变量名或者函数名);
  或者
  EXPORT_SYMBOL_GPL(变量名或者函数名);      
  前者导出的变量或者函数能够给所有的内核程序访问(哪怕它不遵循GPL协议)
  后者导出的变量或者函数只能给那些遵循GPL协议的内核程序访问
   
  案例:编写内核程序,实现多文件之间的访问
  实施步骤:
  上位机执行:
  mkdir /opt/drivers/day01/5.0
  cd /opt/drivers/day01/5.0
  vim test.h //声明
  vim test.c //定义
  vim helloworld.c //调用
  vim Makefile
          obj-m += helloworld.o test.o #test.c->test.ko, helloworld.c->helloworld.ko
          #或者
          #obj-m += helloworld.o
          #obj-m += test.o
  make
  cp *.ko /opt/rootfs/home/driver/
   
  下位机测试:
  cd /home/drivers
  insmod test.ko
  insmod helloworld.ko
  rmmod helloworld
  rmmod test
   
7.linux内核打印函数printk
  7.1.printk VS printf 比较
  前者只能用于内核空间,定义在内核源码中
  后者只能用于用户空间,定义在标准C库中
  两者用法一致
  printk能够指定打印输出级别,共八级
   
  7.2.printk打印函数的打印输出八个级别
    #define KERN_EMERG    "<0>"    /*系统崩溃*/
  #define KERN_ALERT    "<1>"    /*事件需要立即处理*/
  #define KERN_CRIT      "<2>"    /*严重情况*/
  #define KERN_ERR      "<3>"    /*错误情况*/
  #define KERN_WARNING    "<4>"    /*警告*/
  #define KERN_NOTICE    "<5>"    /*正常,但还是需要引起注意*/
  #define KERN_INFO      "<6>"    /*信息*/
  #define KERN_DEBUG    "<7>"    /*调试信息*/
  结论:数字越小,事件就越严重,打印信息的输出级别就越高
  用法:
  printk(KERN_ERR "this is a error msg!\n");
  或者
  printk("<3>" "this is a error msg!\n");             
   
  7.3.printk的默认打印输出级别
  默认打印输出级别用来指示哪些信息能够输出,哪些信息进行屏蔽不输出
  如果printk指定的打印输出级别高于默认打印输出级别,此信息一律输出
  否则进行屏蔽(printk指定的数字小于默认级别的数字,信息输出,否则不输出)
  例如:目前默认打印输出级别为4
  printk("<3>" "...."); //输出
  printk("<4>" "...." ); //不输出,屏蔽
  总结:数字越小,级别越高
  问:默认打印输出如何设置呢?
  答:设置方法有两种:
  方法1:修改配置文件
               echo 8 > /proc/sys/kernel/printk
               指定默认打印输出级别为8
                   此方法缺点是内核启动时候的打印信息无法指定是否输出还是不输出
  方法2:在内核启动参数中添加默认打印输出级别
  案例:编写内核程序,掌握printk打印输出级别
  上位机操作:
  mkdir /opt/drivers/day01/7.0
  cd /opt/drivers/day01/7.0
  vim printk_all.c
  vim Makefile
  make
  cp printk_all.ko /opt/rootfs/home/drivers
   
  下位机测试:
  cd /home/drivers
  insmod printk_all.ko //观察打印信息
  rmmod printk_all.ko
   
  echo 8 > /proc/sys/kernel/printk
    insmod printk_all.ko //观察打印信息
  rmmod printk_all.ko
   
  重启下位机,进入uboot命令行执行:
  setenv bootargs root=/dev/nfs nfsroot=192.168.1.8:/opt/rootfs  
          ip=192.168.1.110:192.168.1.8:192.168.1.1:255.255.255.0  
          init=/linxurc console=ttySAC0,115200  
          maxcpus=1 quiet
  boot //启动,自动执行bootcmd的命令
  系统启动完毕
  cd /home/drivers/
  insmod printk_all.ko
  rmmod printk_all
  总结:quiet对应默认打印输出级别为4
   
  重启下位机,进入uboot命令行执行:
  setenv bootargs root=/dev/nfs nfsroot=192.168.1.8:/opt/rootfs  
          ip=192.168.1.110:192.168.1.8:192.168.1.1:255.255.255.0  
          init=/linxurc console=ttySAC0,115200  
          maxcpus=1 debug
  boot //启动,自动执行bootcmd的命令
  系统启动完毕
  cd /home/drivers/
  insmod printk_all.ko
  rmmod printk_all
  总结:debug对应默认打印输出级别为10
   
  重启下位机,进入uboot命令行执行:
  setenv bootargs root=/dev/nfs nfsroot=192.168.1.8:/opt/rootfs  
          ip=192.168.1.110:192.168.1.8:192.168.1.1:255.255.255.0  
          init=/linxurc console=ttySAC0,115200  
          maxcpus=1 loglevel=0
  boot //启动,自动执行bootcmd的命令
  系统启动完毕
  cd /home/drivers/
  insmod printk_all.ko
  rmmod printk_all
  总结:loglevel=数字,数字代表级别
   
  总结:产品研发阶段,用debug,看到详细的调试信息
        产品发布阶段,用quiet,缩短系统启动时间
               
     
                    
                   
                                            
 

原创粉丝点击