设备号分主设备号和次设备号,主设备号用来标识设备所对应的驱动程序,由于同一个驱动程序可以支持多个设备,次设备号就是用来区分采用同一个驱动程序的不同设备。
Linux内核中采用dev_t来描述设备号,该类型定义在linux/types.h中,阅读内核源码可以看出dev_t实质是一个32位无符号整型,其中使用高12位为设备号,低20位为次设备号,Linux提供了两个宏MAJOR和MINOR用来从dev_t中分解出主设备号和次设备号,将主设备号和次设备号合并为dev_t类型,Linux也提供了宏MKDEV。
2. 设备号的申请(linux/fs.h)
(1) 静态申请
函数原型如下:
int register_chrdev_region(dev_t from, unsigned count, const char *name);
from为设备号起始值,form中的次设备号通常被设置成0,count是所请求连续设备号个数,name是设备号范围关联名称,它将出现在/proc/device和sysfs中。申请成功返回0,失败返回一个负的错误码,并且不能使用所请求的设备号。
(2) 动态申请
register_chrdev_region函数是在已有设备号基础上向内核申请对该设备号的使用,而动态申请是由内核分配一个设备号,函数原型如下:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
申请成功的设备号保存在dev中,baseminor为起始次设备号。
(3) 释放
不再使用该设备号时需要释放设备号,函数原型如下:
void unregister_chrdev_region(dev_t from, unsigned count);
3. 字符设备驱动的注册
Linux内核使用struct cdev结构来表示字符设备,该结构原型定义在linux/cdev.h中,该结构定义如下:
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
(1) cdev结构体初始化
cdev结构体申请有两种方式,一是静态分配cdev,二是动态分配。例如:
struct cdev my_cdev;
cdev_init(&my_cdev, &my_fops);
my_cdev.owner = THIS_MODULE;
my_cdev = cdev_alloc();
my_cdev->ops = my_fops;
my_cdev->owner = THIS_MODULE;
(2) 字符设备驱动注册
注册字符设备驱动使用cdev_add函数,函数原型如下:
int cdev_add(struct cdev *p, dev_t dev, unsigned count);
(3) 字符设备驱动注销
注销字符设备驱动使用cdev_del函数,函数原型如下:
void cdev_del(struct cdev *p);
4. 其它
早期有两个经典的字符设备驱动注册与注销函数register_chrdev和unregister_chrdev,函数原型如下:
int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops);
void unregister_chrdev(unsigned int major, const char *name);
// 2015.09.02 add
下面是一个字符设备驱动的实例:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#define MEM_SIZE 4096 /* 4K */
struct mem_dev {
struct cdev cdev;
char mem[MEM_SIZE];
};
struct mem_dev dev;
dev_t devno;
static ssize_t
mem_read(struct file *file, char __user *buf,
size_t count, loff_t *pos)
{
unsigned long p = *pos;
int ret = 0;
if (p >= MEM_SIZE)
return 0;
if (count > (MEM_SIZE - p))
count = MEM_SIZE - p;
ret = copy_to_user(buf, (void *)(dev.mem + p), count);
if (ret)
return -EFAULT;
else {
*pos += count;
return count;
}
}
static ssize_t
mem_write(struct file *file, const char __user *buf,
size_t count, loff_t *pos)
{
unsigned long p = *pos;
int ret = 0;
if (p >= MEM_SIZE)
return 0;
if (count > (MEM_SIZE - p))
count = MEM_SIZE - p;
ret = copy_from_user((void *)(dev.mem + p), buf, count);
if (ret)
return -EFAULT;
else {
*pos += count;
return count;
}
}
static struct file_operations mem_fops = {
.owner = THIS_MODULE,
.read = mem_read,
.write = mem_write,
};
static int __init mem_init(void)
{
int ret = 0;
ret = alloc_chrdev_region(&devno, 0, 1, "memory");
if (ret < 0) {
return ret;
}
cdev_init(&dev.cdev, &mem_fops);
dev.cdev.owner = THIS_MODULE;
ret = cdev_add(&dev.cdev, devno, 1);
if (ret < 0) {
unregister_chrdev_region(devno, 1);
return ret;
}
return 0;
}
static void __exit mem_exit(void)
{
cdev_del(&dev.cdev);
unregister_chrdev_region(devno, 1);
}
module_init(mem_init);
module_exit(mem_exit);
MODULE_LICENSE("GPL");
编译生成.ko文件之后,使用insmod命令加载该模块,由于设备号是动态分配的,所以需要在/proc/devices找到主设备号,cat /proc/devices得到结果如下:
244 memory
那么244就是该模块的主设备号,然后需要在/dev目录下创建一个设备节点,命令如下:
mknod /dev/memory c 244 0
最后测试一下驱动,echo "hello world" > /dev/memory,然后cat一下,得到结果:
hello world
说明驱动中的read、write方法都是可以工作的。