zoukankan      html  css  js  c++  java
  • 开发环境的搭建,符合导出,打印优先级阈值

    linux驱动程序开发

    1、linux驱动工程师需要具备的知识
      1)硬件的知识
        看懂电路原理图 (二极管 三极管  电阻...)
            底板和核心板中名称相同的导线是同一条导线
            目的:找到要驱动的硬件和CPU的连接方式
        熟悉常见的接口:gpio uart i2c  1-wire spi 485 can  usb
        能够熟练阅读芯片的datasheet(谁读谁头疼)
      2)驱动程序属于内核的一部分(uImage)
        内核态编程要守内核态编程的规矩
      3)内核中已经实现好大量的硬件驱动框架
         字符设备:读写顺序固定 读写以字节为单位
                   例如:鼠标 键盘 ...
         块设备:   读写顺序不固定 读写以块(多字节)为单位
                   例如: 硬盘 flash emmc u盘 ...
         网络设备: 读写顺序固定  读写以帧(多字节)为单位  
       
    2、驱动课程的学习方法
       UC: man
       驱动课:最好的老师是内核源码
       
       内核源码编程的原则:
           1)代码执行效率高
           2)可移植性强
       内核编码使用的C语言,是GNU C
       
       GNU C是标准C的扩展版本
       
       经典书籍:
           LDD3 (linux device driver 3th)
           精通Linux 设备驱动程序开发
           Linux内核设计与实现
    3、搭建驱动开发环境
      3.1 在PC机上装ubuntu (有的企业是提供linux 服务器)
     
      3.2 安装交叉编译工具 (裸板阶段)
     
      3.3 创建driver
          cd ~
          mkdir driver
          1)rootfs  //开发板挂载该目录作为根文件系统
             sudo cp .../_install rootfs -a
               或者
             cp /mnt/hgfs/driver/env/rootfs.tar.gz ./
             sudo tar xf rootfs.tar.gz
             
             sudo vi /etc/exports
                /home/tarena/driver/rootfs  *(rw,sync,no_root_squash)
             sudo service nfs-kernel-server restart
             
             setenv bootcmd mmc read 48000000 800 3000 ;bootm 48000000
             setenv bootargs root=/dev/nfs nfsroot=192.168.1.8:/home/tarena/driver/rootfs ip=192.168.1.6:192.168.1.8:192.168.1.1:255.255.255.0 init=/linuxrc console=ttySAC0 maxcpus=1 lcd=wy070ml tp=gslx680-linux
             saveenv
          2)kernel  //存放内核源码 该源码保证编译通过
            cp /mnt/hgfs/driver/env/kernel.tar.bz2 ./
            tar xf kernel.tar.bz2
            cd kernel/
            cp arch/arm/configs/x6818_defconfig .config
            make uImage
    4、第一个内核模块
       
       //以下两个头文件是写内核模块时必须加上的
       //存在于内核源码目录下
       #include <linux/init.h>
       #include <linux/module.h>
       
       //如果在安装模块时,希望被调用到的函数必须写成如下格式
       //__init,它是一个宏
       //被其修饰的代码 链接时放入“.init.text”段
       //放入该段的代码只要执行一次 其对应的内存空间就释放
       int __init xxx_init(void){...}
       //module_init,它是一个宏
       //被其修饰的函数在insmod时会被调用
       //一个.c文件 最多只能有一个函数被其修饰
       module_init(xxx_init);
       
       
       //希望在卸载模块时 被调用的函数必须写成如下形式
       //__exit, 它也是一个宏
       //被其修饰的函数放入“.exit.text”段
       //该段的代码一旦执行 其对应的内存空间就释放
       void __exit xxx_exit(void){...}
       module_exit(xxx_exit);
       //声明为开源程序
       //如果不声明 内核会被污染 内核中很多函数不让使用         
       MODULE_LICENSE("GPL");
       
       
       vi Makefile
          #编译为内核模块
          obj-m   += hello.o
          
          all:
            make -C $(KERNEL_PATH) M=$(PWD) modules
               -C: 进入指定目录进行编译
                   modinfo hello.ko
               M=$(PWD) :要编译的模块所在路径
               
               modules: 编译内核模块时的固定写法
    5、导出符号
      解决内核模块于模块之间的函数调用问题的
     
      应用程序:
          xxx.c
               int add(int x, int y){....}
               
          yyy.c
               extern int add(int, int);
      内核编程:
          xxx.c
               int add(int x, int y){...}
               EXPORT_SYMBOL(add);或者
               EXPORT_SYMBOL_GPL(add)
          yyy.c
               extern int add(int, int);
               
               res = add(1,2);
                        
         实验步骤:
            insmod export.ko
            lsmod
            insmod import.ko
            lsmod
            rmmod import
            lsmod
            rmmod export
                     
        
        EXPORT_SYMBOL和EXPORT_SYMBOL_GPL的区别?
           vi export.c
              EXPORT_SYMBOL_GPL(my_add);
           vi import.c
              //MODULE_LICENSE("GPL");   
      小结:
         1)如果不做符号导出,其它模块中不能使用my_add函数
         2)如果EXPORT_SYMBOL导出符号
            其它模块不管遵不遵循GPL 都可以使用
         3)如果EXPORT_SYMBOL_GPL导出符号
            只有遵循"GPL"的模块才能调用该函数
       
       编译器内置的宏
          __FILE__: 被会展开为当前文件名称
          __func__或者__FUNCTION__:当前函数名
          __LINE__: 所在行的行号
          __DATE__: 编译代码的日期
          __TIME__: 编译代码的时间

    6、printk
       作用: 用于内核态的代码调试
       语法格式:
              printk(优先级  "要输出的内容%s %d %f %c %p")
       
              printk("<1>"  "helloworld! ");
              
              //使用了默认的打印优先级
              printk("helloworld! ");
       关于printk打印优先级:
          1)linux内核将打印优先级为分为0~7 共8级
          2)值越小优先级越高
          3)可以通过打印优先级阈值控制
             哪些信息可以输出到控制台 哪些不可以输出
       8级:
           include/linux/printk.h
              #define KERN_EMERG    "<0>"
              ...
              
       打印优先级阈值:
           printk函数调用时给定的优先级高于阈值
           该打印信息可以输出到控制台
           否则不能输出到控制台
       如何查看优先级阈值:
          cat /proc/sys/kernel/printk
             7       4       1       7
             
             第一个值:优先级阈值    
             第二个值:默认优先级
                       printk("helloworld! ");     
       如何修改优先级阈值:
           1) setenv bootargs .... loglevel=数字
           2) echo 1 >proc/sys/kernel/printk       
              该修改方式掉电就无效了
              
           注意:/proc目录下的文件不能通过vi 打开访问
               读操作,
                   cat /proc/xxx
               写操作,
                   echo dfasdfa >/proc/xxx    
       想到内核自启动以来所有的prink输出的信息:
           dmesg              
             
       为什么要搞打印优先级阈值?
          控制哪些信息可以输出
          哪些不要输出      
       可以通过配置内核 将printk输出的时间相关信息干掉
          make menuconfig      
              Kernel hacking  --->   
                 [ ] Show timing information on printks
                 #调整默认输出优先级的
                 (4) Default message log level (1-7)
       printk函数的实现代码printk.c
          面试题:C语言中是支持可变参数的,例如printf
                 如何实现的?
             本质上使用了栈
             va_start

     1 #include <linux/init.h>
     2 #include <linux/module.h>
     3 
     4 MODULE_LICENSE("GPL");
     5 
     6 int __init hello_init(void)
     7 {
     8     printk("<1>"  "helloworld!
    ");
     9     return 0;
    10 }
    11 void __exit hello_exit(void)
    12 {
    13     printk("<1>"  "byebye!
    ");
    14 }
    15 module_init(hello_init);
    16 module_exit(hello_exit);
    //符号导出
    #include <linux/init.h> #include <linux/module.h> int my_add(int x, int y) { printk("<1>" "enter %s ", __func__); return x+y; } //导出符号 EXPORT_SYMBOL(my_add); MODULE_LICENSE("GPL");


    //符号导入
    #include <linux/init.h>
    #include <linux/module.h>
    
    MODULE_LICENSE("GPL");
    
    extern int my_add(int, int);
    
    static int __init import_init(void)
    {
        int res = 0;
    
        res = my_add(11,22);
    
        printk("<1>"  "in %s: %s: %d res=%d
    ",
                __FILE__, __func__, __LINE__, res);
        return 0;
    }
    static void __exit import_exit(void)
    {
        printk("<1>"  "byebye!
    ");
    }
    module_init(import_init);
    module_exit(import_exit);
  • 相关阅读:
    Java容器-引用分类与部分Map用法
    Java容器-引用数据类型排序+TreeSet、TreeMap底层实现
    JAVA容器-重点总结与深度解析
    JAVA容器-浅谈HashMap的实现原理
    JAVA容器-模拟LinkedList实现(双链表)
    JAVA容器-模拟ArrayList的底层实现
    在代码动态设置RelativeLayout的属性,比如layout_below
    java substring
    String StringBuffer StringBuilder
    线性布局通过适配器可以动态加载view
  • 原文地址:https://www.cnblogs.com/DXGG-Bond/p/11844531.html
Copyright © 2011-2022 走看看