ldd(linux设备驱动程序)实验2:scull

时间:2022-01-10 22:27:10

按照ldd第三版书上的网址,下载下来的代码是最终版的,没有按章节剥离,不方便读者自己实验。

以下是我手把手敲下的书上第二个实验scull的代码以及安装和测试的步骤。

按上篇搭建linux驱动开发环境(配合LDD阅读)做完实验1后,scull的安装很简单了,在任意位置保存下面两个源文件Makefile,scull.c,执行make,生成目标模块scull.ko,然后insmod scull.ko。

lsmod, 看到第一个就是scull,且会生成这个文件夹/sys/module/scull,可以进去看看里面有什么。cat /proc/devices   可以看到"xxx scull"。因为我的scull驱动(源码贴在下面)是随机生成设备号,所以要进去看。

下面,我们就可以用mknod命令来申请设备文件了。

          mkdir  /dev/scull

           mknod /dev/scull/scull0 c xxx 0

    xxx必须是刚才在proc里看到的主设备号,此设备号是0。主、次设备号如果不符的话,这个命令不会执行失败,但是生成的设备在将来使用时会出这个错:scull0:No such device or address

      然后ls -li,看下新设备的主次设备号。

    到这步之后要注意力,如果你的驱动写得有问题,而编译时没有检测到的话,很可能在执行下面的步骤时使整个linux系统挂起,那只能切断虚拟机电源重新来过了。由于书上的scull代码极简,而官网下载的scull源码相对繁杂,我想用最简的来测试,书上的代码却不全,我只好半用书半用网上的源码,改了非常久。

    然后依据书上的“试试新设备”这一节来测试这个设备。可以用的命令有:cp, dd, 重新输入输出, free;还可以在驱动里写printk来跟踪测试。还可以用strace命令来监视系统调用。

解释下,mknod 的标准形式为:       mknod DEVNAME {b | c}  MAJOR  MINOR

       1,DEVNAME是要创建的设备文件名,如果想将设备文件放在一个特定的文件夹下,就需要先用mkdir在dev目录下新建一个目录;

       2, b和c 分别表示块设备和字符设备:

                  b表示系统从块设备中读取数据的时候,直接从内存的buffer中读取数据,而不经过磁盘;

                  c表示字符设备文件与设备传送数据的时候是以字符的形式传送,一次传送一个字符,比如打印机、终端都是以字符的形式传送数据;

       3,MAJOR和MINOR分别表示主设备号和次设备号:

             为了管理设备,系统为每个设备分配一个编号,一个设备号由主设备号和次设备号组成。主设备号标示某一种类的设备,次设备号用来区分同一类型的设备。 linux操作系统中为设备文件编号分配了32位无符号整数,其中前12位是主设备号,后20位为次设备号,所以在向系统申请设备文件时主设备号不好超过 4095,次设备号不好超过2^20 -1。

 

模块装好后,书本自带的代码中有个scull/scull_load用以装载,但这也是混装了全书的架构,而我们要的是剥离出来的第三章的实验,如果想试一试,可以在实验完毕后(因为这会造成难以估计的混乱)运行它。

 

 

 

Makefile

ifneq ($(KERNELRELEASE),)
obj-m := scull.o
else
PWD := $(shell pwd)
KVER ?= $(shell uname -r)
KDIR := /lib/modules/$(KVER)/build
all:
    $(MAKE) -C $(KDIR) M=$(PWD)
clean:
    rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions *.symvers *.order
endif

 

 

 

scull.c

 /*
 * main.c -- the bare scull char module
 *
 * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet
 * Copyright (C) 2001 O'Reilly & Associates
 *
 * The source code in this file can be freely used, adapted,
 * and redistributed in source or binary form, so long as an
 * acknowledgment appears in derived source files.  The citation
 * should list that the code comes from the book "Linux Device
 * Drivers" by Alessandro Rubini and Jonathan Corbet, published
 * by O'Reilly & Associates.   No warranty is attached;
 * we cannot take responsibility for errors or fitness for use.
 *
 */

#ifndef __KERNEL__
#  define __KERNEL__
#endif
#ifndef MODULE
#  define MODULE
#endif

//#include <linux/config.h>
#include <linux/slab.h>   /* kmalloc() */
#include <asm/uaccess.h>

#include <linux/module.h>
#include <linux/kernel.h>   /* printk() */
#include <linux/fs.h>       /* everything... */
#include <linux/errno.h>    /* error codes */
#include <linux/types.h>    /* size_t */
#include <linux/proc_fs.h>
#include <linux/fcntl.h>    /* O_ACCMODE */

#include <asm/system.h>     /* cli(), *_flags */

//#include "scull.h"          /* local definitions */


int scull_major;
int scull_nr_devs;
int scull_quantum;
int scull_qset;


// struct file_operations *scull_fop_array[]={
//     &scull_fops,      /* type 0 */
//     &scull_priv_fops, /* type 1 */
//     &scull_pipe_fops,  type 2
//     &scull_sngl_fops, /* type 3 */
//     &scull_user_fops, /* type 4 */
//     &scull_wusr_fops  /* type 5 */
// };
// #define SCULL_MAX_TYPE 5

// static void scull_setup_cdev(struct scull_dev *dev, int index){
//     int err, devno = MKDEV(scull_major, scull_minor + index);

//     cdev_init(&dev->cdev, &scull_fops):
//     dev->cdev.owner = THIS_MODULE;
//     dev->cdev.ops = &scull_fops;
//     err = cdev_add (&dev->cdev, devno, 1);
//     if(err)
//         printk(KERN_NOTICE "Error %d adding scull%d", err, index);
// }

struct cdev {
    struct kobject kobj;
    struct module *owner;
    const struct file_operations *ops;
    struct list_head list;
    dev_t dev;
    unsigned int count;
};

struct scull_dev{
    struct scull_qset *data;
    int quantum;
    int qset;
    unsigned long size;
    unsigned int access_key;
    struct semaphore sem;
    struct cdev cdev;
    struct scull_dev* next;
}scull_dev;

/* memory management */

struct scull_qset{
    void **data;
    struct scull_qset* next;
};


int scull_trim(struct scull_dev* dev){
    struct scull_qset *next, *dptr;
    int qset = dev->qset;
    int i;
    for(dptr = dev->data; dptr; dptr = next){
        if(dptr->data){
            for(i=0; i<qset; 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;
}


/*
 * Open and close
 */


int scull_open(struct inode *inode, struct file *filp){
    struct scull_dev *dev; /* device information */

    dev = container_of(inode->i_cdev, struct scull_dev, cdev);
    filp->private_data = dev; /* for other methods */

    /* now trim to 0 the length of the device if open was write-only */
    if( (filp->f_flags & O_ACCMODE) == O_WRONLY){
        scull_trim(dev); /* ignore errors */
    }
    return 0;   /* success */
}

int scull_release(struct inode *inode, struct file *filp)
{
    return 0;
}
/*
 * Follow the list
 */
struct scull_dev *scull_follow(struct scull_dev *dev, int n)
{
    while (n--) {
        if (!dev->next) {
            dev->next = kmalloc(sizeof(scull_dev), GFP_KERNEL);
            memset(dev->next, 0, sizeof(scull_dev));
        }
        dev = dev->next;
        continue;
    }
    return dev;
}

/*
 * Data management: read and write
 */

ssize_t scull_read(struct file *filp, char *buf, size_t count,
                loff_t *f_pos)
{
    struct scull_dev *dev = filp->private_data; /* the first listitem */
    struct scull_qset *dptr;
    int quantum = dev->quantum;
    int qset = dev->qset;
    int itemsize = quantum * qset; /* how many bytes in the listitem */
    int item, s_pos, q_pos, rest;
    ssize_t ret = 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;
    /* find listitem, qset index, and offset in the quantum */
    item = (long)*f_pos / itemsize;
    rest = (long)*f_pos % itemsize;
    s_pos = rest / quantum; q_pos = rest % quantum;

    /* follow the list up to the right position (defined elsewhere) */
    dptr = scull_follow(dev, item);

    if (!dptr->data)
        goto out; /* don't fill holes */
    if (!dptr->data[s_pos])
        goto out;
    /* read only up to the end of this quantum */
    if (count > quantum - q_pos)
        count = quantum - q_pos;

    if (copy_to_user(buf, dptr->data[s_pos]+q_pos, count)) {
        ret = -EFAULT;
    goto out;
    }
    *f_pos += count;
    ret = count;

 out:
    up(&dev->sem);
    return ret;
}

ssize_t scull_write(struct file *filp, const char *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 ret = -ENOMEM; /* value used in "goto out" statements */

    if (down_interruptible(&dev->sem))
            return -ERESTARTSYS;

    /* find listitem, qset index and offset in the quantum */
    item = (long)*f_pos / itemsize;
    rest = (long)*f_pos % itemsize;
    s_pos = rest / quantum; q_pos = rest % quantum;

    /* follow the list up to the right position */
    dptr = scull_follow(dev, item);
    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;
    }
    /* write only up to the end of this quantum */
    if (count > quantum - q_pos)
        count = quantum - q_pos;

    if (copy_from_user(dptr->data[s_pos]+q_pos, buf, count)) {
        ret = -EFAULT;
    goto out;
    }
    *f_pos += count;
    ret = count;

    /* update the size */
    if (dev->size < *f_pos)
        dev-> size = *f_pos;

  out:
    up(&dev->sem);
    return ret;
}


/*
 * The cleanup function is used to handle initialization failures as well.
 * Thefore, it must be careful to work correctly even if some of the items
 * have not been initialized
 */
static void scull_cleanup_module(void){
//     int i;

// #ifndef CONFIG_DEVFS_FS
//     /* cleanup_module is never called if registering failed */
//     unregister_chrdev(scull_major, "scull");
// #endif

// #ifdef SCULL_DEBUG /* use proc only if debugging */
//     scull_remove_proc();
// #endif
//     if (scull_devices) {
//         for (i=0; i<scull_nr_devs; i++) {
//             scull_trim(scull_devices+i);
//             /* the following line is only used for devfs */
//             devfs_unregister(scull_devices[i].handle);
//         }
//         kfree(scull_devices);
//     }

//     /* and call the cleanup functions for friend devices */
//     scull_p_cleanup();
//     scull_access_cleanup();

//     /* once again, only for devfs */
//     devfs_unregister(scull_devfs_dir);

}


static int scull_init_module(void){
//     int result, i;

//     SET_MODULE_OWNER(&scull_fops);
// #ifdef CONFIG_DEVFS_FS
//     /* If we have devfs, create /dev/scull to put files in there */
//     scull_devfs_dir = devfs_mk_dir(NULL, "scull", NULL);
//     if (!scull_devfs_dir) return -EBUSY; /* problem */

// #else /* no devfs, do it the "classic" way  */    

//     /*
//      * Register your major, and accept a dynamic number. This is the
//      * first thing to do, in order to avoid releasing other module's
//      * fops in scull_cleanup_module()
//      */
//     result = register_chrdev(scull_major, "scull", &scull_fops);
//     if (result < 0) {
//         printk(KERN_WARNING "scull: can't get major %d\n",scull_major);
//         return result;
//     }
//     if (scull_major == 0) scull_major = result; /* dynamic */

// #endif /* CONFIG_DEVFS_FS */
//     /*
//      * allocate the devices -- we can't have them static, as the number
//      * can be specified at load time
//      */
//     scull_devices = kmalloc(scull_nr_devs * sizeof(scull_dev), GFP_KERNEL);
//     if (!scull_devices) {
//         result = -ENOMEM;
//         goto fail;
//     }
//     memset(scull_devices, 0, scull_nr_devs * sizeof(scull_dev));
//     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);
// #ifdef CONFIG_DEVFS_FS
//         sprintf(devname, "%i", i);
//         devfs_register(scull_devfs_dir, devname,
//                        DEVFS_FL_AUTO_DEVNUM,
//                        0, 0, S_IFCHR | S_IRUGO | S_IWUGO,
//                        &scull_fops,
//                        scull_devices+i);
// #endif  
//     }

//     /* At this point call the init function for any friend device */
//     if ( (result = scull_p_init()) )
//         goto fail;
//     if ( (result = scull_access_init()) )
//         goto fail;
//     /* ... */

// #ifndef SCULL_DEBUG
//     EXPORT_NO_SYMBOLS; /* otherwise, leave global symbols visible */
// #endif

// #ifdef SCULL_DEBUG /* only when debugging */
//     scull_create_proc();
// #endif
  struct file_operations scull_fops;
    register_chrdev(scull_major, "scull", &scull_fops);
    return 0; /* succeed */

//   fail:
//     scull_cleanup_module();
//     return result;
}



module_init(scull_init_module);
module_exit(scull_cleanup_module);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("DYYR");