赞
踩
初始化/移除字符设备
Linux内核提供了两种方式来定义字符设备,如下所示:
//第一种方式:常见的变量定义
static struct cdev chrdev;
//第二种方式:内核提供的动态分配方式
struct cdev *cdev_alloc(void);
从内核中移除某个字符设备,则需要调用cdev_del函数,如下所示:
void cdev_del(struct cdev *p)
分配/注销设备号
Linux的各种设备都以文件的形式存放在/dev目录下,为了管理这些设备,系统为各个设备进行编号,每个设备号又分为主设备号和次设备号。主设备号用来区分不同种类的设备,如USB,tty等,次设备号用来区分同一类型的多个设备。我们可以通过命令”cat /proc/devices”查询内核分配的主设备号。
内核提供了一种数据类型:dev_t,用于记录设备编号,该数据类型实际上是一个无符号32位整型,其中的12位用于表示主设备号,剩余的20位则用于表示次设备号。
Linux内核为我们提供了生成设备号的宏定义MKDEV,用于将主设备号和次设备号合成一个设备号。除此之外,内核还提供了另外两个宏定义MAJOR和MINOR,可以根据设备的设备号来获取设备的主设备号和次设备号。
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) //通过设备号获取主设备号
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))//过设备号获取次设备号
#define MKDEV(ma,mi) (((ma) << MINORBITS) \| (mi))//通过主设备号和次设备号合成设备号
创建一个新的字符设备之前,我们需要为新的字符设备注册一个新的设备号。内核提供了三种方式,来完成这项工作。
(1) register_chrdev函数
register_chrdev函数用于分配设备号。该函数是一个内联函数,它不仅支持静态申请设备号,也支持动态申请设备号,并将主设备号返回,函数原型如下所示。
static inline int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
{
return __register_chrdev(major, 0, 256, name, fops);
}
使用register_chrdev函数向内核申请设备号,同一类字符设备(即主设备号相同),会在内核中申请了256个,通常情况下,我们不需要用到这么多个设备,这就造成了极大的资源浪费。
(2)register_chrdev_region函数
register_chrdev_region函数用于静态地为字符设备申请一个或多个设备编号。该函数在分配成功时,会返回0;失败则会返回相应的错误码,函数原型如下所示。
int register_chrdev_region(dev_t from, unsigned count, const char *name)
(3)alloc_chrdev_region函数(推荐使用)
使用register_chrdev_region函数时,需要编程者自行确定未使用的设备号, 并不方便。因此,内核又为我们提供了一种能够动态分配设备编号的方式:alloc_chrdev_region。
调用alloc_chrdev_region函数,内核会自动分配给我们一个尚未使用的主设备号。我
们可以通过命令”cat /proc/devices”查询内核分配的主设备号。
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
注销设备号:
当我们删除字符设备时候,我们需要把分配的设备编号交还给内核.
(1)unregister_chrdev函数
使用register函数申请的设备号,则应该使用unregister_chrdev函数进行注销。
static inline void unregister_chrdev(unsigned int major, const char *name)
{
__unregister_chrdev(major, 0, 256, name);
}
(2)unregister_chrdev_region函数
对于使用register_chrdev_region函数以及alloc_chrdev_region函数分配得到的设备编号,可以使用unregister_chrdev_region函数进行注销。
void unregister_chrdev_region(dev_t from, unsigned count)
关联设备的操作方式
编写一个字符设备最重要的事情,就是要实现file_operations这个结构体中的函数。实现之后,如何将该结构体与我们的字符设备结构相关联呢?内核提供了cdev_init函数,来实现这个工作。
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
注册设备
cdev_add函数用于向内核的cdev_map散列表添加一个新的字符设备,如下所示
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
字符设备驱动框架
#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/uaccess.h> #define DEV_NAME "EmbedCharDev" #define DEV_CNT (1) #define BUFF_SIZE 128 //定义字符设备的设备号 static dev_t devno; //定义字符设备结构体chr_dev static struct cdev chr_dev; //数据缓冲区 static char vbuf[BUFF_SIZE]; static int chr_dev_open(struct inode *inode, struct file *filp); static int chr_dev_release(struct inode *inode, struct file *filp); static ssize_t chr_dev_write(struct file *filp, const char __user * buf, size_t count, loff_t *ppos); static ssize_t chr_dev_read(struct file *filp, char __user * buf, size_t count, loff_t *ppos); //定义初始化file_operation结构体 static struct file_operations chr_dev_fops = { .owner = THIS_MODULE, .open = chr_dev_open, .release = chr_dev_release, .write = chr_dev_write, .read = chr_dev_read, } static int chr_dev_open(struct inode *inode, struct file *filp) { printk("\nopen\n"); return 0; } static int chr_dev_release(struct inode *inode, struct file *filp) { printk("\nrelease\n"); return 0; } static ssize_t chr_dev_write(struct file *filp, const char __user * buf, size_t count, loff_t *ppos) { unsigned long p = *ppos; int ret; int tmp = count ; if(p > BUFF_SIZE) return 0; if(tmp > BUFF_SIZE - p) tmp = BUFF_SIZE - p; //读取用户空间的数据存入内核数据缓冲区 ret = copy_from_user(vbuf, buf, tmp); *ppos += tmp; return tmp; } static ssize_t chr_dev_read(struct file *filp, char __user * buf, size_t count, loff_t *ppos) { unsigned long p = *ppos; int ret; int tmp = count ; static int i = 0; i++; if(p >= BUFF_SIZE) return 0; if(tmp > BUFF_SIZE - p) tmp = BUFF_SIZE - p; //将内核数据缓冲区的中的数据发送给用户空间 ret = copy_to_user(buf, vbuf+p, tmp); *ppos +=tmp; return tmp; } //入口函数 static int __init chrdev_init(void) { int ret = 0; printk("chrdev init\n"); //第一步 //采用动态分配的方式,获取设备编号,次设备号为0, //设备名称为EmbedCharDev,可通过命令cat /proc/devices查看 //DEV_CNT为1,当前只申请一个设备编号 ret = alloc_chrdev_region(&devno, 0, DEV_CNT, DEV_NAME); if(ret < 0) { printk("fail to alloc devno\n"); goto alloc_err; } //第二步 //关联字符设备结构体cdev与文件操作结构体file_operations cdev_init(&chr_dev, &chr_dev_fops); //第三步 //添加设备至cdev_map散列表中 ret = cdev_add(&chr_dev, devno, DEV_CNT); if(ret < 0) { printk("fail to add cdev\n"); goto add_err; } return 0; add_err: //添加设备失败时,需要注销设备号 unregister_chrdev_region(devno, DEV_CNT); alloc_err: return ret; } //出口函数 static void __exit chrdev_exit(void) { printk("chrdev exit\n"); unregister_chrdev_region(devno, DEV_CNT); cdev_del(&chr_dev); } module_init(chrdev_init); module_exit(chrdev_exit); MODULE_LICENSE("GPL");
改进:一个驱动程序驱动多个不同次设备号的设备文件
...
//申请两个次设备号
#define DEV_CNT (2)
...
/*虚拟字符设备*/
struct chr_dev {
struct cdev dev;
char vbuf[BUFF_SIZE];
};
//字符设备1
static struct chr_dev vcdev1;
//字符设备2
static struct chr_dev vcdev2;
入口函数修改:
static int __init chrdev_init(void) { int ret; dev_t cur_dev; printk("chrdev init\n"); ret = alloc_chrdev_region(&devno, 0, DEV_CNT, DEV_NAME); if (ret < 0) goto alloc_err; //关联第一个设备:vdev1 cdev_init(&vcdev1.dev, &chr_dev_fops); cur_dev = MKDEV(MAJOR(devno), MINOR(devno) + 0); ret = cdev_add(&vcdev1.dev, cur_dev, 1); if (ret < 0) { printk("fail to add vcdev1 "); goto add_err1; } //关联第二个设备:vdev2 cdev_init(&vcdev2.dev, &chr_dev_fops); cur_dev = MKDEV(MAJOR(devno), MINOR(devno) + 1); ret = cdev_add(&vcdev2.dev, cur_dev, 1); if (ret < 0) { printk("fail to add vcdev2 "); goto add_err2; } return 0; add_err2: cdev_del(&(vcdev1.dev)); add_err1: unregister_chrdev_region(devno, DEV_CNT); alloc_err: return ret; }
出口函数修改:
static void __exit chrdev_exit(void)
{
printk("chrdev exit\n");
unregister_chrdev_region(devno, DEV_CNT);
cdev_del(&(vcdev1.dev));
cdev_del(&(vcdev2.dev));
}
open函数修改:
文件节点inode中的成员i_cdev:为了方便访问设备文件,在打开文件过程中,内核将对应的字符设备结构体cdev自动保存到文件节点inode中的成员变量i_cdev中。
Linux提供了一个宏定义container_of,该宏可以根据结构体的某个成员的地址,来得到该结构体的地址。该宏需要三个参数,分别是代表结构体成员的真实地址,结构体的类型以及结构体成员的名字。
在chr_dev_open函数中,我们需要通过inode的i_cdev成员,来得到对应的虚拟设备结构体,并保存到文件指针filp的私有数据成员中。假如,我们打开虚拟设备1,那么inode->i_cdev便指向了vcdev1的成员dev,利用container_of宏,我们就可以得到vcdev1结构体的地址,也就可以操作对应的数据缓冲区了。
static int chr_dev_open(struct inode *inode, struct file *filp)
{
printk("open\n");
filp->private_data = container_of(inode->i_cdev, struct chr_dev, dev);
return 0;
}
write函数的修改:
通过filp->private_data获取到打开的字符设备所在的虚拟设备结构体变量的指针,同过该指针即可访问该虚拟设备的数据缓冲区vbuf。
static ssize_t chr_dev_write(struct file *filp, const char __user * buf, size_t count, loff_t *ppos) { unsigned long p = *ppos; int ret; //获取文件的私有数据 struct chr_dev *dev = filp->private_data; char *vbuf = dev->vbuf; int tmp = count ; if (p > BUFF_SIZE) return 0; if (tmp > BUFF_SIZE - p) tmp = BUFF_SIZE - p; ret = copy_from_user(vbuf, buf, tmp); *ppos += tmp; return tmp; }
read函数的修改:
与write函数的修改基本一致。
static ssize_t chr_dev_read(struct file *filp, char __user * buf, size_t count, loff_t *ppos) { unsigned long p = *ppos; int ret; int tmp = count ; //获取文件的私有数据 struct chr_dev *dev = filp->private_data; char *vbuf = dev->vbuf; if (p >= BUFF_SIZE) return 0; if (tmp > BUFF_SIZE - p) tmp = BUFF_SIZE - p; ret = copy_to_user(buf, vbuf+p, tmp); *ppos +=tmp; return tmp; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。