• Linux字符设备驱动实例—globalmem驱动


    1、globalmem虚拟设备实例

    globalmem为“全局内存”的意思,在globalmem字符设备中会分配一片大小为GLOBALMEM_SIZE(4KB)的内存空间,并在驱动中提供对这片内存的读写、控制和定位函数,供用户空间的进程能通过Linux系统调用获取和设置这片内存。

    (1)头文件、宏以及设备结构体

    #include <linux/module.h>
    #include <linux/init.h>
    #include <linux/fs.h>
    #include <linux/cdev.h>
    #include <linux/slab.h>
    #include <linux/uaccess.h>
    
    #define GLOBALMEM_SIZE  0x1000
    #define MEM_CLEAR  0x1
    #define GLOBALMEM_MAJOR  230
    
    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 globalmem_dev *globalmem_devp;

    定义的globalmem_dev结构体中,包含了对应于globalmem字符设备的cdev,使用的内存mem[GLOBALMEM_SIZE]。

    (2)globalmem设备驱动模块的加载和卸载函数

    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 ret;
        dev_t devno = MKDEV(globalmem_major, 0);
    
        if (globalmem_major) {
            ret = register_chrdev_region(devno, 1, "globalmem");
        } else {
            ret = alloc_chrdev_region(&devno, 0, 1, "globalmem");
            globalmem_major = MAJOR(devno);
        }
        if (ret < 0)
            return ret;
    
        globalmem_devp = kzalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
        if (!globalmem_devp) {
            ret = -ENOMEM;
            goto fail_malloc;
        }
    
        globalmem_setup_cdev(globalmem_devp, 0);
        return 0;
    
    fail_malloc:
        unregister_chrdev_region(devno, 1);
        return ret;
    }
    
    static void __exit globalmem_exit(void)
    {
        cdev_del(&globalmem_devp->cdev);
        kfree(globalmem_devp);
        unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);
    }

    globalmem_setup_cdev()函数完成cdev的初始化化和添加,kzalloc()申请了一份globalmem_dev结构体的内存,并将其清0,在cdev_init()函数中,与globalmem的cdev关联的file_operations结构体如下所示:

    static const struct file_operations globalmem_fops = {
        .owner = THIS_MODULE,
        .open = globalmem_open,
        .release = globalmem_release,
        .read = globalmem_read,
        .write = globalmem_write,
        .llseek = globalmem_llseek,
        .unlocked_ioctl = globalmem_ioctl,
    };

    (3)读写函数的实现

    首先是读函数,函数的实现如下所示:

    static ssize_t globalmem_read(struct file *filp, 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_to_user(buf, dev->mem + p, count)) {
            ret = -EFAULT;
        } else {
            *ppos += count;
            ret = count;
    
            printk(KERN_INFO "read %u bytes(s) from %lu
    ", count, p);
        }
    
        return ret;
    }

    其中*ppos是读的位置相对于文件开头的漂移,如果该漂移大于或等于GLOBALMEM_SIZE,表示文件已经到了末尾,返回0(EOF)。

    写函数的实现如下所示:

    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)) {
            return -EFAULT;
        } else {
            *ppos += count;
            ret = count;
    
            printk(KERN_INFO "written %u bytes(s) from %lu
    ", count, p);
        }
    
        return ret;
    }

    (4)seek函数的实现

    seek()函数对文件定位的起始地址可以是文件开头(SEEK_SET,0)、当前位置(SEEK_CUR,1)和文件末尾(SEEK_END,2),在定位的时候,要检查用户请求的合法性,若不合法,函数返回错误号,若合法,更新文件的当前位置,并返回新的位置,实现如下所示:

    static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
    {
        loff_t ret = 0;
        switch (orig) {
        case 0:
            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:
            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;
    }

    (5)ioctl函数实现

    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
    ");
            break;
        default:
            return -EINVAL;
        }
    
        return 0;
    }

    (6)使用文件的私有数据

    将文件的私有数据private_data指向设备的结构体,然后使用read()、write()、ioctl()、llseek()等函数通过private_data访问设备结构体,如下所示:

    static int globalmem_open(struct inode *inode, struct file *filp)
    {
        filp->private_data = globalmem_devp;
        return 0;
    }
    
    static int globalmem_release(struct inode *inode, struct file *filp)
    {
        return 0;
    }

    (7)完整的globalmem驱动代码

    #include <linux/module.h>
    #include <linux/init.h>
    #include <linux/fs.h>
    #include <linux/cdev.h>
    #include <linux/slab.h>
    #include <linux/uaccess.h>
    
    #define GLOBALMEM_SIZE  0x1000
    #define MEM_CLEAR  0x1
    #define GLOBALMEM_MAJOR  230
    
    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 globalmem_dev *globalmem_devp;
    
    static int globalmem_open(struct inode *inode, struct file *filp)
    {
        filp->private_data = globalmem_devp;
        return 0;
    }
    
    static int globalmem_release(struct inode *inode, struct file *filp)
    {
        return 0;
    }
    
    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
    ");
            break;
        default:
            return -EINVAL;
        }
    
        return 0;
    }
    
    static ssize_t globalmem_read(struct file *filp, 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_to_user(buf, dev->mem + p, count)) {
            ret = -EFAULT;
        } else {
            *ppos += count;
            ret = count;
    
            printk(KERN_INFO "read %u bytes(s) from %lu
    ", count, p);
        }
    
        return ret;
    }
    
    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)) {
            return -EFAULT;
        } else {
            *ppos += count;
            ret = count;
    
            printk(KERN_INFO "written %u bytes(s) from %lu
    ", count, p);
        }
    
        return ret;
    }
    
    static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
    {
        loff_t ret = 0;
        switch (orig) {
        case 0:
            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:
            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;
    }
    
    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,
    };
    
    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 ret;
        dev_t devno = MKDEV(globalmem_major, 0);
    
        if (globalmem_major) {
            ret = register_chrdev_region(devno, 1, "globalmem");
        } else {
            ret = alloc_chrdev_region(&devno, 0, 1, "globalmem");
            globalmem_major = MAJOR(devno);
        }
        if (ret < 0)
            return ret;
    
        globalmem_devp = kzalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
        if (!globalmem_devp) {
            ret = -ENOMEM;
            goto fail_malloc;
        }
    
        globalmem_setup_cdev(globalmem_devp, 0);
        return 0;
    
    fail_malloc:
        unregister_chrdev_region(devno, 1);
        return ret;
    }
    
    static void __exit globalmem_exit(void)
    {
        cdev_del(&globalmem_devp->cdev);
        kfree(globalmem_devp);
        unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);
    }
    
    module_init(globalmem_init);
    module_exit(globalmem_exit);
    
    MODULE_AUTHOR("HLY");
    MODULE_LICENSE("GPL");

    (8)globalmem驱动验证

    使用make命令将源文件编译出驱动模块globalmem.ko文件,编译需要的Makefile如下所示:

    # Makefile for globalmem driver
    
    obj-m += globalmem.o
    
    all:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
    
    clean:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

    然后使用驱动模块命令加载模块,如下:

    $ sudo insmod globalmem.ko
    $ lsmod

    然后使用下面的命令查看globalmem虚拟设备的设备号:

    $ cat /proc/devices

    然后,使用mknod创建设备节点:

    # mknod /dev/globalmem c 230 0
    # ls -al /dev/globalmem

    接下来使用命令对该文件进行读写以测试:

    # echo “Hello World” > /dev/globalmem
    # cat /dev/globalmem

    也可以使用系统调用函数open、write和read进行该虚拟设备的测试,测试的app.c文件如下:

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <string.h>
    
    #define LENGTH  100
    
    int main(int argc, char *argv[])
    {
        int fd,len;
        char str[LENGTH];
    
        fd = open("/dev/globalmem", O_RDWR);
        if (fd) {
            write(fd, "Hello World", strlen("Hello World"));
            close(fd);
        }
    
        fd = open("/dev/globalmem", O_RDWR);
        len = read(fd, str, LENGTH);
        str[len] = '';
        printf("str:%s
    ", str);
        close(fd);
    
        return 0;
    }

    编写该app.c的Makefile文件,如下:

    # Makefile by HLY
    
    all: myapp
    
    # Which compiler
    CC = gcc
    
    # Where are include files
    INCLUDE = .
    
    # Options for development
    CFLAGS = -g -Wall -ansi
    
    myapp: app.o
        $(CC) -o myapp app.o
    
    clean:
        rm -rf *.o myapp

    使用make命令将app.c编译成可执行文件myapp,然后执行程序,即可完成globalmem虚拟设备/dev/globalmem的读写测试。

    参考:

     《Linux设备驱动开发详解:基于最新的Linux 4.0内核》

  • 相关阅读:
    No architectures to compile for (ONLY_ACTIVE_ARCH=YES, active arch=arm64, VALID_ARCHS=armv7 armv7s).
    播放器 倒计时 闹钟 日期 分秒 时间算法
    iOS 8 以后获取地图坐标:
    数据存储(直接写入、NSUserDefaults、NSkeyedArchiver)
    图片处理 模糊效果
    手把手教你Windows下Go语言的环境搭建
    github 上传或删除 文件 命令
    域名解析-delphi 源码
    指针与引用
    指针
  • 原文地址:https://www.cnblogs.com/Cqlismy/p/11360459.html
Copyright © 2020-2023  润新知