linux设备驱动开发之字符设备驱动结构
字符设备关键结构体
struct cdev结构体
// include/linux/cdev.h
struct cdev {
struct kobject kobj;
struct module *owner;
// 驱动设备的操作接口结构体指针,例如读、写、控制等操作
const struct file_operations *ops;
// 链表,用来记录多个cdev结构体对象的内存位置
struct list_head list;
// 设备号,由主设备号和次设备号共同合成
dev_t dev;
// 使用该结构体信息的设备数量
unsigned int count;
};
struct file_operations结构体
// include/linux/fs.h
struct file_operations {
struct module *owner;
// 定位函数指针,用来定位设备驱动中的文件,与用户端的lseek对应
loff_t (*llseek) (struct file *, loff_t, int);
// 读函数指针,用来读设备驱动中的文件,与用户端的read对应
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
// 写函数指针,用来写设备驱动中的文件,与用户端的write对应
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
// io控制函数指针,用来控制设备驱动中的IO文件,与用户端的ioctl对应
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
// 打开函数指针,用来打开设备驱动中的文件,与用户端的open对应
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
};
字符设备驱动概述
- 字符设备对应着cdev、file_operation这2个结构体。
- 字符设备驱动对应着file_operation中的操作接口,类似read、write、ioctl等。
- 用户空间使用字符设备的方法是通过条用用户态的read、write、ioctl等接口,来间接调用字符设备驱动中的接口,从而达到使用驱动的目的。
- 字符设备使用的前提是,先加载模块函数,在加载函数中,先初始化cdev、file_operation结构体,建立对象,然后将这些结构体信息绑定到设备驱动的kobject map上;如果不想使用,需要卸载这个字符设备时,就要调用卸载函数,在卸载函数中,先解除与kobject map的绑定,然后将上面那些结构体对象释放掉。
字符设备实例
/**
** This file is part of the LinuxTrainningHome project.
** Copyright(C) duanzhonghuan Co., Ltd.
** All Rights Reserved.
** Unauthorized copying of this file, via any medium is strictly prohibited
** Proprietary and confidential
**
** Written by ZhongHuan Duan <[email protected]>, 2019-05-12
**/
#include "linux/init.h"
#include "linux/module.h"
#include "linux/kdev_t.h"
#include "linux/cdev.h"
#include "linux/fs.h"
#include "linux/slab.h"
#include "linux/uaccess.h"
#define GLOBALMEM_SIZE (0X1000)
#define GLOBALMEM_MAJOR (230)
#define DEVICE_NUM (1)
#define MEM_CLEAR (0x01)
static int globalmem_major = GLOBALMEM_MAJOR;
#define globalmem_debug
/**
* @brief The globalmem_dev struct - the description of the global memory
*/
struct globalmem_dev
{
struct cdev chrdev;
unsigned char mem[GLOBALMEM_SIZE];
};
static struct globalmem_dev *globalmem_devp = NULL;
/**
* @brief globalmem_llseek - reposition read/write file offset
* @param filep: the file pointer
* @param offset: the offset
* @param whence: SEEK_SET,SEEK_CUR,SEEK_END
* @return: the actual offset, return -1 when failed
*/
static loff_t globalmem_llseek (struct file *filep, loff_t offset, int whence)
{
loff_t ret = 0;
switch (whence)
{
case SEEK_SET:
if (offset < 0 || offset > GLOBALMEM_SIZE)
{
ret = -EINVAL;
break;
}
filep->f_pos = offset;
ret = offset;
break;
case SEEK_CUR:
if (offset < 0 || filep->f_pos + offset > GLOBALMEM_SIZE)
{
ret = -EINVAL;
break;
}
filep->f_pos += offset;
ret = filep->f_pos;
break;
case SEEK_END:
filep->f_pos = GLOBALMEM_SIZE;
ret = filep->f_pos;
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
/**
* @brief globalmem_read
* @param filep
* @param buf
* @param count
* @param ppos
* @return
*/
static ssize_t globalmem_read (struct file *filep, char __user *buf, size_t count, loff_t *ppos)
{
ssize_t ret = 0;
loff_t curpos = *ppos;
struct globalmem_dev *dev;
#ifdef globalmem_debug
printk("globalmem_read\n");
#endif
if (curpos >= GLOBALMEM_SIZE)
{
return 0;
}
dev = filep->private_data;
if (!dev)
{
ret = -ENOMEM;
return ret;
}
if (curpos + count > GLOBALMEM_SIZE)
{
count = GLOBALMEM_SIZE - curpos;
}
ret = copy_to_user(buf, dev->mem + curpos, count);
*ppos += count;
ret = count;
return ret;
}
static ssize_t globalmem_write (struct file *filep, const char __user *buf, size_t count, loff_t *ppos)
{
ssize_t ret = 0;
loff_t curpos = *ppos;
struct globalmem_dev *dev;
#ifdef globalmem_debug
printk("globalmem_write\n");
#endif
if (curpos >= GLOBALMEM_SIZE)
{
ret = -ENAVAIL;
return ret;
}
dev = filep->private_data;
if (!dev)
{
ret = -ENOMEM;
return ret;
}
if (curpos + count > GLOBALMEM_SIZE)
{
count = GLOBALMEM_SIZE - curpos;
}
ret = copy_from_user(dev->mem + curpos, buf, count);
*ppos += count;
ret = count;
return ret;
}
/**
* @brief globalmem_unlocked_ioctl
* @param filep
* @param cmd
* @param arg
* @return
*/
static long globalmem_unlocked_ioctl (struct file *filep, unsigned int cmd, unsigned long arg)
{
struct globalmem_dev *dev = filep->private_data;
switch (cmd)
{
case MEM_CLEAR:
memset(dev->mem, 0, GLOBALMEM_SIZE);
printk("mem clear success\n");
break;
default:
break;
}
return 0;
}
static int globalmem_open (struct inode *inode, struct file *filep)
{
struct globalmem_dev *dev = container_of(inode->i_cdev, struct globalmem_dev, chrdev);
filep->private_data = dev;
#ifdef globalmem_debug
printk("globalmem_open\n");
#endif
return 0;
}
static int globalmem_release (struct inode *inode, struct file *filep)
{
return 0;
}
static struct file_operations chrdev_file_operations =
{
.owner = THIS_MODULE,
.llseek = globalmem_llseek,
.read = globalmem_read,
.write = globalmem_write,
.unlocked_ioctl = globalmem_unlocked_ioctl,
.open = globalmem_open,
.release = globalmem_release,
};
/**
* @brief globalmem_init_dev - initialize the global memory decice
* @param chrdev: out parameter for a char device
* @param index: the minor device id
*/
static void globalmem_init_dev(struct globalmem_dev *chrdev, int index)
{
int ret = 0;
dev_t devno = MKDEV(globalmem_major, index);
cdev_init(&chrdev->chrdev, &chrdev_file_operations);
// add the globalmem device structure to the kobj_map
ret = cdev_add(&chrdev->chrdev, devno, 1);
if (ret)
{
printk("Error code = %d when adding globalmem %d\n", ret, index);
}
}
/**
* @brief globalmem_init - the function of initializing the global memory
* @return: the status of initializing the global memory
*/
static int __init globalmem_init(void)
{
int i = 0;
int ret = 0;
// 1. get the device id
dev_t devno = MKDEV(globalmem_major, 0);
#ifdef globalmem_debug
printk(KERN_NOTICE "globalmem_init\n");
#endif
// 2. register the DEVICE_NUM of the char device
if (globalmem_major)
{
ret = register_chrdev_region(devno, DEVICE_NUM, "globalmem");
}
else
{
// automatic register char device
ret = alloc_chrdev_region(&devno, 0, DEVICE_NUM, "globalmem");
}
// check the error code
if (ret < 0)
{
return ret;
}
// 3. construct the globalmem devices structure in the heap
globalmem_devp = kzalloc(sizeof(struct globalmem_dev) * DEVICE_NUM, GFP_KERNEL);
if (!globalmem_devp)
{
ret = -ENOMEM;
goto fail_malloc;
}
// 4. add the globalmem decices structure pointer to the kobjct map
for (i = 0; i < DEVICE_NUM; i++)
{
globalmem_init_dev(globalmem_devp + i, i);
}
printk("globalmem_init success\n");
return 0;
fail_malloc:
unregister_chrdev_region(devno, DEVICE_NUM);
return ret;
}
/**
* @brief globalmem_exit - exit the glboalmem device
*/
static void __exit globalmem_exit(void)
{
int i = 0;
#ifdef globalmem_debug
printk(KERN_NOTICE "globalmem_exit\n");
#endif
// 1. remove the globalmem structure from teh kobject map
for (i = 0; i < DEVICE_NUM; i++)
{
cdev_del(&(globalmem_devp + i)->chrdev);
}
// 2. free the glboalmem structure in the heap
kfree(globalmem_devp);
// 3. unregister the device id
unregister_chrdev_region(MKDEV(globalmem_major, 0), DEVICE_NUM);
// 4. remove the device id
printk("globalmem_exit success\n");
}
module_init(globalmem_init)
module_exit(globalmem_exit)
// the declaration of the author
MODULE_AUTHOR("ZhongHuan Duan <[email protected]>");
// the declaration of the licence
MODULE_LICENSE("GPL v2");
编译该文件使用的Makefile内容为:
KVERS = $(shell uname -r)
obj-m += globalmem.o
#EXTRA_CFLAGS=-g -o0
build: kernel_modules
kernel_modules:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules
clean:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean
编译得到.ko文件后,调整.ko文件权限,然后测试验证。
实例测试结果
- 加载.ko模块,创建globamem设备节点
sudo insmod globalmem.ko
sudo mknod /dev/globalmem c 230 0
sudo chmod 777 /dev/globalmem
- 往globalmem节点 中写入hello wrold
echo "hello world" > /dev/globalmem
- 读取glboalmem节点中的数据
cat /dev/globalmem
虚拟文件系统有关globalmem的信息
- 查看/proc文件中关于globalmem的信息
cat /proc/devices | grep globalmem
- 查看/sys文件中关于globalmem的信息
tree /sys/module/globalmem
总结
- 字符设备是3大类设备中的一类,驱动加载初始化步骤为申请设备号,构造并初始化相关结构体对象,绑定结构体对象到kobject map上。
- 驱动卸载的步骤与前面相反,解绑定kobject map上的结构体对象,释放结构体空间,注销申请的设备号。
- 字符设备驱动函数主要实现file_operations结构体中的函数指针即可;实现形式可以参考用户空间调用的对应接口来实现。