• 《Linux设备驱动程序》编译LDD3的scull驱动问题总结***


    由于Linux内核版本更新的原因,LDD3(v2.6.10)提供的源码无法直接使用,下面是本人编译scull源码时出现的一些问题及解决方法。
    编译环境:Ubuntu 10.04 LTS(kernel version 2.6.32-33)

    编译错误:

    make -C /lib/modules/2.6.32-279.14.1.el6.i686/build M=/mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull LDDINC=/mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/../include modules
    make[1]: Entering directory `/usr/src/kernels/2.6.32-279.14.1.el6.i686'
    scripts/Makefile.build:49: *** CFLAGS was changed in "/mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/Makefile". Fix it to use EXTRA_CFLAGS.  Stop.
    make[1]: *** [_module_/mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull] Error 2
    make[1]: Leaving directory `/usr/src/kernels/2.6.32-279.14.1.el6.i686'
    make: *** [modules] Error 2

    解决方案:

    CFLAGS与Makefile.build中的CFLAGS冲突,错误提示要求将CFLAG换成EXTRA_CFLAGS重新编译即可;


    编译错误:

    error: linux/config.h: No such file or directory

    解决方案:

    从linux-2.6.20起,config.h就已经被移除了.

    cd /usr/src/linux-source-2.6.32/include/linux
    ln -s autoconf.h config.h

    编译错误:

    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/pipe.c: In function ‘scull_p_read’:
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/pipe.c:131: error: ‘TASK_INTERRUPTIBLE’ undeclared (first use in this function)
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/pipe.c:131: error: (Each undeclared identifier is reported only once
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/pipe.c:131: error: for each function it appears in.)
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/pipe.c:131: error: implicit declaration of function ‘signal_pending’
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/pipe.c:131: error: implicit declaration of function ‘schedule’
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/pipe.c: In function ‘scull_getwritespace’:
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/pipe.c:168: error: ‘TASK_INTERRUPTIBLE’ undeclared (first use in this function)
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/pipe.c: In function ‘scull_p_write’:
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/pipe.c:219: error: ‘TASK_INTERRUPTIBLE’ undeclared (first use in this function)
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/pipe.c:223: error: ‘SIGIO’ undeclared (first use in this function)
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/pipe.c:223: error: ‘POLL_IN’ undeclared (first use in this function)
    make[2]: *** [/mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/pipe.o] Error 1
    make[1]: *** [_module_/mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull] Error 2

    解决方案:

    头文件变动原因,在pipe.c中添加
    #include <linux/sched.h> 

    编译错误:

    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/access.c: In function ‘scull_u_open’:
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/access.c:106: error: dereferencing pointer to incomplete type
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/access.c:107: error: dereferencing pointer to incomplete type
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/access.c:114: error: dereferencing pointer to incomplete type
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/access.c: In function ‘scull_w_available’:
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/access.c:165: error: dereferencing pointer to incomplete type
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/access.c:166: error: dereferencing pointer to incomplete type
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/access.c: In function ‘scull_w_open’:
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/access.c:179: error: ‘TASK_INTERRUPTIBLE’ undeclared (first use in this function)
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/access.c:179: error: (Each undeclared identifier is reported only once
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/access.c:179: error: for each function it appears in.)
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/access.c:179: error: implicit declaration of function ‘signal_pending’
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/access.c:179: error: implicit declaration of function ‘schedule’
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/access.c:184: error: dereferencing pointer to incomplete type
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/access.c: In function ‘scull_w_release’:
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/access.c:205: error: ‘TASK_INTERRUPTIBLE’ undeclared (first use in this function)
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/access.c: In function ‘scull_c_open’:
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/access.c:277: error: dereferencing pointer to incomplete type
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/access.c:281: error: dereferencing pointer to incomplete type
    make[2]: *** [/mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/access.o] Error 1
    make[1]: *** [_module_/mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull] Error 2
    make[1]: Leaving directory `/usr/src/kernels/2.6.32-279.14.1.el6.i686'
    make: *** [modules] Error 2

    解决方案:

    头文件变动原因,在access.c中添加
    #include <linux/sched.h>   


    编译错误:

    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/access.c: In function ‘scull_u_open’:
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/access.c:107: error: ‘struct task_struct’ has no member named ‘uid’
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/access.c:108: error: ‘struct task_struct’ has no member named ‘euid’
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/access.c:115: error: ‘struct task_struct’ has no member named ‘uid’
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/access.c: In function ‘scull_w_available’:
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/access.c:166: error: ‘struct task_struct’ has no member named ‘uid’
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/access.c:167: error: ‘struct task_struct’ has no member named ‘euid’
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/access.c: In function ‘scull_w_open’:
    /mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/access.c:185: error: ‘struct task_struct’ has no member named ‘uid’
    make[2]: *** [/mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull/access.o] Error 1
    make[1]: *** [_module_/mnt/HappyStudy/MyDesigner/Linux/LDD3/examples/scull] Error 2
    make[1]: Leaving directory `/usr/src/kernels/2.6.32-279.14.1.el6.i686'
    make: *** [modules] Error 2

    解决方案:

    因为内核版本的升级,struct task_struct发生了变动,uid和euid放到了cred域下.
    修改,做如下替换
    current->uid 替换成current->cred->uid
    current->euid替换成current->cred->euid

    重新编译,成功!

    //========================使用============================

    1. 源代码包解压后进入scull文件夹,其中的文件有:
    access.c  
    main.c 主程序,scull设备的初始化、卸载、open、 write等实现都在这里面。
    Makefile  
    pipe.c 第六章《高级字符驱动程序操作》会用到,用来讲解阻塞型设 备。
    scull.h 头文件
    scull.init  
    scull_load 加载scull模块的脚本
    scull_unload  卸载scull模块的脚本
    2. 直接在scull目录下运行make命令就可以编译通过,会生成一个叫scull.ko的文件,然后运行脚本scull_load, 没有任何输出就返回了,scull设备就会自动加载到内核当中去了,此时可以通过查看/proc/devices文件找到刚加载的scull模块,还有内 核为其分配的主设备号。
    在/dev/中也增加很多以scull开头的字符设备。
    3 试试scull设备:
    运行命令:
    # ls -l > /dev/scull  # 没有任何输出就返回了
    # cat /dev/scull     # 再运行cat命令读取scull设备,则会将上面命令的输出信息读出来
    总计 900
    -rw-rw-rw- 1 baobaowu baobaowu 10845 2005-02-01 access.c
    -rw-rw-r-- 1 baobaowu baobaowu 74580 06-15 16:27 access.o
    -rw-rw-rw- 1 baobaowu baobaowu 16631 06-15 17:15 main.c
    -rw-r--r-- 1 baobaowu baobaowu 74512 06-15 17:16 main.o
    -rw-r--r-- 1 baobaowu baobaowu 752 2005-02-01 Makefile
    -rw-rw-rw- 1 baobaowu baobaowu 11138 2005-02-01 pipe.c
    -rw-rw-r-- 1 baobaowu baobaowu 71576 06-15 16:27 pipe.o
    -rw-r--r-- 1 baobaowu baobaowu 5153 2005-02-01 scull.h
    -rwxr-xr-x 1 baobaowu baobaowu 3309 2005-02-01 scull.init
    -rw-r--r-- 1 baobaowu baobaowu 248556 06-15 17:16 scull.ko
    -rwxr-xr-x 1 baobaowu baobaowu 1708 06-15 16:34 scull_load
    -rw-rw-rw- 1 baobaowu baobaowu 1852 06-15 17:16 scull.mod.c
    -rw-r--r-- 1 baobaowu baobaowu 33696 06-15 17:16 scull.mod.o
    -rw-r--r-- 1 baobaowu baobaowu 216043 06-15 17:16 scull.o
    -rwxr-xr-x 1 baobaowu baobaowu 335 2005-02-01 scull_unload
    果然scull设备跟书中介绍的一样,只是存在于内存中的一个缓冲区。
    5. 再运行scull_unload,也是没有任何输出就返回了,但是scull模块却已经从内核中删除了,/proc/devices 文件中也没scull设备了,/dev/中也没有以scull开头的字符设备了。
    6. 下面在scull增加一些调试信息:
    打开main.c文件,在函数scull_init_module()的头部增加一句:
    printk(KERN_ALERT "Debug by baobaowu:scull_init_module()/n");
    在函数scull_read()的头部增加一句:
    printk(KERN_ALERT "Debug by baobaowu:scull_read()/n");
    在函数scull_write()的头部增加一句:
    printk(KERN_ALERT "Debug by baobaowu:scull_write()/n");
    保存后运行make进行编译。
    7. 重复第3步,将scull加载进内核中,此时内核应该调用main.c中的scull_init_module()函数,是不是调用 了呢?我们看看/var/log/messages文件,果然在该文件最后有输出我们的调试信息:
    Debug by baobaowu:scull_init_module()
    那read和write怎么调用呢?我们紧接着下面介绍。
    8. 可以想像,向设备中写数据就会调用scull_write()函数,我们执行下面的命令利用输出重定向来向/dev/scull设备 写数据:
    # ls -l > /dev/scull
    执行完命令后再看看/var/log/messages文件,果然在该文件最后有输出我们的调试信息:
    Debug by baobaowu:scull_write()
    9. 从设备中读取数据应该就会调用scull_read()函数,我们利用dd命令来读scull设备:
    # dd if=/dev/scull of=temp  # 从/dev/scull中读取数据,保存在当前目录下的temp文件中
    执行完命令后再看看/var/log/messages文件,果然在该文件最后有输出我们的调试信息:
    Debug by baobaowu:scull_read()
    10. 本文介绍的scull使用方法调试成功后对读第三章《字符设备驱动程序》很有帮助,当遇到不懂,或不确定的地方时printk一下就 好了^_^
    Makefile文件分析
    # disable/enable debugging
    #DEBUG = y
     
    # 当DEBUG变量等于y时。两个比较变量用括号括起来,逗号分隔。ifeq和括号中间有一个空格。
    ifeq ($(DEBUG), y)  
    # += 追加变量值。如果该变量之前没有被定义过,+=就自动变成=,变量被定义成递归展开式的变量;如果之前已经定义过,就遵循之前的风格。
    # = 递归展开式变量:在定义时,变量中对其它变量的引用不会被替换展开。变量在引用它的地方被替换展开时,变量中其它变量才被同时替换展开。
    # -O 程序优化参数;
    # -g 使生成的debuginfo包额外支持gnu和gdb调试程序,易于使用
    # -DSCULL_DEBUG 即define SCULL_DEBUG   TODO
        DEBFLAGS += -O -g -DSCULL_DEBUG # -O is needed to expand inlines
    else
        DEBFLAGS += -O2
    endif
     
    #  CFLAGS影响编译过程。应在遵循原设置的基础上添加新的设置,注意+=
    CFLAGS += $(DEBFLAGS)
    # -I 选项指出头文件位置。LDDINC是下面定义的一个变量。
    CFLAGS += -I$(LDDINC)
     
    # 如果KERNELRELEASE不等于空。ifneq判断参数是否不相等。
    ifneq ($(KERNELRELEASE),)
    # := 直接展开式变量:在定义时就展开变量中对其它变量或函数的引用,定以后就成了需要定义的文本串。不能实现对其后定义变量的引用。
    scull-objs := main.o pipe.o access.o
    obj-m := scull.o
    # 否则(KERNELRELEASE是空)
    else
    # ?= 条件赋值:只有在此变量之前没有赋值的情况下才会被赋值。
    # shell uname r 内核版本号
    KERNELDIR ?= /lib/modules/$(shell uname r)/build
    # shell pwd 当前在文件系统中的路径
    PWD := $(shell pwd)
     
    modules:
        # $(MAKE) TODO
        # -C $(KERNELDIR) 在读取Makefile之前进入$(KERNELDIR)目录
        # M=$(PWD) LDDINC=$(PWD)/../include 传递2个变量给Makefile
        # modules 是$(KERNELDIR)中的Makefile的target   TODO
        $(MAKE) -C $(KERNELDIR) M=$(PWD) LDDINC=$(PWD)/../include modules
     
    endif
     
    # 清理
    clean:
        rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
     
    # TODO
    # 产生依赖信息文件,如果存在的话则将其包含到Makefile中。
    depend .depend dep:
        $(CC) $(CFLAGS) -M *.c > .depend
        
    ifeq (.depend,$(wildcard .depend))
    include .depend
    endif
     
    ---
    参考
    1.[GNU make中文手册]
    2.[CFLAGS 统一和 gcc 3.4] http://www.magiclinux.org/node/821
    3.[LDD3源码学习笔记之scull_main] http://www.sudu.cn/info/html/edu/20070101/291462.html
    4.http://topic.csdn.net/u/20070815/22/cbd2f64d-f6e3-4938-97f8-4f8fe5a21465.html
     
     
     
     scull.h
     
     
    /*
     * scull.h -- definitions for the 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.
     *
     * $Id: scull.h,v 1.15 2004/11/04 17:51:18 rubini Exp $
     */
     
    #ifndef _SCULL_H_
    #define _SCULL_H_
     
    #include <linux/ioctl.h> /* needed for the _IOW etc stuff used later */
     
    /*
     * Macros to help debugging
     */
     
    // undef取消以前定义的宏
    #undef PDEBUG             /* undef it, just in case */
    #ifdef SCULL_DEBUG
    #  ifdef __KERNEL__
         // 内核空间,printk 输出调试信息
         /* This one if debugging is on, and kernel space */
    #    define PDEBUG(fmt, args) printk( KERN_DEBUG "scull: " fmt, ## args)
    #  else
         // 用户空间,fprintf 输出调试信息
         /* This one for user space */
    #    define PDEBUG(fmt, args) fprintf(stderr, fmt, ## args)
    #  endif
    #else
    #  define PDEBUG(fmt, args) /* not debugging: nothing */
    #endif
     
    #undef PDEBUGG
    #define PDEBUGG(fmt, args) /* nothing: it's a placeholder */
     
    #ifndef SCULL_MAJOR
    // 主设备号指定为0,表示由内核动态分配
    #define SCULL_MAJOR 0   /* dynamic major by default */
    #endif
     
    // bare device的数量,也就是要请求的连续设备编号的总数
    #ifndef SCULL_NR_DEVS
    #define SCULL_NR_DEVS 4    /* scull0 through scull3 */
    #endif
     
    // TODO
    #ifndef SCULL_P_NR_DEVS
    #define SCULL_P_NR_DEVS 4  /* scullpipe0 through scullpipe3 */
    #endif
     
    /*
     * The bare device is a variable-length region of memory.
     * Use a linked list of indirect blocks.
     *
     * "scull_dev->data" points to an array of pointers, each
     * pointer refers to a memory area of SCULL_QUANTUM bytes.
     *
     * The array (quantum-set) is SCULL_QSET long.
     */
     // 每个内存区字节数。(量子长度)
    #ifndef SCULL_QUANTUM
    #define SCULL_QUANTUM 4000
    #endif
     
    // 数组长度(量子集长度)
    #ifndef SCULL_QSET
    #define SCULL_QSET    1000
    #endif
     
    /*
     * The pipe device is a simple circular buffer. Here its default size
     */
    // 管道设备,环形缓冲大小。 
    #ifndef SCULL_P_BUFFER
    #define SCULL_P_BUFFER 4000
    #endif
     
    /*
     * Representation of scull quantum sets.
     */
    struct scull_qset {
        void **data;
        struct scull_qset *next;
    };
     
    // 使用scull_dev表示每个设备
    struct scull_dev {
        struct scull_qset *data;  /* Pointer to first quantum set */
        int quantum;              /* the current quantum size */
        int qset;                 /* the current array size */
        unsigned long size;       /* amount of data stored here */
        unsigned int access_key;  /* used by sculluid and scullpriv */
        struct semaphore sem;     /* mutual exclusion semaphore     */
        struct cdev cdev;      /* Char device structure        */
    };
     
    // TODO 什么用?
    /*
     * Split minors in two parts
     */
    // 高四位
    #define TYPE(minor)    (((minor) >> 4) & 0xf)    /* high nibble */
    // 低四位
    #define NUM(minor)    ((minor) & 0xf)        /* low  nibble */
     
     
    /*
     * The different configurable parameters
     */
    extern int scull_major;     /* main.c */
    extern int scull_nr_devs;
    extern int scull_quantum;
    extern int scull_qset;
     
    extern int scull_p_buffer;    /* pipe.c */
     
     
    /*
     * Prototypes for shared functions
     */
     
    int     scull_p_init(dev_t dev);
    void    scull_p_cleanup(void);
    int     scull_access_init(dev_t dev);
    void    scull_access_cleanup(void);
     
    int     scull_trim(struct scull_dev *dev);
     
    ssize_t scull_read(struct file *filp, char __user *buf, size_t count,
                       loff_t *f_pos);
    ssize_t scull_write(struct file *filp, const char __user *buf, size_t count,
                        loff_t *f_pos);
    loff_t  scull_llseek(struct file *filp, loff_t off, int whence);
    int     scull_ioctl(struct inode *inode, struct file *filp,
                        unsigned int cmd, unsigned long arg);
     
     
    /*
     * Ioctl definitions
     */
     
    // TODO 魔数什么用?
    /* Use 'k' as magic number */
    #define SCULL_IOC_MAGIC  'k'
    /* Please use a different 8-bit number in your code */
     
    #define SCULL_IOCRESET    _IO(SCULL_IOC_MAGIC, 0)
     
    /*
     * S means "Set" through a ptr,
     * T means "Tell" directly with the argument value
     * G means "Get": reply by setting through a pointer
     * Q means "Query": response is on the return value
     * X means "eXchange": switch G and S atomically
     * H means "sHift": switch T and Q atomically
     */
    #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)
     
    /*
     * The other entities only have "Tell" and "Query", because they're
     * not printed in the book, and there's no need to have all six.
     * (The previous stuff was only there to show different ways to do it.
     */
    #define SCULL_P_IOCTSIZE _IO(SCULL_IOC_MAGIC,   13)
    #define SCULL_P_IOCQSIZE _IO(SCULL_IOC_MAGIC,   14)
    /*  more to come */
     
    #define SCULL_IOC_MAXNR 14
     
    #endif /* _SCULL_H_ */
     
     
     
    main.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.
     *
     */
     
    #include <linux/config.h>
    #include <linux/module.h>
    #include <linux/moduleparam.h>
    #include <linux/init.h>
     
    #include <linux/kernel.h>    /* printk() */
    #include <linux/slab.h>        /* kmalloc() */
    #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 <linux/seq_file.h>
    #include <linux/cdev.h>
     
    #include <asm/system.h>        /* cli(), *_flags */
    #include <asm/uaccess.h>    /* copy_*_user */
     
    #include "scull.h"        /* local definitions */
     
    /*
     * Our parameters which can be set at load time.
     */
     
    int scull_major =   SCULL_MAJOR; // 主设备号
    int scull_minor =   0; // 次设备号
    int scull_nr_devs = SCULL_NR_DEVS;    /* number of bare scull devices */
    int scull_quantum = SCULL_QUANTUM;  // 量子大小(字节)
    int scull_qset =    SCULL_QSET; // 量子集大小
     
    // 插入模块时指定的参数
    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("Alessandro Rubini, Jonathan Corbet");
    MODULE_LICENSE("Dual BSD/GPL");
     
    struct scull_dev *scull_devices;    /* allocated in scull_init_module */
     
     
    // 释放整个数据区。简单遍历列表并且释放它发现的任何量子和量子集。
    // 在scull_open 在文件为写而打开时调用。
    // 调用这个函数时必须持有信号量。
    /*
     * Empty out the scull device; must be called with the device
     * semaphore held.
     */
    int scull_trim(struct scull_dev *dev)
    {
        struct scull_qset *next, *dptr;
        int qset = dev->qset;   /* "dev" is not-null */ // 量子集大小
        int i;
     
        // 遍历多个量子集。dev->data 指向第一个量子集。
        for (dptr = dev->data; dptr; dptr = next) { /* all the list items */
            if (dptr->data) { // 量子集中有数据
                for (i = 0; i < qset; i++) // 遍历释放当前量子集中的每个量子。量子集大小为qset。 
                    kfree(dptr->data[i]);
                kfree(dptr->data); // 释放量子指针数组
                dptr->data = NULL; 
            }
            // next获取下一个量子集,释放当前量子集。
            next = dptr->next;
            kfree(dptr);
        }
        // 清理struct scull_dev dev中变量的值
        dev->size = 0;
        dev->quantum = scull_quantum;
        dev->qset = scull_qset;
        dev->data = NULL;
        return 0;
    }
    #ifdef SCULL_DEBUG /* use proc only if debugging */
    /*
     * The proc filesystem: function to read and entry
     */
     
     // 在proc里实现文件,在文件被读时产生数据
     // 当一个进程读 /proc 文件,内核分配了一页内存,驱动可以写入数据返回用户空间
    int scull_read_procmem(char *buf,  // 写数据的缓冲区
                    char **start,  // 数据写在页中的哪里
                    off_t offset, 
                    int count, 
                    int *eof,  // 必须被驱动设置,表示写数据结束
                    void *data) // data传递私有数据
    {
        int i, j, len = 0;
        int limit = count - 80; /* Don't print more than this */
     
        // 遍历每个设备,输出总字节数小于limit
        for (i = 0; i < scull_nr_devs && len <= limit; i++) { 
            struct scull_dev *d = &scull_devices[i];
            struct scull_qset *qs = d->data;
            if (down_interruptible(&d->sem))
                return -ERESTARTSYS;
            len += sprintf(buf+len,"
    Device %i: qset %i, q %i, sz %li
    ",  // 输出当前是第几个scull设备,量子集大小,量子大小,设备大小
                    i, d->qset, d->quantum, d->size);
            // 遍历当前设备的每个量子集
            for (; qs && len <= limit; qs = qs->next) { /* scan the list */ 
                len += sprintf(buf + len, "  item at %p, qset at %p
    ",     // 输出当前量子集 和 量子集中的数据 在内存中的位置。
                        qs, qs->data);
                // 输出最后一个量子集中,每个量子的地址
                if (qs->data && !qs->next) /* dump only the last item */ 
                    for (j = 0; j < d->qset; j++) {
                        if (qs->data[j])
                            len += sprintf(buf + len,
                                    "    % 4i: %8p
    ",
                                    j, qs->data[j]);
                    }
            }
            up(&scull_devices[i].sem);
        }
        *eof = 1;
        return len;
    }
     
     
     
    /// seq_file接口:创建一个虚拟文件,遍历一串数据,这些数据必须返回用户空间。 
    // 步骤:start, next, stop, show
    /*
     * For now, the seq_file implementation will exist in parallel.  The
     * older read_procmem function should maybe go away, though.
     */
     
    /*
     * Here are our sequence iteration methods.  Our "position" is
     * simply the device number.
     */
    // 参数:*s总被忽略;pos指从哪儿开始读,具体意义依赖于实现。
    // seq_file典型的实现是遍历一感兴趣的数据序列,pos就用来指示序列中的下一个元素。
    // 在scull中,pos简单地作为scull_array数组的索引。
    static void *scull_seq_start(struct seq_file *s, loff_t *pos)
    {
        if (*pos >= scull_nr_devs)
            return NULL;   /* No more to read */
        return scull_devices + *pos; // 返回索引号是pos的scull设备
    }
     
    // 返回下一个scull设备
    static void *scull_seq_next(struct seq_file *s, void *v, loff_t *pos)
    {
        (*pos)++;
        if (*pos >= scull_nr_devs)
            return NULL;
        return scull_devices + *pos;
    }
     
    static void scull_seq_stop(struct seq_file *s, void *v)
    {
        /* Actually, there's nothing to do here */
    }
     
    // 输出迭代器v生成的数据到用户空间
    static int scull_seq_show(struct seq_file *s, void *v)
    {
        struct scull_dev *dev = (struct scull_dev *) v;
        struct scull_qset *d;
        int i;
     
        if (down_interruptible(&dev->sem))
            return -ERESTARTSYS;
        seq_printf(s, "
    Device %i: qset %i, q %i, sz %li
    ", // 输出,相当于用于空间的printf。返回非0值表示缓冲区满,超出的数据被丢弃。
                (int) (dev - scull_devices), dev->qset,
                dev->quantum, dev->size);
         // 遍历设备的每一个量子集
        for (d = dev->data; d; d = d->next) { /* scan the list */
            seq_printf(s, "  item at %p, qset at %p
    ", d, d->data); // 量子集地址,量子集中数据地址
            // 输出最后一个量子集中,每个量子的地址
            if (d->data && !d->next) /* dump only the last item */
                for (i = 0; i < dev->qset; i++) {
                    if (d->data[i])
                        seq_printf(s, "    % 4i: %8p
    ",
                                i, d->data[i]);
                }
        }
        up(&dev->sem);
        return 0;
    }
     
    // seq_file的操作集
    /*
     * Tie the sequence operators up.
     */
    static struct seq_operations scull_seq_ops = {
        .start = scull_seq_start,
        .next  = scull_seq_next,
        .stop  = scull_seq_stop,
        .show  = scull_seq_show
    };
     
    // 打开 /proc 文件。在这里也就是初始化seq_file
    /*
     * Now to implement the /proc file we need only make an open
     * method which sets up the sequence operators.
     */
    static int scull_proc_open(struct inode *inode, struct file *file)
    {
        return seq_open(file, &scull_seq_ops); // 连接file和seq_file
    }
     
    // proc文件的操作集
    /*
     * Create a set of file operations for our proc file.
     */
    static struct file_operations scull_proc_ops = {
        .owner   = THIS_MODULE,
        .open    = scull_proc_open,
        .read    = seq_read,
        .llseek  = seq_lseek,
        .release = seq_release
    };
        
     
     
    /*
     * Actually create (and remove) the /proc file(s).
     */
    // 建立通过proc方式debug时需要的proc文件
    static void scull_create_proc(void)
    {
        struct proc_dir_entry *entry;
     
        //TODO 创建了两个文件?
        // 创建文件scullmem并使之关联read函数
        create_proc_read_entry("scullmem",   // name:要创建的文件名 
                0 /* default mode */,   // mode:文件掩码,为0则按照系统默认的掩码创建文件
                NULL /* parent dir */,  // base:指定文件所在目录,为NULL则被创建在/proc根目录下
                scull_read_procmem,  // read_proc:处理读请求的回调函数
                NULL /* client data */); // 内核忽略此参数,但会把它当做参数传递给read_proc
     
        
        entry = create_proc_entry("scullseq", 0, NULL); // 参数:名字,掩码,父目录
                                                       // create_proc_entry 同样用来建立/proc文件,但较create_proc_read_entry 更为底层一些
        if (entry)
            entry->proc_fops = &scull_proc_ops;
    }
     
    // 移除创建的 /proc 文件
    static void scull_remove_proc(void)
    {
        /* no problem if it was not registered */
        // 移除一个proc_dir_entry, 如果这个结构还在使用,设置deleted标志,返回
        remove_proc_entry("scullmem",   // const char *name
                        NULL /* parent dir */);    // struct proc_dir_entry *parent
        remove_proc_entry("scullseq", NULL);
    }
     
     
    #endif /* SCULL_DEBUG */
     
     
     
     
     
    /*
     * Open and close
     */
    /// 打开设备:文件私有数据,设置成对应的scull_dev
    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 */
        /// 文件以只读模式打开时,截断为0
        if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) {
            if (down_interruptible(&dev->sem))
                return -ERESTARTSYS;
            scull_trim(dev); /* ignore errors */
            up(&dev->sem);
        }
        return 0;          /* success */
    }
     
    // file_operations 中的.release
    int scull_release(struct inode *inode, struct file *filp)
    {
        return 0;
    }
    /*
     * Follow the list
     */
     // 返回设备dev的第n个量子集,量子集不够n个就申请新的。
    struct scull_qset *scull_follow(struct scull_dev *dev, int n)
    {
        struct scull_qset *qs = dev->data; // 当前设备的量子集
     
            /* Allocate first qset explicitly if need be */
        // 如果当前设备还没有量子集,就显式分配第一个量子集
        if (! qs) {
                                 // kmalloc 内核模块中,动态分配连续的物理地址,用于小内存分配
            qs = dev->data = kmalloc(sizeof(struct scull_qset), // size_t size. 要分配的块的大小
                                    GFP_KERNEL);    // int flags. GFP_KERNEL 在当前进程缺少内存时,可以睡眠来等待一页。 TODO
                                                    // 使用 GFP_KERNEL 来分配内存的函数,必须可重入 且不能在原子上下文中运行。
            if (qs == NULL)
                return NULL;  /* Never mind */
            memset(qs, 0, sizeof(struct scull_qset));
        }
     
        /* Then follow the list */
        // 遍历当前设备的量子集链表n步,量子集不够就申请新的。
        while (n--) {
            if (!qs->next) {
                qs->next = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);
                if (qs->next == NULL) // 不关心内存分配失败? TODO
                    return NULL;  /* Never mind */
                memset(qs->next, 0, sizeof(struct scull_qset));
            }
            qs = qs->next;
            continue;
        }
        return qs;
    }
     
    /*
     * Data management: read and write
     */
     
    ssize_t scull_read(struct file *filp,   // 设备对应的文件结构
                        char __user *buf,    // 读到用户空间
                        size_t count,       // 字节数
                    loff_t *f_pos)          // 要读的位置,在filp私有数据中的偏移
    {
        struct scull_dev *dev = filp->private_data; 
        struct scull_qset *dptr;    /* the first listitem */
        int quantum = dev->quantum, qset = dev->qset; // 量子、量子集大小
        int itemsize = quantum * qset; /* how many bytes in the listitem */ // 一个量子集的字节数
        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;
        // 要读的count超出了size,裁断count
        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 == NULL || !dptr->data || ! dptr->data[s_pos]) // 没有量子集,量子集中没有data,没有第s_pos个量子
            goto out; /* don't fill holes */
     
        /* read only up to the end of this quantum */
        // 只在一个量子中读:如果count超出当前量子,截断count
        if (count > quantum - q_pos)
            count = quantum - q_pos;
     
        // 将读位置的内容复制到用户空间buf,共复制count字节
        if (copy_to_user(buf,       // void __user *to
                        dptr->data[s_pos] + q_pos,  // const void *from
                        count)) {   // unsigned long n
            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, qset = dev->qset; // 量子、量子集大小
        int itemsize = quantum * qset;      // 一个量子集的字节数
        int item, s_pos, q_pos, rest;
        ssize_t retval = -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);     // 获取设备dev的第item个量子集
        if (dptr == NULL)
            goto out;
        // 如果该量子集数据是NULL,就申请一块新内存
        if (!dptr->data) {
            dptr->data = kmalloc(qset * sizeof(char *), GFP_KERNEL);
            if (!dptr->data)
                goto out;
            memset(dptr->data, 0, qset * sizeof(char *));
        }
        // 如果第s_pos个量子是NULL,申请一块新内存
        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)) { // 从用户空间拷贝数据到内核空间,失败返回没有拷贝的字节数,成功返回0
            retval = -EFAULT;
            goto out;
        }
        *f_pos += count;
        retval = count;
     
            /* update the size */
        if (dev->size < *f_pos)
            dev->size = *f_pos;
     
      out:
        up(&dev->sem);
        return retval;
    }
     
    /*
     * The ioctl() implementation
     */
     
    int scull_ioctl(struct inode *inode, 
                    struct file *filp,      // 设备文件
                    unsigned int cmd,       // 功能号
                    unsigned long arg)      // 参数: 值,或者用户空间指针
    {
     
        int err = 0, tmp;
        int retval = 0;
        
        /*
         * extract the type and number bitfields, and don't decode
         * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()
         */
        // 对错误的命令,返回ENOTTY    TODO
        if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY;   // 提取ioctl的类型和功能号,不解码
        if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY;
     
        /*
         * the direction is a bitmask, and VERIFY_WRITE catches R/W
         * transfers. `Type' is user-oriented, while
         * access_ok is kernel-oriented, so the concept of "read" and
         * "write" is reversed
         */
        // 如果该ioctl为了读数据,检查当前进程是否可写arg(写到用户空间,用户就读到数据了);如果为了写数据,检查arg是否可读
        if (_IOC_DIR(cmd) & _IOC_READ)  // _IOC_DIR 获取读写属性域值
            // access_ok() 如果当前进程被允许访问用户空间addr处的内存,返回1,否则返回0
            err = !access_ok(VERIFY_WRITE,  // type: Type of access: %VERIFY_READ or %VERIFY_WRITE. 
                            (void __user *)arg,  // addr: User space pointer to start of block to check
                            _IOC_SIZE(cmd));     // size: Size of block to check. _IOC_SIZE() 读取数据大小域值
        else if (_IOC_DIR(cmd) & _IOC_WRITE)
            err =  !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
        if (err) return -EFAULT;
     
        switch(cmd) {
     
          // 重置量子集、量子大小 TODO:相关内存不同时整理?
          case SCULL_IOCRESET:
            scull_quantum = SCULL_QUANTUM;
            scull_qset = SCULL_QSET;
            break;
     
          // 设置量子大小,arg是指向量子大小值的指针        
          case SCULL_IOCSQUANTUM: /* Set: arg points to the value */
            if (! capable (CAP_SYS_ADMIN)) // 检查是否包含系统管理权限
                return -EPERM;
            // 取arg所指内容,赋值给scull_quantum
            // __get_user() 从用户空间获取一个简单变量,基本不做检查
            retval = __get_user(scull_quantum, // x: variable to store result.
                                (int __user *)arg); // ptr: source address, in user space
            break;
     
          // 设置量子大小, arg是值
          case SCULL_IOCTQUANTUM: /* Tell: arg is the value */
            if (! capable (CAP_SYS_ADMIN))
                return -EPERM;
            scull_quantum = arg;
            break;
     
          // 获取量子大小,arg是指针
          case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */
            retval = __put_user(scull_quantum, (int __user *)arg);
            break;
     
          // 查询量子大小,返回值 TODO 怎么赋值
          case SCULL_IOCQQUANTUM: /* Query: return it (it's positive) */
            return scull_quantum;
     
          // 交换量子大小,指针:按arg指向值设置量子大小,当前量子大小保存到arg指向空间
          case SCULL_IOCXQUANTUM: /* eXchange: use arg as pointer */
            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: /* sHift: like Tell + Query */
            if (! capable (CAP_SYS_ADMIN))
                return -EPERM;
            tmp = scull_quantum;
            scull_quantum = arg;
            return tmp;
     
          // 量子集大小,和上面量子功能类似
          case SCULL_IOCSQSET:  // set
            if (! capable (CAP_SYS_ADMIN))
                return -EPERM;
            retval = __get_user(scull_qset, (int __user *)arg);
            break;
     
          case SCULL_IOCTQSET: // tell
            if (! capable (CAP_SYS_ADMIN))
                return -EPERM;
            scull_qset = arg;
            break;
     
          case SCULL_IOCGQSET: // get
            retval = __put_user(scull_qset, (int __user *)arg);
            break;
     
          case SCULL_IOCQQSET: // query
            return scull_qset;
     
          case SCULL_IOCXQSET: // eXchange
            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: // sHift
            if (! capable (CAP_SYS_ADMIN))
                return -EPERM;
            tmp = scull_qset;
            scull_qset = arg;
            return tmp;
     
            /*
             * The following two change the buffer size for scullpipe.
             * The scullpipe device uses this same ioctl method, just to
             * write less code. Actually, it's the same driver, isn't it?
             */
     
          // tell & query 管道设备缓存大小
          case SCULL_P_IOCTSIZE:
            scull_p_buffer = arg;
            break;
     
          case SCULL_P_IOCQSIZE:
            return scull_p_buffer;
     
     
          default:  /* redundant, as cmd was checked against MAXNR */
            return -ENOTTY;
        }
        return retval;
     
    }
     
     
     
    /*
     * The "extended" operations -- only seek
     */
     
    loff_t scull_llseek(struct file *filp,  // 设备文件
                        loff_t off,         // 偏移
                        int whence)         // seek方式
    {
        struct scull_dev *dev = filp->private_data;
        loff_t newpos;
     
        switch(whence) {
          case 0: /* SEEK_SET */
            newpos = off;
            break;
     
          case 1: /* SEEK_CUR */
            newpos = filp->f_pos + off;
            break;
     
          case 2: /* SEEK_END */
            newpos = dev->size + off;
            break;
     
          default: /* can't happen */
            return -EINVAL;
        }
        if (newpos < 0) return -EINVAL;
        filp->f_pos = newpos;
        return newpos;
    }
     
     
     
    struct file_operations scull_fops = {
        .owner =    THIS_MODULE,
        .llseek =   scull_llseek,
        .read =     scull_read,
        .write =    scull_write,
        .ioctl =    scull_ioctl,
        .open =     scull_open,
        .release =  scull_release,
    };
     
    /*
     * Finally, the module stuff
     */
     
    /*
     * 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
     */
    void scull_cleanup_module(void)
    {
        int i;
        dev_t devno = MKDEV(scull_major, scull_minor); // MKDEV 把主次设备号合成为一个dev_t结构
     
        /* Get rid of our char dev entries */
        // 清除字符设备入口
        if (scull_devices) {
            // 遍历释放每个设备的数据区
            for (i = 0; i < scull_nr_devs; i++) {
                scull_trim(scull_devices + i);  // 释放数据区
                cdev_del(&scull_devices[i].cdev); // 移除cdev
            }
            kfree(scull_devices);  // 释放scull_devices本身
        }
     
    // 如果使用了/proc来debug,移除创建的/proc文件
    #ifdef SCULL_DEBUG /* use proc only if debugging */
        scull_remove_proc();
    #endif
     
        /* cleanup_module is never called if registering failed */
        // 解注册scull_nr_devs个设备号,从devno开始
        unregister_chrdev_region(devno,         // dev_t from 
                                 scull_nr_devs); // unsigned count
     
        /* and call the cleanup functions for friend devices */
        scull_p_cleanup();
        scull_access_cleanup();
     
    }
     
     
    /*
     * Set up the char_dev structure for this device.
     */
     // 建立 char_dev 结构
    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;
        // 添加字符设备dev->cdev,立即生效
        err = cdev_add (&dev->cdev,     // struct cdev *p: the cdev structure for the device
                        devno,          // dev_t dev: 第一个设备号
                        1);             // unsigned count: 该设备连续次设备号的数目
        /* Fail gracefully if need be */
        if (err)
            printk(KERN_NOTICE "Error %d adding scull%d", err, index);
    }
     
     
    int scull_init_module(void)
    {
        int result, i;
        dev_t dev = 0;
     
    /*
     * Get a range of minor numbers to work with, asking for a dynamic
     * major unless directed otherwise at load time.
     */
        // 申请设备号:获取一系列次设备号, 如果在加载时没有指定主设备号就动态申请一个
        if (scull_major) {
            dev = MKDEV(scull_major, scull_minor);
            // register_chrdev_region 用于已知起始设备的设备号的情况
            result = register_chrdev_region(dev, scull_nr_devs, "scull");
        } else {
            // alloc_chrdev_region 用于设备号未知,向系统动态申请未被占用的设备号情况
            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;
        }
     
            /* 
         * allocate the devices -- we can't have them static, as the number
         * can be specified at load time
         */
        // 给scull_dev对象申请内存
        scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);
        if (!scull_devices) {
            result = -ENOMEM;
            goto fail;  /* Make this more graceful */
        }
        memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev));
     
        // 初始化 scull_dev
            /* Initialize each device. */
        for (i = 0; i < scull_nr_devs; i++) {
            scull_devices[i].quantum = scull_quantum;
            scull_devices[i].qset = scull_qset;
            init_MUTEX(&scull_devices[i].sem); // 初始化互斥锁,把信号量sem置为1
            scull_setup_cdev(&scull_devices[i], i); // 建立char_dev结构
        }
     
            /* At this point call the init function for any friend device */
        // TODO
        dev = MKDEV(scull_major, scull_minor + scull_nr_devs);
        dev += scull_p_init(dev);
        dev += scull_access_init(dev);
     
    #ifdef SCULL_DEBUG /* only when debugging */
        scull_create_proc();
    #endif
     
        return 0; /* succeed */
     
      fail:
        scull_cleanup_module();
        return result;
    }
     
    module_init(scull_init_module);
    module_exit(scull_cleanup_module);
     
     
    access.c
    /*
     * access.c -- the files with access control on open
     *
     * 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.
     *
     * $Id: access.c,v 1.17 2004/09/26 07:29:56 gregkh Exp $
     */
     
    /* FIXME: cloned devices as a use for kobjects? */
     
    #include <linux/kernel.h> /* printk() */
    #include <linux/module.h>
    #include <linux/slab.h>   /* kmalloc() */
    #include <linux/fs.h>     /* everything */
    #include <linux/errno.h>  /* error codes */
    #include <linux/types.h>  /* size_t */
    #include <linux/fcntl.h>
    #include <linux/cdev.h>
    #include <linux/tty.h>
    #include <asm/atomic.h>
    #include <linux/list.h>
     
    #include "scull.h"        /* local definitions */
     
    static dev_t scull_a_firstdev;  /* Where our range begins */
     
    /*
     * These devices fall back on the main scull operations. They only
     * differ in the implementation of open() and close()
     */
     
    // 这些设备主要的scull操作是相同的,仅读写实现不同?
     
     
     
    /************************************************************************
     *
     * The first device is the single-open one,
     *  it has an hw structure and an open count
     */
    // 第一个设备,只能打开一次。有一个hw结构,和打开计数。 TODO: hw结构是什么?
     
    static struct scull_dev scull_s_device;
    // 原子类型,用于计数。初始化原子值为1.
    static atomic_t scull_s_available = ATOMIC_INIT(1); 
     
    static int scull_s_open(struct inode *inode, struct file *filp)
    {
        struct scull_dev *dev = &scull_s_device; /* device information */
     
        // 如果scull_s_available计数减1不等于0,计数加1,返回-EBUSY
         if (! atomic_dec_and_test (&scull_s_available)) { // 递减原子值;检查是0返回true,否则false
             atomic_inc(&scull_s_available);
            return -EBUSY; /* already open */
        }
     
        // 否则打开设备
        /* then, everything else is copied from the bare scull device */
        if ( (filp->f_flags & O_ACCMODE) == O_WRONLY)
            scull_trim(dev);
        filp->private_data = dev;
        return 0;          /* success */
    }
     
    static int scull_s_release(struct inode *inode, struct file *filp)
    {
        // scull_s_available加1,恢复1了
        atomic_inc(&scull_s_available); /* release the device */
        return 0;
    }
     
     
    /*
     * The other operations for the single-open device come from the bare device
     */
    struct file_operations scull_sngl_fops = {
        .owner =    THIS_MODULE,
        .llseek =         scull_llseek,
        .read =           scull_read,
        .write =          scull_write,
        .ioctl =          scull_ioctl,
        .open =           scull_s_open,
        .release =        scull_s_release,
    };
     
     
    /************************************************************************
     *
     * Next, the "uid" device. It can be opened multiple times by the
     * same user, but access is denied to other users if the device is open
     */
     
    // "uid"设备。可以被同一用户多次打开。拒绝其他用户同时打开。
     
    static struct scull_dev scull_u_device;
    static int scull_u_count;    /* initialized to 0 by default */
    static uid_t scull_u_owner;    /* initialized to 0 by default */
    static spinlock_t scull_u_lock = SPIN_LOCK_UNLOCKED; // 初始化自旋锁为0
     
    static int scull_u_open(struct inode *inode, struct file *filp)
    {
        struct scull_dev *dev = &scull_u_device; /* device information */
     
        // 获取自旋锁
        spin_lock(&scull_u_lock);
        // 如果设备已经被打开,且已打开设备的用户不是当前进程的uid或euid,且不具备CAP_DAC_OVERRIDE能力,则释放自旋锁,返回-EBUSY
        // uid表示进程的创建者,euid表示进程对文件和资源的访问权限(具备等同于哪个用户的权限)
        // CAP_DAC_OVERRIDE 越过在文件和目录上的访问限制的能力
        if (scull_u_count && 
                (scull_u_owner != current->uid) &&  /* allow user */
                (scull_u_owner != current->euid) && /* allow whoever did su */
                !capable(CAP_DAC_OVERRIDE)) { /* still allow root */
            spin_unlock(&scull_u_lock);
            return -EBUSY;   /* -EPERM would confuse the user */
        }
     
        // 如果是第一次打开设备,scull_u_owner设置成当前进程uid
        if (scull_u_count == 0)
            scull_u_owner = current->uid; /* grab it */
     
        // 递增打开计数。释放自旋锁。
        scull_u_count++;
        spin_unlock(&scull_u_lock);
     
    /* then, everything else is copied from the bare scull device */
        // 打开设备
     
        if ((filp->f_flags & O_ACCMODE) == O_WRONLY)
            scull_trim(dev);
        filp->private_data = dev;
        return 0;          /* success */
    }
     
    static int scull_u_release(struct inode *inode, struct file *filp)
    {
        // 获取自旋锁,递减打开计数,释放自旋锁。
        spin_lock(&scull_u_lock);
        scull_u_count--; /* nothing else */
        spin_unlock(&scull_u_lock);
        return 0;
    }
     
     
     
    /*
     * The other operations for the device come from the bare device
     */
    struct file_operations scull_user_fops = {
        .owner =      THIS_MODULE,
        .llseek =     scull_llseek,
        .read =       scull_read,
        .write =      scull_write,
        .ioctl =      scull_ioctl,
        .open =       scull_u_open,
        .release =    scull_u_release,
    };
     
    /************************************************************************
     *
     * Next, the device with blocking-open based on uid
     */
    // 设备阻塞于open TODO
     
    static struct scull_dev scull_w_device;
    static int scull_w_count;    /* initialized to 0 by default */
    static uid_t scull_w_owner;    /* initialized to 0 by default */
    static DECLARE_WAIT_QUEUE_HEAD(scull_w_wait);
    static spinlock_t scull_w_lock = SPIN_LOCK_UNLOCKED;
     
    // 判断设备是否可用
    static inline int scull_w_available(void)
    {
        // 设备没有被打开,或已打开设备的用户是当前进程的uid或euid,或具备CAP_DAC_OVERRIDE能力,则可用
        return scull_w_count == 0 ||
            scull_w_owner == current->uid ||
            scull_w_owner == current->euid ||
            capable(CAP_DAC_OVERRIDE);
    }
     
     
    static int scull_w_open(struct inode *inode, struct file *filp)
    {
        struct scull_dev *dev = &scull_w_device; /* device information */
     
        // 获取自旋锁
        spin_lock(&scull_w_lock);
     
        // 如果设备不可用,则释放自旋锁,等待设备可用后重新申请自旋锁,循环检查设备是否可用
        while (! scull_w_available()) {
            spin_unlock(&scull_w_lock);
            if (filp->f_flags & O_NONBLOCK) return -EAGAIN; // 如果文件以非阻塞模式打开,返回-EAGAIN,让用户层程序再试
            if (wait_event_interruptible (scull_w_wait, scull_w_available())) // 等待设备可用。如果等待过程中有异步信号,返回-ERESTARTSYS
                return -ERESTARTSYS; /* tell the fs layer to handle it */
            spin_lock(&scull_w_lock);
        }
     
        // 和上面的"uid"设备一样了
        if (scull_w_count == 0)
            scull_w_owner = current->uid; /* grab it */
        scull_w_count++;
        spin_unlock(&scull_w_lock);
     
        /* then, everything else is copied from the bare scull device */
        if ((filp->f_flags & O_ACCMODE) == O_WRONLY)
            scull_trim(dev);
        filp->private_data = dev;
        return 0;          /* success */
    }
     
    static int scull_w_release(struct inode *inode, struct file *filp)
    {
        int temp;
     
        // 申请自旋锁,递减打开计数,释放自旋锁。
        spin_lock(&scull_w_lock);
        scull_w_count--;
        temp = scull_w_count;
        spin_unlock(&scull_w_lock);
     
        // 如果打开计数是0,唤醒scull_w_wait信号
        if (temp == 0)
            wake_up_interruptible_sync(&scull_w_wait); /* awake other uid's */
        return 0;
    }
     
     
    /*
     * The other operations for the device come from the bare device
     */
    struct file_operations scull_wusr_fops = {
        .owner =      THIS_MODULE,
        .llseek =     scull_llseek,
        .read =       scull_read,
        .write =      scull_write,
        .ioctl =      scull_ioctl,
        .open =       scull_w_open,
        .release =    scull_w_release,
    };
     
    /************************************************************************
     *
     * Finally the `cloned' private device. This is trickier because it
     * involves list management, and dynamic allocation.
     */
     // 最后是被克隆的私有设备。
     // 这是棘手的,因为涉及列表管理和动态分配。
     
    /* The clone-specific data structure includes a key field */
     
    struct scull_listitem {
        struct scull_dev device;
        dev_t key;
        struct list_head list;
        
    };
     
    // 设备列表,和保护列表的自旋锁
    /* The list of devices, and a lock to protect it */
    static LIST_HEAD(scull_c_list); // 宏,声明并初始化一个list_head为scull_c_list的静态列表
    static spinlock_t scull_c_lock = SPIN_LOCK_UNLOCKED;
     
    /* A placeholder scull_dev which really just holds the cdev stuff. */
    // 占位符 scull_dev,实际只有cdev结构 TODO 没看懂。。
    static struct scull_dev scull_c_device;   
     
    /* Look for a device or create one if missing */
    // 查找指定key的设备,如果没有找到就创建一个新的
    static struct scull_dev *scull_c_lookfor_device(dev_t key)
    {
        struct scull_listitem *lptr;
     
        // 遍历scull_listitem,查找指定key的设备
        // 结构 struct scull_listitem lptr 中的成员struct list_head list组成了一个链表,链表头是struct list_head scull_c_list
        list_for_each_entry(lptr,  // pos: the type * to use as a loop cursor
                            &scull_c_list, // head: 要遍历的链表头
                            list) {        // member: the name of the list_struct within the struct
            if (lptr->key == key)
                return &(lptr->device); // 返回scull_dev 成员
        }
     
        /* not found */
        // 如果没有找到指定key的设备,创建一个scull_listitem结构,加在链表中,返回scull_dev成员
        lptr = kmalloc(sizeof(struct scull_listitem), GFP_KERNEL);
        if (!lptr)
            return NULL;
     
        /* initialize the device */
        memset(lptr, 0, sizeof(struct scull_listitem));
        lptr->key = key;
        scull_trim(&(lptr->device)); /* initialize it */ 
        init_MUTEX(&(lptr->device.sem));
     
        /* place it in the list */
        list_add(&lptr->list, &scull_c_list);
     
        return &(lptr->device);
    }
     
    static int scull_c_open(struct inode *inode, struct file *filp)
    {
        struct scull_dev *dev;
        dev_t key;
     
        // 如果当前设备没有终端,返回-EINVAL(参数无效)
        if (!current->signal->tty) { 
            PDEBUG("Process "%s" has no ctl tty
    ", current->comm);
            return -EINVAL;
        }
     
        // 获得当前终端的设备号保存到key中
        key = tty_devnum(current->signal->tty);
     
        /* look for a scullc device in the list */
        // 从 scull_listitem 中获取指定key的设备(如果没有就创建一个新的返回)-同一终端(key)共用一个设备
        spin_lock(&scull_c_lock);
        dev = scull_c_lookfor_device(key);
        spin_unlock(&scull_c_lock);
     
        if (!dev)
            return -ENOMEM;
     
        /* then, everything else is copied from the bare scull device */
        if ( (filp->f_flags & O_ACCMODE) == O_WRONLY)
            scull_trim(dev);
        filp->private_data = dev;
        return 0;          /* success */
    }
     
    static int scull_c_release(struct inode *inode, struct file *filp)
    {
        /*
         * Nothing to do, because the device is persistent.
         * A `real' cloned device should be freed on last close
         */
        return 0;
    }
     
     
     
    /*
     * The other operations for the device come from the bare device
     */
    struct file_operations scull_priv_fops = {
        .owner =    THIS_MODULE,
        .llseek =   scull_llseek,
        .read =     scull_read,
        .write =    scull_write,
        .ioctl =    scull_ioctl,
        .open =     scull_c_open,
        .release =  scull_c_release,
    };
     
    /************************************************************************
     *
     * And the init and cleanup functions come last
     */
     
    static struct scull_adev_info {
        char *name;
        struct scull_dev *sculldev;
        struct file_operations *fops;
    } scull_access_devs[] = {
        { "scullsingle", &scull_s_device, &scull_sngl_fops },
        { "sculluid", &scull_u_device, &scull_user_fops },
        { "scullwuid", &scull_w_device, &scull_wusr_fops },
        { "sullpriv", &scull_c_device, &scull_priv_fops }
    };
    #define SCULL_N_ADEVS 4
     
    /*
     * Set up a single device.
     */
     // 给定dev_t和scull_adev_info,建立一个设备
    static void scull_access_setup (dev_t devno, struct scull_adev_info *devinfo)
    {
        struct scull_dev *dev = devinfo->sculldev;
        int err;
     
        /* Initialize the device structure */
        // 初始化scull_dev结构
        dev->quantum = scull_quantum;
        dev->qset = scull_qset;
        init_MUTEX(&dev->sem);
     
        /* Do the cdev stuff. */
        // cdev_init 初始化一个cdev结构
        cdev_init(&dev->cdev,  // struct cdev *cdev: 要初始化的结构
                  devinfo->fops);  // const struct file_operations *fops: 该设备的 file_operations
                  
        // kobject_set_name 给kobject设置名字
        kobject_set_name(&dev->cdev.kobj,  // struct kobject *kobj: 要设置名字的结构 
                        devinfo->name);     // const char *fmt: 用来创建名字的格式化字符串
        dev->cdev.owner = THIS_MODULE;
        
        // cdev_add() 给系统中添加一个字符设备 &dev->cdev。立即生效。出错时返回一个负的错误码。
        err = cdev_add (&dev->cdev,     // struct cdev *p:  设备的cdev结构
                        devno,          // dev_t dev: 设备的第一个设备号
                        1);             // unsigned count: 设备对应的连续次设备号
            /* Fail gracefully if need be */
        
        // 如果添加设备出错,dev->cdev.kobject引用计数减1
        if (err) {
            printk(KERN_NOTICE "Error %d adding %s
    ", err, devinfo->name);
            // kobject_put 递减设备的引用计数。如果引用计数为0,调用 kobject_cleanup()
            kobject_put(&dev->cdev.kobj);  // struct kobject *kobj
        } else
            printk(KERN_NOTICE "%s registered at %x
    ", devinfo->name, devno);
    }
     
     
    int scull_access_init(dev_t firstdev)
    {
        int result, i;
     
        /* Get our number space */
        // 注册 SCULL_N_ADEVS 个设备号
        // register_chrdev_region() 注册一系列设备号。成功返回0,失败返回一个负的错误码。
        result = register_chrdev_region (firstdev,      // dev_t from: 要注册的第一个设备号,必须包含主设备号。 
                                         SCULL_N_ADEVS,     // unsigned count: 需要的连续设备号个数
                                         "sculla");         // const char *name: 设备或驱动名称
        if (result < 0) {
            printk(KERN_WARNING "sculla: device number registration failed
    ");
            return 0;
        }
        scull_a_firstdev = firstdev;
     
        /* Set up each device. */
        // 循环给系统中添加 SCULL_N_ADEVS 个字符设备
        for (i = 0; i < SCULL_N_ADEVS; i++)
            scull_access_setup (firstdev + i, scull_access_devs + i);
        return SCULL_N_ADEVS;
    }
     
    /*
     * This is called by cleanup_module or on failure.
     * It is required to never fail, even if nothing was initialized first
     */
    void scull_access_cleanup(void)
    {
        struct scull_listitem *lptr, *next;
        int i;
     
        /* Clean up the static devs */
        // 遍历清除scull_acess_dev中每个静态设备: 移除cdev,释放sculldev数据区
        for (i = 0; i < SCULL_N_ADEVS; i++) {
            struct scull_dev *dev = scull_access_devs[i].sculldev;
            // 从系统中删除dev->cdev
            // cdev_del() 从系统中移除一个 cdev ,可能要自己释放结构
            cdev_del(&dev->cdev);  // struct cdev *p: 要移除的cdev结构
            scull_trim(scull_access_devs[i].sculldev);
        }
     
            /* And all the cloned devices */
        // 遍历清除 scull_listitem中每个cloned设备: 删除list_head,释放sculldev数据区,释放scull_listitem结构内存
        // list_for_each_entry_safe 要求调用者另外提供一个与pos(lptr)同类型的指针next,在for循环中暂存pos的下一个节点的地址,避免因pos节点被释放而造成的断链
        list_for_each_entry_safe(lptr, next, &scull_c_list, list) { 
            // list_del 删除链表的entry
            list_del(&lptr->list);  // struct list_head *entry: 要从链表中删除的元素
            scull_trim(&(lptr->device));
            kfree(lptr);
        }
     
        /* Free up our number space */
        // unregister_chrdev_region 解注册从scull_a_firstdev开始的连续SCULL_N_ADEVS个设备
        unregister_chrdev_region(scull_a_firstdev,  // dev_t from 
                                SCULL_N_ADEVS);     // unsigned count
        return;
    }
     
     
    pipe.c
    /*
     * pipe.c -- fifo driver for scull
     *
     * 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.
     *
     */
     
    #include <linux/module.h>
    #include <linux/moduleparam.h>
     
    #include <linux/kernel.h>    /* printk(), min() */
    #include <linux/slab.h>        /* kmalloc() */
    #include <linux/fs.h>        /* everything */
    #include <linux/proc_fs.h>
    #include <linux/errno.h>    /* error codes */
    #include <linux/types.h>    /* size_t */
    #include <linux/fcntl.h>
    #include <linux/poll.h>
    #include <linux/cdev.h>
    #include <asm/uaccess.h>
     
    #include "scull.h"        /* local definitions */
     
    struct scull_pipe {
            wait_queue_head_t inq, outq;       /* read and write queues */ // wait_queue_head_t 等待队列头
            char *buffer, *end;                /* begin of buf, end of buf */
            int buffersize;                    /* used in pointer arithmetic */
            char *rp, *wp;                     /* where to read, where to write */
            int nreaders, nwriters;            /* number of openings for r/w */
            struct fasync_struct *async_queue; /* asynchronous readers */ // 异步操作的文件指针结构 TODO
            struct semaphore sem;              /* mutual exclusion semaphore */
            struct cdev cdev;                  /* Char device structure */
    };
     
    /* parameters */
    static int scull_p_nr_devs = SCULL_P_NR_DEVS;    /* number of pipe devices */
    int scull_p_buffer =  SCULL_P_BUFFER;    /* buffer size */
    dev_t scull_p_devno;            /* Our first device number */
     
    // 模块加载时,指定管道设备数量、缓存大小
    module_param(scull_p_nr_devs, int, 0);    /* FIXME check perms */
    module_param(scull_p_buffer, int, 0);
     
    static struct scull_pipe *scull_p_devices;
     
    static int scull_p_fasync(int fd, struct file *filp, int mode);
    static int spacefree(struct scull_pipe *dev);
    /*
     * Open and close
     */
     
     
    static int scull_p_open(struct inode *inode, struct file *filp)
    {
        struct scull_pipe *dev;
     
        // 获取和inode->i_cdev对应的管道设备(scull_pipe结构)指针
        // container_of: cdev嵌套在struct scull_pipe中,由指向cdev成员的指针,获得指向struct scull_pipe的指针
        dev = container_of(inode->i_cdev,  // ptr: 指向成员的指针
                           struct scull_pipe,   // type: 容器结构的类型
                           cdev);           // member: 成员名称
        filp->private_data = dev;
     
        if (down_interruptible(&dev->sem))
            return -ERESTARTSYS;
        // 如果dev->buffer为NULL,申请缓存
        if (!dev->buffer) {
            /* allocate the buffer */
            dev->buffer = kmalloc(scull_p_buffer, GFP_KERNEL);
            if (!dev->buffer) {
                up(&dev->sem);
                return -ENOMEM;
            }
        }
        // 给dev成员设初值
        dev->buffersize = scull_p_buffer;
        dev->end = dev->buffer + dev->buffersize;
        dev->rp = dev->wp = dev->buffer; /* rd and wr from the beginning */
     
        /* use f_mode,not  f_flags: it's cleaner (fs/open.c tells why) */
        // 递增设备打开计数,区分读写模式。
        if (filp->f_mode & FMODE_READ)
            dev->nreaders++;
        if (filp->f_mode & FMODE_WRITE)
            dev->nwriters++;
        up(&dev->sem);
     
        // 通知内核设备不支持移位llseek 
        return nonseekable_open(inode, filp);
    }
     
     
     
    static int scull_p_release(struct inode *inode, struct file *filp)
    {
        struct scull_pipe *dev = filp->private_data;
     
        /* remove this filp from the asynchronously notified filp's */
        scull_p_fasync(-1, filp, 0); // TODO
        down(&dev->sem);
        // 设备打开计数减1。打开计数为0时清空设备缓存。
        if (filp->f_mode & FMODE_READ)
            dev->nreaders--;
        if (filp->f_mode & FMODE_WRITE)
            dev->nwriters--;
        if (dev->nreaders + dev->nwriters == 0) {
            kfree(dev->buffer);
            dev->buffer = NULL; /* the other fields are not checked on open */
        }
        up(&dev->sem);
        return 0;
    }
     
    /*
     * Data management: read and write
     */
     
    static ssize_t scull_p_read (struct file *filp, char __user *buf, size_t count,
                    loff_t *f_pos)
    {
        struct scull_pipe *dev = filp->private_data;
     
        if (down_interruptible(&dev->sem))
            return -ERESTARTSYS;
     
        // 如果缓冲区空(读写指针重合),循环等待缓冲区中有数据可读。
        while (dev->rp == dev->wp) { /* nothing to read */
            up(&dev->sem); /* release the lock */
            if (filp->f_flags & O_NONBLOCK) // 非阻塞打开
                return -EAGAIN;
            PDEBUG(""%s" reading: going to sleep
    ", current->comm);
            if (wait_event_interruptible(dev->inq, (dev->rp != dev->wp)))   // 异步信号
                return -ERESTARTSYS; /* signal: tell the fs layer to handle it */
            /* otherwise loop, but first reacquire the lock */
            if (down_interruptible(&dev->sem))      // 可被中断地获取信号量
                return -ERESTARTSYS;
        }
     
        // 数据读取到用户空间。最多读取到dev->end(线程非循环缓冲)
        /* ok, data is there, return something */
        if (dev->wp > dev->rp)
            count = min(count, (size_t)(dev->wp - dev->rp));
        else /* the write pointer has wrapped, return data up to dev->end */
            count = min(count, (size_t)(dev->end - dev->rp));
        if (copy_to_user(buf, dev->rp, count)) {
            up (&dev->sem);
            return -EFAULT;
        }
     
        // 读位置修改
        dev->rp += count;
        if (dev->rp == dev->end) // 如果读到缓存结尾了,读位置移至缓存开头
            dev->rp = dev->buffer; /* wrapped */
        up (&dev->sem);
     
        /* finally, awake any writers and return */
        wake_up_interruptible(&dev->outq);
        PDEBUG(""%s" did read %li bytes
    ",current->comm, (long)count);
        return count;
    }
     
    /* Wait for space for writing; caller must hold device semaphore.  On
     * error the semaphore will be released before returning. */
    static int scull_getwritespace(struct scull_pipe *dev, struct file *filp)
    {
        // 如果缓存已满
        // apacefree返回缓存中可写空间个数
        while (spacefree(dev) == 0) { /* full */
            // 初始化等待队列
            DEFINE_WAIT(wait);  // DEFINE_WAIT 创建和初始化一个等待队列。wait是队列入口项的名字。
            
            up(&dev->sem);
            if (filp->f_flags & O_NONBLOCK) // 如果文件在非阻塞模式,立即返回
                return -EAGAIN;
            PDEBUG(""%s" writing: going to sleep
    ",current->comm);
            // 添加写等待队列入口,设置进程状态为可中断休眠
            // prepare_to_wait()添加等待队列入口到队列,并设置进程状态
            prepare_to_wait(&dev->outq,  // wait_queue_head_t *queue: 等待队列头
                            &wait,      // wait_queue_t *wait: 进程入口
                            TASK_INTERRUPTIBLE);    // int state: 进程新状态 TASK_INTERRUPTIBLE 可中断休眠
            // 在检查确认仍然需要休眠之后,调用schedule
            if (spacefree(dev) == 0)
                schedule(); // TODO 
            // 条件满足退出后,确保状态为running,同时将进程从等待队列中删除。
            finish_wait(&dev->outq,  // wait_queue_head_t *queue
                        &wait);      // wait_queue_t *wait
            // 如果当前进程有信号处理,返回-ERESTARTSYS
            if (signal_pending(current)) // signal_pending 检查当前进程是否有信号处理,返回不为0表示有信号需要处理
                return -ERESTARTSYS; /* signal: tell the fs layer to handle it */
            // 获取信号量,除非发生中断
            // down_iterruptible尝试获取信号量。如果没有更多任务被允许获取信号量,该任务投入睡眠等待;如果睡眠被信号打断,返回-EINTR;如果成功获取信号量,返回0
            if (down_interruptible(&dev->sem))
                return -ERESTARTSYS;
        }
        return 0;
    }    
     
    /* How much space is free? */
    // 获取dev空闲空间个数
    static int spacefree(struct scull_pipe *dev)
    {
        if (dev->rp == dev->wp)
            return dev->buffersize - 1;
        return ((dev->rp + dev->buffersize - dev->wp) % dev->buffersize) - 1; // todo
    }
     
    static ssize_t scull_p_write(struct file *filp, const char __user *buf, size_t count,
                    loff_t *f_pos)
    {
        struct scull_pipe *dev = filp->private_data;
        int result;
     
        // 获取信号量
        if (down_interruptible(&dev->sem))
            return -ERESTARTSYS;
     
        /* Make sure there's space to write */
        // 如果没有空间可写,返回错误码
        result = scull_getwritespace(dev, filp);
        if (result)
            return result; /* scull_getwritespace called up(&dev->sem) */
     
        /* ok, space is there, accept something */
        count = min(count, (size_t)spacefree(dev)); // apacefree 获取空闲空间个数
        if (dev->wp >= dev->rp)
            count = min(count, (size_t)(dev->end - dev->wp)); /* to end-of-buf */
        else /* the write pointer has wrapped, fill up to rp-1 */
            count = min(count, (size_t)(dev->rp - dev->wp - 1));
        PDEBUG("Going to accept %li bytes to %p from %p
    ", (long)count, dev->wp, buf);
        if (copy_from_user(dev->wp, buf, count)) {
            up (&dev->sem);
            return -EFAULT;
        }
        dev->wp += count;
        if (dev->wp == dev->end)
            dev->wp = dev->buffer; /* wrapped */
        up(&dev->sem);
     
        /* finally, awake any reader */
        wake_up_interruptible(&dev->inq);  /* blocked in read() and select() */
     
        /* and signal asynchronous readers, explained late in chapter 5 */
        if (dev->async_queue)
            kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
        PDEBUG(""%s" did write %li bytes
    ",current->comm, (long)count);
        return count;
    }
     
    static unsigned int scull_p_poll(struct file *filp, poll_table *wait)
    {
        struct scull_pipe *dev = filp->private_data;
        unsigned int mask = 0;
     
        /*
         * The buffer is circular; it is considered full
         * if "wp" is right behind "rp" and empty if the
         * two are equal.
         */
         // 环形缓冲: wp在rp右侧表示缓冲区满;wp==rp表示空。
        // down() 检查dev->sem是否大于0.如果大于0,将值减1;如果等于0,进程投入睡眠.
        down(&dev->sem);
        // 把当前进程添加到wait参数指定的等待列表中
        poll_wait(filp,   // struct file *filp
                  &dev->inq,  // wait_queue_head_t * wait_address
                  wait);    // poll_table *p
        poll_wait(filp, &dev->outq, wait);
        // 如果缓存不空,可读
        if (dev->rp != dev->wp)
            mask |= POLLIN | POLLRDNORM;    /* readable */
        // 如果缓存不满,可写
        if (spacefree(dev))
            mask |= POLLOUT | POLLWRNORM;    /* writable */
        // 释放信号量
        up(&dev->sem);
        return mask;
    }
     
     
    static int scull_p_fasync(int fd, struct file *filp, int mode)
    {
        struct scull_pipe *dev = filp->private_data;
     
        // 设置 fasync 队列。
        // fasync_helper()从相关的进程列表中添加或去除入口项。出错返回负数,没有改变返回0,添加删除entry返回正数。
        return fasync_helper(fd, filp, mode, &dev->async_queue);
    }
     
    /* FIXME this should use seq_file */
    #ifdef SCULL_DEBUG
    static void scullp_proc_offset(char *buf, char **start, off_t *offset, int *len)
    {
        if (*offset == 0)
            return;
        if (*offset >= *len) {    /* Not there yet */
            *offset -= *len;
            *len = 0;
        }
        else {            /* We're into the interesting stuff now */
            *start = buf + *offset;
            *offset = 0;
        }
    }
     
     
    static int scull_read_p_mem(char *buf, char **start, off_t offset, int count,
            int *eof, void *data)
    {
        int i, len;
        struct scull_pipe *p;
     
    #define LIMIT (PAGE_SIZE-200)    /* don't print any more after this size */
        *start = buf;
        // len 记录读取字节数
        len = sprintf(buf, "Default buffersize is %i
    ", scull_p_buffer);
        // 遍历每一个管道设备,总输出长度不大于LIMIT
        for(i = 0; i<scull_p_nr_devs && len <= LIMIT; i++) {
            p = &scull_p_devices[i];
            if (down_interruptible(&p->sem)) // 可中断的申请信号量
                return -ERESTARTSYS;
            len += sprintf(buf+len, "
    Device %i: %p
    ", i, p);
    /*        len += sprintf(buf+len, "   Queues: %p %p
    ", p->inq, p->outq);*/
            len += sprintf(buf+len, "   Buffer: %p to %p (%i bytes)
    ", p->buffer, p->end, p->buffersize);
            len += sprintf(buf+len, "   rp %p   wp %p
    ", p->rp, p->wp);
            len += sprintf(buf+len, "   readers %i   writers %i
    ", p->nreaders, p->nwriters);
            up(&p->sem);
            // TODO: 移动偏移量?
            scullp_proc_offset(buf, start, &offset, &len);
        }
        *eof = (len <= LIMIT);
        return len;
    }
     
     
    #endif
     
     
     
    /*
     * The file operations for the pipe device
     * (some are overlayed with bare scull)
     */
    struct file_operations scull_pipe_fops = {
        .owner =    THIS_MODULE,
        .llseek =    no_llseek,
        .read =        scull_p_read,
        .write =    scull_p_write,
        .poll =        scull_p_poll,
        .ioctl =    scull_ioctl,
        .open =        scull_p_open,
        .release =    scull_p_release,
        .fasync =    scull_p_fasync,
    };
     
     
    /*
     * Set up a cdev entry.
     */
    static void scull_p_setup_cdev(struct scull_pipe *dev, int index)
    {
        int err, devno = scull_p_devno + index;
        
        cdev_init(&dev->cdev, &scull_pipe_fops);
        dev->cdev.owner = THIS_MODULE;
        err = cdev_add (&dev->cdev, devno, 1);
        /* Fail gracefully if need be */
        if (err)
            printk(KERN_NOTICE "Error %d adding scullpipe%d", err, index);
    }
     
     
     
    /*
     * Initialize the pipe devs; return how many we did.
     */
     // 初始化管道设备; 返回管道设备个数。
    int scull_p_init(dev_t firstdev)
    {
        int i, result;
     
        // 注册一系列设备号。成功返回0,失败返回一个负的错误码。
        result = register_chrdev_region(firstdev,  // dev_t from: 要注册的第一个设备号,必须包含主设备号
                                        scull_p_nr_devs,    // unsigned count: 需要的连续设备号个数
                                        "scullp");          // const char *name: 设备或驱动名称
        if (result < 0) {
            printk(KERN_NOTICE "Unable to get scullp region, error %d
    ", result);
            return 0;
        }
        scull_p_devno = firstdev;  // 第一个设备号
        // 申请scull_p_nr_devs个scull_pipe设备的存储空间
        scull_p_devices = kmalloc(scull_p_nr_devs * sizeof(struct scull_pipe), GFP_KERNEL);
        if (scull_p_devices == NULL) {
            unregister_chrdev_region(firstdev, scull_p_nr_devs);
            return 0;
        }
        memset(scull_p_devices, 0, scull_p_nr_devs * sizeof(struct scull_pipe));
        for (i = 0; i < scull_p_nr_devs; i++) {
            // 初始化读写等待队列头
            init_waitqueue_head(&(scull_p_devices[i].inq)); 
            init_waitqueue_head(&(scull_p_devices[i].outq));
            // 初始化互斥信号量,sem置1
            init_MUTEX(&scull_p_devices[i].sem);
            // 建立字符设备入口
            scull_p_setup_cdev(scull_p_devices + i, i);
        }
    #ifdef SCULL_DEBUG
        create_proc_read_entry("scullpipe", 0, NULL, scull_read_p_mem, NULL);
    #endif
        return scull_p_nr_devs;
    }
     
    /*
     * This is called by cleanup_module or on failure.
     * It is required to never fail, even if nothing was initialized first
     */
    void scull_p_cleanup(void)
    {
        int i;
     
    #ifdef SCULL_DEBUG
        remove_proc_entry("scullpipe", NULL);
    #endif
     
        if (!scull_p_devices)
            return; /* nothing else to release */
     
        for (i = 0; i < scull_p_nr_devs; i++) {
            cdev_del(&scull_p_devices[i].cdev);
            kfree(scull_p_devices[i].buffer);
        }
        kfree(scull_p_devices);
        unregister_chrdev_region(scull_p_devno, scull_p_nr_devs);
        scull_p_devices = NULL; /* pedantic */
    }
     
  • 相关阅读:
    ES6 Symbol类型 附带:Proxy和Set
    why updating the Real DOM is slow, what is Virtaul DOM, and how updating Virtual DOM increase the performance?
    React高级指南
    池(Pool)
    计算机网络Intro
    React基础
    CommonJS 与 ES6 的依赖操作方法(require、import)
    webpack初识(biaoyansu)
    关于时间安排贪心算法正确性的证明
    DP总结
  • 原文地址:https://www.cnblogs.com/jiangzhaowei/p/11111251.html
Copyright © 2020-2023  润新知