开发环境的搭建,符合导出,打印优先级阈值

成长共勉 2019-11-12

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!\n");
          
          //使用了默认的打印优先级
          printk("helloworld!\n");
   关于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!\n");     
   如何修改优先级阈值:
       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

#include <linux/init.h>
#include <linux/module.h>

MODULE_LICENSE("GPL");

int __init hello_init(void)
{
    printk("<1>"  "helloworld!\n");
    return 0;
}
void __exit hello_exit(void)
{
    printk("<1>"  "byebye!\n");
}
module_init(hello_init);
module_exit(hello_exit);
//符号导出#include <linux/init.h>
#include <linux/module.h>

int my_add(int x, int y)
{
    printk("<1>"  "enter %s\n", __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\n",
            __FILE__, __func__, __LINE__, res);
    return 0;
}
static void __exit import_exit(void)
{
    printk("<1>"  "byebye!\n");
}
module_init(import_init);
module_exit(import_exit);

相关推荐

qscool / 0评论 2020-01-24