0915-----Linux設備驅動 學習筆記----------一個簡單的字符設備驅動程序

0.前言html

  研究生生活一切都在步入正軌,我也開始了新的學習,由於實在不想搞存儲,因此就決定跟師兄學習設備驅動,看了兩星期書,終於有點頭緒了,開始記錄吧!node

1.準備工做linux

  a)查看內核版本shell

    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)代替。

相關文章
相關標籤/搜索