-
驱动之字符设备注册
目前尚不是最终版本,还望有心人自己学习的时候,把自己整合的知识点相关的答案也好问题也好,或者实践过程中的一些操作截图,再或者其他的一些想要分享材料发给笔者邮箱:uestc_ganlin@163.com,我们一起完善这篇博客!笔者写这篇博客的时候已经工作第四个年头了,目前是在整理之前有过的学习资料,仅作为笔记,供同志们参考!短时间内可能不会去全部完善。
-
思路和框架
目的:给空模块添加驱动壳子;
核心工作量:file_operations及其元素填充、注册驱动。
-
如何动手写驱动代码
脑海里先有框架,知道自己要干嘛;
细节代码不需要一个字一个字敲,可以到内核中去寻找参考代码复制过来改;
写下的所有代码必须心里清楚明白,不能似懂非懂。
-
开始动手
先定义file_operations结构体变量;
// 自定义一个file_operations结构体变量,并且去填充
static const struct file_operations test_fops = {
// 惯例,直接写即可
.owner = THIS_MODULE,
// 将来应用open打开这个设备时实际调用的
// 就是这个.open对应的函数
.open = test_chrdev_open,
.release = test_chrdev_release,
};
open和close函数原型确定、内容填充;
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;
}
完整的相关代码见下述文件:
Makefile
# ubuntu的内核源码树,如果要编译在ubuntu中安装的模块就打开这2个
# KERN_VER = $(shell uname -r)
# KERN_DIR = /lib/modules/$(KERN_VER)/build
# 开发板的linux内核的源码树目录
KERN_DIR = /root/qt/kernel
obj-m += module_test.o
all:
make -C $(KERN_DIR) M=`pwd` modules
# arm-none-linux-gnueabi-gcc app.c -o app
cp:
cp *.ko /root/removal/rootfs/root/driver_test
# cp app /root/removal/rootfs/root/driver_test
.PHONY: clean
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
module_test.c
// 为了module_init,module_exit相关的,加入下面头文件
#include <linux/module.h>
// 为了__init,__exit相关的,加入下面头文件
#include <linux/init.h>
#include <linux/fs.h>
#define MYMAJOR 250
#define MYNAME "test_chrdev"
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结构体变量,并且去填充
static const struct file_operations test_fops = {
// 惯例,直接写即可
.owner = THIS_MODULE,
// 将来应用open打开这个设备时实际调用的
// 就是这个.open对应的函数
.open = test_chrdev_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");
-
注册驱动
主设备号的选择?
返回值的检测?
-
驱动测试
编译等 make && make cp?
insmod并且查看设备注册的现象?
rmmod并且查看设备注销的现象?
-
让内核自动分配主设备号
为什么要让内核自动分配?
如何实现?
int mymajor;
// major传0进去表示要让内核帮我们自动分配一个合适的空白的没被使用的主设备号
// 内核如果成功分配就会返回分配的主设备号;如果分配失败会返回负数
mymajor = register_chrdev(0, MYNAME, &test_fops);
if (mymajor < 0)
{
printk(KERN_ERR "register_chrdev fail\n");
return -EINVAL;
}
printk(KERN_INFO "register_chrdev success... mymajor = %d.\n", mymajor);
测试?
实现的完整代码见下述文件:
module_test.c
// 为了module_init,module_exit相关的,加入下面头文件
#include <linux/module.h>
// 为了__init,__exit相关的,加入下面头文件
#include <linux/init.h>
#include <linux/fs.h>
#define MYMAJOR 250
#define MYNAME "test_chrdev"
int mymajor;
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结构体变量,并且去填充
static const struct file_operations test_fops = {
// 惯例,直接写即可
.owner = THIS_MODULE,
// 将来应用open打开这个设备时实际调用的
// 就是这个.open对应的函数
.open = test_chrdev_open,
.release = test_chrdev_release,
};
// 模块安装函数
static int __init chrdev_init(void)
{
printk(KERN_INFO "chrdev_init helloworld init\n");
// 在module_init宏调用的函数中去注册字符设备驱动
// major传0进去表示要让内核帮我们自动分配一个合适的空白的没被使用的主设备号
// 内核如果成功分配就会返回分配的主设备号;如果分配失败会返回负数
mymajor = register_chrdev(0, MYNAME, &test_fops);
if (mymajor < 0)
{
printk(KERN_ERR "register_chrdev fail\n");
return -EINVAL;
}
printk(KERN_INFO "register_chrdev success... mymajor = %d.\n", mymajor);
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");