scull设备是一个操作内存的字符设备,不是真正的设备,只是向内核注册为字符设备,并使用内存来存取数据。
当用户向其中写数据时,它就申请空闲内存来存放用户数据;当用户读取其数据时,便按顺序读取数据。
接下来两个结构贯穿整个驱动,理解它是很必要的。
struct scull_qset {
void **data;
struct scull_qset *next;
};
struct scull_dev {
struct scull_qset *data;
int quantum;  
int qset;  
unsigned long size;  
unsigned int access_key;
struct semaphore sem;  
struct cdev cdev;
};
当申请内存空间时,不大可能一次完全申请需要的空间,只有需要(用户写)的时候才去申请内存,这就存在零散的内存区域,也需要管理这些零散的区域。故此,出现了上述的结构来来维护之!

struct scull_qset 量子集结构
所谓量子就是每次申请分配内存的最小单位,量子集就是这些最小单位的集合。(作者这么设计的,不是内核的概念)
void **data 这是个二重指针,表明它指向的单元存放的是一个指针,这个指针指向的地址才存放着真正的数据(量子)。
struct scull_qset *next 指向下一个量子集。
这个量子集是多大呢,通过这两个成员无法判断这个集合究竟多大。这是scull_dev来维护的。

struct scull_dev scull设备结构,维护该设备的所有数据
struct scull_qset *data,它指向第一个量子集。
quantum 量子大小。所有量子大小相同,只需这里维护。如果说每个量子集中量子大小不一致,那就需要把这个成员下移到量子集管理结构中。
qset 量子集大小,表明量子集中量子个数。同上面的理由一样,scull设备被设计为它的所有量子集大小一致。
size 设备中保存的数据量。
access_key  /* used by sculluid and scullpriv */ 未见使用
sem 互斥量,互斥量经常使用于存在可能同时访问共享资源的情况下。
cdev 注册字符设备就要靠它了。把它集成在cull_dev是相当有用的。看cull_open函数(用户open时)中,dev = container_of(inode->i_cdev, struct scull_dev, cdev); 这样可方便地从cdev得到cull_dev,有时也把它设置为全局变量,但是这并不够灵活。在后面的驱动程序会体会到这种做法的好处。