在熟悉了模块的基本框架后,可以开启模块编程的大门了。
如我们所知,模块编程的目的是驱动各种各样的设备。那么设备分为哪些类型呢?对Linux内核来说,设备分为字符设备、块设备和网络设备。
今天先谈字符设备。字符设备的特性:只能一个字节一个字节地按顺序读取,不能任意读取。常见的字符设备有键盘、LED、串口等。
预备知识
一、设备号
每个字符设备都有一个主设备号和次设备号。
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_NOTEICE "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); } module_init(mycdev_init); module_exit(mycdev_exit); MODULE_LICENSE(“Dual BSD/GPL”);