scull 代码分析

scull

Posted by cslqm on March 17, 2020

模块相关

1
2
module_init(scull_init_module);
module_exit(scull_cleanup_module);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
//scull.h
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              */
};

// 模块初始化
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) {  // scull_major != 0 静态初始化
                dev = MKDEV(scull_major, scull_minor);
                result = register_chrdev_region(dev, scull_nr_devs, "scull");
        } else { // scull_major == 0 动态初始化
                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_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));

        /* Initialize each device. */
        for (i = 0; i < scull_nr_devs; i++) {  //初始化设备
                scull_devices[i].quantum = scull_quantum;
                scull_devices[i].qset = scull_qset;
                sema_init(&scull_devices[i].sem, 1); // 初始化互斥锁,把信号量sem置为1
                scull_setup_cdev(&scull_devices[i], i);
        }

        /* At this point call the init function for any friend device */
        dev = MKDEV(scull_major, scull_minor + scull_nr_devs); //
        dev += scull_p_init(dev); // pipe 初始化
        dev += scull_access_init(dev);

#ifdef SCULL_DEBUG /* only when debugging */
        scull_create_proc();
#endif

        return 0; /* succeed */

  fail:
        scull_cleanup_module(); //失败时
        return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*
 * 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);

        /* 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);
                }
                kfree(scull_devices);
        }

#ifdef SCULL_DEBUG /* use proc only if debugging */
        scull_remove_proc();
#endif

        /* cleanup_module is never called if registering failed */
        unregister_chrdev_region(devno, scull_nr_devs); // 注销 设备

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

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
struct scull_pipe {
        wait_queue_head_t inq, outq;       /* read and write queues */
        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 */
        struct semaphore sem;              /* mutual exclusion semaphore */
        struct cdev cdev;                  /* Char device structure */
};


// scull.h
#define SCULL_P_NR_DEVS 4

//pipe.c
/*
 * Initialize the pipe devs; return how many we did.
 */
static int scull_p_nr_devs = SCULL_P_NR_DEVS;   /* number of pipe devices */
int scull_p_init(dev_t firstdev)
{
        int i, result;

        result = register_chrdev_region(firstdev, scull_p_nr_devs, "scullp");// 静态初始化
        if (result < 0) {
                printk(KERN_NOTICE "Unable to get scullp region, error %d\n", result);
                return 0;
        }
        scull_p_devno = firstdev;
        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));
                sema_init(&scull_p_devices[i].sem, 1);
                scull_p_setup_cdev(scull_p_devices + i, i);
        }
#ifdef SCULL_DEBUG
        proc_create("scullpipe", 0, NULL, &scullpipe_proc_ops);
#endif
        return scull_p_nr_devs; // 创建的个数
}

//pipe.c
/*
 * 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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//access.c
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 },
        { "scullpriv", &scull_c_device, &scull_priv_fops }
};
#define SCULL_N_ADEVS 4


int scull_access_init(dev_t firstdev)
{
        int result, i;

        /* Get our number space */
        result = register_chrdev_region (firstdev, SCULL_N_ADEVS, "sculla");
        if (result < 0) {
                printk(KERN_WARNING "sculla: device number registration failed\n");
                return 0;
        }
        scull_a_firstdev = firstdev;

        /* Set up each device. */
        for (i = 0; i < SCULL_N_ADEVS; i++)
                scull_access_setup (firstdev + i, scull_access_devs + i);
        return SCULL_N_ADEVS;
}

/*
 * Set up a single device.
 */
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 */
        dev->quantum = scull_quantum; //量子大小
        dev->qset = scull_qset;       //量子集大小
        sema_init(&dev->sem, 1);

        /* Do the cdev stuff. */
        cdev_init(&dev->cdev, devinfo->fops);
        kobject_set_name(&dev->cdev.kobj, devinfo->name);
        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 %s\n", err, devinfo->name);
                kobject_put(&dev->cdev.kobj);
        } else
                printk(KERN_NOTICE "%s registered at %x\n", devinfo->name, devno);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 释放整个数据区。简单遍历列表并且释放它发现的任何量子和量子集。
// 在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;

        // 遍历量子集
        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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
struct file_operations scull_fops = {
        .owner =    THIS_MODULE,
        .llseek =   scull_llseek,
        .read =     scull_read,
        .write =    scull_write,
        .unlocked_ioctl = scull_ioctl,
        .open =     scull_open,
        .release =  scull_release,
};

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

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

        // 文件以只读模式打开时,截断为0
        /* now trim to 0 the length of the device if open was write-only */
        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 */
}

int scull_release(struct inode *inode, struct file *filp)
{
        return 0;
}

ssize_t scull_read(struct file *filp, char __user *buf, size_t count,
                loff_t *f_pos)
{
        struct scull_dev *dev = filp->private_data;
        struct scull_qset *dptr;        /* 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, dptr->data[s_pos] + q_pos, count)) {
                retval = -EFAULT;
                goto out;
        }
        *f_pos += count;
        retval = count;

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

//scull.h
struct scull_qset {
        void **data;
        struct scull_qset *next;
};

//main.c
/*
 * 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), 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)
                                return NULL;  /* Never mind */
                        memset(qs->next, 0, sizeof(struct scull_qset));
                }
                qs = qs->next;
                continue;
        }
        return qs;
}

关于scull_qset 的理解,偷个懒复制了别人的博客(https://www.cnblogs.com/shadox/archive/2011/08/01/2124365.html)。

1
2
3
4
5
//scull.h
struct scull_qset {
        void **data;
        struct scull_qset *next;
};

data指向一个指针数组,这个里面的指针指向Quantum,Quantum就处在第二维了,而Quantum中的一个个字节,就是第三维了(这个是我个人的理解)。

以这个三维数组的理解为基础。源码中的 SCULL_QUANTUM 宏用来设置第三维的长度(也就是 Quantum 中的字节数),SCULL_QSET宏用来设置第二维的长度(也就是一个 Scull_device 中 Quantum 的数量)。至于第一维的长度,Scull_device的数量是没有限制的,它直接由机器的内存决定。

这样做是为了什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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;
        //s 如果该量子集数据是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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*
 * Create a set of file operations for our proc files.
 */
static struct file_operations scullmem_proc_ops = {
        .owner   = THIS_MODULE,
        .open    = scullmem_proc_open,
        .read    = seq_read,
        .llseek  = seq_lseek,
        .release = single_release
};

static struct file_operations scullseq_proc_ops = {
        .owner   = THIS_MODULE,
        .open    = scullseq_proc_open,
        .read    = seq_read,
        .llseek  = seq_lseek,
        .release = seq_release
};

static void scull_create_proc(void)
{
        proc_create_data("scullmem", 0 /* default mode */,
                        NULL /* parent dir */, &scullmem_proc_ops,
                        NULL /* client data */);
        proc_create("scullseq", 0, NULL, &scullseq_proc_ops);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
static int scullmem_proc_open(struct inode *inode, struct file *file)
{
        return single_open(file, scull_read_procmem, NULL);
}

#ifdef SCULL_DEBUG /* use proc only if debugging */
/*
 * The proc filesystem: function to read and entry
 */
 // 在proc里实现文件,在文件被读时产生数据
// 当一个进程读 /proc 文件,内核分配了一页内存,驱动可以写入数据返回用户空间
int scull_read_procmem(struct seq_file *s, void *v)
{
        int i, j;
        int limit = s->size - 80; /* Don't print more than this */

        for (i = 0; i < scull_nr_devs && s->count <= limit; i++) {
                struct scull_dev *d = &scull_devices[i];
                struct scull_qset *qs = d->data;
                if (down_interruptible(&d->sem))
                        return -ERESTARTSYS;
                seq_printf(s,"\nDevice %i: qset %i, q %i, sz %li\n",
                             i, d->qset, d->quantum, d->size);
                // 遍历当前设备的每个量子集
                for (; qs && s->count <= limit; qs = qs->next) { /* scan the list */
                        seq_printf(s, "  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])
                                                seq_printf(s, "    % 4i: %8p\n",
                                                             j, qs->data[j]);
                                }
                }
                up(&scull_devices[i].sem);
        }
        return 0;
}

seq_file 从内核中导出信息到用户空间有很多方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
static int scullseq_proc_open(struct inode *inode, struct file *file)
{
        return seq_open(file, &scull_seq_ops);
}

/*
 * 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
};

/*
 * 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",
                        (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;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
// scull.h

/*
 * Ioctl definitions
 */

/* 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


/*
 * The ioctl() implementation
 */

long scull_ioctl(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
        if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY;
        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, (void __user *)arg, _IOC_SIZE(cmd));
        else if (_IOC_DIR(cmd) & _IOC_WRITE)
                err =  !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
        if (err) return -EFAULT;

        switch(cmd) {
          // 重置量子集、量子大小 
          case SCULL_IOCRESET:
                scull_quantum = SCULL_QUANTUM;
                scull_qset = SCULL_QSET;
                break;
          // 设置量子大小,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, (int __user *)arg);
                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:
                if (! capable (CAP_SYS_ADMIN))
                        return -EPERM;
                retval = __get_user(scull_qset, (int __user *)arg);
                break;

          case SCULL_IOCTQSET:
                if (! capable (CAP_SYS_ADMIN))
                        return -EPERM;
                scull_qset = arg;
                break;

          case SCULL_IOCGQSET:
                retval = __put_user(scull_qset, (int __user *)arg);
                break;

          case SCULL_IOCQQSET:
                return scull_qset;

          case SCULL_IOCXQSET:
                if (! capable (CAP_SYS_ADMIN))
                        return -EPERM;
                tmp = scull_qset;
                retval = __get_user(scull_qset, (int __user *)arg);
                if (retval == 0)
                        retval = put_user(tmp, (int __user *)arg);
                break;

          case SCULL_IOCHQSET:
                if (! capable (CAP_SYS_ADMIN))
                        return -EPERM;
                tmp = scull_qset;
                scull_qset = arg;
                return tmp;

        /*
         * 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;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/*
 * The "extended" operations -- only seek
 */

loff_t scull_llseek(struct file *filp, loff_t off, int whence)
{
        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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
struct scull_pipe {
        wait_queue_head_t inq, outq;       /* read and write queues */
        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 */
        struct semaphore sem;              /* mutual exclusion semaphore */
        struct cdev cdev;                  /* Char device structure */
};

/*
 * 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,
        .unlocked_ioctl = scull_ioctl,
        .open =         scull_p_open,
        .release =      scull_p_release,
        .fasync =       scull_p_fasync,
};

/*
 * 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); // 通知 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);

                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, TASK_INTERRUPTIBLE);
                // 在检查确认仍然需要休眠之后,调用schedule
                if (spacefree(dev) == 0)
                        schedule();
                // 条件满足退出后,确保状态为running,同时将进程从等待队列中删除。
                finish_wait(&dev->outq, &wait);
                // 如果当前进程有信号处理,返回-ERESTARTSYS
                if (signal_pending(current)) // signal_pending 检查当前进程是否有信号处理,返回不为0表示有信号需要处理
                        return -ERESTARTSYS; /* signal: tell the fs layer to handle it */
                // 获取信号量,除非发生中断
                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;
}

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));
        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,进程投入睡眠.
        // 环形缓冲: wp在rp右侧表示缓冲区满;wp==rp表示空。]   down(&dev->sem);
        // 把当前进程添加到wait参数指定的等待列表中
        down(&dev->sem);
        poll_wait(filp, &dev->inq,  wait);
        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;
}

/*
 * 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);
        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;
}

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);
}

access.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/************************************************************************
 *
 * 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 },
        { "scullpriv", &scull_c_device, &scull_priv_fops }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/*
 * 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,
        .unlocked_ioctl = scull_ioctl,
        .open =         scull_s_open,
        .release =      scull_s_release,
};


/************************************************************************
 *
 * The first device is the single-open one,
 *  it has an hw structure and an open count
 */
// 只能打开一次,有打开计数
static struct scull_dev scull_s_device;
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)) { // 原子操作
                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)
{
		// 加 1
        atomic_inc(&scull_s_available); /* release the device */
        return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/*
 * 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,
        .unlocked_ioctl = scull_ioctl,
        .open =       scull_u_open,
        .release =    scull_u_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 DEFINE_SPINLOCK(scull_u_lock);

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().val) &&  /* allow user */
                        (scull_u_owner != current_euid().val) && /* 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().val; /* 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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/*
 * 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,
        .unlocked_ioctl = scull_ioctl,
        .open =       scull_w_open,
        .release =    scull_w_release,
};

/************************************************************************
 *
 * Next, the device with blocking-open based on uid
 */

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 DEFINE_SPINLOCK(scull_w_lock);

static inline int scull_w_available(void)
{
		// 设备没有被打开,或已打开设备的用户是当前进程的uid或euid,或具备CAP_DAC_OVERRIDE能力,则可用
        return scull_w_count == 0 ||
                scull_w_owner == current_uid().val ||
                scull_w_owner == current_euid().val ||
                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);
                // 如果文件以非阻塞模式打开,返回-EAGAIN,让用户层程序再试
                if (filp->f_flags & O_NONBLOCK) return -EAGAIN;
                // 等待设备可用。如果等待过程中有异步信号,返回-ERESTARTSYS
                if (wait_event_interruptible (scull_w_wait, scull_w_available()))
                        return -ERESTARTSYS; /* tell the fs layer to handle it */
                spin_lock(&scull_w_lock);
        }
        if (scull_w_count == 0)
                scull_w_owner = current_uid().val; /* 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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/*
 * 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,
        .unlocked_ioctl = scull_ioctl,
        .open =     scull_c_open,
        .release =  scull_c_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);
static DEFINE_SPINLOCK(scull_c_lock);

/* A placeholder scull_dev which really just holds the cdev stuff. */
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, &scull_c_list, list) {
                if (lptr->key == key)
                        return &(lptr->device);
        }
        
        /* 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 */
        sema_init(&(lptr->device.sem), 1);
        
        /* 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;
}