• 0915-----Linux设备驱动 学习笔记----------一个简单的字符设备驱动程序


    0.前言

      研究生生活一切都在步入正轨,我也开始了新的学习,因为实在不想搞存储,所以就决定跟师兄学习设备驱动,看了两星期书,终于有点头绪了,开始记录吧!

    1.准备工作

      a)查看内核版本

        uname -r

      b)安装内核源码树(http://www.cnblogs.com/Jezze/archive/2011/12/23/2299871.html)

        在www.linux.org上下载源码编译,这里是.xz格式,需要安装解压工具,xz-utils; 

        解压方法示例:xz -d linux-3.1-rc4.tar.xz 
               tar -xf linux-3.1-rc4.tar 

      c)安装内核函数man手册

        编译 make mandocs; 安装 make installmandocs; 测试 man printk。

    2.对字符驱动的简单认识

      a)我所理解的驱动程序就是使用Linux内核函数编写一个内核模块,实现对设备文件的打开,关闭,读写,控制等操作,这要对设备文件结构体的构成有深入的了解,让人宽慰的是驱动程序基本框架不变,难点就是程序要涉及并发控制,内存分配,中断等问题,因此会较为复杂,所以任重而道远呀。

      b)三个重要的数据结构:file_operations结构体里面定义的主要是各种函数指针,通过定义设备的一系列函数,再将函数指针赋值,就完成了设备和函数的关联,驱动程序会实现系统调用到实际硬件设备的操作的映射;file结构体表示一个打开的文件描述符,里面有打开的标志(只读只写),文件指针等等,该结构体和一个打开的设备文件相对应;inode结构体主要是用来和硬盘上的文件意义对应,硬盘上每新建一个文件都对应一个inode节点,包括inode号,块号,大小等信息(不懂时看这个,阮一峰,理解inode:http://www.ruanyifeng.com/blog/2011/12/inode.html),查看他们的位置在 /usr/src/linux-3.**.pae/include/linux/fs.h 中。

      c) Linux内核驱动模块的剖析,这篇文章讲了模块加载到内核的具体过程。

      http://www.ibm.com/developerworks/cn/linux/l-lkm/

      d)设备文件:实现对硬件设备的抽象,使得对硬件的读写等价于对设备文件的读写。

      e)主设备号和次设备号:主设备号用来表示不同种类的设备,次设备号主要用来区分设备。(http://blog.csdn.net/gqb_driver/article/details/8805179)。

    3.测试HelloWorld模块

      a)源码

    #include <linux/init.h>
    #include <linux/module.h>
    MODULE_LICENSE("Dual BSD/GPL");
    
    
    static int hello_init(void){
        printk(KERN_ALERT "Hello, world
    ");
        return 0;
    }
    
    static void hello_exit(void){
        printk(KERN_ALERT "Goodbye, cruel world
    ");
    }
    
    
    module_init(hello_init);
    module_exit(hello_exit);

      b)Makefile

    ifneq ($(KERNELRELEASE),)
            obj-m := hello.o
    else
            KERNELDIR ?= /lib/modules/$(shell uname -r)/build
    PWD := $(shell pwd)
    default:
            $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
    endif

      c)加载和卸载,这里要切换到命令行模式(ctrl  Alt f1 切换到 命令行模式, ctrl alt f7 切换到图形界面模式)才会显示出结果,否则需要在 /var/log/syslg 中查看。

    4.一个简单的字符设备驱动程序

      a)程序源码 scull.h scull.c

    #ifndef __SCULL_H__
    #define __SCULL_H__
    
    #include <linux/init.h>
    #include <linux/module.h>
    
    #include <linux/kernel.h>
    #include <linux/slab.h>
    #include <linux/fs.h>
    #include <linux/errno.h>
    #include <linux/fcntl.h>
    #include <linux/cdev.h>
    #include <linux/ioctl.h>
    #include <asm/uaccess.h>
    
    
    #define SCULL_MAJOR 0
    #define SCULL_NR_DEVS 4
    #define SCULL_QUANTUM 100
    #define SCULL_QSET 10
    
    #define SCULL_IOC_MAGIC 'C'
    
    #define SCULL_IOCRESET      _IO(SCULL_IOC_MAGIC, 0)
    #define SCULL_IOCSQUANTUM    _IOW(SCULL_IOC_MAGIC, 1, int)
    #define SCULL_IOCSQSET      _IOW(SCULL_IOC_MAGIC, 2, int)
    #define SCULL_IOCTQUANTUM   _IO(SCULL_IOC_MAGIC, 3)
    #define SCULL_IOCTQSET      _IO(SCULL_IOC_MAGIC, 4)
    #define SCULL_IOCGQUANTUM   _IOR(SCULL_IOC_MAGIC, 5, int)
    #define SCULL_IOCGQSET      _IOR(SCULL_IOC_MAGIC, 6, int)
    #define SCULL_IOCQQUANTUM   _IO(SCULL_IOC_MAGIC, 7)
    #define SCULL_IOCQQSET      _IO(SCULL_IOC_MAGIC, 8)
    #define SCULL_IOCXQUANTUM   _IOWR(SCULL_IOC_MAGIC, 9, int)
    #define SCULL_IOCXQSET      _IOWR(SCULL_IOC_MAGIC, 10, int)
    #define SCULL_IOCHQUANTUM   _IO(SCULL_IOC_MAGIC, 11)
    #define SCULL_IOCHQSET      _IO(SCULL_IOC_MAGIC, 12)
    
    #define SCULL_IOC_MAXNR 12
    
    
    
    struct scull_qset{
        void **data;                //量子集数组
        struct scull_qset *next;
    };
    
    
    struct scull_dev{
        struct scull_qset *data;    //量子集链表指针
        int quantum;                //量子集大小
        int qset;                   //量子集数组的大小
        unsigned long size;         //数据的大小
        struct semaphore sem;
        struct cdev cdev;
    };
    
    int     scull_init_module(void);
    void    scull_cleanup_module(void);
    
    int     scull_open(struct inode *, struct file *);
    int     scull_release(struct inode *, struct file *);
    loff_t  scull_llseek(struct file *, loff_t, int);
    long    scull_ioctl(struct file *, unsigned int, unsigned long);
    ssize_t scull_read(struct file *, char __user *, size_t, loff_t*);
    ssize_t scull_write(struct file *, const char __user*, size_t, loff_t *);
    
    void    scull_setup_cdev(struct scull_dev *, int);
    int     scull_trim(struct scull_dev *);
    struct  scull_qset *scull_follow(struct scull_dev *, int);
    
    
    
    
    #endif
    #include "scull.h"
    
    
    int scull_major = SCULL_MAJOR;
    int scull_minor = 0;
    int scull_nr_devs = SCULL_NR_DEVS;
    int scull_quantum = SCULL_QUANTUM;
    int scull_qset = SCULL_QSET;
    
    struct scull_dev *scull_devices;
    struct file_operations scull_fops = {
        .owner   = THIS_MODULE,
        .llseek  = scull_llseek,
        .read    = scull_read,
        .write   = scull_write,
        .unlocked_ioctl   = scull_ioctl,
        .open    = scull_open,
        .release = scull_release,
    };
    
    int scull_open(struct inode *inode, struct file *filp){
        struct scull_dev *dev;
        dev = container_of(inode->i_cdev,struct scull_dev, cdev);
        filp->private_data = dev;
    
        printk(KERN_WARNING "In OPen
    ");
        if((filp->f_flags & O_ACCMODE) == O_WRONLY){
            if(down_interruptible(&dev->sem))
                return -ERESTARTSYS;
            scull_trim(dev);
            up(&dev->sem);
        }
        return 0;
    }
    
    int scull_release(struct inode *inode, struct file *filp)
    {
        return 0;
    }
    
    loff_t scull_llseek(struct file *filp, loff_t off, int whence){
        struct scull_dev *dev = filp->private_data;
        loff_t newpos = 0;
        switch(whence){
            case 0:                             //SEEK_SET
                newpos = off;
                break;
            case 1:                             //SEEK_CUR
                newpos += off;
                break;
            case 2:                             //SEEK_END
                newpos += dev->size + off;
                break;
            default:
                return -EINVAL;
        }
        if(newpos < 0)
            return -EINVAL;
        filp->f_pos = newpos;
        return newpos;
    }
    
    ssize_t scull_read(struct file* filp, char __user *buf, size_t count, loff_t *f_pos){
        struct scull_dev *dev = filp->private_data;
        struct scull_qset *dptr;
        int quantum = dev->quantum;
        int qset = dev->qset;
        int itemsize = quantum * qset;
        int item, s_pos, q_pos, rest;
        ssize_t retval = 0;
    
        if(down_interruptible(&dev->sem))
            return -ERESTARTSYS;
        if(*f_pos >= dev->size)
            goto out;
        if(*f_pos + count > dev->size)
            count = dev->size - *f_pos;
    
        item = (long)*f_pos / itemsize;
        rest = (long)*f_pos % itemsize;
        s_pos = rest / quantum;
        q_pos = rest % quantum;
    
        dptr = scull_follow(dev, item);
        if(dptr == NULL || !dptr->data || !dptr->data[s_pos])
            goto out;
        if(count > quantum - q_pos)
            count = quantum - q_pos;
        if(copy_to_user(buf, dptr->data[s_pos] + q_pos, count)){
            retval = -EFAULT;
            goto out;
        }
        *f_pos += count;
        retval = count;
    
    out:
        up(&dev->sem);
        return retval;
    }
    
    ssize_t scull_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos){
        struct scull_dev *dev = filp->private_data;
        struct scull_qset *dptr;
        int quantum = dev->quantum;
        int qset = dev->qset;
        int itemsize = quantum * qset;
        int item, rest, s_pos, q_pos;
        ssize_t retval = -ENOMEM;
    
        printk(KERN_INFO "before down_interruptible!
    ");
        if(down_interruptible(&dev->sem))
            return -ERESTARTSYS;
        item = (long)*f_pos / itemsize;
        rest = (long)*f_pos % itemsize;
        s_pos = rest / quantum;
        q_pos = rest % quantum;
    
        dptr = scull_follow(dev, item);
        if(dptr == NULL)
            goto out;
        printk(KERN_INFO "before kmalloc!
    ");
        if(!dptr->data){
            dptr->data = kmalloc(qset * sizeof(char *), GFP_KERNEL);
            if(!dptr->data)
                goto out;
            memset(dptr->data, 0, qset * sizeof(char *));
        }
        if(!dptr->data[s_pos]){
            dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL);
            if(!dptr->data[s_pos])
                goto out;
        }
        if(count > quantum - q_pos)
            count = quantum - q_pos;
        if(copy_from_user(dptr->data[s_pos] + q_pos, buf, count)){
            retval = -EFAULT;
            goto out;
        }
        *f_pos += count;
        retval = count;
    
        if(dev->size < *f_pos)
            dev->size = *f_pos;
    out:
        up(&dev->sem);
        return retval;
    }
    
    long scull_ioctl(struct file *filp, unsigned int cmd, unsigned long arg){
        int err = 0, tmp;
        int retval = 0;
    
        if(_IOC_TYPE(cmd) != SCULL_IOC_MAGIC)
            return -ENOTTY;
        if(_IOC_NR(cmd) > SCULL_IOC_MAXNR)
            return -ENOTTY;
        if(_IOC_DIR(cmd) & _IOC_READ)
            err = ! access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
        else if(_IOC_DIR(cmd) & _IOC_WRITE)
            err = ! access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
        if(err)
            return -EFAULT;
    
        switch(cmd){
            case SCULL_IOCRESET:
                scull_quantum = SCULL_QUANTUM;
                scull_qset = SCULL_QSET;
                break;
            case SCULL_IOCSQUANTUM:
                if(!capable(CAP_SYS_ADMIN))
                    return -EPERM;
                retval = __get_user(scull_quantum, (int __user*)arg);       // arg 是个指针
                break;
            case SCULL_IOCTQUANTUM:
                if(!capable(CAP_SYS_ADMIN))                                 // arg 是个数值
                    return -EPERM;
                scull_quantum = arg;
                break;
            case SCULL_IOCGQUANTUM:
                retval = __put_user(scull_quantum, (int __user*)arg);
                break;
            case SCULL_IOCQQUANTUM:
                return scull_quantum;
            case SCULL_IOCXQUANTUM:
                if(!capable(CAP_SYS_ADMIN))
                    return -EPERM;
                tmp = scull_quantum;
                retval = __get_user(scull_quantum, (int __user*)arg);
                if(retval == 0)
                    retval = __put_user(tmp, (int __user*)arg);
                break;
            case SCULL_IOCHQUANTUM:
                if(!capable(CAP_SYS_ADMIN))
                    return EPERM;
                tmp = scull_quantum;
                scull_quantum = arg;
                return arg;
            case SCULL_IOCSQSET:
                if(!capable(CAP_SYS_ADMIN))
                    return -EPERM;
                retval = __get_user(scull_qset, (int __user*)arg);
                break;
            case SCULL_IOCTQSET:
                if(!capable(CAP_SYS_ADMIN))
                    return -EPERM;
                scull_qset = arg;
                break;
            case SCULL_IOCGQSET:
                retval = __put_user(scull_qset, (int __user*)arg);
                break;
            case SCULL_IOCQQSET:
                return scull_qset;
            case SCULL_IOCXQSET:
                if(!capable(CAP_SYS_ADMIN))
                    return -EPERM;
                tmp = scull_qset;
                retval = __get_user(scull_qset, (int __user*)arg);
                if(retval == 0)
                    retval = __put_user(tmp, (int __user*)arg);
                break;
            case SCULL_IOCHQSET:
                if(!capable(CAP_SYS_ADMIN))
                    return -EPERM;
                tmp = scull_qset;
                scull_qset = arg;
                return tmp;
            default:
                return -ENOTTY;
        }
        return retval;
    }
    
    
    
    struct scull_qset *scull_follow(struct scull_dev *dev, int n){
        struct scull_qset *qs = dev->data;
        if(!qs){
            qs = dev->data = kmalloc(sizeof(struct scull_qset), GFP_KERNEL) ;
            if(qs == NULL)
                return NULL;
            memset(qs, 0, sizeof(struct scull_qset));
        }
    
        while(n--){
            if(!qs->next){
                qs->next = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);
                if(qs->next == NULL)
                    memset(qs->next, 0, sizeof(struct scull_qset));
            }
            qs = qs->next;
            continue;
        }
        return qs;
    }
    
    
    void scull_setup_cdev(struct scull_dev *dev, int index){
        int err, devno = MKDEV(scull_major, scull_minor + index);   //设备号
    
        //1.初始化字符设备
        cdev_init(&dev->cdev, &scull_fops);
        dev->cdev.owner = THIS_MODULE;
        //2.把该字符设备添加到内核
        err = cdev_add(&dev->cdev, devno, 1);
        if(err)
            printk(KERN_NOTICE "Errno %d adding scull %d", err, index);
    }
    
    int scull_trim(struct scull_dev *dev){             //清空scull的data数据域
        struct scull_qset *next, *dptr;
        int q_set = dev->qset;
        int i;
    
        for(dptr = dev->data; dptr; dptr = next){
            if(dptr->data){
                for(i = 0; i < q_set; i++)
                    kfree(dptr->data[i]);
                kfree(dptr->data);
                dptr->data = NULL;
            }
            next = dptr->next;
            kfree(dptr);
        }
        dev->size = 0;
        dev->quantum = scull_quantum;
        dev->qset = scull_qset;
        dev->data = NULL;
        return 0;
    }
    
    
    void scull_cleanup_module(void){
        int i;
        dev_t devno = MKDEV(scull_major, scull_minor);
        //1.释放内存空间
        if(scull_devices){
            for(i = 0; i < scull_nr_devs; i++){
                scull_trim(scull_devices + i);
                cdev_del(&scull_devices[i].cdev);
            }
            kfree(scull_devices);
        }
        printk(KERN_WARNING "rmmod module!");
        //2.释放设备号
        unregister_chrdev_region(devno, scull_nr_devs);
    }
    
    
    int scull_init_module(void){
        int result, i;              //返回值 索引
        dev_t dev = 0;              //设备号
    
    
        //1.申请设备号
        if(scull_major){            //已知主设备号
            dev = MKDEV(scull_major, scull_minor);
            result = register_chrdev_region(dev, scull_nr_devs, "scull");
        }
        else{
            result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs, "scull"); //不知道主设备号
            scull_major = MAJOR(dev);                                               //内核动态分配合适的
        }
        if(result < 0){
            printk(KERN_WARNING "scull:can't get major %d
    ", scull_major);
            return result;
        }
    
        //2.为设备申请内存空间
        scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);
        if(!scull_devices){
            result = -ENOMEM;
            goto fail;
        }
        memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev));
    
        //3.初始化每个设备
        for(i = 0; i < scull_nr_devs; i++){
            scull_devices[i].quantum = scull_quantum;
            scull_devices[i].qset = scull_qset;
            sema_init(&scull_devices[i].sem, 1);
            scull_setup_cdev(&scull_devices[i], i);     //初始化字符设备结构体
        }
        return 0;
    
        fail:
            scull_cleanup_module();
            return  result;
    }
    
    
    
    
    module_param(scull_major, int, S_IRUGO);
    module_param(scull_minor, int, S_IRUGO);
    module_param(scull_nr_devs, int, S_IRUGO);
    module_param(scull_quantum, int, S_IRUGO);
    module_param(scull_qset, int, S_IRUGO);
    
    
    MODULE_AUTHOR("Monica Lee");
    MODULE_LICENSE("Dual BSD/GPL");
    
    module_init(scull_init_module);
    module_exit(scull_cleanup_module);

      c)测试文件

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    
    int main(int argc, const char *argv[])
    {
        char buffer[20] = "Hello World!";
        int fd, count;
    
        // 向字符设备中写数据
        fd = open("/dev/scull0", O_WRONLY);
        if(fd == -1)
           perror("Open failed");
        count = write(fd, buffer, strlen(buffer));
        if(count == -1)
            perror("Write failed");
        else
            printf("Write count :%d
    ", count);
        close(fd);
    
        //从字符设备中读数据
        memset(buffer, 0, sizeof buffer);
        fd = open("/dev/scull0", O_RDONLY);
        if(fd == -1)
           perror("Open failed");
        count = read(fd, buffer, sizeof buffer);
        if(count == -1)
            perror("Read failed");
        printf("Read data: %s
    ", buffer);
        close(fd);
    
        return 0;
    }

      d)编译和执行过程(sudo模式)

        make

        insmod加载到内核;

        cat /proc/devices 查看主设备号;

        mknod /dev/scull0 c 250 0 创建设备文件;

        执行测试程序;

        rmmod 卸载模块。

      e)调试过程中遇到的错误:

        最新版本的内核中 ioctl 函数和init_MUTEX 函数都不存在了,原先的 int (*ioctl)(struct inode*, struct file*, unsigned int, unsigned long);被改为了long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);init_MUTEX函数,使用sema_init(sem, 1)代替。

  • 相关阅读:
    课堂作业04 2017.10.27
    课程作业 03 动手动脑 2017.10.20
    课程作业 03 2017.10.20
    HDU 3974 Assign the task
    POJ 2155 Matrix
    POJ 2481 Cows
    HDU 3038 How Many Answers Are Wrong
    CS Academy Array Removal
    POJ_1330 Nearest Common Ancestors LCA
    CF Round 427 D. Palindromic characteristics
  • 原文地址:https://www.cnblogs.com/monicalee/p/3959283.html
Copyright © 2020-2023  润新知