Linux字符设备驱动
2013-07-11 17:09
211 查看
1. 关于设备号
设备号分主设备号和次设备号,主设备号用来标识设备所对应的驱动程序,由于同一个驱动程序可以支持多个设备,次设备号就是用来区分采用同一个驱动程序的不同设备。
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;
struct cdev *my_cdev;
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
下面是一个字符设备驱动的实例:
那么244就是该模块的主设备号,然后需要在/dev目录下创建一个设备节点,命令如下:
最后测试一下驱动,echo "hello world" > /dev/memory,然后cat一下,得到结果:
说明驱动中的read、write方法都是可以工作的。
设备号分主设备号和次设备号,主设备号用来标识设备所对应的驱动程序,由于同一个驱动程序可以支持多个设备,次设备号就是用来区分采用同一个驱动程序的不同设备。
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;
struct cdev *my_cdev;
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方法都是可以工作的。
相关文章推荐
- Linux驱动开发-字符设备驱动笔记 2
- linux设备驱动程序之简单字符设备驱动
- Linux 字符设备驱动开发基础(六)—— VFS 虚拟文件系统解析
- Linux 字符设备驱动结构(二)—— 自动创建设备节点
- Linux设备驱动入门----globalmem字符设备驱动
- 11-S3C2440驱动学习(七)嵌入式linux-字符设备的另一种写法及RTC驱动程序分析和字符设备驱动框架总结
- linux分类驱动对字符设备框架压力的卸载
- 嵌入式Linux字符设备驱动LED驱动编写
- s3c2440基于linux的gpio led字符设备驱动实践
- Linux驱动开发之字符设备模板
- LINUX—字符设备驱动之-globalmem
- 深入理解Linux字符设备驱动
- Linux字符设备驱动开发基础
- Linux字符设备驱动之概述篇
- linux字符设备驱动
- linux ------ 字符设备驱动框架
- linux字符设备驱动总结之:全自动创建设备及节点
- 嵌入式Linux驱动学习之路(二十七)字符设备驱动的另一种写法
- Linux驱动编程 step-by-step (二) 简单字符设备驱动
- Linux驱动开发之字符设备