zoukankan      html  css  js  c++  java
  • 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)代替。

  • 相关阅读:
    Python httpServer服务器(初级)
    分布式服务管理zookeeper的java api
    基于netty的异步http请求
    for之Python vs C#
    表单验证
    contact表单错误解决记录
    表单
    Django后台管理界面
    正则表达式替换和不包含指定字符串
    Django模型-数据库操作
  • 原文地址:https://www.cnblogs.com/monicalee/p/3959283.html
Copyright © 2011-2022 走看看