本文以归纳总结性来概述linux字符设备
- 一、编写字符设备驱动涉及的头文件:
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/fs.h>
另外也算是扩展:
#include <linux/kernel.h>
#include <asm/uaccess.h>
#include <linux/device.h>
- 二、字符设备开发时,每个头文件主要的变量和函数
include/linux/types.h一般只涉及到设备号类型dev_t,用它定义设备号类型变量:
dev_t devNumber;
include/linux/kdev_t.h主要用到三个宏:
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
MAJOR(dev)——dev是dev_t类型变量,从这个设备类型变量中获取主编号。
MINOR(dev)——dev是dev_t类型变量,从这个设备类型变量中获取次编号。
MKDEV(ma,mi)—ma是主编号,mi是次编号,当已知主次编号,用这个宏合成一个设备编号
include/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_alloc:
struct cdev *cdev_alloc(void);
cdev_alloc调用来分配一个静态的cdev空间:
struct cdev *cddd = cdev_alloc();
(2)函数cdev_init:
void cdev_init(struct cdev *, const struct file_operations *);
cdev_init初始化cdev类型变量,其中cdev类型变量就是cdev_alloc函数分配的,当然也可以自己定义全局变量;file_operations字符设备操作函数。
cdev_init调用完后记得加上:
cddd.owner = THIS_MODULE;
(3)函数cdev_add:
int cdev_add(struct cdev *, dev_t, unsigned);
功能:将字符设备添加进系统
第一个参数:由cdev_alloc分配的,或者自己定义的全局cdev变量指针。
第二个参数:设备类型号
第三个参数:添加的字符设备数(次设备数),以第二个参数设备号中包含的次设备号为起始(一般情况是0为起始)。
(4)函数cdev_del:
void cdev_del(struct cdev *);
功能:从系统中删除指定的字符设备
第一个参数:字符设备结构体指针。
include/linux/fs.h 字符设备关注如下几个函数:
/* fs/char_dev.c */
#define CHRDEV_MAJOR_HASH_SIZE 255
extern int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);
extern int register_chrdev_region(dev_t, unsigned, const char *);
extern int __register_chrdev(unsigned int major, unsigned int baseminor,
unsigned int count, const char *name,
const struct file_operations *fops);
extern void __unregister_chrdev(unsigned int major, unsigned int baseminor,
unsigned int count, const char *name);
extern void unregister_chrdev_region(dev_t, unsigned);
extern void chrdev_show(struct seq_file *,off_t);
static inline int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
{
return __register_chrdev(major, 0, 256, name, fops);
}
static inline void unregister_chrdev(unsigned int major, const char *name)
{
__unregister_chrdev(major, 0, 256, name);
}
(1)函数:alloc_chrdev_region
功能:动态分配设备编号,并注册
参数:
第一个参数:设备编号指针,为输入的,分配好的设备编号会存放在指针所指的内存里。
第二个参数:次设备号的起始,一般都是从0开始
第三个参数:要分配几个次设备
第四个参数:字符设备的名字
(2)函数:register_chrdev_region
功能:在已知设备编号时用这个注册,而不是用alloc_chrdev_region,会和上面的cdev_add添加的cdev绑定(cdev,dev_t,name绑定到一起)
第一个参数:设备类型编号
第二个参数:次设备数
第三个参数:设备名字
alloc_chrdev_region和register_chrdev_region根据是否已知设备编号选择其一个来注册(同一个字符设备,只能选择其中一个函数注册),注册之后,通过设备号,字符设备会和cdev绑定到一起。
(3)函数:unregister_chrdev_region
功能:从系统中删除设备号和名字,可以删除alloc_chrdev_region和register_chrdev_region注册的
第一个参数:删除的设备编号
第二个参数:删除的次设备数
register_chrdev()和unregister_chrdev()早期版本(2.6前)用的,现在基本不用这种形式,用法看函数定义,也知道怎么用。
另外fs.h里面几个需要理解的结构体:
struct file_operations:持有一个字符驱动的方法。
struct file:代表一个打开的文件。
struct inode:代表磁盘或flash上的一个文件。
include/linux/kernel.h主要关注下宏container_of:
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
功能:在已知结构体类型,并且知道结构体某个成员的地址,然后推算这个结构体变量起始地址,从而可以访问这个结构体其它成员。
第一个参数:已知结构体成员的地址
第二个参数:结构体类型
第三个参数:结构体成员
include/asm-generic/uaccess.h关注两个函数:
(1)函数:
inline long copy_from_user(void *to,const void __user * from, unsigned long n);
功能:从用户空间copy数据到内核
第一个参数:内核空间地址
第二个参数:用户空间地址
第三个参数:数据size,字节单位
返回值:实际copy的字节数
(2)函数:
inline long copy_to_user(void __user *to,
const void *from, unsigned long n)
功能:内核空间copy数据到用户空间
第一个参数:用户空间地址
第二个参数:内核空间地址
第三个参数:数据size,字节单位
返回值:实际copy的字节数
include/linux/device.h:
(1)将字符设备的设备节点动态的创建到/dev目录下:
首先,创建一个class,用宏:
/* This is a #define to keep the compiler from merging different
* instances of the __key variable */
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
功能:创建一个struct class
第一个参数:一般固定不变THIS_MODULE.
第二个参数:名字,字符串(在这里,要和字符设备的名字相同,即alloc_chrdev_region中第4个参数,register_chrdev_region第三个参数)
返回值:一个指向class结构的内存空间指针
struct class *cclls = class_create(THIS_MODULE,"cclls")
然后,调用device_creat在/dev下创建节点:
struct device *device_create(struct class *cls, struct device *parent,
dev_t devt, void *drvdata,
const char *fmt, ...);
功能:动态创建设备节点,节点在/dev目录下
第一个参数:由class_create创建class
第二个参数:设备节点的父节点,设置为NULL,创建的节点在/dev目录下
第三个参数:设备编号(字符设备,要和字符设备编号一样,alloc_chrdev_region中第4个参数,register_chrdev_region第三个参数)
第四个参数:节点私有数据,没有设置为NULL
第五个参数:设备名称
第六个参数:次设备号,
如下:
device_create( my_class, NULL, MKDEV(hello_major, 0), "hello" "%d", 0 );
hello就是设备名称,次设备号就是0,最终次设备号的名字就是hello0
(2)BUS_ATTR宏
定义:
struct bus_attribute {
struct attribute attr;
ssize_t (*show)(struct bus_type *bus, char *buf);
ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);
};
#define BUS_ATTR(_name, _mode, _show, _store) \
struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store)
extern int __must_check bus_create_file(struct bus_type *,
struct bus_attribute *);
extern void bus_remove_file(struct bus_type *, struct bus_attribute *);
功能:定义一个struct bus_attribute结构体类型的变量(bus属性)
第一个参数:bus属性名字(创建成功后,在相应目录下会有这个名字的文件)
第二个参数:属性文件权限
第三个参数:读取属性文件函数
第四个参数:写属性文件函数
BUS_ATTR定义后,调用函数bus_create_file()可以在指定bus下面创建属性属性文件,对属性文件的读写,相应的调用show(读),store(写)
bus_creat_file()函数第一个参数是指定的bus类型,如过指定为drivers/base/platform.c里面:
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
将会在/sys/bus/pltform目录下创建属性文件。
如果这个参数是自己定义的一个bus_type,将在/sys/bus下创建名字为bus_type成员name设置的名字
bus_remove_file动态删除bus_create_file创建的属性文件。
(3)DRIVER_ATTR宏
定义:
struct driver_attribute {
struct attribute attr;
ssize_t (*show)(struct device_driver *driver, char *buf);
ssize_t (*store)(struct device_driver *driver, const char *buf,
size_t count);
};
#define DRIVER_ATTR(_name, _mode, _show, _store) \
struct driver_attribute driver_attr_##_name = \
__ATTR(_name, _mode, _show, _store)
extern int __must_check driver_create_file(struct device_driver *driver,
const struct driver_attribute *attr);
extern void driver_remove_file(struct device_driver *driver,
const struct driver_attribute *attr);
和BUS_ATTR差不多,
定义完成后,调用driver_create_file(),指定一个struct device_driver,将在对应驱动文件节点下创建属性文件。
(4)CLASS_ATTR宏
定义:
struct class_attribute {
struct attribute attr;
ssize_t (*show)(struct class *class, struct class_attribute *attr,
char *buf);
ssize_t (*store)(struct class *class, struct class_attribute *attr,
const char *buf, size_t count);
const void *(*namespace)(struct class *class,
const struct class_attribute *attr);
};
#define CLASS_ATTR(_name, _mode, _show, _store) \
struct class_attribute class_attr_##_name = __ATTR(_name, _mode, _show, _store)
extern int __must_check class_create_file(struct class *class,
const struct class_attribute *attr);
extern void class_remove_file(struct class *class,
const struct class_attribute *attr);
和BUS_ATTR差不多,
定义完成后,调用class_create_file(),指定一个struct class,将在对应class文件节点下创建属性文件。
如果第一个参数由宏class_creat创建来的话,将在/sys/class下创建名字为class_creat指定名字的文件。
(5)DEVICE_ATTR宏
定义:
struct device_attribute {
struct attribute attr;
ssize_t (*show)(struct device *dev, struct device_attribute *attr,
char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count);
};
#define DEVICE_ATTR(_name, _mode, _show, _store) \
struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
extern int device_create_file(struct device *device,
const struct device_attribute *entry);
extern void device_remove_file(struct device *dev,
const struct device_attribute *attr);
一般按照如下格式:
ssize_t de_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
}
ssize_t de_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
}
DEVICE_ATTR(dename,0664,de_show,de_store);
static struct device de = {
.init_name = "dechr";
};
device_register(&de);
device_create_file(&de,&dev_attr_dename);
按照如上格式,就会在/sys/devices/下创建dechr文件,在dechr下又会出现dename(/sys/devices/dechr/dename),对dename读会调用de_show函数,写会调用de_store函数。(注意指定init_name,以及调用device_register注册,很重要)
另外,发现,对de_show,de_store参数buf的操作,可以直接操作,用copy_to_user,copy_from_user反而会失败,对de_show一般直接操作,如下:
ssize_t de_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
sprintf(buf,"de_show");
return 7;//返回要传送的字节数
}
对de_store如下:
ssize_t de_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
char name[100];
memcopy(name,buf,10);
return count;//送下来的字节个数,直接返回count,告诉上层本次操作成功的
}
但是安全起见,我一般按照如下用:
ssize_t de_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
long re = 0;
char name[10];
sprintf(name,"de_show");
re = copy_to_user(buf,name,7);
if(0 != re)
{
sprintf(buf,"de_show");
}
return 7;//返回要传送的字节数
}
ssize_t de_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
long re = 0;
char name[100];
re = copy_from_user(name,buf,10);
if(0 != re)
{
memset(name,0,10);
memcpy(name,buf,10);
}
return count;//送下来的字节个数,直接返回count,告诉上层本次操作成功的
}
copy_to_user,copy_from_user如果copy指定的字节,返回0,表示成功;如果返回非0,返回的值表示未copy完成的字节数。
在linux代码里常常看到copy_to_user,copy_from_user用在/dev这种设备节点文件,用户空间和内核空间数据的交换。
至于DRIVER_ATTR,CLASS_ATTR,BUS_ATTR是否也是一定要指定name,加注册,才能create,待验证后再补充。DEVICE_ATTR这种固定格式,是通过验证过的。
三、字符设备的基本步骤:
创建:
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/device.h>
dev_t cdevNumber;
struct cdev *chardev;
struct class *chardevclass;
ssize_t read(struct file *, char __user *, size_t, loff_t *)
{
return 0;
};
struct file_opertions chardevop = {
.owner = THIS_MODULE,
.read = read
};
void init()
{
alloc_chrdev_region(&cdevNumber,0,1,"chardev");
chardev = cdev_alloc();
cdev_init(chardev,&chardevop );
chardevop->owner = THIS_MODULE;
cdev_add(chardev ,cdevNumber,1);
//用alloc_chrdev_region后就不用register_chrdev_region
//register_chrdev_region(cdevNumber,1,"chardev");
chardevclass = class_create(THIS_MODULE,"chardev");
device_create(chardevclass ,NULL,cdevNumber,NULL,"chardev");
}
void exite()
{
device_destroy(chardevclass ,cdevNumber);
class_destroy(chardevclass);
unregister_chrdev_region(cdevNumber,1);
cdev_del(chardev);
}
以上只表明基本步骤,没有编译过,也没考虑返回错误的情况。
exite函数个人觉得,应该尽量严格按照与注册顺序相反来注销。