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");
这就是基本的字符设备模块的初始化和清除函数。至于字符设备的初始化和注册函数与设备注销函数留待后面实现。

浙公网安备 33010602011771号