小默

scull源码分析 //未完待续,囧

Makefile

 

# disable/enable debugging
#
DEBUG = y

# 当DEBUG变量等于y时。两个比较变量用括号括起来,逗号分隔。ifeq和括号中间有一个空格。
ifeq ($(DEBUG), y)  
# += 追加变量值。如果该变量之前没有被定义过,+=就自动变成=,变量被定义成递归展开式的变量;如果之前已经定义过,就遵循之前的风格。
#
 = 递归展开式变量:在定义时,变量中对其它变量的引用不会被替换展开。变量在引用它的地方被替换展开时,变量中其它变量才被同时替换展开。
#
 -O 程序优化参数;
#
 -g 使生成的debuginfo包额外支持gnu和gdb调试程序,易于使用
#
 -DSCULL_DEBUG 即define SCULL_DEBUG   TODO
    DEBFLAGS += ---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) 
-*.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 *= &scull_devices[i];
        
struct scull_qset *qs = d->data;
        
if (down_interruptible(&d->sem))
            
return -ERESTARTSYS;
        len 
+= sprintf(buf+len,"\nDevice %i: qset %i, q %i, sz %li\n",  // 输出当前是第几个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\n",     // 输出当前量子集 和 量子集中的数据 在内存中的位置。
                    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\n",
                                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, 
"\nDevice %i: qset %i, q %i, sz %li\n"// 输出,相当于用于空间的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\n", 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\n",
                            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, 
0sizeof(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, 0sizeof(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 < 0return -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\n", 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, 
0sizeof(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\n", 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\n", err, devinfo->name);
        
// kobject_put 递减设备的引用计数。如果引用计数为0,调用 kobject_cleanup()
        kobject_put(&dev->cdev.kobj);  // struct kobject *kobj
    } else
        printk(KERN_NOTICE 
"%s registered at %x\n", 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\n");
        
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, int0);    /* FIXME check perms */
module_param(scull_p_buffer, 
int0);

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\n", 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\n",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\n",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\n", (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\n",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\n", 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, "\nDevice %i: %p\n", i, p);
/*        len += sprintf(buf+len, "   Queues: %p %p\n", p->inq, p->outq);*/
        len 
+= sprintf(buf+len, "   Buffer: %p to %p (%i bytes)\n", p->buffer, p->end, p->buffersize);
        len 
+= sprintf(buf+len, "   rp %p   wp %p\n", p->rp, p->wp);
        len 
+= sprintf(buf+len, "   readers %i   writers %i\n", 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\n", 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 */
}
http://linux.chinaunix.net/bbs/thread-967565-1-1.html
http://www.ibm.com/developerworks/cn/linux/l-proc.html

 

源码:/Files/momoxiao/scull.zip

posted on 2010-12-10 23:48 小默 阅读(3031) 评论(0)  编辑 收藏 引用 所属分类: Linux


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理


导航

统计

留言簿(13)

随笔分类(287)

随笔档案(289)

漏洞

搜索

积分与排名

最新评论

阅读排行榜