一.字符设备开发的基本步骤
1.确定主设备号和次设备号
2.实现初始化函数,注册字符设备
3.实现字符驱动程序
4.实现file_operations结构体
5.实现销毁函数,释放字符设备
6.创建设备文件节点
二.常用概念
1.主设备号是内核识别一个设备的标识。整数(占12bits),范围从0到4095,通常使用1到255
2.次设备号由内核使用,用于正确确定设备文件所指的设备。整数(占20bits),范围从0到1048575,一般使用0到255。
3.inode与file的区别
file表示打开的文件描述符
多个表示打开的文件描述符的file结构,可以指向单个inode结构。
三.驱动相关结构体
1.inode 结构体
inode类型作用:存在于磁盘的文件系统中,内核用inode结构在内部表示文件
inode成员:
dev_t i_rdev;对表示设备文件的inode结构,该字段包含了真正的设备编号。
struct cdev *i_cdev;
struct cdev是表示字符设备的内核的内部结构。当inode指向一个字符设备文件时,该字段包含了指向struct cdev结构的指针。
inode成员函数:
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);
2.file 结构体
file类型作用:file_operations结构相关的一个结构体。内核系统描述一个正在打开的设备文件。
file成员: loff_t f_pos: 当前读/写位置
unsigned int f_flags:O_RDONLY,O_NONBLOCK,O_SYNC标识文件打开时,是否可读或可写
struct file_operations *f_op:struct file_operations 文件相关的操作,指向所实现的
void *private_data: 私有数据指针。驱动程序可以将这个字段用于任何目的或者忽略这个字段。
3.file_operations 结构体:字符驱动和内核的接口,在#include <linux/fs.h>定义
字符驱动只要实现一个file_operations结构体,并注册到内核中,内核就有了操作此设备的能力。
file_operations成员:
struct module *owner: 指向模块自身
open:打开设备
release:关闭设备 read:从设备上读数据
write:向设备上写数据
ioctl:I/O控制函数
llseek:定位读写指针
mmap:映射设备空间到进程的地址空间
4.dev_t类型(32位):#include <linux/types.h>
dev_t类型作用:对应于驱动(write/read),向系统申请设备号,并注册。设备编号的内部表达,用来保存设备编号(包括主设备号(12位)和次设备号(20位))
dev_t成员函数:#include <linux/fs.h>
初始化:在内核系统中注册申请驱动设备号
a.静态
MAJOR(dev_t);获得主设备号
MINOR(dev_t);获得次设备号
MKDEV(int major,int minor);将主设备号和次设备号转换成dev_t类型
intregister_chrdev_region( dev_t first,unsigned int count,char *name );手工分配主设备号:找一个内核没有使用的主设备号来使用
b.动态
int alloc_chrdev_region(dev_t *dev,unsigned int firstminor,unsigned int count,char *name);动态分配主设备号
释放资源:释放在内核系统中的资源(设备号)
void unregister_chrdev_region(dev_t first, unsigned int count);//释放设备号
5.cdev 结构体:
struct cdev
{
struct kobject kobj; /*内嵌的kobject 对象 */
struct module *owner; /*所属模块*/
struct file_operations *ops; /*文件操作结构体*/
struct list_head list; //与cdev 对应的字符设备文件的inode->i_devices 的链表头
dev_t dev; /*设备号*/
unsigned int count; //设备范围号大小
};
cdev 结构体作用:对应于具体设备,将具体的设备注册到kernel中。
cdev成员函数:#include <linux/cdev.h>
初始化:定义并初始化cdev 结构体
a.静态
static struct cdvm_cdev;
void cdev_init( struct cdev *, struc t file_operations *);//静态初始化
m_cdev.owner= THIS_MODULE;
b.动态
struct cdev *cdev_alloc(void);//动态初始化
m_cdev->owner= THIS_MODULE;
m_cdev->ops= &fop;
功能实现函数:cdev 结构体添加到内核系统中
int cdev_add(struct cdev *p,dev_t dev,unsigned count) ;//将具体设备对应到设备号,最终对应到设备驱动(read/write)。
释放资源:cdev 结构体移除内核系统
void cdev_del(struct cdev *);//模块卸载,释放 cdev 占用的内存
6.class结构体:
class结构体作用:对应于设备文件,最终功能在/dev/下添加一个设备文件
class成员函数:#include <linux/device.h>
初始化:在内核资源中定义并初始化class
static struct class*m_pcls;
m_pcls = #define class_create(owner,name) 功能实现函数:在/dev/下添加一个设备文件
struct device *device_create(struct class* pcls,struct device* parent,dev_t devt,void * devdata,const char * fmt, ... );
释放资源:删除/dev/下添加一个设备文件和内核资源class
void device_destroy(struct class *pcls,dev_t devt);
void class_destroy(struct class *m_pcls);
四.其他功能函数
读写用户区函数<asm/uaccess.h>
unsignedlong copy_from_user(void *to, const void __user *from, unsigned long count) ; unsignedlong copy_to_user(void __user *to, const void *from,unsigned long count );
put_user()和get_user();
五.字符设备源代码
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>//struct cdev等函数
#include <linux/types.h> //dev_t类型(32位)
MODULE_LICENSE("GPL");
static int first_open(struct inode *inode,struct file *file)
{
printk(KERN_ALERT "first_open\n");
return 0;
}
static ssize_t first_write(struct file *file,const char *buf,size_t count,loff_t *ppos)
{
printk("first_writre\n");
return 0;
}
static struct file_operations first_drv_fops={
.owner = THIS_MODULE,
.open = first_open,
.write = first_write,
};
dev_t ret_t;
struct cdev lz_dev;
static int __init first_drv_init(void)
{
ret_t = MKDEV(125,0);
register_chrdev_region(ret_t,1,"first");
//ret=register_chrdev(ret,"first_drv",&first_drv_fops);
}
static void __exit first_drv_exit(void)
{
//unregister_chrdev(ret,"first_drv");
}
module_init(first_drv_init);
module_exit(first_drv_exit);
lz_dev.owner = THIS_MODULE;
cdev_add(&lz_dev,ret_t,1) ;
printk(KERN_ALERT "lz_drv_int\n");
//ret=register_chrdev(ret,"first_drv",&first_drv_fops);
return 0;
}
static void __exit first_drv_exit(void)
{
unregister_chrdev_region(ret_t, 1); //释放占用的设备号
cdev_del(&lz_dev); //注销设备
printk("lz_drv_exit\n");
//unregister_chrdev(ret,"first_drv");
}
module_init(first_drv_init);
module_exit(first_drv_exit);
六.Makefile
KERN_RELEASE = 1
ifeq ($(KERN_RELEASE),)
KERN_DIR = /lib/modules/$(shell uname -r)/build/
else
KERN_DIR = /mnt/share/port/kernel/
endif
obj-m = mque.o
mque-objs = que.o
default:
make -C $(KERN_DIR) M=$(shell pwd) modules
gcc read.c -o read
gcc write.c -o write
clean:
make -C $(KERN_DIR) M=$(shell pwd) clean
rm write read
1.确定主设备号和次设备号
2.实现初始化函数,注册字符设备
3.实现字符驱动程序
4.实现file_operations结构体
5.实现销毁函数,释放字符设备
6.创建设备文件节点
二.常用概念
1.主设备号是内核识别一个设备的标识。整数(占12bits),范围从0到4095,通常使用1到255
2.次设备号由内核使用,用于正确确定设备文件所指的设备。整数(占20bits),范围从0到1048575,一般使用0到255。
3.inode与file的区别
file表示打开的文件描述符
多个表示打开的文件描述符的file结构,可以指向单个inode结构。
三.驱动相关结构体
1.inode 结构体
inode类型作用:存在于磁盘的文件系统中,内核用inode结构在内部表示文件
inode成员:
dev_t i_rdev;对表示设备文件的inode结构,该字段包含了真正的设备编号。
struct cdev *i_cdev;
struct cdev是表示字符设备的内核的内部结构。当inode指向一个字符设备文件时,该字段包含了指向struct cdev结构的指针。
inode成员函数:
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);
2.file 结构体
file类型作用:file_operations结构相关的一个结构体。内核系统描述一个正在打开的设备文件。
file成员: loff_t f_pos: 当前读/写位置
unsigned int f_flags:O_RDONLY,O_NONBLOCK,O_SYNC标识文件打开时,是否可读或可写
struct file_operations *f_op:struct file_operations 文件相关的操作,指向所实现的
void *private_data: 私有数据指针。驱动程序可以将这个字段用于任何目的或者忽略这个字段。
3.file_operations 结构体:字符驱动和内核的接口,在#include <linux/fs.h>定义
字符驱动只要实现一个file_operations结构体,并注册到内核中,内核就有了操作此设备的能力。
file_operations成员:
struct module *owner: 指向模块自身
open:打开设备
release:关闭设备 read:从设备上读数据
write:向设备上写数据
ioctl:I/O控制函数
llseek:定位读写指针
mmap:映射设备空间到进程的地址空间
4.dev_t类型(32位):#include <linux/types.h>
dev_t类型作用:对应于驱动(write/read),向系统申请设备号,并注册。设备编号的内部表达,用来保存设备编号(包括主设备号(12位)和次设备号(20位))
dev_t成员函数:#include <linux/fs.h>
初始化:在内核系统中注册申请驱动设备号
a.静态
MAJOR(dev_t);获得主设备号
MINOR(dev_t);获得次设备号
MKDEV(int major,int minor);将主设备号和次设备号转换成dev_t类型
intregister_chrdev_region( dev_t first,unsigned int count,char *name );手工分配主设备号:找一个内核没有使用的主设备号来使用
b.动态
int alloc_chrdev_region(dev_t *dev,unsigned int firstminor,unsigned int count,char *name);动态分配主设备号
释放资源:释放在内核系统中的资源(设备号)
void unregister_chrdev_region(dev_t first, unsigned int count);//释放设备号
5.cdev 结构体:
struct cdev
{
struct kobject kobj; /*内嵌的kobject 对象 */
struct module *owner; /*所属模块*/
struct file_operations *ops; /*文件操作结构体*/
struct list_head list; //与cdev 对应的字符设备文件的inode->i_devices 的链表头
dev_t dev; /*设备号*/
unsigned int count; //设备范围号大小
};
cdev 结构体作用:对应于具体设备,将具体的设备注册到kernel中。
cdev成员函数:#include <linux/cdev.h>
初始化:定义并初始化cdev 结构体
a.静态
static struct cdvm_cdev;
void cdev_init( struct cdev *, struc t file_operations *);//静态初始化
m_cdev.owner= THIS_MODULE;
b.动态
struct cdev *cdev_alloc(void);//动态初始化
m_cdev->owner= THIS_MODULE;
m_cdev->ops= &fop;
功能实现函数:cdev 结构体添加到内核系统中
int cdev_add(struct cdev *p,dev_t dev,unsigned count) ;//将具体设备对应到设备号,最终对应到设备驱动(read/write)。
释放资源:cdev 结构体移除内核系统
void cdev_del(struct cdev *);//模块卸载,释放 cdev 占用的内存
6.class结构体:
class结构体作用:对应于设备文件,最终功能在/dev/下添加一个设备文件
class成员函数:#include <linux/device.h>
初始化:在内核资源中定义并初始化class
static struct class*m_pcls;
m_pcls = #define class_create(owner,name) 功能实现函数:在/dev/下添加一个设备文件
struct device *device_create(struct class* pcls,struct device* parent,dev_t devt,void * devdata,const char * fmt, ... );
释放资源:删除/dev/下添加一个设备文件和内核资源class
void device_destroy(struct class *pcls,dev_t devt);
void class_destroy(struct class *m_pcls);
四.其他功能函数
读写用户区函数<asm/uaccess.h>
unsignedlong copy_from_user(void *to, const void __user *from, unsigned long count) ; unsignedlong copy_to_user(void __user *to, const void *from,unsigned long count );
put_user()和get_user();
五.字符设备源代码
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>//struct cdev等函数
#include <linux/types.h> //dev_t类型(32位)
MODULE_LICENSE("GPL");
static int first_open(struct inode *inode,struct file *file)
{
printk(KERN_ALERT "first_open\n");
return 0;
}
static ssize_t first_write(struct file *file,const char *buf,size_t count,loff_t *ppos)
{
printk("first_writre\n");
return 0;
}
static struct file_operations first_drv_fops={
.owner = THIS_MODULE,
.open = first_open,
.write = first_write,
};
dev_t ret_t;
struct cdev lz_dev;
static int __init first_drv_init(void)
{
ret_t = MKDEV(125,0);
register_chrdev_region(ret_t,1,"first");
cdev_init(&lz_dev, &first_drv_fops); //初始化cdev
lz_dev.owner = THIS_MODULE;
cdev_add(&lz_dev,ret_t,1) ;
printk(KERN_ALERT "lz_drv_int\n");
//ret=register_chrdev(ret,"first_drv",&first_drv_fops);
return 0;
}
static void __exit first_drv_exit(void)
{
unregister_chrdev_region(ret_t, 1); //释放占用的设备号
cdev_del(&lz_dev); //注销设备
printk("lz_drv_exit\n");
//unregister_chrdev(ret,"first_drv");
}
module_init(first_drv_init);
module_exit(first_drv_exit);
六.Makefile
ifneq ($(KERNELRELEASE),)
obj-m := lz_dev.o
#module-objs := first_dev.o
else
#KERNELDIR ?=/lib/modules/$(shell uname -r)/build
KERNELDIR ?=/home/lz/linux/2.0android-kernel-samsung-dev
PWD := $(shell pwd)
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm *.o *.mod.c *.order *.symvers -rf
endif
cdev_init(&lz_dev, &first_drv_fops); //初始化cdev
lz_dev.owner = THIS_MODULE;
cdev_add(&lz_dev,ret_t,1) ;
printk(KERN_ALERT "lz_drv_int\n");
//ret=register_chrdev(ret,"first_drv",&first_drv_fops);
return 0;
}
static void __exit first_drv_exit(void)
{
unregister_chrdev_region(ret_t, 1); //释放占用的设备号
cdev_del(&lz_dev); //注销设备
printk("lz_drv_exit\n");
//unregister_chrdev(ret,"first_drv");
}
module_init(first_drv_init);
module_exit(first_drv_exit);
六.Makefile
KERN_RELEASE = 1
ifeq ($(KERN_RELEASE),)
KERN_DIR = /lib/modules/$(shell uname -r)/build/
else
KERN_DIR = /mnt/share/port/kernel/
endif
obj-m = mque.o
mque-objs = que.o
default:
make -C $(KERN_DIR) M=$(shell pwd) modules
gcc read.c -o read
gcc write.c -o write
clean:
make -C $(KERN_DIR) M=$(shell pwd) clean
rm write read