本文分析另一个简单的字符设备框架,重点在框架上。
最基本的字符设备驱动:
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
/*file_operations的具体实现*/
static int hello_open(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t hello_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
return 0;
}
static ssize_t hello_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
return 0;
}
static int hello_release(struct inode *inode, struct file *filp)
{
return 0;
}
static struct file_operations hello_fops = {
.owner = THIS_MODULE,
.open = hello_open,
.read = hello_read,
.write = hello_write,
.release = hello_release,
};
static int __init hello_init(void)
{
int ret = 0;
ret = register_chrdev(200, "hello", &hello_fops);
if(ret < 0)
{
printk("register chardev fail.\n");
return -1;
}
else
{
printk("register chardev success.\n");
return 0;
}
}
static void __exit hello_exit(void)
{
unregister_chrdev(200, "hello");
printk("hello exit!\r\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
一、字符驱动的组成部分
一个字符设备的驱动要有以下及部分组成。
(1)入口和出口函数module_init和module_exit。
(2)file_operations结构体变量以及open、read、write、release的具体实现。
(3)MODULE_LICENSE("GPL")。
二、module_init和module_exit
module_init(hello_init);
module_exit(hello_exit);
作为驱动的入口和出口,在insmod加载模块时,会调用module_int宏函数,从而调用hello_init(自己写的init函数)。在rmmod卸载模块时,会调用module_exit宏函数,从而调用hello_exit(自己写的init函数)。
2.1module_init和module_exit
insmod加载模块时,调用到自己的init函数,那init函数需要完成哪些任务呢?
作为init函数,需要完成一些初始化任务对于最基本,通用的驱动程序来说,需要注册设备号。
同理exit函数里面也应该有设备的注销。
2.1.1设备号
每一个驱动程序要注册到内核中,都需要一个设备号来进行管理。设备号分为主设备号和此设备号。主设备号代表驱动的类型,次设备号代表第几个设备。比如开发板上可能有几个LED,他们都需要有一个驱动的话,因为操作的寄存器不同,驱动也有所差异。他们可以主设备号一样,但是次设备号不同,他们都是LED驱动,只不过不是同一个LED驱动。
2.1.2重要结构体
在讲分配设备号之前,还需要了解几个重要的结构体。参考飞腾实训课。
2.1.2分配设备号
设备号的分配有两种分配方式:静态分配和动态分配。
2.1.2.1静态分配设备号register_chrdev
#include <linux/fs.h>
static inline int register_chrdev(unsigned int major,
const char *name, const struct file_operations *fops)
major:主设备号 |
|
name:名字 |
|
fops:操作接口 |
|
返回值 |
|
注销设备号:
#include <linux/fs.h>
static inline void unregister_chrdev(unsigned int major, const char *name)
major:设备号 |
|
name:名字 |
|
2.1.2.2动态分配设备号
利用静态分配有两个缺点:
(1)需要事先知道设备号。
(2)静态注册时会占用所有的次设备号。
为了解决这些问题,有更好的办法进行注册。
(1)如果设备号已知,解决问题2。
//注册
dev_t devid;
devid = MKDEV(major, 0);
register_chrdev_region(devid, 1, "test");
//注销
unregister_chrdev_region(devid, 1);
cdev_del(&testcdev);
(2)如果设备号未知,解决问题1,2。
dev_t devid;
struct cdev testcdev;
//注册
alloc_chrdev_region(&devid, 0, 1, "test"); //申请设备号给devid
cdev_init(&testcdev, &test_fops);//将fops和cdev绑定
cdev_add(&testcdev, devid, 1);//将dev和cdev绑定
//注销
unregister_chrdev_region(devid, 1);
cdev_del(&testcdev);
结构体cdev里面有两项重要的结构体dev_t和file_operations。
2.2file_operations
static struct file_operations hello_fops = {
.owner = THIS_MODULE,
.open = hello_open,
.read = hello_read,
.write = hello_write,
.release = hello_release,
};
利用函数指针的方式可以实现面向对象。