linux设备驱动开发之字符设备驱动结构

字符设备关键结构体

struct cdev结构体

// include/linux/cdev.h
struct cdev {
	struct kobject kobj;
	struct module *owner;
	// 驱动设备的操作接口结构体指针,例如读、写、控制等操作
	const struct file_operations *ops;
	// 链表,用来记录多个cdev结构体对象的内存位置
	struct list_head list;
	// 设备号,由主设备号和次设备号共同合成
	dev_t dev;
	// 使用该结构体信息的设备数量
	unsigned int count;
};

struct file_operations结构体

// include/linux/fs.h
struct file_operations {
	struct module *owner;
	// 定位函数指针,用来定位设备驱动中的文件,与用户端的lseek对应
	loff_t (*llseek) (struct file *, loff_t, int);
	// 读函数指针,用来读设备驱动中的文件,与用户端的read对应
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	// 写函数指针,用来写设备驱动中的文件,与用户端的write对应
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	int (*readdir) (struct file *, void *, filldir_t);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	// io控制函数指针,用来控制设备驱动中的IO文件,与用户端的ioctl对应
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	// 打开函数指针,用来打开设备驱动中的文件,与用户端的open对应
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, loff_t, loff_t, int datasync);
	int (*aio_fsync) (struct kiocb *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
	int (*check_flags)(int);
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
	int (*setlease)(struct file *, long, struct file_lock **);
	long (*fallocate)(struct file *file, int mode, loff_t offset,
			  loff_t len);
};

字符设备驱动概述

linux设备驱动开发之字符设备驱动结构

  1. 字符设备对应着cdev、file_operation这2个结构体。
  2. 字符设备驱动对应着file_operation中的操作接口,类似read、write、ioctl等。
  3. 用户空间使用字符设备的方法是通过条用用户态的read、write、ioctl等接口,来间接调用字符设备驱动中的接口,从而达到使用驱动的目的。
  4. 字符设备使用的前提是,先加载模块函数,在加载函数中,先初始化cdev、file_operation结构体,建立对象,然后将这些结构体信息绑定到设备驱动的kobject map上;如果不想使用,需要卸载这个字符设备时,就要调用卸载函数,在卸载函数中,先解除与kobject map的绑定,然后将上面那些结构体对象释放掉。

字符设备实例

/**
 ** This file is part of the LinuxTrainningHome project.
 ** Copyright(C) duanzhonghuan Co., Ltd.
 ** All Rights Reserved.
 ** Unauthorized copying of this file, via any medium is strictly prohibited
 ** Proprietary and confidential
 **
 ** Written by ZhongHuan Duan <[email protected]>, 2019-05-12
 **/


#include "linux/init.h"
#include "linux/module.h"
#include "linux/kdev_t.h"
#include "linux/cdev.h"
#include "linux/fs.h"
#include "linux/slab.h"
#include "linux/uaccess.h"

#define  GLOBALMEM_SIZE  (0X1000)
#define  GLOBALMEM_MAJOR  (230)
#define  DEVICE_NUM  (1)
#define  MEM_CLEAR  (0x01)
static int globalmem_major = GLOBALMEM_MAJOR;
#define globalmem_debug

/**
 * @brief The globalmem_dev struct - the description of the global memory
 */
struct globalmem_dev
{
    struct cdev chrdev;
    unsigned char mem[GLOBALMEM_SIZE];
};
static struct globalmem_dev *globalmem_devp = NULL;

/**
 * @brief globalmem_llseek - reposition read/write file offset
 * @param filep: the file pointer
 * @param offset: the offset
 * @param whence: SEEK_SET,SEEK_CUR,SEEK_END
 * @return: the actual offset, return -1 when failed
 */
static loff_t globalmem_llseek (struct file *filep, loff_t offset, int whence)
{
    loff_t ret = 0;
    switch (whence)
    {
        case SEEK_SET:
            if (offset < 0 || offset > GLOBALMEM_SIZE)
            {
                ret = -EINVAL;
                break;
            }
            filep->f_pos = offset;
            ret = offset;
        break;
        case SEEK_CUR:
            if (offset < 0 || filep->f_pos + offset > GLOBALMEM_SIZE)
            {
                ret = -EINVAL;
                break;
            }
            filep->f_pos += offset;
            ret = filep->f_pos;
        break;
        case SEEK_END:
            filep->f_pos = GLOBALMEM_SIZE;
            ret = filep->f_pos;
        break;
    default:
            ret = -EINVAL;
        break;
    }
    return ret;
}

/**
 * @brief globalmem_read
 * @param filep
 * @param buf
 * @param count
 * @param ppos
 * @return
 */
static ssize_t globalmem_read (struct file *filep, char __user *buf, size_t count, loff_t *ppos)
{
    ssize_t ret = 0;
    loff_t  curpos = *ppos;
    struct globalmem_dev *dev;
#ifdef globalmem_debug
    printk("globalmem_read\n");
#endif
    if (curpos >= GLOBALMEM_SIZE)
    {
        return 0;
    }
    dev = filep->private_data;
    if (!dev)
    {
        ret = -ENOMEM;
        return ret;
    }
    if (curpos + count > GLOBALMEM_SIZE)
    {
        count = GLOBALMEM_SIZE - curpos;
    }
    ret = copy_to_user(buf, dev->mem + curpos, count);
    *ppos += count;
    ret = count;
    return ret;
}

static ssize_t globalmem_write (struct file *filep, const char __user *buf, size_t count, loff_t *ppos)
{
    ssize_t ret = 0;
    loff_t  curpos = *ppos;
    struct globalmem_dev *dev;
#ifdef globalmem_debug
    printk("globalmem_write\n");
#endif
    if (curpos >= GLOBALMEM_SIZE)
    {
        ret = -ENAVAIL;
        return ret;
    }
    dev = filep->private_data;
    if (!dev)
    {
        ret = -ENOMEM;
        return ret;
    }
    if (curpos + count > GLOBALMEM_SIZE)
    {
        count = GLOBALMEM_SIZE - curpos;
    }
    ret = copy_from_user(dev->mem + curpos, buf, count);
    *ppos += count;
    ret = count;
    return ret;
}

/**
 * @brief globalmem_unlocked_ioctl
 * @param filep
 * @param cmd
 * @param arg
 * @return
 */
static long globalmem_unlocked_ioctl (struct file *filep, unsigned int cmd, unsigned long arg)
{
    struct globalmem_dev *dev = filep->private_data;
    switch (cmd)
    {
        case MEM_CLEAR:
        memset(dev->mem, 0, GLOBALMEM_SIZE);
        printk("mem clear success\n");
        break;
    default:
        break;
    }
    return 0;
}

static int globalmem_open (struct inode *inode, struct file *filep)
{
    struct globalmem_dev *dev = container_of(inode->i_cdev, struct globalmem_dev, chrdev);
    filep->private_data = dev;
#ifdef globalmem_debug
    printk("globalmem_open\n");
#endif
    return 0;
}

static int globalmem_release (struct inode *inode, struct file *filep)
{
    return 0;
}

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

/**
 * @brief globalmem_init_dev - initialize the global memory decice
 * @param chrdev: out parameter for a char device
 * @param index: the minor device id
 */
static void globalmem_init_dev(struct globalmem_dev *chrdev, int index)
{
    int ret = 0;
    dev_t devno = MKDEV(globalmem_major, index);
    cdev_init(&chrdev->chrdev, &chrdev_file_operations);
    // add the globalmem device structure to the kobj_map
    ret = cdev_add(&chrdev->chrdev, devno, 1);
    if (ret)
    {
        printk("Error code = %d when adding globalmem %d\n", ret, index);
    }
}

/**
 * @brief globalmem_init - the function of initializing the global memory
 * @return: the status of initializing the global memory
 */
static int __init globalmem_init(void)
{
    int i = 0;
    int ret = 0;
    // 1. get the device id
    dev_t devno = MKDEV(globalmem_major, 0);
#ifdef globalmem_debug
    printk(KERN_NOTICE "globalmem_init\n");
#endif
    // 2. register the DEVICE_NUM of the char device
    if (globalmem_major)
    {
        ret = register_chrdev_region(devno, DEVICE_NUM, "globalmem");
    }
    else
    {
        // automatic register char device
        ret = alloc_chrdev_region(&devno, 0, DEVICE_NUM, "globalmem");
    }
    // check the error code
    if (ret < 0)
    {
        return ret;
    }

    // 3. construct the globalmem devices structure in the heap
    globalmem_devp = kzalloc(sizeof(struct globalmem_dev) * DEVICE_NUM, GFP_KERNEL);
    if (!globalmem_devp)
    {
        ret = -ENOMEM;
        goto fail_malloc;
    }

    // 4. add the globalmem decices structure pointer to the kobjct map
    for (i = 0; i < DEVICE_NUM; i++)
    {
        globalmem_init_dev(globalmem_devp + i, i);
    }
    printk("globalmem_init success\n");
    return 0;

 fail_malloc:
    unregister_chrdev_region(devno, DEVICE_NUM);
    return ret;
}

/**
 * @brief globalmem_exit - exit the glboalmem device
 */
static void __exit globalmem_exit(void)
{
    int i = 0;
#ifdef globalmem_debug
    printk(KERN_NOTICE "globalmem_exit\n");
#endif
    // 1. remove the globalmem structure from teh kobject map
    for (i = 0; i < DEVICE_NUM; i++)
    {
        cdev_del(&(globalmem_devp + i)->chrdev);
    }

    // 2. free the glboalmem structure in the heap
    kfree(globalmem_devp);

    // 3. unregister the device id
    unregister_chrdev_region(MKDEV(globalmem_major, 0), DEVICE_NUM);

    // 4. remove the device id
    printk("globalmem_exit success\n");
}

module_init(globalmem_init)
module_exit(globalmem_exit)
// the declaration	of the author
MODULE_AUTHOR("ZhongHuan Duan <[email protected]>");
// the declaration of the licence
MODULE_LICENSE("GPL v2");

编译该文件使用的Makefile内容为:

KVERS = $(shell uname -r)

obj-m += globalmem.o

#EXTRA_CFLAGS=-g -o0

build: kernel_modules
kernel_modules:
	make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules
clean:
	make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean

编译得到.ko文件后,调整.ko文件权限,然后测试验证。

实例测试结果

  1. 加载.ko模块,创建globamem设备节点
sudo insmod globalmem.ko
sudo mknod /dev/globalmem c 230 0
sudo chmod 777 /dev/globalmem
  1. 往globalmem节点 中写入hello wrold
echo "hello world" > /dev/globalmem
  1. 读取glboalmem节点中的数据
cat   /dev/globalmem

linux设备驱动开发之字符设备驱动结构

虚拟文件系统有关globalmem的信息

  1. 查看/proc文件中关于globalmem的信息
cat /proc/devices | grep globalmem

linux设备驱动开发之字符设备驱动结构

  1. 查看/sys文件中关于globalmem的信息
tree /sys/module/globalmem

linux设备驱动开发之字符设备驱动结构

总结

  1. 字符设备是3大类设备中的一类,驱动加载初始化步骤为申请设备号,构造并初始化相关结构体对象,绑定结构体对象到kobject map上。
  2. 驱动卸载的步骤与前面相反,解绑定kobject map上的结构体对象,释放结构体空间,注销申请的设备号。
  3. 字符设备驱动函数主要实现file_operations结构体中的函数指针即可;实现形式可以参考用户空间调用的对应接口来实现。