准备:
JZ2440开发板烧写好uboot、linux、文件系统、
启动后开始运行
我们将编译好的驱动复制到nfs文件夹,开发板就挂载这个网络文件系统访问这个驱动程序。
前提:
参考https://blog.csdn.net/RadianceBlau/article/details/55259627
学习第一个linux驱动,建议使用现成的linx驱动源文件!!!
先看猪跑,再吃猪肉!!
Linux内核模块的特点:
1, 模块本身不被编译进内核镜像,能够控制内核的大小。
2, 模块可以在需要的时候中被动态加载,一旦加载完成就和内核其它部分完全一样。
下面便是linux内核模块的helloworld程序,结构十分固定。编译完成后生成hello.ko,通过insmod hello.ko进行加载,加载时输出” hello module has been mount!”,使用rmmod hello进行卸载,卸载时输出” hellomodule has been remove!”。
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Radia");
static int hello_init()
{
printk(KERN_EMERG "hello module has been mount!\n");
return 0;
}
static void hello_exit()
{
printk(KERN_EMERG "hello module has been remove!\n");
}
module_init(hello_init);
module_exit(hello_exit);
在Linux下可以通过两种方式加载驱动程序:静态加载和动态加载。
- 静态加载就是把驱动程序直接编译进内核,系统启动后可以直接调用。静态加载的缺点是调试起来比较麻烦,每次修改一个地方都要重新编译和下载内核,效率较低。若采用静态加载的驱动较多,会导致内核容量很大,浪费存储空间。
- 动态加载利用了Linux的module特性,可以在系统启动后用insmod命令添加模块(.ko),在不需要的时候用rmmod命令卸载模块,采用这种动态加载的方式便于驱动程序的调试,同时可以针对产品的功能需求,进行内核的裁剪,将不需要的驱动去除,大大减小了内核的存储容量。
在台式机上,一般采用动态加载的方式;在嵌入式产品里,可以先采用动态加载的方式进行调试,调试成功后再编译进内核。
在服务器编译驱动程序 再复制到网络文件系统给开发板
先分析makefie用户。
- makefile指定arm内核(要先编译好)的路径 /work/system/linux-2.6.22.6
- 进入内核目录,在此目录(pwd)执行make modeule命令,modeule意思是把驱动编译成模块
- obj-m += first_drv.o意思 有一个模块需要从目标文件first_drv.o中构造,构造的模块名称为first_drv.ko.
- 执行makefile就把first_drv.o 目标通过makeile 就生成first_drv.ko.文件 驱动程序编译为.ko文件
- 在arm-linux-gcc -o firstdrvtest firstdrvtest.c 生成可执行文件firstdrvtest main函数编译为可执行文件
把.ko驱动模块复制到网络文件系统,开发板通过nfs挂载网络文件系统
insmod ./firstdrvtest .ko加载驱动模块到内核
./firstdrvtest 执行程序调用驱动程序
book@book-desktop:/work/drivers_and_test/first_drv$ cat Makefile
KERN_DIR = /work/system/linux-2.6.22.6
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += first_drv.o
以下内容在开发板终端操作
内核模块和应用程序
(1)每个内核模块只注册自己以便来服务将来的请求, 并且它的初始化函数立刻终止. 换句话说, 模块初始化函数的任务是为以后调用模块的函数做准备; 好像是模块说, ” 我在这里, 这是我能做的.”模块的退出函数(例子里是 hello_exit)就在模块被卸载时调用. 它好像告诉内核, “我不再在那里了, 不要要求我做任何事了.” 每个内核模块都是这种类似于事件驱动的编程方法, 但不是所有的应用程序都是事件驱动的.
(2)事件驱动的应用程序和内核代码的退出函数不同: 一个终止的应用程序可以在释放资源方面”懒惰”, 或者完全不做清理工作, 但是模块的退出函数必须小心恢复每个由初始化函数建立的东西, 否则会保留一些东西直到系统重启.
(3) 一个模块在内核空间运行, 而应用程序在用户空间运行. 内核空间和用户空间特权级别不同,而且每个模式有它自己的内存映射–它自己的地址空间.
(4) 内核编程与传统应用程序编程方式很大不同的是并发问题. 大部分应用程序, 多线程的应用程序是一个明显的例外, 典型地是顺序运行的, 从头至尾, 不必要担心其他事情会发生而改变它们的环境. 内核代码没有运行在这样的简单世界中, 即便最简单的内核模块必须在这样的概念下编写, “很多事情可能马上发生”.
# cat /proc/devices
显示出字符设备和块设备两类设备,显示出设备的主设备好和名字。
# insmod first_drv.ko
insmod命令用于将给定的模块加载到内核中。Linux有许多功能是通过模块的方式,在需要时才载入kernel。如此可使kernel较为精简,进而提高效率,以及保有较大的弹性。这类可载入的模块,通常是设备驱动程序。例如:insmod xxx.ko
rmmod命令用于从当前运行的内核中移除指定的内核模块。执行rmmod指令,可删除不需要的模块。Linux操作系统的核心具有模块化的特性,应此在编译核心时,务须把全部的功能都放入核心。你可以将这些功能编译成一个个单独的模块,待有需要时再分别载入它们。例如:rmmod xxx.ko
# cat /proc/devices
Character devices:252 first_drv
驱动设备号
- ·系统分配(推荐)
- ·手动分配
1.1系统设备号自动分配,驱动怎么写
注册驱动的时候写0
int major;
static int first_drv_init(void)
{
major = register_chrdev(0, "first_drv", &first_drv_fops); // 注册, 告诉内核
//这里三个参数,0表示系统自动分配主设备号,第二个是任意名字,第三个是申请的结构体实体数据类型是static struct //file_operations
}
static void first_drv_exit(void)
{
unregister_chrdev(major, "first_drv"); // 卸载
}
1.2系统设备号手动分配,驱动怎么写
查看设备号的空缺项,写入这个驱动
major = register_chrdev(空缺的主设备号, "first_drv", &first_drv_fops); // 注册, 告诉内核
应用程序打开一个设备文件原理
fd = open("/dev/xyz", O_RDWR);这个/dev/xyz是怎么来的?
- 手工建立 mknod /dev/xyz c 主设备号 次设备号
- 自动创建 dev机制 根据系统信息创建设备节点(推荐)
如何自动创建设备节点信息?
驱动个里面定义两个变量
static struct class *firstdrv_class;
static struct class_device *firstdrv_class_dev;
······注册驱动后,建立一个类, 类下创建一个设备/dev/xyz
`````注意这类这相关要声明license才能用 ,写 MODULE_LICENSE("GPL");
这样就在/dev下自动生成xyz这个设备,
在/sys目录下class自动生成相关信息
# ls /dev/xyz
/dev/xyz
# ls /dev/xyz
/dev/xyz
#
#
# cd /sys/class/ firstdrv/ xyz
# ls
dev subsystem uevent
#
# cat dev
252:0
static struct class *firstdrv_class;
static struct class_device *firstdrv_class_dev;
int major;
static int first_drv_init(void)
{
major = register_chrdev(0, "first_drv", &first_drv_fops); // 注册, 告诉内核
firstdrv_class = class_create(THIS_MODULE, "firstdrv");
firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */
//ioremap函数 将物理地址映射为虚拟地址
gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
gpfdat = gpfcon + 1;
return 0;
}
static void first_drv_exit(void)
{
unregister_chrdev(major, "first_drv"); // 卸载
class_device_unregister(firstdrv_class_dev);
class_destroy(firstdrv_class);
iounmap(gpfcon);
}
MODULE_LICENSE("GPL");
查看dev下自动生成的设备xyz
# ls /dev/xyz
/dev/xyz
卸载驱动
# rmmod first_drv
卸载后自动清除
# ls /dev/xyz
ls: /dev/xyz: No such file or directory
再加载驱动
# cd /mnt/
# insmod ./first_drv.ko
加载驱动后有自动生成
# ls /dev/xyz
/dev/xyz
查看和卸载驱动
# lsmod
# rmmod first_drv
cat /proc/devices查看注册进内核的驱动程序。
在当前文件目录下进行make modules,生成驱动模块led_drv.ko,将他放到单板根文件系统的/lib/modules/2.622.6/目录下,然后“insmod led_drv.ko”命令进行加装进内核,“rmmod led_drv.ko”为卸载命令。