用正确的工具,做正确的事情
scull驱动程序涉及到的相关数据结构以及数据结构之间的关系如下:
说明:
linux系统上字符设备以文件的形式提供给用户空间,应用程序通过对文件的操作还实现对设备的访问,因此字符设备驱动需要实现两类的函数,一类是驱动的初始化和卸载的函数,另一类即实现file_operations结构定义的相关文件操作接口函数。
scull_init_module函数主要完成设备驱动的初始化工作,主要干了三件事情:1)设备号的申请和注册,2)设备数据结构scull_dev的创建和初始化,3)字符设备cdev的初始化和注册,关键函数调用关系如下图:
详细代码实现及分析如下:
615 int scull_init_module(void)
616 {
617 int result, i;
618 dev_t dev = 0;
619
620 /*
621 * Get a range of minor numbers to work with, asking for a dynamic
622 * major unless directed otherwise at load time.
623 */
624 if (scull_major) {
625 dev = MKDEV(scull_major, scull_minor);
626 result = register_chrdev_region(dev, scull_nr_devs, "scull");
627 } else {
628 result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs,
629 "scull");
630 scull_major = MAJOR(dev);
631 }
632 if (result < 0) {
633 printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
634 return result;
635 }
636
637 /*
638 * allocate the devices -- we can't have them static, as the number
639 * can be specified at load time
640 */
641 scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);
642 if (!scull_devices) {
643 result = -ENOMEM;
644 goto fail; /* Make this more graceful */
645 }
646 memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev));
647
648 /* Initialize each device. */
649 for (i = 0; i < scull_nr_devs; i++) {
650 scull_devices[i].quantum = scull_quantum;
651 scull_devices[i].qset = scull_qset;
652 init_MUTEX(&scull_devices[i].sem);
653 scull_setup_cdev(&scull_devices[i], i);
654 }
655
656 /* At this point call the init function for any friend device */
657 dev = MKDEV(scull_major, scull_minor + scull_nr_devs);
658 dev += scull_p_init(dev);
659 dev += scull_access_init(dev);
660
661 #ifdef SCULL_DEBUG /* only when debugging */
662 scull_create_proc();
663 #endif
664
665 return 0; /* succeed */
666
667 fail:
668 scull_cleanup_module();
669 return result;
670 }
620-631申请并注册主次设备号,如果驱动加载时没有传入住设备号则动态从内核申请并分配,如果驱动加载时传入了主设备号参数则注册主设备号到内核。641-654 创建字符设备数据结构scull_dev,分配内存并且通过memset初始化内存区域,然后设置scull_dev数据结构的成员变量quantum、qset,之后通过调用scull_setup_cdev注册设备到内核。 scull_setup_cdev函数的实现如下:
601 static void scull_setup_cdev(struct scull_dev *dev, int index)
602 {
603 int err, devno = MKDEV(scull_major, scull_minor + index);
604
605 cdev_init(&dev->cdev, &scull_fops);
606 dev->cdev.owner = THIS_MODULE;
607 dev->cdev.ops = &scull_fops;
608 err = cdev_add (&dev->cdev, devno, 1);
609 /* Fail gracefully if need be */
610 if (err)
611 printk(KERN_NOTICE "Error %d adding scull%d", err, index);
612 }
605行调用内核函数cdev_init初始化scull_dev 成员变量 cdev, 在scull_init_module动态分配内存的形式创建了scull_dev结构,但是其成员变量cdev 并没有被初始化,通过cdev_init初始化cdev,将scull_fops与 cdev 做了关联,因此607行代码其实是不需要的。608行调用cdev_add将字符设备注册到内核,调用cdev_add时传入了设备号devno参数,将cdev与设备号关联。
cdev为字符设备在内核中的表示,cdev 比较重要的两个成员变量为file_operations ops和dev_t dev, file_operations定义该字符设备的访问操作接口,并最终由驱动来实现。dev_t保存具体的设备号,用户空间通过设备号来为设备创建设备文件的时候(创建inode),通过设备号将用户空间的设备文件(以及内核的inode)与内核的cdev对象关联起来。
238 int scull_open(struct inode *inode, struct file *filp)
239 {
240 struct scull_dev *dev; /* device information */
241
242 dev = container_of(inode->i_cdev, struct scull_dev, cdev);
243 filp->private_data = dev; /* for other methods */
244
245 /* now trim to 0 the length of the device if open was write-only */
246 if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) {
247 if (down_interruptible(&dev->sem))
248 return -ERESTARTSYS;
249 scull_trim(dev); /* ignore errors */
250 up(&dev->sem);
251 }
252 return 0; /* success */
253 }
scull_open的传入参数是struct inode和struct file结构对象的指针,struct inode和struct file均为内核层创建管理以及使用的数据结构(猜测struct inode结构是应用程序mknode创建设备文件时内核创建的结构,struct file结构是应用程序打开设备文件时内核层在内存中创建的结构),242~243行 根据struct inode的成员变量i_cdev得到包含该变量的struct scull_dev结构对象地址,并且将该结构对象地址赋值给struct file结构的 private_data字段,对private_data进行赋值的意义在于?之后应用层系统调用通过设备文件来访问和管理设备时只需要通过 内核的struct file结构就可以了。
通过filp->private_data将内核层抽象的struct file对象与具体的设备关联起来。
scull_read的实现如下:
/*
289 * Data management: read and write
290 */
291
292 ssize_t scull_read(struct file *filp, char __user *buf, size_t count,
293 loff_t *f_pos)
294 {
295 struct scull_dev *dev = filp->private_data;
296 struct scull_qset *dptr; /* the first listitem */
297 int quantum = dev->quantum, qset = dev->qset;
298 int itemsize = quantum * qset; /* how many bytes in the listitem */
299 int item, s_pos, q_pos, rest;
300 ssize_t retval = 0;
301
302 if (down_interruptible(&dev->sem))
303 return -ERESTARTSYS;
304 if (*f_pos >= dev->size)
305 goto out;
306 if (*f_pos + count > dev->size)
307 count = dev->size - *f_pos;
308
309 /* find listitem, qset index, and offset in the quantum */
310 item = (long)*f_pos / itemsize;
311 rest = (long)*f_pos % itemsize;
312 s_pos = rest / quantum; q_pos = rest % quantum;
313
314 /* follow the list up to the right position (defined elsewhere) */
315 dptr = scull_follow(dev, item);
316
317 if (dptr == NULL || !dptr->data || ! dptr->data[s_pos])
318 goto out; /* don't fill holes */
319
320 /* read only up to the end of this quantum */
321 if (count > quantum - q_pos)
322 count = quantum - q_pos;
323
324 if (copy_to_user(buf, dptr->data[s_pos] + q_pos, count)) {
325 retval = -EFAULT;
326 goto out;
327 }
328 *f_pos += count;
329 retval = count;
330
331 out:
332 up(&dev->sem);
333 return retval;
334 }
295行从传入参数struct file结构指针的private_data字段获取struct scull_dev变量指针, private_data是在 scull_open函数中被赋值的。297~298行获取设备量子集的长度以及量子的长度,并根据量集的长度以及量子的长度计算出每个量子集可以存储的字节数也即 itemsize。struct scull_dev和struct scull_qset结构的定义如下:
/*
* Representation of scull quantum sets.
*/
struct scull_qset {
void **data;
struct scull_qset *next;
};
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 */
};
scull_write的实现如下:
336 ssize_t scull_write(struct file *filp, const char __user *buf, size_t count,
337 loff_t *f_pos)
338 {
339 struct scull_dev *dev = filp->private_data;
340 struct scull_qset *dptr;
341 int quantum = dev->quantum, qset = dev->qset;
342 int itemsize = quantum * qset;
343 int item, s_pos, q_pos, rest;
344 ssize_t retval = -ENOMEM; /* value used in "goto out" statements */
345
346 if (down_interruptible(&dev->sem))
347 return -ERESTARTSYS;
348
349 /* find listitem, qset index and offset in the quantum */
350 item = (long)*f_pos / itemsize;
351 rest = (long)*f_pos % itemsize;
352 s_pos = rest / quantum; q_pos = rest % quantum;
353
354 /* follow the list up to the right position */
355 dptr = scull_follow(dev, item);
356 if (dptr == NULL)
357 goto out;
358 if (!dptr->data) {
359 dptr->data = kmalloc(qset * sizeof(char *), GFP_KERNEL);
360 if (!dptr->data)
361 goto out;
362 memset(dptr->data, 0, qset * sizeof(char *));
363 }
364 if (!dptr->data[s_pos]) {
365 dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL);
366 if (!dptr->data[s_pos])
367 goto out;
368 }
369 /* write only up to the end of this quantum */
370 if (count > quantum - q_pos)
371 count = quantum - q_pos;
372
373 if (copy_from_user(dptr->data[s_pos]+q_pos, buf, count)) {
374 retval = -EFAULT;
375 goto out;
376 }
377 *f_pos += count;
378 retval = count;
379
380 /* update the size */
381 if (dev->size < *f_pos)
382 dev->size = *f_pos;
383
384 out:
385 up(&dev->sem);
386 return retval;
387 }
玩的开心 !!!