【学习笔记】字符驱动程序

320阅读 0评论2016-10-06 诺亚方舟破土巴郎
分类:LINUX

以 linux device dervers 3td 中的字符设备驱动程序scull为模板,进行总结,主要是记录在学习过程中的知识点等。

在使用insmod时,会执行module_init宏定义的函数,如下
  1. module_init(scull_init_module); //本例中函数为scull_init_module。
在分析这个函数之前先理清楚几个概念。
设备编号:
设备编号由主设备号和次设备号组成,在内核内部使用dev_t类型来保存设备号包含了主和次设备号。
使用MKDEV(int major, int minor)来生成一个设备号,
使用MAJOR(dev_t dev)分离出主设备号,MINOR(dev_t dev)分离出次设备号。

申请设备号函数
int register_chrdev_region(dev_t from,unsigned count,const char * name)
此函数将申请from参数指定的设备号。count参数表示从from参数中的次设备号开始再连续申请多少个次设备号。name参数是该设备号范围关联的设备名称(这个名称其实不重要,mknod产生设备节点时和设备号有关和名称并无关系)。

  1. int alloc_chrdev_region(dev_t * dev,unsigned baseminor,unsigned count,const char * name)
这个函数是动态分配设备号。即不指定设备号,由内核给定。
dev参数是dev_t的指针类型,输出型参数,保存由此函数申请到的设备编号。baseminor参数是动态分配时的起始次设备号,一般指定0。其他参数同上一个函数一样。

在卸载设备驱动时要释放申请到的设备编号。
  1. unregister_chrdev_region(dev_t from,unsigned count)
文件操作结构file_operations
此结构体保存了驱动程序中实现的各种操作函数指针。这个结构体保存内核中系统调用支持的函数和驱动程序中实现函数的关联。

字符设备struct cdev:
此结构体在内核中表示字符设备。在内核调用设备的操作前,必须有一个cdev被分配。cdev中会包含file_operations结构。开始使用cdev一般是以下步骤:
1.分配空间给cdev,可以编译时(即定义变量),或运行时分配cdev_alloc函数。(scull程序中是运行时分配的但并没有使用cdev_alloc函数,因为cdev是包含在scull_dev结构体中,scull_dev类型的变量是kmalloc函数分配的,所以也是行时分配的)
2.初始化cdev,使用函数cdev_init(struct cdev * cdev,const struct file_operations * fops)
3.赋值cdev结构中的owner(拥有者)字段,和ops(file_operations类型)字段。
4.添加cdev到内核(也即关联cdev和设备编号),使用函数cdev_add(struct cdev * p,dev_t dev,unsigned count)
p参数是cdev变量的指针, dev参数是设备编号,count是连续关联的次设备号数量 (一般为1,即只关联一个次 设备,dev中指定的次设备)

当然在设备驱动卸载时要删除这个cdev字符设备结构在内核中的信息。
void cdev_del(struct cdev *dev);

scull_init_module函数

  1. //首先完成设备编号的分配。根据全局变量scull_major(主设备号)是否为0,指定或动态分配设备编号。
  2.     if (scull_major) {
  3.         dev = MKDEV(scull_major, scull_minor);
  4.         result = register_chrdev_region(dev, scull_nr_devs, "scull");
  5.     } else {
  6.         result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs,
  7.                 "scull");
  8.         scull_major = MAJOR(dev);    //将分配到的设备编号 保存到全局变量中
  9.         
  10.     }

  11.     //给scull_devices分配空间。在分析这部分时我们先看一个概念。
  12.     //scull_devices是struct scull_dev类型,它是sucll设备驱动程序中定义的一个用来表示此设备的一个结构体, //如下:
  13. struct scull_dev {
  14.     struct scull_qset *data; /* Pointer to first quantum set */
  15.     int quantum; /* the current quantum size */
  16.     int qset; /* the current array size */
  17.     unsigned long size; /* amount of data stored here */
  18.     unsigned int access_key; /* used by sculluid and scullpriv */
  19.     struct semaphore sem; /* mutual exclusion semaphore */
  20.     struct cdev cdev;     /* Char device structure        */
  21. };

一般驱动程序中都会自己构建一个结构体来表示这个驱动的设备,从上面结构体可以看出他包含一些和这个设备配置相关的变量。和一个cdev结构,一般此结构都要包含一个cdev结构(因为内核中cdev结构就代表了一个字符设备),用面向对象的说法就是cdev是scull_dev的父类。
这样看来给scull_devices分配空间,从标准上讲就是给分配了cdev,代码如下
  1. scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);
  2.         //这里连续分配了scull_nr_devs(程序中定义为4)个scull_dev结构的空间
  3.     if (!scull_devices) {
  4.         result = -ENOMEM;
  5.         goto fail; /* Make this more graceful */
  6.     }
  7.     memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev));

  8.     for(i=0;i<scull_nr_devs;i++){        //设置每个申请到的scull_dev结构
  9.         scull_devices[i].quantum = scull_quantum;    
  10.         scull_devices[i].qset = scull_qset_var;
  11.         init_MUTEX(&scull_devices[i].sem);
  12.         scull_setup_cdev(&scull_devices[i],i);    //给每个scull_dev结构中的cdev绑定相应的次设备号
  13.         }
分析scull_setup_cdev函数

  1. static void scull_setup_cdev(struct scull_dev *dev, int index)
  2. {
  3.     int err, devno = MKDEV(scull_major, scull_minor + index); //对传来的每个次设备号生产设备号

  4.     cdev_init(&dev->cdev, &scull_fops);
  5.     dev->cdev.owner = THIS_MODULE;
  6.     dev->cdev.ops = &scull_fops;    //设置文件操作结构(file_operations)
  7.     err = cdev_add(&dev->cdev,devno,1);    //关联设备号
  8.     if(err)
  9.         printk(KERN_NOTICE "Error for cdev_add on device %d ,error code is %d", index ,err);
  10. }
通过以上总结出是module_init宏指定的初始化函数一般要完成以下任务
1申请设备编号
2分配cdev结构并关联相应设备编号

-----------------------------------------------------------------------------
udev为了在模块添加和删除时自动创建和删除/dev目录下的设备节点,udev是用户空间的东西,不属于内

核空间,也即是为了让策略在用户空间。

udev程序背负着与namedev和libsysfs库的交互任务,在/sbin/hotplug程序被内核调用时,udev将被运行
udev依靠在/sys目录中的信息,创建和删除设备节点。

在驱动中使用udev机制,
1.在申请完相应的设备编号后,调用
  1. struct class *class_create(struct module *owner, const char *name)
此函数会在/sys/class目录下创建目录,owner参数一般设置为THIS_MODULE。name是要创建类的名字,即

/sys/class/下创建目录的名字。


2.调用device_create函数,在模块初始化时udev会相应此函数,在/dev下创建设备节点。函数原型如下

  1. struct device *device_create(struct class * cls,struct device * parent,dev_t devt,void *

  2. drvdata,const char * fmt,...)
参数说明,
cls:由第一步的class_create生成的类结构的指针。
parent: 此设备的父设备,没有为NULL。
devt: 设备号
drvdata:添加到设备中的回调数据(the data to be added to the device for callbacks),一般为NULL
fmt: 设备名称

最后在设备移除时顺序调用

  1. device_unregister(struct device *dev);    
  2. class_destroy(struct class *cls);
总结,申请设备号可以用register_chrdev,但书上说新版本中不建议使用这个函数,建议使用

register_chrdev_region函数申请设备号。老的函数不仅申请设备号而且会申请字符设备cdev,并注册。

但新函数只申请了设备号,cdev的处理要自己搞。老函数感觉比新函数方便,现在还没有体会到为什么要

建议使用新函数。

所以如果要利用udev机制自动创建设备节点,须在上述的初始化两个步骤后,再使用class_create和device_create函数,自动创建设备节点。


上一篇:裸机实验之MMU
下一篇:【学习笔记】字符驱动程序--阻塞型I/O字符