linux字符设备驱动是必须要学会的框架。在跟着itop4412视频学习时碰到了困惑:
1.出现了Platform_device的设备驱动
2.出现msic杂项的设备驱动
以上2个驱动与我们在ldd书上看到的驱动方式很大的不同,首先了解下ldd中说的驱动流程。
以下是驱动模型步骤:
1)获取设备号
这个可以自动获取也可以通过 cat /proc/devices查看已经被注册好的主设备号。
获取设备号需要用到以下函数和宏:
/**************************************************************
* 描述:申请已知设备号(若是0会
自动从末尾处遍历找出未用设备号)
* 参数1: 设备号
* 参数2: 连续编号范围(可理解为多个连续设备号注册)
* 参数3:设备名称 最终显示到/dev目录下
* 返回:0 - 成功
**************************************************************/
int register_chrdev_region(dev_t,unsigned,const char *)
/**************************************************************
* 描述:动态获取设备号(与上一个函数用0传递不同,他是从其实开始遍历)
* 参数1: 获取到的设备号 (不能为空)
* 参数2: 从起始设备号
* 参数3: 从设备个数
* 参数3:设备名称
* 返回:0 - 成功
**************************************************************/
int alloc_chrdev_region(dev_t *,unsigned,unsigned,const char *);
#define MAJOR(dev) ((unsigned int)((dev) >> MINORBITS))//取高12位主设备号
#define MINOR(dev)((unsigned int)((dev) & MINORMASK)) //提取低20位次设备号
#define MKDEV(ma,mi)(((ma) << MINORBITS) | (mi)) //对主设备号和低设备号操作合并为dev类型。
在不同的平台 unsigned int的类型不同,尽量调用该宏来处理。
2)根据设备号确定其实现
首先看下设备实现最关键的2个结构体:
1.struct cdev{
struct kobject kobj;//内嵌内核对象
struct module *owner;//该字符设备所在的内核模块(所有者)的对象指针,一般为THIS_MODULES主要用于模块计数
const struct file_operations *ops;//该结构描述了字符设备所能实现的操作集(打开、关闭、读/写、........),是极为关键的一个结构体
struct list_head list;//用来将已经向内核注册的所有字符设备形成链表
dev_t dev; //字符设备的设备号,由主设备号和次设备构成
unsigned int count;//隶属同个设备号的次设备号个数
...
};
2. linux下“一切皆文件”的实现结构体如下:
struct file_operations {
struct module *owner;
/* 模块拥有者,一般为 THIS——MODULE */
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
/* 从设备中读取数据,成功时返回读取的字节数,出错返回负值(绝对值是错误码) */
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
/* 向设备发送数据,成功时该函数返回写入字节数。若为被实现,用户调层用write()时系统将返回 -EINVAL*/
int (*mmap) (struct file *, struct vm_area_struct *);
/* 将设备内存映射内核空间进程内存中,若未实现,用户层调用 mmap()系统将返回 -ENODEV */
long (*unlocked_ioctl)(struct file *filp, unsigned int cmd, unsigned long arg);
/* 提供设备相关控制命令(读写设备参数、状态,控制设备进行读写...)的实现,当调用成功时返回一个非负值 */
int (*open) (struct inode *, struct file *);
/* 打开设备 */
int (*release) (struct inode *, struct file *);
/* 关闭设备 */
int (*flush) (struct file *, fl_owner_t id);
/* 刷新设备 */
loff_t (*llseek) (struct file *, loff_t, int);
/* 用来修改文件读写位置,并将新位置返回,出错时返回一个负值 */
int (*fasync) (int, struct file *, int);
/* 通知设备 FASYNC 标志发生变化 */
unsigned int (*poll) (struct file *, struct poll_table_struct *);
/* POLL机制,用于询问设备是否可以被非阻塞地立即读写。当询问的条件未被触发时,用户空间进行select()和poll()系统调用将引起进程阻塞 */
...
};
以下的函数操作都是对上面结构体的操作:
/******************************************************
* 描述:动态申请cdev内存(设备对象)
* 返回值: 成功 cdev对象首地址
失败 NULL
*******************************************************/
struct cdev *cdev_alloc(void);
/******************************************************
* 描述:初始化cdev的成员,并建立cdev和file_operations之间关联
* 参数1: 被初始化的cdev对象
* 参数2:字符设备操作方法集
*******************************************************/
void cdev_init(struct cdev *p,const struct file_operations *p);
/******************************************************
* 描述:注册cdev设备对象
* 参数1: 被注册的cdev对象
* 参数2:设备的第一个设备号
* 参数3:这个设备连续的次设备号数量
* 返回值: 成功 0
*******************************************************/
int cdev_add(struct cdev *p,dev_t dev,unsigned count)
/******************************************************
* 描述:将cdev对象从系统中移除(注销)
* 参数1: struct cdev *p - 要移除的cdev对象
*******************************************************/
int cdev_del(struct cdev *p)
/******************************************************
* 描述:释放cdev内存
* 参数:struct cdev *p 要移除的cdev对象
*******************************************************/
void cdev_put(struct cdev *p)
(3)创建设备节点
可以自动创建使用 device_create函数 头文件"include/linux/device.h"
函数 extern struct device *device_create(struct class *cls, struct device *parent,dev_t devt, void *drvdata,const char *fmt, ...);中的参数比较多。
参数 struct class *cls:设备所属于的类,前面创建类的返回值
参数 struct device *parent:设备的父设备,NULL
参数 dev_t devt:设备号
参数 void *drvdata:设备数据,NULL
参数 const char *fmt:设备名称
只有两个参数,分别是设备类和设备号。
cdev_module.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <asm/current.h>
#include <linux/sched.h>
#include <linux/device.h>
MODULE_LICENSE("GPL");
static int major = 0;
static int minor = 0;
const int count = 3;
#define DEVNAME "demo"
static struct cdev *demop = NULL;
//打开设备
static int demo_open(struct inode *inode, struct file *filp)
{
//get command and pid
printk(KERN_INFO "(%s:pid=%d), %s : %s : %d\n", current->comm, current->pid, __FILE__, __func__, __LINE__);
//get major and minor from inode
printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n", imajor(inode), iminor(inode), __FILE__, __func__, __LINE__);
return 0;
}
//关闭设备
static int demo_release(struct inode *inode, struct file *filp)
{
//get command and pid
printk(KERN_INFO "(%s:pid=%d), %s : %s : %d\n", current->comm, current->pid, __FILE__, __func__, __LINE__);
//get major and minor from inode
printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n", imajor(inode), iminor(inode), __FILE__, __func__, __LINE__);
return 0;
}
//读设备
static ssize_t demo_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
struct inode *inode = filp->f_path.dentry->d_inode;
//get command and pid
printk(KERN_INFO "(%s:pid=%d), %s : %s : %d\n", current->comm, current->pid, __FILE__, __func__, __LINE__);
//get major and minor from inode
printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n", imajor(inode), iminor(inode), __FILE__, __func__, __LINE__);
return 0;
}
//写设备
static ssize_t demo_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
struct inode *inode = filp->f_path.dentry->d_inode;
//get command and pid
printk(KERN_INFO "(%s:pid=%d), %s : %s : %d\n", current->comm, current->pid, __FILE__, __func__, __LINE__);
//get major and minor from inode
printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n", imajor(inode), iminor(inode), __FILE__, __func__, __LINE__);
return 0;
}
//操作方法集
static struct file_operations fops = {
.owner = THIS_MODULE, .open = demo_open,
.release= demo_release,
.read = demo_read,
.write = demo_write,
};
//cdev设备模块初始化
static int __init demo_init(void)
{
dev_t devnum; int ret;
//get command and pid
printk(KERN_INFO "(%s:pid=%d), %s : %s : %d\n", current->comm, current->pid, __FILE__, __func__, __LINE__);
//1. alloc cdev obj
demop = cdev_alloc();
if(NULL == demop) {
return -ENOMEM;
}
//2. init cdev obj
cdev_init(demop, &fops);
ret = alloc_chrdev_region(&devnum, minor, count, DEVNAME);
if(ret){
goto ERR_STEP;
}
major = MAJOR(devnum);
//3. register cdev obj
ret = cdev_add(demop, devnum, count);
if(ret){
goto ERR_STEP1;
}
//创建设备节点
device_create(class_create(THIS_MODULE,DEVNAME),NULL,devnum,NULL,DEVNAME);
printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - ok.\n", current->comm, current->pid, __FILE__, __func__, __LINE__);
return 0;
ERR_STEP1:
unregister_chrdev_region(devnum, count);
ERR_STEP:
cdev_del(demop);
//get command and pid
printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - fail.\n", current->comm, current->pid, __FILE__, __func__, __LINE__);
return ret;
}
static void __exit demo_exit(void)
{
//get command and pid
printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - leave.\n", current->comm, current->pid, __FILE__, __func__, __LINE__);
unregister_chrdev_region(MKDEV(major, minor), count);
cdev_del(demop);
}
module_init(demo_init);
module_exit(demo_exit);
test.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int num, char *arg[])
{
if(2 != num){
printf("Usage: %s /dev/devfile\n", arg[0]);
return -1;
}
int fd = open(arg[1], O_RDWR);
if(0 > fd){
perror("open");
return -1;
}
getchar();
int ret = read(fd, 0x321, 0);
printf("read: ret = %d.\n", ret);
getchar();
ret = write(fd, 0x123, 0);
printf("write: ret = %d.\n", ret);
getchar();
close(fd);
return 0;
}
编译成功后,使用 insmod 命令加载:
然后用cat /proc/devices 查看,会发现设备号已经申请成功;