字符设备基础了解

一.字符设备驱动

  1. 使用一个结构描述字符设备对象:
struct cdev {
struct kobject kobj;  // 文件系统相关,由系统来管理,不需要自己添加
struct module *owner;  // 用于模块计数
const struct file_operations *ops; // 对硬件的操作方法集
struct list_head list;  // 用于管理字符设备的链表
dev_t dev;   // 设备号;把软件和硬件结合起来;主设备号<<20 +次设备号
unsigned int count; //  同一主设备号下的,次设备号的个数
};

  2. 字符设备注册分 3 个步骤

        分配cdev

    初始化cdev

    添加cdev

   3. 驱动中字符设备文件的相关 3 个结构

    struct file 代表一个打开的文件,由内核打开时创建,关闭时释放

    struct inode 记录文件的物理上信息inode

    struct file_operations 函数指针集合

   4. 字符设备结构中的 struct file_operations 结构是对硬件操作的方法集,通过在驱动程序中重新实现并注册到内核中,供应用层调用

 

 

 

方法一:手动申请字符设备cdev,静态创建设备节点

  1. 对字符设备的操作流程:总结编写一个字符设备驱动要必做的几步:

    (1)定义一个字符设备:struct cdev *cdev;

    (2)为字符设备分配空间:cdev_alloc(void);

    (3)初始化字符设备对象(对硬件操作的方法集):cdev_init(struct cdev *, const struct file_operations *);

    (4)向内核申请一个设备号:register_chrdev_region(dev_t from, unsigned count, const char * name);

    (5)添加当前的字符设备到内核中:cdev_add(struct cdev *, dev_t, unsigned);

    (6)卸载字符设备对象:

        a) cdev_del(struct cdev *); // 删除字符设备

        b) unregister_chrdev_region(dev_t from, unsigned count); //删除

   2. 设备号静态申请:int register_chrdev_region(dev_t from, unsigned count, const char *name)

   3. 手动创建设备节点: mknod  /dev/devchar0     c                222          0

                      命令  设备节点名称   字符设备  主设备号  次设备号

 

方法二:自动申请cdev获取主设备号,动态创建设备节点( 3个步骤 )

1.通过 register_chrdev 自动申请一个cdev字符设备,动态获取主设备号,并注册方法集,就完成了上面方法一中的前 5

  static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops);

  参数:major0时自动获取主设备号,>0 时静态获取不一定成功获取

        name:设备名称

        fops:对硬件操作的方法集

  返回值:return major ? 0 : cd->major;,成功返回主设备号,失败返回0

2.创建放设备节点的目录:创建一个目录在 /sys/class/xxx

  class_create(owner, name) 这是一个宏函数,具体通过下面函数实现

    struct class *__class_create(struct module *owner, const char *name, struct lock_class_key *key)

  参数:owner:是一个宏表示这个模块

        name:创建的目录名,放在  /sys/class/ 下,如name=mychar--  /sys/class/mychar

  返回值:创建的这个目录对象的指针,根据这个指针找这个目录

3.创建设备节点,代替mknod的操作

struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);

  参数:class:创建存放设备节点的目录

        parent:指向此新设备的父结构设备的指针(如果有的话),一般NULL

     devt:要添加的设备号,包含主设备号和次设备号

     drvdata:要添加到设备中的回调的数据

     fmt:用于设备名称的字符串,后面是可变参数,用于格式化fmt的;

  返回值:成功返回device  ;失败返回ERR_PTR(retval). 看内核中的例子怎么使用返回值

如device_create(cls, NULL, MKDEV(major, 0), NULL, "Demochar%d",0); 这个设备节点的名称:“Demochar0

 

4. 对于向系统申请的资源要记得归还

void class_destroy(struct class *cls):删除cls创建的目录

void device_destroy(struct class *class, dev_t devt):功能:释放设备节点

  参数:cls : 申请到的class

      devt: 设备号,包含主设备号和次设备号;通过宏函数 MKDEV(major,minor)制作

 

 

二.关于设备号:

主设备号用来标识与设备文件相连的驱动程序;反映的是设备类型。

次设备号被驱动程序用来识别操作的是哪一个设备;用来区分同类型的设备。

 

Linux使用一个结构描述设备号:dev_t 12位是主设备号,低20位是次设备号

关于设备号操作的 3 个宏函数

#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

 

主设备号的 2 种获取方法

  1.静态申请

    在内核源码路径下确定一个没有使用的主设备号 Documentation/devices.txt

    通过函数  register_chrdev_region 注册设备号

      int register_chrdev_region(dev_t from, unsigned count, const char *name)

  2. 动态分配:

    通过函数  alloc_chrdev_region 动态分配

      int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

      参数: dev :设备号,0 动态分配; >0

         baseminor:起始的次设备号

            count:需要分配的设备号数目

         name:设备名(体现在/proc/devices中)

     注销设备:void unregister_chrdev_region(dev_t from, unsigned count)

      释放从 from 开始的 count 个设备号

 

有了设备号,创建设备文件 2 种方法

3.使用 mknod 命令手动创建

  mknod  drvname  type  major  minor

  drvname:设备文件名

  type:设备文件类型 c字符设备; d块设备

  major:主设备号

  minor:次设备号

4.自动创建(加载模块时注册创建,卸载模块时释放)

  1) 函数 register_chrdev 自动获取注册一个字符设备并申请设备号,同时注册file_operations操作方法集

  2)  函数class_create创建一个struct class类 ,这个类存放在sysfs下面,一旦创建好了这个类, 调用device_create函数在/dev目录下创建相应的设备节点。这样加载模块时,用户空间中udev会自动响应device_create函数,去/sysfs下寻找对用的类从而创建设备节点。

  3)  函数device_create返回值是创建一个struct device型设备关联设备号,class,和设备节点

 

 

三.应用层与驱动间拷贝数据

从底层拷贝数据给应用层:copy_to_user   

从应用层拷贝数据给底层:copy_from_user

内核提供了专门的函数用于访问用户号空间指针

unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)

unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)

 

__builtin_constant_p(EXP) GCC编译器的内嵌函数,用于判断一个值是否为编译时常数,如果参数EXP 的值是常数,函数返回 1,否则返回 0

 

 --------驱动程序demo.c

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/uaccess.h>
#include <linux/err.h>
#include <linux/kernel.h>

/* 变量定义区域 */ 
const char * name = "demochdrev"; // 字符驱动名字
unsigned int major;     // 主设备号
const char *clsname = "mychr"; // 创建class类的名字(不清楚具体什么用途)
struct class *mycls;
struct device *mydev;

// read 系统调用
ssize_t demo_read(struct file *filp, char __user *buf, size_t count, loff_t * ppos)
{
    printk(KERN_INFO "kernel read \n");
    return 0; 
}

// write 系统调用
ssize_t demo_write(struct file *filp, const char __user *buf, size_t count, loff_t *offset)
{
    printk(KERN_INFO "kernel write \n");
    return 0;
}

// close 系统调用
int demo_release(struct inode *iod, struct file *filp)
{
    printk(KERN_INFO "kernel release \n"); 
    return 0;
}

// open 系统调用
int demo_open(struct inode *iod, struct file *filp)
{
    printk(KERN_INFO "kernel open \n");
    return 0;
}

// 对文件的操作方法集
struct file_operations fops = {
        .owner = THIS_MODULE,
        .read = demo_read,     // 函数实现要在其上面,不然在此处找不到函数的声明
        .write = demo_write,
        .open = demo_open,
        .release = demo_release,
};

/* 模块 3 步操作 */
// 模块入口,申请字符设备用到的资源
static int __init demo_init(void)
{
    printk(KERN_INFO "module init \n");
    // 1. 注册一个字符设备 cdev
    major = register_chrdev(0, name, &fops); // major=0 表示自动分配主设备号,其返回值是主设备号
    if(major <= 0){         // 注册字符设备失败
        printk(KERN_INFO "register chrdev fail \n");
    }

    // 2.自动创建设备节点 /sys/class 目录下的文件夹名
    // 2.1 创建一个 class 类
    mycls = class_create(THIS_MODULE, clsname); // 返回值 struct class* 类型
    if (IS_ERR(mycls)){     // 创建 class 失败
        printk(KERN_INFO "class create fail \n");
        unregister_chrdev(major, name); // 在每步检测申请失败了,就要释放前面申请的资源
        return PTR_ERR(mycls);
    }
    // 2.2 创建设备节点的名字
    mydev = device_create(mycls, NULL,MKDEV(major, 0) , NULL, "demochr%d", 0); // 最后 2 个参数会制作这个驱动的可见的名字,安装模块时会看见/dev/demochr0
    if (IS_ERR(mydev)) {     // 创建设备节点失败
        printk(KERN_INFO "failed to create device\n");
        unregister_chrdev(major, name); 
        class_destroy(mycls);
        return PTR_ERR(mydev);
    }
    return 0;
}

// 模块出口,释放申请的资源
static void __exit demo_exit(void)
{
    printk(KERN_INFO "module exit \n");
    device_destroy(mycls, MKDEV(major, 0)); // 上面创建的顺序反向操作,栈操作
    class_destroy(mycls);
    unregister_chrdev(major, name);     
}

// 模块三要素
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");

----makefile文件

#KERNELDIR=/lib/modules/3.13.0-32-generic/build   # Ubuntu系统上的内核源码路径
KERNELDIR=/home/linux/share/kernel-3.4.39   # 目标板上使用内核源码路径

PWD=$(shell pwd) // 当前路径
all:
    make -C $(KERNELDIR) M=$(PWD) modules
clean:
    make -C $(KERNELDIR) M=$(PWD) clean

obj-m +=demo.o  # 生成的目标 demo.ko

 

posted on 2019-07-18 20:10  管理员D  阅读(557)  评论(0编辑  收藏  举报

导航