二.字符设备驱动基础

目录

一.开启驱动开发之路

二.最简单的模块源码分析1

2.1、常用的模块操作命令

三.最简单的模块源码分析2

3.1、模块卸载

3.2、模块中常用宏

四.最简单的模块源码分析3

4.1、printk函数详解

4.2、关于驱动模块中的头文件

4.3、驱动编译的Makefile分析

五.用开发板来调试模块

5.1、设置bootcmd使开发板通过tftp下载自己建立的内核源码树编译得到的zImage

5.2、设置bootargs使开发板从nfs去挂载rootfs(内核配置记得打开使能nfs形式的rootfs)

六.字符设备驱动工作原理1

6.1、系统整体工作原理

七.字符设备驱动工作原理2

7.1、register_chrdev详解(#include )

八.字符设备驱动代码实践1

8.1、思路和框架

8.2、如何动手写驱动代码

8.3、开始动手(这些结构体变量和函数我们可以直接在内核源码中cp修改)

九.字符设备驱动代码实践2

9.1、注册驱动

9.2、驱动测试

9.3、让内核自动分配主设备号

十.应用程序如何调用驱动

10.1、驱动设备文件的创建

10.2、写应用来测试驱动

10.3、总结

十一.添加读写接口

11.1、在驱动中添加读写接口

十二.读写接口实践

12.1、完成write和read函数

12.2、读写回环测试

12.3、总结

十三.驱动中如何操控硬件

13.1、还是那个硬件

13.2、哪里不同了?

13.3、内核的虚拟地址映射方法

13.4、如何选择虚拟地址映射方法

十四.静态映射操作LED1

14.1、关于静态映射要说的

14.2、三星版本内核中的静态映射表

十五.静态映射操作LED2

15.1、参考裸机中的操作方法添加LED操作代码

15.2、实践测试

15.3、将代码移动到open和close函数中去

十六.静态映射操作LED3

十七.动态映射操作LED


 

我们的目录源码树为:/root/driver/kernel

一.开启驱动开发之路

E:\Linux\3.AppNet\4.process\2.CharDevBasic\4.1

1.1、驱动开发的准备工作

(1)正常运行linux系统的开发板。要求开发板中的linux的zImage必须是自己编译的,不能是别人编译的。

(2)内核源码树,其实就是一个经过了配置编译之后的内核源码。

(3)nfs挂载的rootfs,主机ubuntu中必须搭建一个nfs服务器。

1.2、驱动开发的步骤

(1)驱动源码编写、Makefile编写、编译

(2)insmod装载模块、测试、rmmod卸载模块

1.3、实践(参考十六.linux开发之Kernel移植——内核的配置和编译原理)

(1)copy原来提供的x210kernel.tar.bz2,找一个干净的目录(/root/driver),解压,并且配置编译。编译完成后得到了:1、内核源码树。2、编译ok的zImage

(参考十六,我们这里直接将事先编译配置好的内核复制到新的/root/driver目录下)

(2)fastboot将第1步中得到的zImage烧录到开发板中去启动(或者将zImage丢到tftp的共享目录,uboot启动时tftp下载启动),将来驱动编译好后,就可以在这个内核中去测试。因为这个zImage和内核源码树是一伙的,所以驱动安装时版本校验不会出错。

(3)测试代码(第一个设备驱动测序的引入):

Makefile代码:

#ubuntu的内核源码树,如果要编译在ubuntu中安装的模块就打开这2个

#KERN_VER = $(shell uname -r)

#KERN_DIR = /lib/modules/$(KERN_VER)/build

#KERN_DIR = /usr/src/linux-headers-3.13.0-32-generic

 

#开发板的linux内核的源码树目录

KERN_DIR = /root/driver/kernel

 

obj-m += module_test.o

 

all:

       make -C $(KERN_DIR) M=`pwd` modules

 

#一下指令将*.ko文件拷贝到nfs共享目录下

cp:

cp *.ko /root/porting_x210/rootfs/rootfs/driver_test

 

.PHONY: clean

clean:

make -C $(KERN_DIR) M=`pwd` modules clean

 

模块代码:

#include <linux/module.h>  // module_init  module_exit

#include <linux/init.h>   // __init   __exit

 

// 模块安装函数

static int __init chrdev_init(void)

{

printk(KERN_INFO "chrdev_init helloworld init\n");

//KERN_INFO:为打印级别

//https://www.cnblogs.com/mylinux/p/4028787.html

//printk("<7>" "chrdev_init helloworld init\n");

//printk("<7> chrdev_init helloworld init\n");

 

return 0;

}

 

// 模块下载函数

static void __exit chrdev_exit(void)

{

printk(KERN_INFO "chrdev_exit helloworld exit\n");

}

 

 

module_init(chrdev_init);

module_exit(chrdev_exit);

 

// MODULE_xxx这种宏作用是用来添加模块描述信息

MODULE_LICENSE("GPL"); // 描述模块的许可证

MODULE_AUTHOR("aliya"); // 描述模块的作者

MODULE_DESCRIPTION("module one test");//描述模块的介绍信息

MODULE_ALIAS("aliya wwj");   // 描述模块的别名信息

二.最简单的模块源码分析1

2.1、常用的模块操作命令

(1)lsmod(listmodule,将模块列表显示),功能是打印出当前内核中已经安装的模块列表

(2)insmod(install module,安装模块),功能是向当前内核中去安装一个模块,用法是insmod xxx.ko

(3)modinfo(module information,模块信息),功能是打印出一个内核模块的自带信息。,用法是modinfo xxx.ko

(4)rmmod(remove module,卸载模块),功能是从当前内核中卸载一个已经安装了的模块,用法是rmmod xxx(注意卸载模块时只需要输入模块名即可,不能加.ko后缀

2.2、模块的安装

(1)先lsmod再insmod xxx安装后lsmod查看系统内模块记录。实践测试标明内核会将最新安装的模块放在lsmod显示的最前面。

(2)insmod与module_init宏。模块源代码中用module_init宏声明了一个函数(在我们这个例子里是chrdev_init函数),作用就是指定chrdev_init这个函数和insmod命令绑定起来,也就是说当我们insmod module_test.ko时,insmod命令内部实际执行的操作就是帮我们调用chrdev_init函数。

照此分析,那insmod时就应该能看到c hrdev_init中使用printk打印出来的一个chrdev_init字符串,但是实际没看到。原因是ubuntu中拦截了,要怎么才能看到呢?在ubuntu中使用dmesg命令就可以看到了。

(3)模块安装时insmod内部除了帮我们调用module_init宏所声明的函数外,实际还做了一些别的事(譬如lsmod能看到多了一个模块也是insmod帮我们在内部做了记录),但是我们就不用管了。

2.3、模块的版本信息

(1)使用modinfo查看模块的版本信息

(2)内核zImage中也有一个确定的版本信息

(3)insmod时模块的vermagic必须和内核的相同,否则不能安装,报错信息为:insmod: ERROR: could not insert module module_test.ko: Invalid module format

(4)模块的版本信息是为了保证模块和内核的兼容性,是一种安全措施

(5)如何保证模块的vermagic和内核的vermagic一致?当前插入的模块xxx.ko的版本信息(version magic)与正运行的kernel的版本信息一致。说白了就是模块和内核要同出一门。

当出现这种问题时,使用# dmesg | tail 打印信息,找原因

我出现的问题也是这个提示,但原因并不是版本问题,故使用dmesg | tail  也看不出什么。 版本问题可参考解决方法:http://blog.chinaunix.net/uid-20448327-id-172345.html

后来发现是makefile中将开发板的KERN_DIR目录没有屏蔽,导致这个问题。

屏蔽开发板的KERN_DIR后,从新insmod xxx安装,打印如下,使用rmmod xxx卸载后从新make安装即可。

三.最简单的模块源码分析2

3.1、模块卸载

(1)module_exit和rmmod的对应关系

(2)lsmod查看rmmod前后系统的模块记录变化

3.2、模块中常用宏

(1)MODULE_LICENSE,模块的许可证。一般声明为GPL许可证,而且最好不要少,否则可能会出现莫名其妙的错误(譬如一些明显存在的函数提升找不到)。

(2)MODULE_AUTHOR:// 描述模块的作者

(3)MODULE_DESCRIPTION://描述模块的介绍信息

(4)MODULE_ALIAS:模块别名

3.3、函数修饰符(由内核直接调用的)

(1)__init,本质上是个宏定义,在内核源代码中就有#define __init xxxx。

这个__init的作用就是将被他修饰的函数放入.init.text段中去(本来默认情况下函数是被放入.text段中)。

整个内核中的所有的这类 __init 函数都会被链接器链接放入.init.text段中,所以所有的内核模块的__init修饰的函数其实是被统一放在一起的。内核启动时统一会加载.init.text段中的这些模块安装函数,加载完后就会把这个段给释放掉以节省内存

(2)__exit,和)__init同理。

 

 

四.最简单的模块源码分析3

4.1、printk函数详解

(1)printk就是printf的孪生兄弟,一个用于windows,一个用于内核。

(2)printk相比printf来说还多了个:打印级别的设置。printk的打印级别是用来控制printk打印的这条信息是否在终端上显示的。应用程序中的调试信息要么全部打开要么全部关闭,一般用条件编译来实现(DEBUG宏),但是在内核中,因为内核非常庞大,打印信息非常多,有时候整体调试内核时打印信息要么太多找不到想要的,要么一个没有,没法调试。所以才有了打印级别这个概念。

(3)ubuntu中这个printk的打印级别控制没法实践,ubuntu中不管你把级别怎么设置都不能直接打印出来,必须dmesg命令去查看。

4.2、关于驱动模块中的头文件

(1)驱动源代码中包含的头文件和原来应用编程程序中包含的头文件不是一回事。应用编程中包含的头文件是应用层的头文件,是应用程序的编译器带来的(譬如gcc的头文件路径在 /usr/include下,这些东西是和操作系统无关的)。

驱动源码属于内核源码的一部分,驱动源码中的头文件其实就是内核源代码目录下的include目录下的头文件。

4.3、驱动编译的Makefile分析

#开发板的linux内核的源码树目录

KERN_DIR = /root/driver/kernel

 

#这一行就表示我们要将module_test.c文件编译成一个模块

obj-m += module_test.o

 

#这个命令用来编译实际的模块, make 执行的命令, `pwd`表示我们要把 pwd 当命令去执行, M=`pwd`表示我们

进入到当前这个目录下编译完成后还能回来( 记录当前目录) 。 modules 表示内核中的目标, 然后内核中的目标负责对

我们的程序进行编译

all:

make -C $(KERN_DIR) M=`pwd` modules

arm-linux-gcc app.c -o app

 

#一下指令将*.ko文件拷贝到nfs共享目录下

cp:

cp *.ko /root/porting_x210/rootfs/rootfs/driver_test

cp app /root/porting_x210/rootfs/rootfs/driver_test

 

.PHONY: clean

clean:

make -C $(KERN_DIR) M=`pwd` modules clean

rm -rf app

(1)KERN_DIR,变量的值就是我们用来编译这个模块的内核源码树的目录

(2)obj-m += module_test.o,这一行就表示我们要将module_test.c文件编译成一个模块

(3)make -C $(KERN_DIR) M=`pwd` modules 这个命令用来实际编译模块,工作原理就是:利用make -C进入到我们指定的内核源码树目录下,然后在源码目录树下借用内核源码中定义的模块编译规则去编译这个模块,编译完成后把生成的文件还拷贝到当前目录下,完成编译。

(4)make clean ,用来清除编译痕迹

总结:模块的makefile非常简单,本身并不能完成模块的编译,而是通过make -C进入到内核源码树下借用内核源码的体系来完成模块的编译链接的。这个Makefile本身是非常模式化的,3和4部分是永远不用动的,只有1和2需要动。1是内核源码树的目录,必须根据自己的编译环境输入对应目录!

五.用开发板来调试模块

E:\Linux\3.AppNet\4.process\2.CharDevBasic\4.1

5.1、设置bootcmd使开发板通过tftp下载自己建立的内核源码树编译得到的zImage

设置好TFTP下载方式及IP地址配对等,详细参考uboot笔记第二节

在控制台设置bootcmd:: 

set bootcmd 'tftp 0x30008000 zImage;bootm 0x30008000'

并将内核生成的zImage拷贝到tftpboot目录下

重启开发板,自动下载新的zImage。

5.2、设置bootargs使开发板从nfs去挂载rootfs(内核配置记得打开使能nfs形式的rootfs)

故我们使用nfs需要设置的bootargs 如下:

setenv bootargs root=/dev/nfs nfsroot=192.168.1.141:/root/porting_x210/rootfs/rootfs ip=192.168.1.10:192.168.1.141:192.168.1.1:255.255.255.0::eth0:off  init=/linuxrc console=ttySAC2,115200

使用tftp烧录zImage后,显示如下,原因是在menuconfig没有开启对NFS的启动支持

(2)在menuconfig中配置支持nfs启动方式

按照如下方法设置make  menuconfig,

之后从新make,再次重启开发板即可,打印出如下信息,且进入终端控制台。

上面图片中有一句话 Rreeint init memory:172K 这句话就是系统启动完成后, 释放掉了__init 指定的段。

5.3、修改Makefile中的KERN_DIR使其指向开发板的内核源码树,然后从新make

5.4、将自己编译好的驱动.ko文件放入nfs共享目录下去

在makefile中已写入如下信息,直接make cp即可

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值