#include <linux/module.h>
#include <linux/init.h>
#include <linux/cdev.h>/*此头文件包含写字符设备时调用的api函数*/
#include <linux/fs.h> /*包含file_operations这个结构体中的内容*/
//#define DEVNO_MAJOR 250 /*cat /proc/devices 此命令可以看设备里面哪些主设备号没有被使用*/
struct cdev cdev;/*(1)定义一个cdev字符设备结构体*/
dev_t devno;/*32位无符号设备号:12位主设备号+20位的次设备号*/
//int devno_major = DEVNO_MAJOR;/*静态的获得设备的主设备号,提前在设备里面通过cat /proc/devices命令看没有被使用的主设备号*/
int devno_major = 0;
/*字符设备驱动主要就是实现对设备的读写等等,此结构体后续在对其需要的成员变量进行初始化*/
struct file_operations i2cfops=
{
};
static int __init i2cdriver_init(void)
{
int ret = 0;
printk("init\n");
cdev_init(&cdev, &i2cfops);/*(2)初始化这个结构体*/
printk("222\n");
if(devno_major)/*给定了主设备号*/
{
printk("111\n");
ret = register_chrdev_region(devno, 1, "hello");/*cat /proc/devices*/
if(ret < 0)
{
printk("register_chrdev_region fail\n");
}
}
else/*动态分配设备号*/
{
printk("333\n");
ret = alloc_chrdev_region(&devno, 0, 1, "hello");/*cat /proc/devices*/
printk("444 ret = %d\n", ret);
if(ret < 0)
{
printk("alloc_chrdev_region fail\n");
}
devno_major = MAJOR(devno);
printk("devno_major = %d\n", devno_major);
}
ret = cdev_add(&cdev, devno, 1);/*(3)将这个字符设备添加进linux系统中*/
printk("add ret = %d\n",ret);
return 0;
}
static void __exit i2cdriver_exit(void)
{
printk("exit\n");
cdev_del(&cdev);/*完成资源的释放,删除掉向内核添加的cdev这个结构体*/
unregister_chrdev_region(devno, 1);/*删除掉申请额设备号*/
}
module_init(i2cdriver_init);
module_exit(i2cdriver_exit);
MODULE_LICENSE("GPL");
上述源代码是在入门第一步chrdev.c的源代码基础上添加了字符设备驱动那一套。
Mafile文件的代码使用入门第一步的代码。
结果:动态分配设备号的结果
# insmod i2cdriver.ko
init
222
333
444 ret = 0
devno_major = 245
add ret = 0
# cat /proc/devices
Character devices:
...
245 hello
...
#mknod /dev/mydev c 245 0
#cd /dev
#ls
...
mydev
...
说明:
有了主设备号以后在dev目录下创建设备节点时可以自己随便起个名字,c代表字符设备,245是主设备号,0是次设备号,由于此模块中用不到次设备号就设置为0即可。
字符设备驱动三步走:(1)定义一个cdev结构体;(2)初始化这个结构体;(3)将cdev这个结构体添加到内核里面。
(2)(3)步直接是调用的库里面的api函数,在使用这些api函数时发现其入口参数需要设备号,需要调用file_operations,在去调用设备号的函数。
总之,先三步走写出字符设备驱动框架,在一步步填充使用的其他api函数。