(一)Linux驱动开发·字符串设备驱动
一、基础结构体
- file结构体
struct file { ... struct path f_path; struct inode *f_inode; const struct file_operations *f_op; unsigned int f_flags; /*文件标志,如O_RDONLY、O_NONBLOCK、O_SYNC*/ fmode_t f_mode; /*文件读写模式*/ loff_t f_pos /*当前读写位置*/ void *private_data; /*文件私有数据,一般指向描述设备的结构体如cdev*/ ... } __attribute__{(aligned(4))};
- inode结构体
struct inode { ... umode_t i_mode; /*inode的权限*/ dev_t i_rdev; /*记录设备的设备号,高12位为主设备号,低20位为次设备号*/ struct timespec i_atime; /*inode 最近一次的存取时间*/ struct timespec i_mtime; /*inode 最近一次的修改时间*/ struct timespec i_ctime; /*inode 的产生时间*/ union { struct pipe_inode_info *i_pipe; struct block_device *i_bdev;/*若是块设备,为其对应的block_device结构体指针*/ struct cdev *i_cdev; /*若是字符设备,为其对应的cdev结构体指针*/ } ... };
- cdev 结构体
struct cdev { struct kobject kobj; /*内嵌kobject对象*/ struct module *owner; /*所属模块*/ struct file_operations *ops; /*文件操作结构体*/ struct list_head list; dev_t dev; /*设备号*/ unsigned int count; };
- file_operations
/**llseek():修改文件当前读写位置,并将新位置放回,出错时返回负数。 *read():从设备中读取数据,成功时返回读取的字节数,出错时返回一个负数。 *write():向设备发送数据,成功时返回写入的字节数。 *unlocked_ioctl():提供设备相关的控制指令的实现,调用成功时返回一个非负值。 *与用户空间的int fcntl(int fd,int cmd,...)和int ioctl(int d,int request,...)对应 *poll():用于询问设备是否可被非阻塞地立即读写。当询问的条件未触发时, *用户空间进行select()和poll()系统调用将引起进程的阻塞。 **/ struct file_operations { struct module *owner; loff_t (*llseek) (struct file *,loff_t,int); ssize_t (*read) (struct file *,char __user *,size_t,loff_t *); ssize_t (*write) (struct file *,const char __user *,size_t,loff_t *); int (*open)(struct inode *,struct file *); int (*release)(struct inode *,struct file *); long (*unlocked_ioctl)(struct file *,unsigned int ,unsigned long); long (*compat_ioctl)(struct file *,unsigned int,unsigned long); unsigned int (*poll)(struct file *,struct poll_table_struct *);; ... };
二、基础API
API | 功能说明 |
---|---|
void cdev_init(struct cdev*,struct file_operations *) | 初始化cdev并建立cdev和file_operations的连接 |
struct cdev *cdev_alloc(void) | 动态申请一个cdev内存 |
void cdev_add(struct cdev *,dev_t, unsigned) | 向系统添加一个cdev完成字符设备的注册 |
void cdev_del(struct cdev *) | 从系统中删除一个cdev完成字符设备的注销 |
int register_chrdev_region(dev_t from, unsigned count, const char *name) | 向系统注册一个设备号 |
int alloc_chardev_region(dev_t *dev,unsigned baseminor,unsigned count,const char name) | 向系统申请并注册一个未被占用的设备号,通过*dev指针返回 |
void unregister_chrdev_regin(dev_t from, unsigned count) | 从系统中注销一个设备号 |
MAJOR(dev_t dev) | 返回设备号的主设备号 |
MINOR(dev_t dev) | 返回设备号的次设备号 |
MKDEV(int major, int minor) | 根据主设备号和次设备号生成 dev_t |
unsigned long copy_from_user(void *to, const void __user *from, unsigned long count) | 从用户空间拷贝数据到内核空间 |
unsigned long copy_to_user(void __user *to, const void *from, unsigned long count) | 从内核空间拷贝数据到用户空间 |
access_ok(type,addr,size) | 判断传入的缓冲区确实属于用户空间 |
put_user(auto data,auto __user *to) | 简单类型的数据(char int long)拷贝到用户空间,有做缓冲区检测 |
get_user(auto data,auto __user *from) | 简单类型的数据(char int long)拷贝到内核空间,有做缓冲区检测 |
__put_user(auto data,auto __user *to) | 与put__user 类似,不做缓冲区检测 |
__get_user(auto data, auto __user *from) | 与get_user类似,不做缓冲区检测 |
三、框架代码
/*
* a simple char device driver: globalmem with mutex
*
* Copyright (C) 2014 Barry Song (baohua@kernel.org)
*
* Licensed under GPLv2 or later.
*
* 驱动功能说明:
* 该程序是一个虚拟内存设备的驱动程序。每个设备指向一块0x1000大小的内存。该驱动程序支持对该
* 虚拟设备进行如下操作:
* 1. open: 打开
* 2. read: 读取内存中的内容
* 3. write: 向内存中写入数据
* 4. seek: 支持seek方法
* 5. ioctl: 支持ioctl方法,发送MEM_CLEAR可清除内存中的数据
* 6. release: 释放内存
* 该驱动支持最多同时操作10个设备
*/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#define GLOBALMEM_SIZE 0x1000
#define MEM_CLEAR 0x1
#define GLOBALMEM_MAJOR 230 //主设备号
#define DEVICE_NUM 10
static int globalmem_major = GLOBALMEM_MAJOR;
module_param(globalmem_major, int, S_IRUGO); //申明内核参数
struct globalmem_dev {
struct cdev cdev;
unsigned char mem[GLOBALMEM_SIZE];
struct mutex mutex;
};
struct globalmem_dev *globalmem_devp;
static int globalmem_open(struct inode *inode, struct file *filp)
{
struct globalmem_dev *dev = container_of(inode->i_cdev,struct globalmem_dev,cdev);
filp->private_data = dev; //字符设备的结构体赋值给文件的私有数据
return 0;
}
int globalmem_release(struct inode *inode, struct file *filp)
{
return 0;
}
static long globalmem_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
struct globalmem_dev *dev = filp->private_data;
switch (cmd) {
case MEM_CLEAR:
mutex_lock(&dev->mutex);
memset(dev->mem, 0, GLOBALMEM_SIZE);
mutex_unlock(&dev->mutex);
printk(KERN_INFO "globalmem is set to zero\n");
break;
default:
return -EINVAL;
}
return 0;
}
static ssize_t globalmem_read(struct file *filp, char __user * buf, size_t size,
loff_t * ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct globalmem_dev *dev = filp->private_data;
if (p >= GLOBALMEM_SIZE)
return 0;
if (count > GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;
mutex_lock(&dev->mutex);
if (copy_to_user(buf, dev->mem + p, count)) {
ret = -EFAULT;
} else {
*ppos += count;
ret = count;
printk(KERN_INFO "read %u bytes(s) from %lu\n", count, p);
}
mutex_unlock(&dev->mutex);
return ret;
}
static ssize_t globalmem_write(struct file *filp, const char __user * buf,
size_t size, loff_t * ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct globalmem_dev *dev = filp->private_data;
if (p >= GLOBALMEM_SIZE)
return 0;
if (count > GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;
mutex_lock(&dev->mutex);
if (copy_from_user(dev->mem + p, buf, count))
ret = -EFAULT;
else {
*ppos += count;
ret = count;
printk(KERN_INFO "written %u bytes(s) from %lu\n", count, p);
}
mutex_unlock(&dev->mutex);
return ret;
}
static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
{
loff_t ret = 0;
switch (orig) {
case 0:
if (offset < 0) {
ret = -EINVAL;
break;
}
if ((unsigned int)offset > GLOBALMEM_SIZE) {
ret = -EINVAL;
break;
}
filp->f_pos = (unsigned int)offset;
ret = filp->f_pos;
break;
case 1:
if ((filp->f_pos + offset) > GLOBALMEM_SIZE) {
ret = -EINVAL;
break;
}
if ((filp->f_pos + offset) < 0) {
ret = -EINVAL;
break;
}
filp->f_pos += offset;
ret = filp->f_pos;
break;
default:
ret = - EINVAL;
break;
}
return ret;
}
static const struct file_operations globalmem_fops = {
.owner = THIS_MODULE,
.llseek = globalmem_llseek,
.read = globalmem_read,
.write = globalmem_write,
.unlocked_ioctl = globalmem_ioctl,
.open = globalmem_open,
.release = globalmem_release,
};
static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
int err, devno = MKDEV(globalmem_major, index); //构建具体的设备号
cdev_init(&dev->cdev, &globalmem_fops); //初始化设备
dev->cdev.owner = THIS_MODULE;
err = cdev_add(&dev->cdev, devno, 1); //将设备添加到系统中
if (err)
printk(KERN_NOTICE "Error %d adding globalmem%d", err, index);
}
static int __init globalmem_init(void)
{
int ret;
int i;
dev_t devno = MKDEV(globalmem_major, 0); //构建基础的设备号
if (globalmem_major)
ret = register_chrdev_region(devno, DEVICE_NUM, "globalmem"); //申请并注册多个设备号
else {
ret = alloc_chrdev_region(&devno, 0, DEVICE_NUM, "globalmem");
globalmem_major = MAJOR(devno);
}
if (ret < 0)
return ret;
// 动态分配连续多个设备的内存
globalmem_devp = kzalloc(sizeof(struct globalmem_dev)*DEVICE_NUM, GFP_KERNEL);
if (!globalmem_devp) {
ret = -ENOMEM;
goto fail_malloc;
}
for (i = 0;i<DEVICE_NUM;i++)
{
globalmem_setup_cdev(globalmem_devp+i, i);
mutex_init(&(globalmem_devp+i)->mutex);
}
return 0;
fail_malloc:
unregister_chrdev_region(devno, DEVICE_NUM);
return ret;
}
module_init(globalmem_init);
static void __exit globalmem_exit(void)
{
int i;
for (i =0;i<DEVICE_NUM;i++)
{
cdev_del(&(globalmem_devp+i)->cdev); //从系统中删除设备
}
kfree(globalmem_devp); //释放设备的内存
unregister_chrdev_region(MKDEV(globalmem_major, 0), DEVICE_NUM); //注销所有的设备号
}
module_exit(globalmem_exit);
MODULE_AUTHOR("Barry Song <baohua@kernel.org>");
MODULE_LICENSE("GPL v2");
测试方法:
# 创建设备节点0
mknod /dev/globalmem0 c 230 0 # 修改为 mknod /dev/globalmem1 c 230 1 可创建设备节点1
echo "hello world" > /dev/globalmem0
cat /dev/globalmem0 # 输出内存中的内容: hellow world