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\n");
    return 0;
}

static void hello_exit(void){
    printk(KERN_ALERT "Goodbye, cruel world\n");
}


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\n");
    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!\n");
    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!\n");
    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\n", 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\n", 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\n", 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)代替。

 

posted @ 2014-09-16 22:19  Monica_Lee  阅读(461)  评论(5编辑  收藏  举报