以下内容源于朱有鹏嵌入式课程的学习,如有侵权请告知删除。
参考博客:linux驱动开发(一) - biaohc - 博客园
一、驱动源代码示例
/********module_test.c代码*********/#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>//这里手动定义主设备号,之前必须确认200没有被使用,查看方法:cat /proc/devices。
//比较好的方法是内核自动分配。
#define MYMAJOR 200
#define MYNAME "testchar"static int test_chrdev_open(struct inode *inode, struct file *file)
{// 这个函数中真正应该放置的是打开这个设备的硬件操作代码部分// 但是现在暂时我们写不了这么多,所以用一个printk打印个信息来做代表。printk(KERN_INFO "test_chrdev_open\n");return 0;
}static int test_chrdev_release(struct inode *inode, struct file *file)
{printk(KERN_INFO "test_chrdev_release\n");return 0;
}// 自定义一个file_operations结构体变量,并且去填充.
//这里的.只是结构体变量初始化的一种方法而已。
//file_operations是内核中已经定义好的结构体。
static const struct file_operations test_fops = {.owner = THIS_MODULE, // 惯例,直接写即可.open = test_chrdev_open, // 将来应用open打开这个设备时// 实际调用的就是这个.open对应的函数.release = test_chrdev_release,
};// 模块安装函数
static int __init chrdev_init(void)
{ int ret = -1;printk(KERN_INFO "chrdev_init helloworld init\n");// 在module_init宏调用的函数中去注册字符设备驱动ret = register_chrdev(MYMAJOR, MYNAME, &test_fops);if (ret){printk(KERN_ERR "register_chrdev fail\n");return -EINVAL;}printk(KERN_INFO "register_chrdev success...\n");return 0;
}// 模块卸载函数
static void __exit chrdev_exit(void)
{printk(KERN_INFO "chrdev_exit helloworld exit\n");// 在module_exit宏调用的函数中去注销字符设备驱动unregister_chrdev(MYMAJOR, MYNAME);}module_init(chrdev_init);
module_exit(chrdev_exit);// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("aston"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("alias xxx"); // 描述模块的别名信息
二、解释说明
1、常用的模块操作命令
lsmod命令(list module,将模块列表显示)
功能是打印出当前内核中已经安装的模块列表。
insmod命令(install module,安装模块)
功能是向当前内核中安装一个模块,用法是insmod xxx.ko。
modinfo(module information,模块信息)
功能是打印出一个内核模块的自带信息,用法是modinfo xxx.ko。
rmmod(remove module,卸载模块)
功能是从当前内核中卸载一个已经安装的模块,用法是rmmod xxx(不加.ko后缀)。
2、模块的安装与卸载
(1)模块的安装:insmod与module_init宏
执行insmod命令时,insmod内部实际执行的是使用module_init宏所声明的函数。
比如insmod module_test.ko时,insmod命令内部实际执行的操作是调用chrdev_init函数
(2)模块的卸载:rmmod和module_exit宏
和上面阐述的是同一个道理,可以使用lsmod命令查看rmmod前后系统的模块记录变化。
3、模块的版本信息
(1)使用modinfo命令查看模块的版本信息
root@ubuntu:/home/xjh/iot/embedded_basic/rootfs/tmp# modinfo module_test.ko filename: /home/xjh/iot/embedded_basic/rootfs/tmp/module_test.ko alias: alias xxx description: module test author: aston license: GPL depends: //这是依赖,比如usb-wifi的驱动依赖usb的驱动 vermagic: 2.6.35.7 preempt mod_unload ARMv7 //这是版本号 root@ubuntu:/home/xjh/iot/embedded_basic/rootfs/tmp#
(2)驱动的版本信息与内核的版本信息要一致
使用insmod命令安装驱动模块时,驱动模块的版本信息要与内核版本信息一致。这是一种安全措施,目的是为了保证模块和内核的兼容性。如果不一致,将报错:
insmod: ERROR: could not insert module module_test.ko: Invalid module format;
如何保持一致?确保某个内核源码树,既用来生成用来烧录的内核镜像,也用来编译驱动模块。
4、模块中常用宏
MODULE_xxx,这些宏用来添加驱动模块描述信息。
(1)MODULE_LICENSE,模块的许可证。
一般声明为GPL许可证,缺少可能会出现莫名其妙的错误(一些明显存在的函数提升找不到)。
(2)MODULE_AUTHOR,编写作者。
(3)MODULE_DESCRIPTION,模块描述。
(4)MODULE_ALIAS,别名信息。
5、函数修饰符__init、__exit
(1)函数修饰符__init
1)本质
__init 本质上是宏定义,在内核源代码中有#define __init xxxx。
作用
它的作用,是将所修饰的函数放入.init.text段(默认情况下函数被放入.text段中)。
使用这个修饰符有什么好处呢?
被__init 所修饰的函数,都被链接器链接放入.init.text段中。内核启动时会统一加载.init.text段中的这些模块安装函数,加载完后就会把这个段给释放掉以节省内存(因为安装完后这些函数就没用了)。
补充说明
下划线越多,越深入内核深处。
(2)函数修饰符__exit
和__init类似的意义,不赘述。
6、printk函数详解
(1)printk函数作用
在内核源码中用来打印信息的函数。
(2)printk和printf的差别
printf是C库函数,在应用层编程中使用,不能在linux内核源代码中使用;
printk是内核源码中的一个普通函数,只能在内核源码范围内使用,不能在应用编程中使用。
(3)printk可以设置打印级别
1)打印级别的意义
应用程序中的调试信息要么全部打开要么全部关闭,一般用条件编译(DEBUG宏)来实现;但是内核非常庞大,打印信息非常多,因此需要设置打印级别。printk的打印级别,可以用来控制printk打印的这条信息是否在终端上显示的。
2)打印级别
3)查看打印级别
可以使用“cat /proc/sys/kernel/printk”查看打印级别。
4)设置打印级别
可以用“echo 4 /proc/sys/kernel/printk”设置打印级别为4。
5)命令行设置打印信息级别
在命令行中也可以设置打印信息级别,级别值为0-7。在执行printk的时候,对比printk中的打印级别与命令行中设置的打印级别,发现printk小于命令行设置级别的信息会被放行打印出来,大于的就被拦截。但ubuntu中不管如何设置级别,都不能直接打印出来,必须使用dmesg命令去查看。
7、关于驱动模块中的头文件
驱动源代码中包含的头文件,和应用程序中包含的头文件不同。
应用程序所包含的头文件,是应用层的头文件,一般是编译器带来的,与操作系统无关。比如 gcc 的头文件路径在 /usr/include下。
驱动源码所包含的头文件,是内核源代码include目录下的头文件,属于内核源码。