linux驱动之字符设备

本文以归纳总结性来概述linux字符设备

  • 一、编写字符设备驱动涉及的头文件:
 #include 
 #include 
 #include 
 #include 

另外也算是扩展:

#include 
#include 
#include 
  • 二、字符设备开发时,每个头文件主要的变量和函数
    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 
#include 
#include 
#include 

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函数个人觉得,应该尽量严格按照与注册顺序相反来注销。

你可能感兴趣的:(linux驱动)