天天看點

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結構體中的函數指針即可;實作形式可以參考使用者空間調用的對應接口來實作。