第6章 字符设备驱动之globalmem(全局内存)虚拟设备实例(支持N个globalmem设备的globalmem驱动)

一、支持N个globalmem设备的驱动,在加载模块后需创建多个设备节点,如运行 mknod /dev/globalmem0 c 230 0使得/dev/globalmem0对应主设备号为globalmem_major、次设备号为0的设备,运行mknod/dev/globalmem1 c 230 1使得/dev/globalmem1对应主设备号为globalmem_major、次设备号为1的设备。分别读写/dev/globalmem0和/dev/globalmem1,发现都读写到了正确的对应的设备。

1、驱动源代码头文件global_mem.h

#ifndef __GLOBAL_MEM_H
#define __GLOBAL_MEM_H

#include <linux/cdev.h>

#define DRIVER_AUTHOR "xz@vi-chip.com.cn"
#define DRIVER_DESC   "A sample driver" 

#define GLOBALMEM_SIZE 0x1000
#define MEM_CLEAR 0x1
#define DEV_NAME "globalmem"
#define GLOBALMEM_MAJOR 230 /* 主设备号 */
#define DEVICE_NUM 10

/**
*
* 定义全局内存字符设备的结构体:
* 借用面向对象程序设计中“封装”的思想,体现良好的编程习惯。
*
*/
struct globalmem_dev {
struct cdev cdev;
unsigned char mem[GLOBALMEM_SIZE];
};

#endif /* __GLOBAL_MEM_H */

2、驱动源代码文件global_mem.c

#include <linux/init.h>  
#include <linux/kernel.h>
#include <linux/module.h> 
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>

/* include local head files */
#include "global_mem.h"

static int globalmem_major = GLOBALMEM_MAJOR;
module_param(globalmem_major, int, S_IRUGO); /* mode:S_IRUGO */

struct globalmem_dev *globalmem_devp = NULL;

/**
*
* 读写函数
* 让设备结构体globalmem_dev的mem[]数组与用户空间交互数据,
* 并随着访问的字节数变更更新文件读写偏移位置。
*/
// globalmem设备驱动的读函数,从设备中读取数据
static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
unsigned long p = *ppos; // 要读的位置相对于文件开头的偏移,如果该偏移大于或等于GLOBALMEM_SIZE,表示已经到达文件末尾,所以返回0(EOF)。
unsigned int count = size; // 要读取的字节数
int ret = 0;
struct globalmem_dev *dev = filp->private_data; // 使用文件的私有数据访问设备结构体

if (p >= GLOBALMEM_SIZE)
return 0; // 表示已经到达文件末尾,所以返回0(EOF)
if (count > (GLOBALMEM_SIZE - p))
count = GLOBALMEM_SIZE - p;

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);
}

return ret; // 实际读取的字节数
}

// globalmem设备驱动的写函数,向设备发送数据
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;

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);
}

return ret;
}

// globalmem设备驱动的seek函数
// seek函数对文件定位的起始地址可以是文件开头(SEEK_SET,0)、当前位置(SEEK_CUR,1)和文件尾(SEEK_END,2),假设globalmem支持从文件开头和当前位置的相对偏移。
// 在定位的时候,应该检查用户请求的合法性,若不合法,函数返回-EINVAL,合法时更新文件的当前位置并返回该位置。
static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
{
loff_t ret = 0;
switch (orig) {
case 0: /* 从文件开头位置 seek */
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: /* 从文件当前位置开始 seek */
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;
}

// ioctl函数
// globalmem设备驱动的ioctl函数接受MEM_CLEAR命令,这个命令会将全局内存的有效数据长度清0,对于设备不支持的命令,ioctl函数应该返回-EINVAL。
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:
memset(dev->mem, 0, GLOBALMEM_SIZE); // 清空内存
printk(KERN_INFO "globalmem is set to zero\n");
break;
default:
return -EINVAL;
}

return 0;
}

static int globalmem_open(struct inode *inode, struct file *filep)
{
printk(KERN_INFO "globalmem_open\n");
// container_of宏的解释说明 宏container_of()的作用是通过结构体成员的指针找到对应结构体的指针
// 1、ptr:表示结构体中member的地址
// 2、type:表示结构体类型
// 3、member:表示结构体中的成员
// 4、通过ptr的地址可以返回结构体的首地址
struct globalmem_dev *dev = container_of(inode->i_cdev, struct globalmem_dev, cdev);// 通过结构体变量中某个成员的首地址获得整个结构体变量的首地址。
filep->private_data = dev;// 设置私有数据,将文件的私有数据private_data指向设备结构体的实例,体现Linux的面向对象的设计思想

return 0;
}

static int globalmem_release(struct inode *inode, struct file *filep)
{
printk(KERN_INFO "globalmem_release\n");
return 0;
}

// globalmem设备驱动的文件操作结构体
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,
};
 
void globalmem_setup_cdev(struct globalmem_dev *dev, int minor)
{
int err, devno = MKDEV(globalmem_major, minor);

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, minor);
}

static int __init global_mem_init(void)
{
int ret;
int i;

dev_t devno = MKDEV(globalmem_major, 0); /* 主设备号、次设备号合并为设备号 */
if (globalmem_major)
ret = register_chrdev_region(devno, DEVICE_NUM, DEV_NAME); // 静态分配设备号
else {
ret = alloc_chrdev_region(&devno, 0, DEVICE_NUM, DEV_NAME); // 动态分配设备号
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_kmalloc;
}

for (i = 0; i < DEVICE_NUM; i++)
globalmem_setup_cdev(globalmem_devp + i, i);// 注册字符设备


    printk(KERN_INFO "----global_mem_init----\n");
    return 0; // 表示成功

fail_kmalloc:
unregister_chrdev_region(devno, DEVICE_NUM); // 注销分配设备号
return ret;
}


static void __exit global_mem_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);// 注销分配设备号

    printk(KERN_INFO "----global_mem_exit----\n");
}

module_init(global_mem_init);
module_exit(global_mem_exit);

MODULE_VERSION("v1.0");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);

MODULE_ALIAS(DRIVER_DESC);

3、Makefile

ifeq ($(KERNELRELEASE),)
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:                               
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:                                             
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
else
    obj-m := global_mem.o

endif

4、验证

支持N个globalmem设备的驱动,在加载模块后需创建多个设备节点,如运行mknod /dev/globalmem0 c 230 0,使得/dev/globalmem0对应主设备号为globalmem_major、次设备号为0的设备,运行mknod /dev/globalmem1 c 230 1,使得/dev/globalmem1对应主设备号为globalmem_major、次设备号为1的设备。分别读写/dev/globalmem0和/dev/globalmem1,发现都读写到了正确的对应的设备。

二、总结

字符设备是三大类设备(字符设备、块设备和网络设备)中的一类,其驱动程序完成的主要工作是初始化、添加和删除cdev结构体,申请和释放设备号,以及填充file_operations结构体中的操作函数,实现file_operations结构体中的read()、write()和ioctl()等函数是驱动设计的主体工作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值