Driver:搭建linux驱动开发环境、内核驱动开发基础、导出符号、打印优先级

【什么是驱动程序】
    uart_init
    uart_puts
    uart_gets
    这就是裸板上的串口驱动

    linux中将串口驱动封装了一套框架
    其中实现了大量硬件无关的代码
    只需要编程实现硬件相关的代码

    linux下完成驱动相关的开发,需要三方面的知识:
    1)硬件相关的知识
        电路原理图
        芯片的数据手册
        RS232通信协议
        I2C通信协议
    2)内核的知识
        内核驱动属于内核的一部分,它运行在内核态
        内存管理
        解决竞争状态
        ...
    3)驱动框架的知识
        内核中已经实现了大量的硬件驱动,完成了驱动的框架编程,只需要驱动工程师编程实现硬件相关的代码就可以了。

1、搭建linux驱动开发环境
1.1 安装交叉编译工具
    '安装包 - 解压缩
   // 位置:~/workdir/arm-linux-gcc/arm-cortex_a9-eabi-4.7-eglibc-2.18.tar.gz
    'PATH修改
    // :/opt/arm-cortex_a9-eabi-4.7-eglibc-2.18/bin
1.2 拿到uboot、编译、烧写进开发板

1.3 拿到kernel、编译、烧写到开发板

1.4 制作根文件系统,通过nfs方式让开发板可以加载该文件系统

    $:'sudo vi /etc/exports 
       /home/tarena/driver/rootfs  *(rw,sync,no_root_squash)
    $:'sudo /etc/init.d/nfs-kernel-server restart

    #:'setenv bootcmd mmc read 0x48000000 0x800 0x3000 \; bootm 0x48000000
    #:'setenv bootargs root=/dev/nfs nfsroot=192.168.1.8:/home/tarena/jy/driver/rootfs ip=192.168.1.6:192.168.1.8:192.168.1.1:255.255.255.0 lcd=vs070cxn tp=gslx680-linux console=ttySAC0,115200 maxcpus=1 init=/linuxrc 
    #:'saveenv
    driver/kernel:编译内核源码 // 确保编译通过
    driver/rootfs:根文件系统数据(能安装卸载模块,能远程登录开发板,让开发板可以挂载该文件系统)

2、内核驱动开发的基本知识
    如何学习驱动编程?
    1)最好的老师是 内核源码
        man open // 以前用法

        要去使用某个陌生函数:
            '查看内核中关于该函数的定义,注释;
            '查看内核中其他模块是如何使用该函数的。

    2)经典的书籍
        内核开发:《linux内核设计与实现 - 第3版》.pdf
        驱动开发:
            LDD3 - 《linux设备驱动 - 第3版》.chm  // 概念性的,没什么错误。
            《精通linux设备驱动程序开发》.pdf      // 比较细的。
    
    关于linux内核:
        1)linux中使用的函数都是自身实现的,它肯定不会调用c库中的函数
            strcmp memcpy
        2)linux中绝大部分代码是用GNU c语言完成的,少部分是用汇编实现的
            GNU c可以理解为标准C的扩展版。
            // 可变参数的实现原理,后补查询了解。
    
    内核态编程需要注意的问题:
        1)内核态不能做浮点数运算
        2)用户空间的每一个进程都有独立的0~3G
           所有的进程共享同一个内核
           内核使用的地址空间是3G~4G
        3)'每个线程有独立的栈空间
           用户态的栈
           内核态的栈:当产生了系统调用,进入内核时;
                      该栈空间非常小,一般只有一个内存页(4K)

3、hello,world驱动模块
    cd driver
    mkdir day01/01 -p
    cd day01/01
    vi helloworld.c
    
    'make -C /home/tarena/jy/driver/kernel M=$(PWD) modules
      -C:指定进入哪个目录进行编译
            helloworld.c
      M:  M=当前路径
          指定要编译的模块文件所在的路径
      modules: 是编译内核模块的固定写法

>>>>模块可以安装卸载,看不到打印信息的话,需要配置此项:
    $:'make menuconfig
        Kernel hacking  --->
            [ 4 ]Default message log level (1-7)  '修改为4,默认打印优先级

    $:'make uImage -j4
    // 烧写新的内核到开发板,再次安装、卸载模块。

/** 代码演示 - helloworld.c **/
#include <linux/init.h>
#include <linux/module.h>

MODULE_LICENSE ("GPL"); // 声明为开源软件
int __init helloworld_init (void) {
    printk ("func: %s, line: %d\n", __func__, __LINE__);
    return 0;
}

void __exit helloworld_exit (void) {
    printk ("func: %s, line: %d\n", __func__, __LINE__);
    return 0;
}

module_init (helloworld_init);
module_exit (helloworld_exit);
/** 代码演示 - Makefile **/
obj-m   += helloworld.o

all:
    make -C /home/tarena/jy/driver/kernel M=$(PWD) modules
    cp *.ko ../../rootfs -v

clean:
    make -C /home/tarena/jy/driver/kernel M=$(PWD) clean

    【规律】
    安装模块时 helloworld_init 被调用;
    卸载模块时 helloworld_exit 被调用。

   helloworld: module license 'unspecified' taints kernel
    模块许可未指定污染了内核。
    是由于 helloworld.c没有声明 license
    GNU linux
        MODULE_LICENSE ("GPL");// 声明为开源软件【必须添加的项】
        MODULE_DESCRIPTION("Intel Footbridge (21285) serial driver");
        // 描述
        MODULE_AUTHOR(SERIAL_21285_MAJOR, SERIAL_21285_MINOR);
        // 作者
        MODULE_VERSION(TW_DRIVER_VERSION);
        // 版本
    $:'modinfo helloworld.ko

3.1 关于helloworld.c文件
    #include <linux/init.h>
    #include <linux/module.h>

    // 以上两个头文件位于内核源码在 kernel/include/linux/ 下。

    MODULE_LICENSE ("GPL"); 
    // 声明为开源软件【必须添加的项】,GPL:版权,GNU copyleft

    如果该模块在'安装'时希望该模块中的某个函数被调用:
    int __init xxx (void) { // 函数必须是int返回值,参数为void
        /* ... */
        return 0;
    }
    module_init (xxx); // 函数必须被宏module_init修饰,注册自己提供的服务,加载函数执行完毕,模块依旧存在。
    如果该模块在'卸载'时希望该模块中的某个函数被调用:
    int __exit yyy (void) { // 函数必须是int返回值,参数为void
        /* ... */
        return 0;
    }
    module_exit (yyy); 
    // 函数必须被宏module_exit修饰,内核调用,模块消失。
    __init:
        #define __init	__section(.init.text)
    __exit:
        #define __exit	__section(.exit.text)
    // __init 和 __exit修饰的函数在执行一次后对应的内存空间就会释放。

    linux内核中追求的是:
        更高的执行效率、更好的可移植性、可扩展性。

4、 导出符号
"a.c"
        int func (int a, int b) {
            return a + b;
        }
        EXPORT_SYMBOL (func);    // 其他模块使用a.c的函数的话,需要加导出符号
        EXPORT_SYMBOL_GPL (func);
        // EXPORT_SYMBOL和EXPORT_SYMBOL_GPL的区别:
        EXPORT_SYMBOL 导出符号可以被任意引用;
        EXPORT_SYMBOL_GPL 导出的符号只能被遵循"GPL"的模块所引用。
"a.h"
        extern int func (int, int);
"b.c"
        extern int func (int, int);
        #include "a.h"
        func (10, 20);

/** 代码演示 - export.h **/
#ifndef _EXPORT_H
#define _EXPORT_H

extern int my_func (int, int);

#endif //_EXPORT_H
/** 代码演示 - export.c **/
include <linux/init.h>
#include <linux/module.h>

MODULE_LICENSE ("GPL");

int my_func (int a, int b) {
return a + b;
}

// 添加导出符号

EXPORT_SYMBOL (my_func);
/** 代码演示 - import.c **/
#include <linux/init.h>
#include <linux/module.h>
#include "export.h"

static int __init import_init (void) {
int res = 0;
res = my_func (10, 20);
printk ("%s: res = %d\n", __func__, res);
return 0;
}

static void __exit import_exit (void) {
printk ("enter %s\n", __func__);
}

module_init (import_init);
module_exit (import_exit);
MODULE_LICENSE ("GPL");
/** 代码演示 - Makefile **/
obj-m	+= export.o
obj-m	+= import.o

all:
make -C /home/tarena/jy/driver/kernel M=$(PWD) modules
cp *.ko ../../rootfs -v

clean:
make -C /home/tarena/jy/driver/kernel M=$(PWD) clean

~ #:'lsmod
import   817    0       -           Live 0xbf00c000 (O)
export   663    1       import,     Live 0xbf008000 (O)
模块名   大小  引用次数  被引用函数   存在地址


'安装卸载模块在有调用函数的时候,有其先后顺序。
#:' insmod export.ko
#:' insmod import.ko
#:'rmmod import
#:'rmmod export

5、打印优先级控制

    printf 和 printk

   printk ("<优先级>" "%s: helloworld!\n", __FILE__);// 中间是空格。
    与printf相比多了打印优先级的控制。
   printk ("helloworld!\n");// 使用的是默认的优先级
    函数的实现原型在内核的printk.c文件中:

/** 代码演示 - prink.c **/
asmlinkage int printk(const char *fmt, ...)
{
va_list args;
int r;

#ifdef CONFIG_KGDB_KDB
if (unlikely(kdb_trap_printk)) {
va_start(args, fmt);
r = vkdb_printf(fmt, args);
va_end(args);
return r;
}
#endif
va_start(args, fmt);
r = vprintk(fmt, args);
va_end(args);

return r;
}

linux内核的打印优先级分为 8 级。
#define KERN_EMERG	   "<0>"	/* system is unusable	*/
#define KERN_ALERT	   "<1>"	/* action must be taken immediately	*/
#define KERN_CRIT	   "<2>"	/* critical conditions	*/
#define KERN_ERR	   "<3>"	/* error conditions	*/
#define KERN_WARNING	"<4>"	/* warning conditions	*/
#define KERN_NOTICE	   "<5>"	/* normal but significant condition	*/
#define KERN_INFO	   "<6>"	/* informational	*/
#define KERN_DEBUG	   "<7>"	/* debug-level messages	*/

验证8个优先级的具体效果:
/** 代码演示 -  **/
#include <linux/init.h>
#include <linux/module.h>

MODULE_LICENSE ("GPL");

int __init printkall_init (void) {
printk ("<0>" "level 0\n"); 
printk ("<1>" "level 1\n"); 
printk ("<2>" "level 2\n"); 
printk ("<3>" "level 3\n"); 
printk ("<4>" "level 4\n"); 
printk ("<5>" "level 5\n"); 
printk ("<6>" "level 6\n"); 
printk ("<7>" "level 7\n"); 
return 0;
} 

void __exit printkall_exit (void) {
printk ("<0>" "level 0\n"); 
printk ("<1>" "level 1\n"); 
printk ("<2>" "level 2\n"); 
printk ("<3>" "level 3\n"); 
printk ("<4>" "level 4\n"); 
printk ("<5>" "level 5\n"); 
printk ("<6>" "level 6\n"); 
printk ("<7>" "level 7\n"); 
}

module_init (printkall_init);
module_exit (printkall_exit);
#:'cat /proc/sys/kernel/printk
7       4       1       7
第一项:打印优先级的阈值,printk中指定的优先级高于该值,可以正常打印;
        优先级低于该值,打印信息被屏蔽。
第二项:默认打印优先级
        printk ("helloworld!\n");// 没加打印优先级的时候,是默认值 4

'修改打印控制级别
方式一:
    #:'echo 4 >/proc/sys/kernel/printk
    // 该方式不能影响内核启动时打印信息的输出
方式二:
    #:'setenv bootargs ... loglevel=数字
       setenv bootargs root=/dev/nfs nfsroot=192.168.1.8:/home/tarena/jy/driver/rootfs ip=192.168.1.6:192.168.1.8:192.168.1.1:255.255.255.0 lcd=vs070cxn tp=gslx680-linux console=ttySAC0,115200 maxcpus=1 loglevel=4
    #:'saveenv
    // 修改uboot的环境变量
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

姜源Jerry

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值