博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
linux驱动:[2]字符设备驱动memdev(cdev结构解析)
阅读量:4192 次
发布时间:2019-05-26

本文共 6610 字,大约阅读时间需要 22 分钟。

linux驱动:[2]字符设备驱动memdev

Linux 内存模拟字符设备 驱动程序

测试平台:

代码一览(解析见下方)

驱动程序以及Makefile如下:

  • memdev.c:
#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifndef MEMDEV_MAJOR#define MEMDEV_MAJOR 254#endif#ifndef MEMDEV_NR_DEVS#define MEMDEV_NR_DEVS 2#endif#ifndef MEMDEV_SIZE#define MEMDEV_SIZE 4096#endifstruct mem_dev { char *data; unsigned long size;};static int mem_major = MEMDEV_MAJOR;module_param(mem_major, int, S_IRUGO);struct mem_dev *mem_devp;struct cdev cdev;int mem_open(struct inode *inode, struct file *filp){ struct mem_dev *dev; int num = MINOR(inode->i_rdev); if (num >= MEMDEV_NR_DEVS) return -ENODEV; dev = &mem_devp[num]; filp->private_data = dev; return 0;}int mem_release(struct inode *inode, struct file *filp){ return 0;}static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *poss){ unsigned long p = *poss; unsigned int count = size; int ret = 0; struct mem_dev *dev = filp->private_data; if (p >= MEMDEV_SIZE) return 0; if (count > MEMDEV_SIZE-p) count = MEMDEV_SIZE-p; if(copy_to_user(buf, (void*)(dev->data + p), count)) { ret = -EFAULT; } else { *poss += count; ret = count; printk(KERN_INFO "read %d bytes from %lu\n", count, p); } return ret;}static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *poss){ unsigned long p = *poss; unsigned int count = size; int ret = 0; struct mem_dev *dev = filp->private_data; if (p >= MEMDEV_SIZE) return 0; if (count > MEMDEV_SIZE-p) count = MEMDEV_SIZE - p; if (copy_from_user(dev->data + p, buf, count)) { ret = -EFAULT; } else { *poss += count; ret = count; printk(KERN_INFO "write %d bytes from %lu\n", count, p); } return ret;}static loff_t mem_llseek(struct file *filp, loff_t offset, int whence){ loff_t newpos; switch (whence) { case 0: newpos = offset; break; case 1: newpos = filp->f_pos + offset; break; case 2: newpos = MEMDEV_SIZE - 1 + offset; break; default: return -EINVAL; } if ((newpos < 0) || (newpos > MEMDEV_SIZE)) return -EINVAL; filp->f_pos = newpos; return newpos;}static const struct file_operations mem_fops ={ .owner = THIS_MODULE, .llseek = mem_llseek, .read = mem_read, .write = mem_write, .open = mem_open, .release = mem_release,};static int memdev_init(void){ int result; int i; dev_t devno = MKDEV(mem_major, 0); if (mem_major) result = register_chrdev_region(devno, 2, "memdev"); else { result = alloc_chrdev_region(&devno, 0, 2, "memdev"); mem_major = MAJOR(devno); } if (result < 0) return result; cdev_init(&cdev, &mem_fops); cdev.owner = THIS_MODULE; cdev.ops = &mem_fops; cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS); mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL); if (!mem_devp) { result = -ENOMEM; goto fail_malloc; } memset(mem_devp, 0, MEMDEV_NR_DEVS * sizeof(struct mem_dev)); for (i = 0; i < MEMDEV_NR_DEVS; i++) { mem_devp[i].size = MEMDEV_SIZE; mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL); memset(mem_devp[i].data, 0, MEMDEV_SIZE); } printk("memdev init success\n"); return 0;fail_malloc: unregister_chrdev_region(devno, 2); return result;}static void memdev_exit(void){ cdev_del(&cdev); kfree(mem_devp); unregister_chrdev_region(MKDEV(mem_major, 0), 2); printk("memdev exit success\n");}MODULE_AUTHOR("Ziping Chen
");MODULE_LICENSE("GPL");module_init(memdev_init);module_exit(memdev_exit);
  • Makefile:
obj-m := memdev.o #编译进模块KERNELDIR := /lib/modules/4.11.0-rc4-00064-g89970a0-dirty/build #此处为linux内核库目录PWD := $(shell pwd) #获取当前目录OUTPUT := $(obj-m) $(obj-m:.o=.ko) $(obj-m:.o=.mod.o) $(obj-m:.o=.mod.c) modules.order Module.symversmodules:    $(MAKE) -C $(KERNELDIR) M=$(PWD) modulesclean:    rm -rf $(OUTPUT)

在shell中使用以下命令装载驱动程序:

(这里以主设备号为181进行测试)

$ make$ insmod memdev.ko mem_major=181$ mknod /dev/memdev0 c 181 0

使用linux c进行测试:

  • memapp.c:
#include 
#include
#include
#include
#include
int main(){ int fd; char buf[4096]; strcpy(buf,"memory simulate char device test...\n"); printf("original buf:%s\n",buf); fd = open("/dev/memdev0",O_RDWR); if (fd == -1) { printf("open memdev failed!\n"); return -1; } write(fd, buf, sizeof(buf)); lseek(fd, 0, SEEK_SET); strcpy(buf, "nothing here"); read(fd, buf, sizeof(buf)); printf("read buf:%s\n", buf); return 0;}

进行编译、测试:

$ gcc -o memapp memapp.c

实验成功!


代码解析:

一、分配设备号

if (mem_major)        result = register_chrdev_region(devno, 2, "memdev");else {        result = alloc_chrdev_region(&devno, 0, 2, "memdev");        mem_major = MAJOR(devno);}

如果定义的参数mem_major不为0(上面测试用了181),则进行静态分配

register_chrdev_region(devno, 2, "memdev");//静态分配设备号为devno的设备

如果mem_major为0则进行动态分配

alloc_chrdev_region(&devno, 0, 2, "memdev");//动态分配主设备号为devno,次设备号为0的设备

二、初始化cdev结构

/linux/include/linux/cdev.h:

struct cdev {    struct kobject kobj;//每个 cdev 都是一个 kobject    struct module *owner;//指向实现驱动的模块    const struct file_operations *ops;//操纵这个字符设备文件的方法    struct list_head list;//与 cdev 对应的字符设备文件的 inode->i_devices 的链表头    dev_t dev;//起始设备编号    unsigned int count;//设备范围号大小};

一个 cdev 一般它有两种定义初始化方式:静态的和动态的。

静态内存定义初始化:

struct cdev my_cdev;cdev_init(&my_cdev, &fops);my_cdev.owner = THIS_MODULE;

动态内存定义初始化:

struct cdev *my_cdev = cdev_alloc();my_cdev->ops = &fops;my_cdev->owner = THIS_MODULE;

两种使用方式的功能是一样的,只是使用的内存区不一样,一般视实际的数据结构需求而定。

源码分析:

struct cdev *cdev_alloc(void){   struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);   if (p) {       INIT_LIST_HEAD(&p->list);       kobject_init(&p->kobj, &ktype_cdev_dynamic);   }   return p;}
void cdev_init(struct cdev *cdev, const struct file_operations *fops){   memset(cdev, 0, sizeof *cdev);   INIT_LIST_HEAD(&cdev->list);   kobject_init(&cdev->kobj, &ktype_cdev_default);   cdev->ops = fops;}

可见,两个函数完成都功能基本一致。

三、添加cdev

初始化 cdev 后,需要把它添加到系统中去。为此可以调用 cdev_add() 函数。传入 cdev 结构的指针,起始设备编号,以及设备编号范围。

int cdev_add(struct cdev *p, dev_t dev, unsigned count){   p->dev = dev;   p->count = count;   return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);}

简单地说,设备驱动程序通过调用cdev_add把它所管理的设备对象的指针嵌入到一个类型为struct probe的节点之中,然后再把该节点加入到cdev_map所实现的哈希链表中。

对系统而言,当设备驱动程序成功调用了cdev_add之后,就意味着一个字符设备对象已经加入到了系统,在需要的时候,系统就可以找到它。对用户态的程序而言,cdev_add调用之后,就已经可以通过文件系统的接口调用到我们的驱动程序。

四、卸载cdev

当一个字符设备驱动不再需要的时候(比如模块卸载),就可以用 cdev_del() 函数来释放 cdev 占用的内存。

void cdev_del(struct cdev *p){   cdev_unmap(p->dev, p->count);   kobject_put(&p->kobj);}

其中 cdev_unmap() 调用 kobj_unmap() 来释放 cdev_map 散列表中的对象。kobject_put() 释放 cdev 结构本身。


  • 我的个人主页:
  • 我的个人站点博客:
  • 我的CSDN博客:
  • 我的简书:
  • 我的GitHub:
    欢迎相互follow~

转载地址:http://onloi.baihongyu.com/

你可能感兴趣的文章
三大运营商突遭纽交所“摘牌”,回应......
查看>>
4090万美元成交!马斯克又卖出三处住宅以兑现“无房产”诺言
查看>>
支付宝2020年度账单出炉!又是白忙活的一年......
查看>>
90后互联网打工人:为了买小两居,爸妈打零工帮我凑首付
查看>>
“洗净净”、“洗香香”、“洗爽爽” 京东又申请了这些商标
查看>>
戴志坚接替李小加出任职港交所行政总裁 基本年薪700万港元
查看>>
Redmi K40 Pro渲染图曝光:后置相机模组成最大焦点
查看>>
网友泪崩,虾米音乐宣布关停!一代人的音乐回忆落幕
查看>>
长城汽车申请“哈弗单身狗”、“哈弗奶狗”、“哈弗溜狗”等商标
查看>>
为什么所有的APP都在炫富?
查看>>
社区团购重启,有钱的巨头们不能再为所欲为
查看>>
抖音发布2020数据报告:日均视频搜索量破4亿,70后最爱发表情包
查看>>
全球首富换人了!一年内财富增加超1500亿美元,本人淡定回应...
查看>>
纽交所再次反转重新推动“摘牌” 三大运营商回应来了
查看>>
FF官宣新CFO推进融资和产品交付 贾跃亭激动发声
查看>>
新年首发成功!SpaceX“猎鹰9号”火箭发射升空
查看>>
广告创意还是侮辱女性?全棉时代卸妆巾广告被骂上热搜......
查看>>
漫画《灌篮高手》将拍电影?井上雄彦发文确认
查看>>
一加9系列搭载徕卡相机实锤?刘作虎:力争影像功能全球第一
查看>>
失敬,这届年轻人居然这么会搞钱
查看>>