代码
先把完整代码粘上,后面再逐个解释
#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>
#define GLOBALMEM_SIZE 0x1000
#define GLOBALMEM_MAJOR 0
#define GLOBALMEM_MAGIC 'g'
#define MEM_CLEAR _IO(GLOBALMEM_MAGIC, 0)
#define GLOBALMEM_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 ssize_t globalmem_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 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(buff, dev->mem + p, count)){
ret = -EFAULT;
}else{
*ppos += count;
ret = count;
printk(KERN_INFO"read %u bytes from %lu\n", count, p);
}
mutex_unlock(&dev->mutex);
return ret;
}
static ssize_t globalmem_write(struct file *filp, const char __user *buff, size_t size, loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret;
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, 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;
}
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;
}
static int globalmem_release(struct inode *inode, struct file *filp)
{
return 0;
}
static loff_t globalmem_llseek(struct file *filp, loff_t offset, int arg)
{
loff_t ret = 0;
switch(arg){
case 0://begin from the start
if (offset < 0)
{
ret = -EINVAL;
break;
}
if ((unsigned int)offset > GLOBALMEM_SIZE){
ret = -EINVAL;
break;
}
filp->f_pos = offset;
ret = filp->f_pos;
break;
case 1://begin from current position
if ((unsigned int)offset + (unsigned int)filp->f_pos < 0)
{
ret = -EINVAL;
break;
}
if ((unsigned int)offset + (unsigned int)filp->f_pos > GLOBALMEM_SIZE)
{
ret = -EINVAL;
break;
}
filp->f_pos += offset;
ret = filp->f_pos;
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
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 const struct file_operations globalmem_fops = {
.owner = THIS_MODULE,
.llseek = globalmem_llseek,
.read = globalmem_read,
.write = globalmem_write,
.open = globalmem_open,
.release = globalmem_release,
.unlocked_ioctl = globalmem_ioctl,
};
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 i = 0;
int ret;
//allocate dev_t
dev_t devno = MKDEV(globalmem_major, 0);
if (globalmem_major)
{
ret = register_chrdev_region(globalmem_major, GLOBALMEM_NUM, "globalmem");
}else
{
ret = alloc_chrdev_region(&devno, 0, GLOBALMEM_NUM, "globalmem");
globalmem_major = MAJOR(devno);
}
if (ret < 0)
{
return ret;
}
// allocate memory for device
globalmem_devp = kzalloc(sizeof(struct globalmem_dev) * GLOBALMEM_NUM, GFP_KERNEL);
if (!globalmem_devp)
{
ret = -ENOMEM;
goto fail_malloc;
}
mutex_init(&globalmem_devp->mutex);
//register cdev
for (i = 0; i < GLOBALMEM_NUM; ++i)
{
globalmem_setup_cdev(globalmem_devp, i);
}
return 0;
fail_malloc:
unregister_chrdev_region(devno, GLOBALMEM_NUM);
return ret;
}
module_init(globalmem_init);
static void __exit globalmem_exit(void)
{
int i = 0;
for (i = 0; i < GLOBALMEM_NUM; ++i)
cdev_del(&(globalmem_devp+i)->cdev);
kfree(globalmem_devp);
unregister_chrdev_region(MKDEV(globalmem_major, 0), GLOBALMEM_NUM);
}
module_exit(globalmem_exit);
MODULE_LICENSE("Dual BSD/GPL");
解析
列表内容
有这么一个globalmem字符设备,该设备只是“虚拟的”用内存开辟出来的设备,只是我等新人练手用,并无实际用处。
该设备在代码中用结构体来表示
struct globalmem_dev{
struct cdev cdev; //字符设备最核心的内容,该结构体表明globalmem是一个字符设备
unsigned char mem[GLOBALMEM_SIZE];//该设备其实就是一段内存
struct mutex mutex;//互斥体
};
struct globalmem_dev *globalmem_devp;//指向具体设备的指针,会在初始化的时候开辟内存,重点关注这是一个**全局变量**,多线程有可能对此资源争夺发生竞争,因此要有加锁的意识
驱动入口函数 XXX_init
一般需要完成的工作大致顺序:
1>申请设备号
2>cdev的注册
1>> 为第n个设备分配设备号
2>> 初始化cdev结构,并将其和file_operations关联起来cdev_init
3>>cdev所有者设置为THIS_MODULE
4>>将设备添加到系统
3>初始化互斥体结构,一般互斥体上锁都在即将操作临界区(共享资源)时,比如本例都是即将访问设备结构devp时上锁。
static int __init globalmem_init(void){
int i = 0;
int ret;
dev_t devno = MKDEV(globalmem_major, 0);//构造设备号dev_t
if (globalmem_major)
{
//分配已知的设备号,关联的设备名称(将出现在/proc/devices和sysfs中)
ret = register_chrdev_region(devno, GLOBALMEM_NUM, "globalmem");
}else
{
//动态分配设备号,将分配好的设备号赋给devno,与设备名称关联
ret = alloc_chrdev_region(&devno, 0, GLOBALMEM_NUM, "globalmem");
//取得主设备号
globalmem_major = MAJOR(devno);
}
if (ret < 0)
{
return ret;
}
// 为设备分配内存
globalmem_devp = kzalloc(sizeof(struct globalmem_dev) * GLOBALMEM_NUM, GFP_KERNEL);
if (!globalmem_devp)
{
ret = -ENOMEM;
goto fail_malloc;
}
mutex_init(&globalmem_devp->mutex);//初始化互斥体
//为设备注册cdev
for (i = 0; i < GLOBALMEM_NUM; ++i)
{
globalmem_setup_cdev(globalmem_devp, i);
}
return 0;
fail_malloc:
unregister_chrdev_region(devno, GLOBALMEM_NUM);
return ret;
}
module_init(globalmem_init);
static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
int err, devno = MKDEV(globalmem_major, index);//设备号
cdev_init(&dev->cdev, &globalmem_fops);//初始化cdev结构,并将其和file_operations关联起来
dev->cdev.owner = THIS_MODULE;//设置所有者
err = cdev_add(&dev->cdev, devno, 1);//将设备添加到系统
if (err)
printk(KERN_NOTICE"ERROR %d adding globalmem %d", err, index);
}
打开设备open函数和释放设备release函数
open函数最重要的一步就是:将dev设备地址交给filp结构体的private_data,后面的read()、write等函数将可以通过filp结构体的private_data来取得设备地址
release函数一般释放由open分配的、保存在filp->private_data中的内容(该例子无需操作),在最后一次关闭操作时关闭设备
static int globalmem_open(struct inode *inode, struct file *filp)
{
struct globalmem_dev *dev = container_of(inode->i_cdev, struct globalmem_dev, cdev);
/*********************************************
* 重要代码块 将dev设备地址交给filp结构体的private_data
**********************************************/
filp->private_data = dev;
return 0;
}
static int globalmem_release(struct inode *inode, struct file *filp)
{
return 0;
}
设备读写函数read(),write()
读函数read()一般操作
通过filp->private_data来获得设备dev(这里显示出open函数给filp->private_data赋值的重要性)
copy_to_user函数:从设备中读取数据复制给用户内存(该例子中将设备mem中的内容赋给用户的buff)
static ssize_t globalmem_read(struct file *filp, char __user *buff, size_t size, loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
/**********************************************************
* 通过filp->private_data来获得设备dev
**********************************************************/
struct globalmem_dev *dev = filp->private_data;
if (p >= GLOBALMEM_SIZE)//p是偏移量(要从这里开始读取),若是偏移量大于设备mem的大小,直接返回
return 0;
if (count > GLOBALMEM_SIZE - p)//count是读取字节数
count = GLOBALMEM_SIZE - p;
mutex_lock(&dev->mutex);
if (copy_to_user(buff, dev->mem + p, count)){//设备数据拷贝给用户
ret = -EFAULT;
}else{
*ppos += count;//偏移量要修改,下一次读取从新的偏移量开始读取
ret = count;//返回读取字节数
printk(KERN_INFO"read %u bytes from %lu\n", count, p);
}
mutex_unlock(&dev->mutex);
return ret;
}
写函数write()一般操作
通过filp->private_data来获得设备dev(这里显示出open函数给filp->private_data赋值的重要性)
copy_from_user函数:用户数据写给设备(这里是将用户数据写到dev的mem中)
static ssize_t globalmem_write(struct file *filp, const char __user *buff, size_t size, loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret;
/**********************************************************
* 通过filp->private_data来获得设备dev
**********************************************************/
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, 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;
}
修改文件当前读写函数llseek
static loff_t globalmem_llseek(struct file *filp, loff_t offset, int arg)
{
loff_t ret = 0;
switch(arg){
case 0://begin from the start
if (offset < 0)
{
ret = -EINVAL;
break;
}
if ((unsigned int)offset > GLOBALMEM_SIZE){
ret = -EINVAL;
break;
}
filp->f_pos = offset;//偏移量赋给filp->f_pos
ret = filp->f_pos;
break;
case 1://begin from current position
if ((unsigned int)offset + (unsigned int)filp->f_pos < 0)
{
ret = -EINVAL;
break;
}
if ((unsigned int)offset + (unsigned int)filp->f_pos > GLOBALMEM_SIZE)
{
ret = -EINVAL;
break;
}
filp->f_pos += offset;//当前位置加上偏移量
ret = filp->f_pos;
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
执行设备特殊命令的函数ioctl()
1.首先要创建ioctl命令号(幻数+序号),约定方法为:
选择一个幻数号码(type),通过宏定义来构造命令号
_IO(type, nr) 构造无参数的命令编号;nr表示用户自主取的序数
_IOR(type, nr, datatype)构造从驱动程序中读取数据的命令编号;datatype表示数据编号
_IOW(type,nr,datatype)用于写入数据的命令
_IOWR(type,nr,datatype)用于双向传输
static long globalmem_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
/***************************************************
*struct globalmem_dev *dev = filp->private_data;
***************************************************/
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;
}
驱动卸载函数XXX_exit
卸载函数一般顺序:
从系统中移除cdev设备
释放设备开辟的内存
释放设备号
static void __exit globalmem_exit(void)
{
int i = 0;
for (i = 0; i < GLOBALMEM_NUM; ++i)
cdev_del(&(globalmem_devp+i)->cdev);//从系统中移除cdev设备
kfree(globalmem_devp);//释放设备开辟的内存
unregister_chrdev_region(MKDEV(globalmem_major, 0), GLOBALMEM_NUM);//释放设备编号
}
Makefile
刚开始KERNELRELEASE未定义,执行else,KERNELDIR表示内核源码库目录,PWD表示模块源码目录;
make会执行两次,第一次进入‘-c’后面KERNELDIR的目录,执行该目录下的Makefile,并且给KERNELRELEASE定义;
第二次执行‘M=’后面目录的Makefile,即此时的Makefile,所以又从头开始执行;
KERNELRELEASE已经定义,将会执行obj-m := globalmem.o,说明有一个模块将从目标文件globalmem.o中构造,从该目标文件中构造的模块名称为globalmem.ko。
ifneq ($(KERNELRELEASE),)
obj-m := globalmem.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *.order *symvers *.ko *.mod.c
endif
验证
- 执行make生成globalmem.ko文件
- 加载模块 insmod globalmem.ko
注:ubuntu一般用户执行该命令时,需执行sudo insmod globalmem.ko - 创建设备文件 mknod /dev/globalmem c (major)0注:c表示面向字符设备0表示次设备号(major)表示主设备号(可通过cat /proc/devices查询)
- 在root状态下执行 echo ‘hello drivers’ > /dev/globalmem (会执行write函数)
在root状态下执行 cat /dev/globalmem (会执行read函数) 会在控制终端显示“hello drivers” - 查看驱动主设备号 cat /proc/devices
查看模块信息 lsmod | grep “globalmem”
查看printk信息 dmesg | tail (实际上是查看/var/log/messages的信息)
本文详细解析了《Linux设备驱动开发详解》中的globalmem字符设备驱动,包括驱动入口函数、设备打开与释放、读写函数、ioctl命令及驱动卸载过程。通过实例展示了如何在Linux中创建和操作虚拟内存设备。

2362

被折叠的 条评论
为什么被折叠?



