接触过linux的人都知道linux下有七类文件分别是”bcd-lsp”,即b:块设备文件、c:字符设备文件、d:目录、-:普通文件、l:符号链接文件、s:socket文件、p:管道文件。在这些文件里面有连个文件和我们驱动是息息相关的,它们就是字符设备文件和块设备文件。不同于其他文件块设备文件和字符设备文件一般都统一放在/dev目录下。
首先我们看看这类文件是如何得到的,在linux下有个命令mknod,这个命令就是创建设备文件的,命令执行示例:
mknod /dev/console c 5 1
mknod /dev/sda1 b 8 1
上面命令执行的时候有四个参数,第一个是我们创建设备文件的名字,第二个参数c(character)表示创建的是字符设备文件,b(block)表示创建的块设备文件,第三个参数表示主设备号,第四个参数表示此设备号。
Linux下一切皆文件,也就是说可以按照文件的方式访问设备,即访问/dev/下的这些设备文件就能访问到我们的设备。可是这个文件是我们创建出来的,为什么它就是一个设备了呢。其实设备文件本省并不重要,除了一些标准的设备有自己固定的名字外,其他的设备名字是什么并不重要,只要应用程序知道这个设备节点对应哪个设备就可以了,那么怎么样让设备文件和设备对应呢,那么这就是设备号的作用了。
我们在写设备驱动的时候会为每个设备分配一个设备号,而设备号有分为两部分,一部分是主设备号,一部分是次设备好,主设备好表示是类型的设备,次设备号表示是第几个这类设备。
前面我们介绍了模块的组成,这里就按部就班的把模块变成驱动,这里先写字符驱动,那么第一步也就是为我们的驱动分配设备号:
首先我们要构建一个设备号,在内核里有如下定义:
#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))
MKDEV就是构建设备号的一个宏,这个宏有两个参数分别对应的是主设备号和此设备号。从宏的定义我们能够看出一个设备号是一个无符号长整型的数,且主设备号和次设备号分别占12位和20位。另外两个宏MAJOR和MINOR是从一个设备号中取出主次设备号。
接着我们要做的工作是向操作系统申请这个号,在内核里有如下函数:
int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
……
}
我们使用上面这个函数来申请设备号我们先了解下他的参数和返回值:
参数 from:我们要申请的第一个设备号
count:我们要申请从from开始连续的多少个设备号
name:内核用来记录这段设备设备号被那个驱动申请走了
返回值 0 :表示成功
负数:表示errno即什么原因导致的我们的失败,errno可以参考include/asm-generic/errno.h和include/asm-generic/errno-base.h
有申请就有释放,释放设备号使用如下函数:
void unregister_chrdev_region(dev_t from, unsigned count)
{
……
}
参数 from:我们要申请的第一个设备号
count:我们要申请从from开始连续的多少个设备号
返回值:这个函数是linux里为数不多的没有返回值的函数
接着我们要做的工作就是把这些内容添加到我们的模块中:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
MODULE_LICENSE ("GPL");
int hello_major = 250;
int hello_minor = 0;
int number_of_devices = 1;
static int __init hello_2_init (void)
{
int result;
dev_t dev = 0;
dev = MKDEV (hello_major, hello_minor);
result = register_chrdev_region (dev, number_of_devices, "hello");
if (result<0) {
printk (KERN_WARNING "hello: can't get major number %d\n", hello_major);
return result;
}
printk (KERN_INFO "Registered character driver\n");
return 0;
}
static void __exit hello_2_exit (void)
{
dev_t devno = MKDEV (hello_major, hello_minor);
unregister_chrdev_region (devno, number_of_devices);
printk (KERN_INFO "Char driver cleaned up\n");
}
module_init (hello_2_init);
module_exit (hello_2_exit);
重新编译模块插入内核后我们可以通过cat /proc/devices查看我们的设备号是否申请成功如果是如下现象就OK了:
root@ubuntu:# cat /proc/devices
Character devices:
1 mem
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
6 lp
7 vcs
10 misc
13 input
21 sg
29 fb
99 ppdev
108 ppp
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
250 hello //这个名字和我们申请设备号函数传递的名字必须相同
251 vmci
252 usbmon
253 bsg
254 rtc