Linux驱动入门篇(二):基本的字符设备模块(1)

  在熟悉了模块的基本框架后,可以开启模块编程的大门了。


  如我们所知,模块编程的目的是驱动各种各样的设备。那么设备分为哪些类型呢?对Linux内核来说,设备分为字符设备、块设备和网络设备。

 

  今天先谈字符设备。字符设备的特性:只能一个字节一个字节地按顺序读取,不能任意读取。常见的字符设备有键盘、LED、串口等。


  一个基本的字符设备模块程序需要做的事情有以下几件:

  1.申请设备号;

  2.初始化并注册字符设备;

  3.创建设备文件;

  4.实现文件操作接口,以供上层调用。

预备知识

一、设备号

  每个字符设备都有一个主设备号和次设备号。

 

  Linux内核允许多个驱动程序共享主设备号,但大多数设备都遵循“一个主设备号对应着一个驱动程序”原则。而次设备号供内核使用,用于确定指向的设备。

 

  内核中用 dev_t 类型来保存设备号。定义在<linux/types.h>中。dev_t 类型定义一个32位的数,前12位表示主设备号,后20位表示次设备号。为了安全地使用设备号(比如向后兼容),应该始终使用定义在<linux/kdev_t.h>中的宏。

#define MINORBITS    20
#define MINORMASK    ((1U << MINORBITS) - 1)

#define MAJOR(dev)    ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)    ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))

  若要通过 dev_t 类型获得主设备号和次设备号:

  MAJOR(dev_t dev);

  MINOR(dev_t dev);

 

  若要通过主、次设备号得到 dev_t 类型的数:

  MKDEV(int major, int minor);

二、申请和释放设备号

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
            const char *name);

  在<linux/fs.h>中声明了动态分配设备号的函数,成功则返回0。(静态分配使用register_chrdev_region函数,此处不叙述)

 

  dev:保存申请到的设备号;

  baseminor:请求的第一个次设备号;

  count:连续请求多少个设备号;

  name:设备名,它将出现在/proc/devices和sysfs中。


void unregister_chrdev_region(dev_t from, unsigned count);


  当不再使用设备号时,应该释放它们,以留给其他设备使用。释放设备号函数中,from 为设备号,count 为连续释放的个数。

 

三、创建和销毁设备文件

  类 UNIX 操作系统中的一条设计思想就是“一切皆文件”。自然,设备也是文件咯。访问设备即是访问设备文件。那么我们就必须为设备创建一个设备文件,以便访问它。

 

  首先,我们需要通过 cat /proc/devices 来查看我们的设备名对应的设备号。之后就可以创建设备文件了。

 

  (1)手动创建:mknod filename filetype major minor (在/dev目录下创建)

 

  (2)自动创建:在系统支持 udev/mdev 的时候,可以自动创建设备文件。首先,在模块初始化代码里调用 class_create宏 为设备在 /sys/class 下创建一个class,再调用 device_create函数 创建对应的设备文件。它们在<linux/device.h>中被声明和定义。

 

  在模块的清除函数中还需调用 device_destroy 以及 class_destroy 来进行清除不再使用的文件节点和设备文件。

初始化及清除函数

#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
  
dev_t devno;    //设备号
  
static struct class *my_class;

static int __init mycdev_init(void)
{
        int ret;
        ret = alloc_chrdev_region(&devno, 0, 1, "mycdev");
        if(ret != 0){
                printk(KERN_NOTICE "Alloc device number failed.");
                return -1;
        }
 
        mycdev_setup();	//初始化及注册设备到内核,暂不实现
 
        my_class = class_create(THIS_MODULE, "mycdev");
        device_create(my_class, NULL, devno, NULL, "mycdev");
  
        return 0;
}
  
static void mycdev_exit(void)
{
        mycdev_del()	//此处实现设备注销
  
        device_destroy(my_class, devno);
        class_destroy(my_class);
  
        unregister_chrdev_region(devno, 1);
}
  
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("Dual BSD/GPL");

  这就是基本的字符设备模块的初始化和清除函数。至于字符设备的初始化和注册函数与设备注销函数留待后面实现。

posted @ 2018-01-30 21:01  Salvare  阅读(746)  评论(0)    收藏  举报