Linux驱动学习笔记(2)----字符型设备驱动基本框架
来源:互联网 发布:最新avtaobao域名 编辑:程序博客网 时间:2024/05/27 01:32
本文介绍字符型设备驱动的基本框架。
一个字符型设备驱动基本框架主要包括:
1. 设备结构体、全局变量的定义
2. 实现设备模块加载、卸载函数
3. 实现 fops 中操作设备相关的函数
1、结构体、全局变量定义:
a. 通常会定义一个设备结构体:
struct xxx_dev{
struct cdev cdev; //字符设备结构体,必须要有
/* 以下均为驱动中可能需要用到的变量,如缓存数组、信号量、等待队列头等 */
unsignedint current_len; //当前fifo中的数据长度
unsignedchar mem[GLOBALFIFO_SIZE]; //全局内存
struct semaphore sem; //信号量,并发访问用到
wait_queue_head_t r_wait; //用于阻塞读
wait_queue_head_t w_wait; //用于阻塞写
struct fasync_struct *async_queue; //异步结构体指针,用于读
};
b. 然后实例化一个设备结构体指针:
struct xxx_dev *xxx_devp;
通常该结构体指针会在 xxx_open() 函数中赋给 filp->private_data,这样,在read、write、ioctl等函数中就可以直接通过filp->private_data获取设备结构体。
c. 实例化文件操作结果体:
static const struct file_operations xxx_fops={
.owner = THIS_MODULE,
.read = xxx_read,
.write = xxx_write,
.open = xxx_open,
.release = xxx_release,
.poll = xxx_poll,
.fasync = xxx_fasync,
};
该结构体中的成员很多,但通常需要实现的只有read、write、open、release、ioctl,而poll用于非阻塞访问的查询操作,fasync用于异步通知。
这些函数在应用程序调用响应的系统调用时被调用,系统调用相当于应用程序与内核的接口,而驱动中的这些函数即是内核与硬件之间的接口。 xxx_fops 是在驱动模块装载时通过 cdev_init() 函数与 xxx_dev->cdev 建立起联系的。
d. 主设备号:
static int xxx_major = XXX_MAJOR;
一般来说,XXX_MAJOR定义为0,而变量 xxx_major 会作为模块参数:
module_param(xxx_major,int, S_IRUGO);
这样做是可以让用户指定主设备号:
insmod xxx.ko xxx_major=249
而如果用户没有指定,那么默认为0,在初始化时,xxx_init()函数中会让系统分配一个还没有被使用的主设备号,以避免主设备号冲突的情况。
2、驱动模块装载、卸载函数
a. 模块装载函数 xxx_init()
模块加载函数主要要做以下几件事情:
1) 注册设备号:
32位的设备号由12位的主设备号和20位的次设备号构成。
主设备号可以指定,也可以由系统分配,为了防止主设备号冲突,由系统分配是个不错的办法。
此设备号通常从0开始。
2) 为设备结构体申请内存空间:
xxx_devp= kmalloc(sizeof(struct xxx_dev), GFP_KERNEL);
3) 初始化设备结构体中的字符设备结构体cdev :
首先,调用 cdev_init() 将 xxx_fops 与 xxx_devp->cdev 绑定。(cdev结构体中有个ops成员, 实际上就是将该成员指向xxx_fops)
其次,xxx_devp->cdev.owner= THIS_MODULE;
最后,调用 cdev_add()向系统添加一个cdev,完成字符型设备的注册
4) 初始化一些需要用到的其他变量,如信号量、等待队列头等
5) 增加udev支持,linux2.6之后引入了udev 设备文件系统,如果不加该语句,insmod之后,还需要通过mknod创建设备文件节点,而在驱动程序添加了udev支持后,有udev的linux系统会自动创建设备文件节点。
static int __init xxx_init(void)
{
int ret;
/* 由主设备号和次设备号生成设备号 */
dev_t devno = MKDEV(xxx_major,0);
/* 注册设备号 */
if(xxx_major>0)
/* 用户设置好了主设备号,1表示注册1个设备,
"xxx" 是设备名称,在cat /proc/devices时可以看到 */
ret = register_chrdev_region(devno,1,"xxx");
else {
/* 用户没有设置主设备号,由系统自动分配主设备号,
0表示次设备号从0开始, 1表示注册1个设备, 返回设备号 */
ret = alloc_chrdev_region(&devno,0,1,"xxx");
/* 由返回的设备号计算主设备号 */
xxx_major = MAJOR(devno);
}
/* 注册失败,返回错误代码*/
if (ret<0)
return ret;
/* 为设备结构体申请内存空间 */
xxx_devp = kmalloc(sizeof(struct xxx_dev), GFP_KERNEL);
/* 申请失败,返回错误代码,并且需要注销之前注册成功的设备号 */
if (xxx_devp==NULL){
ret = -ENOMEM;
goto fail_malloc;
}
memset(xxx_devp,0,sizeof(struct xxx_dev));
/* 初始化信号量和等待队列头 */
sema_init(&xxx_devp->sem,1);//init_MUTEX(&xxx_devp->sem);
init_waitqueue_head(&xxx_devp->r_wait);
init_waitqueue_head(&xxx_devp->w_wait);
/* 初始化字符型设备结构体cdev */
ret = xxx_setup_cdev(xxx_devp,0);
if (ret<0)
gotofail_add_cdev;
return 0;
fail_add_cdev:
kfree(xxx_devp);
fail_malloc:
unregister_chrdev_region(devno,1);
return ret;
}
static int xxx_setup_cdev(struct xxx_dev*dev,int index)
{
int err, devno= MKDEV(xxx_major, index);
/* 初始化cdev */
cdev_init(&dev->cdev,&xxx_fops);
dev->cdev.owner= THIS_MODULE;
/* 添加字符型设备驱动 */
err = cdev_add(&dev->cdev, devno,1);
if(err)
printk(KERN_ALERT"Error %d adding xxx", err);
return err;
}
b.模块卸载函数 xxx_exit()
static void __exit xxx_exit(void)
{
/* udev支持 */
device_destroy(xxx_devp->myclass, MKDEV(xxx_major,0));
class_destroy(xxx_devp->myclass);
/* 删除cdev结构 */
cdev_del(&xxx_devp->cdev);
/* 释放设备结构体内存 */
kfree(xxx_devp);
/* 注销设备区域 */
unregister_chrdev_region(MKDEV(xxx_major,0),1);
}
3. 实现结构体 xxx_fops 中的函数
a. 打开和关闭
首先来看文件打开和关闭所对应的 xxx_open() 和xxx_release()函数。
函数原型如下:
static int xxx_open(struct inode*inode,struct file *filp);
static int xxx_release(struct inode*inode,struct file *file);
通常在xxx_open()中要做的事情是一些硬件上的初始化,例如配置一些寄存器,申请中断等。通常还会把 xxx_devp放到 filp->private_data中:
file->private_data = key_devp;
以便在读写函数中获取设备结构体指针。
在xxx_release()中则是做和 xxx_open()相反的事情,例如:释放中断等。
b. 读写函数
与文件读写相关的 xxx_read()和 xxx_write()函数原型如下:
static ssize_txxx_read(struct file*filp,char __user *buf, size_t size, loff_t* ppos);
static ssize_t xxx_write(struct file*filp,constchar __user*buf, size_t size, loff_t*ppos)
其中buf是用户空间用于读写的指针,size是用户需要读写的字节数,ppos是文件读写指针的位置,返回值是实际读写的字节数。
在 xxx_read()中可以调用 copy_to_user()函数将数据拷贝到buf处,而在xxx_write中可以调用copy_from_user()函数从buf中读取数据。
c. 其他函数
ioctl用于io控制命令;
poll用于非阻塞访问的查询操作;
fasync用于异步通知等,
以后用到再细说。
- Linux驱动学习笔记(2)----字符型设备驱动基本框架
- Linux驱动学习笔记;字符设备驱动
- linux字符设备驱动学习笔记2
- 字符设备驱动基本框架
- linux设备驱动学习笔记(1)-字符设备驱动
- linux字符设备驱动学习笔记1
- linux字符设备驱动学习笔记3
- linux 设备驱动笔记 - 字符设备驱动
- Linux字符设备驱动框架
- linux 字符设备驱动框架
- Linux字符设备驱动框架
- linux ------ 字符设备驱动框架
- linux 字符设备驱动框架
- Linux字符设备驱动框架
- Linux字符设备驱动框架
- linux驱动字符设备框架
- 学习笔记:Linux杂项设备驱动框架
- [Linux驱动]字符设备驱动学习笔记(一)
- Zend Studio 修改高亮变量的颜色、括号颜色
- 机器学习框架、库
- Android UI 之一步步教你自定义控件(自定义属性、合理设计onMeasure、合理设计onDraw等)
- Android程序中安装APP
- iOS中 第三方LBXScan库二维码扫描
- Linux驱动学习笔记(2)----字符型设备驱动基本框架
- Sports
- 关于两个tabbar之见的跳转
- java提高篇-----异常(二)
- C/CPP点滴积累—数组名作为常量的错误典型
- 《Cocos2d-x游戏开发实战精解》学习笔记4--实战一个简单的钢琴
- 文章标题
- LabVIEW宝典
- leetcode-136-singleNumber