• 字符设备驱动结构与开发


    字符设备驱动框架结构体

    image

    下面来分析这段代码:

    struct cdev {

    struct kobject kobj;

    struct module *owner;

    const struct file_operations *ops;

    struct list_head list;

    dev_t dev;

    unsigned int count;

    };

    这实际上是通过C语言的结构体来模拟面向对象的封装特性。

    struct kobject kobj;这是系统内核维护的数据结构,在开发过程中我们可以不用管。

    struct module *owner;通常会赋值为一个宏THIS_MODULE,表示当前的模块。

    const struct file_operations *ops;表示这个结构体的方法,这是实现驱动模块接口函数的定义,正是这个结构体提供了统一的函数接口。

    struct file_operations {

    struct module *owner;

    loff_t (*llseek) (struct file *, loff_t, int);

    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

    ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

    ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

    int (*readdir) (struct file *, void *, filldir_t);

    unsigned int (*poll) (struct file *, struct poll_table_struct *);

    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

    int (*mmap) (struct file *, struct vm_area_struct *);

    int (*open) (struct inode *, struct file *);

    int (*flush) (struct file *, fl_owner_t id);

    int (*release) (struct inode *, struct file *);

    int (*fsync) (struct file *, loff_t, loff_t, int datasync);

    int (*aio_fsync) (struct kiocb *, int datasync);

    int (*fasync) (int, struct file *, int);

    int (*lock) (struct file *, int, struct file_lock *);

    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);

    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);

    int (*check_flags)(int);

    int (*flock) (struct file *, int, struct file_lock *);

    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);

    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);

    int (*setlease)(struct file *, long, struct file_lock **);

    long (*fallocate)(struct file *file, int mode, loff_t offset,

    loff_t len);

    };

    这个复杂的file_operations结构体就定义了我们可能需要实现的函数指针,我们只需要定义相应类型的函数,将这些函数赋值给相应的函数指针,就能完成框架函数和自己实现函数之间的关联。

    struct list_head list;这是系统用来将这个结构体加入系统维护链表的方式,我们不需要关心。

    dev_t dev;表示设备在系统中的设备号,在Linux2.6.35中高12位表示主设备号,低20位表示从设备号

    image

    由于Linux内核在不断的进化更新中,这种分配主从设备号的方式可能会改变,所以Linux系统提供了一个宏函数来帮助我们实现构建设备号的工作。

    MKDEV(主设备号,从设备号),这个宏函数会帮我们构建这个系统需要的设备号;

    image

    同时系统还提供两个宏函数来提取设备号的主设备号和从设备号,分别为:

    MAJOR(设备号),返回主设备号;MINOR(设备号),返回从设备号。

    #define MINORBITS 20 //定义从设备号为20位

    #define MINORMASK ((1U << MINORBITS) - 1) //定义设备掩码,主要为了屏蔽主设备号使用

    #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) //向右移动MINORBITS位,将从设备号全部剔除

    #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) //和设备掩码与,去掉主设备号

    #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) //主设备号移动MINORBITS位和从设备号或,构建设备号。

    在这个结构体中,我们只关心dev_t dev和file_operations *ops。框架介绍完了,下面介绍基本的开发步骤。在一个模块的基础上构建驱动模块,步骤如下:

    A、申明许可证,MODULE_LICENSE(“Dual BSD/GPL”);

    定义设备cdev

    struct cdev dev;

    B、加载函数

    1、申请设备号

    1、静态申请

    register_chrdev_region

    2、动态申请

    alloc_chrdev_region

    2、初始化设备cdev

    cdev_init

    3、注册设备

    cdev_add

    C、卸载函数

    1、注销设备

    cdev_del

    2、注销设备号

    静态申请和动态申请都使用:unregister_chrdev_region,来注销

    定义方法file_operations,并注册函数

    struct file_operations fops = {

    ..open = XXXX,

    .release = XXXX,

    .write = XXXXX,

    .read = XXXXXXX,

    }

    D、实现功能函数

    根据不同的函数指针定义,定义相应的实现函数。

    根据上面编写代码

    my_hello.c

    #include <linux/kernel.h>

    #include <linux/module.h>

    #include <linux/fs.h>

    #include <linux/cdev.h>

    //尽量避免出现整数常量

    #define MAJOR_NO 365

    #define MINOR_NO 0

    #define DEV_COUNT 1

    //声明许可证

    MODULE_LICENSE("Dual BSD/GPL");

    //声明方法

    int my_open(struct inode *, struct file *);

    int my_close(struct inode *, struct file *);

    //定义属性cdev和方法file_operations,同时注册方法

    static struct cdev dev;

    static struct file_operations fops = {

    .open = my_open,

    .release = my_close,

    };

    dev_t dev_no; //设备号

    static int my_hello_init(void)

    {

    int ret; //返回值

    //申请设备号

    //静态申请

    dev_no = MKDEV(MAJOR_NO,MINOR_NO);

    ret = register_chrdev_region(dev_no, DEV_COUNT, "my_hello_test");

    //出错判断,编写驱动必须做好出错判断,否则会给应用开发人员造成无穷的麻烦

    if (ret < 0)

    {

    printk("Apply for device numbers error! ");

    return ret;

    }

    dev.owner = THIS_MODULE;

    //初始化设备

    cdev_init(&dev, &fops);

    //注册设备

    ret = cdev_add(&dev, dev_no, DEV_COUNT);

    if (ret < 0)

    {

    printk("Fail to add a char device to the system ");

    return ret;

    }

    printk("The device is initialzed,waiting for invokeing. ");

    return 0;

    }

    static void my_hello_cleanup(void)

    {

    //有注册必须要注销

    cdev_del(&dev);

    //有申请必须有释放

    unregister_chrdev_region(dev_no, DEV_COUNT);

    printk("The device is cleanup. ");

    }

    //定义方法

    //定义open方法,函数原型参照file_operations中的

    //int (*open) (struct inode *, struct file *);

    int my_open(struct inode *inode, struct file *file)

    {

    printk("This is my_open. ");

    return 0;

    }

    //定义close方法,函数原型参照

    //int (*release) (struct inode *, struct file *);

    int my_close(struct inode *inode, struct file *file)

    {

    printk("This is my_close. ");

    return 0;

    }

    module_init(my_hello_init); //使用宏函数将自己定义的初始化函数转化为init_module,主要是防止加载时函数重名造成的问题

    module_exit(my_hello_cleanup); //转化为cleanup_module

    编译上面代码并测试:

    test.c

    #include <stdio.h>

    #include <stdlib.h>

    #include <unistd.h>

    #include <fcntl.h>

    #include <sys/stat.h>

    #include <sys/types.h>

    int main(void)

    {

    int fd;

    int ret;

    fd = open("/dev/my_hello",O_RDWR);

    if(fd < 0)

    {

    perror("open");

    exit(1);

    }

    printf("打开成功 ");

    sleep(1);

    ret = close(fd);

    if (ret < 0)

    {

    perror("close");

    exit(1);

    }

    printf("关闭成功 ");

    return 0;

    }

    Makefile:

    ifeq ($(KERNELRELEASE),)

    KERNELDIR ?= /lib/modules/$(shell uname -r)/build

    PWD := $(shell pwd)

    TEST = test

    modules:

    $(MAKE) -C $(KERNELDIR) M=$(PWD)

    clean:

    rm -rvf *.ko *.o *.mod.c Module* module*

    test:

    gcc $(TEST).c -o $(TEST)

    mknod:

    sudo mknod /dev/my_hello c 365 0

    insmod:

    sudo insmod my_hello.ko

    .PHONY:modules clean mknod test insmod

    else

    obj-m := my_hello.o

    endif

    image

  • 相关阅读:
    将自己的web应用发布到Tomcat
    JavaEE复制后项目出错或者无法运行的解决方法
    Java中eq、ne、ge、gt、le、lt的含义
    Spring中声明式事务处理和编程式事务处理的区别
    Java中获取当前时间并格式化
    Computer Vision Resources
    从信息论到哈弗曼树
    二 图像处理opencv mfc学习
    OpenMP的学习
    图像处理的学习
  • 原文地址:https://www.cnblogs.com/wangluojisuan/p/3366731.html
Copyright © 2020-2023  润新知