Linux字符设备驱动开发
参考挺多的,有时间列举一下,?:并未完全完成详细备注
#include <linux/cdev.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <asm-generic/ioctl.h>
#include <linux/device.h>
#define FENGMEM_SIZE 0x1000 //设备内存大小4k,能存放4096个字符
#define FENGMEM_MAGIC 'g'
#define MEM_CLEAR _IO(FENGMEM_MAGIC, 0) //#define MEM_CLEAR 0x1 /*清零设备内存*/
#define FENGMEM_MAJOR 100 //预设主设备号
#define FENGMEM_NUM 9
static int fengmem_major = FENGMEM_MAJOR;
struct class* fengmem_class;
//所有函数,都应该加上static关键字,表示仅在该文件中可以使用
//向当前模块传入的参数
module_param(fengmem_major, int, S_IRUGO); //可以去掉试试,权限是否变化// S_IRUGO = S_IRUSR|S_IRGRP|S_IROTH, 用户读, 用户组读, 其他读
//用户设备
struct fengmem_dev{
struct cdev cdev; //用户字符设备的dev
unsigned char mem[FENGMEM_SIZE];
struct mutex mutex;
};
struct fengmem_dev *fengmem_devp;
/* EINVAL表示 无效的参数 22,即为 invalid argument
* llseek 函数原型: loff_t (*llseek) (struct file* filp, loff_t offset, int whence);
* struct file* 代表上一级调用函数传入的已打开文件,该结构体与struct inode* 区别在于struct inode*用于代表一个文件,但是该文件可以是未打开的。
* loff_t代表用户空间的文件光标移动数量值,可以是正数,也可以是负数。
* int whence代表移动光标的参考位置,有3种(即当前光标、文件开头、文件结尾)。
* 返回值:非负数,当前文件的指针位置;负数,函数调用失败。
* llseek 实现的功能是修改驱动中write/read函数的文件指针。
* llseek函数实现的目的:为了使用用户空间的llseek()系统调用这个函数功能来移动设备的文件指针,
* 然后读/写接口就可以对移动后的位置进行读取功能,而不是每次读/写都只能从0开始一次读取全部数据。
* llseek函数改变了filp->f_pos,返回新的位置
*/
static loff_t fengmem_llseek(struct file *filp, loff_t offset, int orig)
{
loff_t ret = 0;
switch(orig){
case 0: // 0 SEEK_SET代表以文件头为偏移起始值
if (offset < 0){ // 读写数据偏移最小阈值不能小于0
ret = -EINVAL; // 非法参数
break;
}
if ((unsigned int)offset > FENGMEM_SIZE){ // 读写数据偏移最大阈值不能超过设备本身占用空间阈值
ret = -EINVAL;
break;
}
filp->f_pos = (unsigned int)offset;
ret = filp->f_pos;
break;
case 1: //1 SEEK_CUR代表以当前位置为偏移起始值
if ((filp->f_pos + offset) < 0){
ret = -EINVAL;
break;
}
if ((filp->f_pos + offset) > FENGMEM_SIZE){
ret = -EINVAL;
break;
}
filp->f_pos += offset;
ret = filp->f_pos;
break;
// 以文件尾位置为偏移起始值,offset其实就是0,filp->f_pos就等于他自己。可不写
default:
ret = -EINVAL;
break;
}
return ret;
}
/*
* Linux 驱动遵循潜规则: 将文件的私有数据private_data 指向设备结构体
* 供read/write/ioctl/llseek 等文件操作函数通过private_data 访问设备结构体
*
* struct inode是当用户空间打开一个文件(设备节点)内核自动构建的一个结构体,用来表征一个物理文件,它是唯一性的
* inode->i_cdev 当我们打开的设备是字符设备,内核便帮我们把struct inode中的cdev域赋值为我们目标具体的字符设备,如struct scull_dev.这时候可以通过内核提供的函数container_of把具体的字符设备提取出来
* struct file文件结构代表一个打开的文件描述符,它不是专门给驱动程序使用的,系统中每一个打开的文件在内核中都有一个关联的struct file.它由内核在open时创建,并传递给在文件上操作的任何函数(如read(),write()等),直到最后关闭.当文件的所有实例都关闭之后,内核释放这个数据结构.
* filp->private_data 在struct file_operations里面基本上所有的函数的参数都带了这个结构体.从宏观上来讲,这个结构体一旦被open之后,对后续的操作,如read、write、ioctl等动作都是可见的.尤其是其private_data,一般在open的时候用来存放具体的目标操作数据,后续的read、write、ioctl便通过此域来获取到目标数据实现交互
* const struct file_operations: 这个是用来绑定具体的struct file_operations.内核在执行open时对这个指针赋值.可见,只要拿到了struct file这个结构体,极大的方便的后续准确无误的操作
*/
//打开文件操作
static int fengmem_open(struct inode *inode, struct file *filp)
{
struct fengmem_dev *dev = container_of(inode->i_cdev, struct fengmem_dev, cdev);
filp->private_data = dev;
return 0;
}
//关闭文件操作 release函数一般释放由open分配的、保存在filp->private_data中的内容
static int fengmem_release(struct inode *inode, struct file *filp)
{
filp->private_data = NULL;
return 0;
}
/* EFAULT 错误地址 14
read(struct file *filp, char _ _user *buff, size_t count, loff_t *ppos)
ssize_t:这个数据类型表示可以被执行读写操作的数据块的大小.它和size_t类似,但必需是signed
struct file:file结构体,现在暂时不用,可以先不传参。
buff 是文件要读到什么地方去,用户buf
size_t:count 其实size_t这只是unsigned int。count是要读多少东西。
loff_t:*ppos 这是用于存放文件的偏移量的,回想一下系统编程时,读写文件的操作都会使偏移量往后移。ppos最终反映的就是缓冲buf在内存中最后的位置。
返回值:
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
如果数据拷贝成功,则返回零;否则,返回没有拷贝成功的数据字节数。
*to是用户空间的指针,
*from是内核空间指针,
n表示从内核空间向用户空间拷贝数据的字节数
从设备中读取数据复制给用户内存(该例子中将设备mem中的内容赋给用户的buff)
*/
// 读取操作,接下来是完成符合llseek的write/read函数,使用函数参数传递进来的文件指针作为依据和位置的定位来实现数据在指定位置的读写
// 重写能与llseek配套的write函数框架
static ssize_t fengmem_read(struct file *filp, char __user *buff, size_t size, loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct fengmem_dev *dev = filp -> private_data;
/*分析和获取有效的读长度*/
if (p >= FENGMEM_SIZE) //p是偏移量(要从这里开始读取),若是偏移量大于设备mem的大小,直接返回,?p和count关系
return 0;
//要读的字节数太大
if (count > FENGMEM_SIZE - p)
count = FENGMEM_SIZE - p;
mutex_lock(&dev->mutex);
if (copy_to_user(buff, dev->mem + p, count)){ //设备数据拷贝给用户 //复制成功返回0,进入else;否则返回拷贝失败的字节数进入if
ret = -EFAULT;
}else{
*ppos += count;
ret = count;
printk(KERN_INFO "read %u bytes from %lu\n", count, p); //读两次会怎么样?
}
mutex_unlock(&dev->mutex);
return ret;
}
/* 在Linux系统中,每一个打开的文件,在内核中都会关联一个struct file结构,它是由内核在打开文件时创建,在文件关闭后释放。
// struct file结构中的重要成员
// * struct file_operations* f_op; //文件操作函数集
// * loff_t f_pos; //文件读写指针
*/
//写入操作
static ssize_t fengmem_write(struct file *filp, const char __user *buff, size_t size, loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct fengmem_dev *dev = filp -> private_data;
/*分析和获取有效的写长度*/
if (p >= FENGMEM_SIZE) // 要写的位置超出mem[]边界
return 0; //EOF
if (count > FENGMEM_SIZE - p) // 要写的用户缓冲区大小超过mem[]大小, 截断
count = FENGMEM_SIZE - p;
mutex_lock(&dev->mutex);
if (copy_from_user(dev->mem + p, buff, count)){
ret = -EFAULT;
}else{
*ppos += count; // 更新偏移位置
ret = count; // 已读取字节数
printk(KERN_INFO "written %u bytes from %lu\n", count, p);
}
mutex_unlock(&dev->mutex);
return ret;
}
//执行设备特殊命令的函数ioctl()
static long fengmem_ioctl (struct file *filp, unsigned int cmd, unsigned long arg)
{
struct fengmem_dev *dev = filp->private_data;
switch(cmd){
case MEM_CLEAR:
mutex_lock(&dev->mutex);
memset(dev->mem, 0, FENGMEM_SIZE); //设备内存清空
mutex_unlock(&dev->mutex);
printk(KERN_INFO"fengmem is set to zero\n");
break;
default:
return -EINVAL;
}
return 0;
}
//文件操作函数集
struct file_operations fengmem_fops = {
.owner = THIS_MODULE, //上面头文件的宏定义中
.llseek = fengmem_llseek, //上面定义的
.read = fengmem_read, //上面定义的
.write = fengmem_write, //上面定义的
.unlocked_ioctl = fengmem_ioctl, //上面定义的
.open = fengmem_open,
.release = fengmem_release,
};
/*
* 完成cdev的初始化与添加 //?待补充完整
*/
static void fengmem_setup_cdev(struct fengmem_dev* dev, int index)
{
int err, devno = MKDEV(fengmem_major, index); //设备号
cdev_init(&dev->cdev, &fengmem_fops); //初始化cdev结构,并将其和file_operations关联起来
dev->cdev.owner = THIS_MODULE; //设置所有者
err = cdev_add(&dev->cdev, devno, 1); //将设备添加到系统 //cdev_add() 函数。传入cdev结构的指针,起始设备编号,以及设备编号范围。
if (err)
printk(KERN_NOTICE "Error %d adding fengmem%d\n", err, index);
}
static void fengmem_unsetup_cdev(struct fengmem_dev* dev, int index)
{
cdev_del(&dev->cdev);
}
// 驱动入口函数
static int __init fengmem_init(void)
{
int ret;
dev_t devno = MKDEV(fengmem_major, 0); // 指定设备号
// 申请设备号
if (fengmem_major) {
ret = register_chrdev_region(devno, 1, "fengmem"); // 调用者指定设备号
} else {
ret = alloc_chrdev_region(&devno, 0, 1, "fengmem"); // 系统决定设备号
}
if (ret < 0) {
return ret;
}
fengmem_devp = kzalloc(sizeof(struct fengmem_dev), GFP_KERNEL); // 向内核申请内存
if (!fengmem_devp) {
ret = -ENOMEM;
goto fail_malloc;
}
fengmem_setup_cdev(fengmem_devp, 0);
fengmem_class = class_create(THIS_MODULE, "fengmem_class");
if (IS_ERR(fengmem_class)) {
ret = -1;
goto fail_class;
}
device_create(fengmem_class, NULL, devno, NULL, "fengmem");
return 0;
fail_class: // class_create 异常处理
;
fail_malloc: // kzalloc 异常处理
unregister_chrdev_region(devno, 1);
return ret;
}
module_init(fengmem_init);
static void __exit fengmem_exit(void)
{
device_destroy(fengmem_class, MKDEV(fengmem_major, 0));
class_destroy(fengmem_class);
fengmem_unsetup_cdev(fengmem_devp, 0);
kfree(fengmem_devp);
unregister_chrdev_region(MKDEV(fengmem_major, 0), 1);
}
module_exit(fengmem_exit);
MODULE_AUTHOR("zhangsan");
MODULE_LICENSE("GPL v2");