• 【驱动】——字符设备驱动程序


    字符设备不得不说的那些事:

    一: 设备号:主设备号,次设备号:

      数据类型 dev_t(unsigned int) 定义设备号  高12位主设备号 低20位次设备号;

    二: 设备号的作用:

      应用程序通过主设备号找到驱动程序;

    三:如何分配设备号:

      ①:静态分配:

        1: cat /proc/devices 查看linux系统哪个设备号没有被占用;

        2: dev_t dev_id = MKDEV(主设备号,次设备号)  根据你的设备个数分配次设备号 如果设备个数只有一个,一般此设备号从0开始;

        3: 调用 register_chrdev_region(dev_t from,unsigned count,const char *name);

          from:待申请的设备号     count:待申请的设备号数目   name:设备名称(出现在 /proc/devices)  

      ②:动态分配:

        1:调用 alloc_chrdev_region 直接向内核去申请设备号,也就是让操作系统内核帮你分配设备号。

      ③:设备号的注销:

        1:void unregister_chrdev_region(dev_t from, unsigned count);

    四:重要的数据结构:

      ①:文件结构: struct file; //描述打开文件后的状态

        生命周期:当打开文件后,内核自动创建一个结构体 struct file  文件关闭之后 struct file 被自动销毁。

        重要成员:

          struct file_operations *f_op;   //指向驱动程序中实现的各个硬件操作方法;

          unsigned int f_flags;   //文件操作属性;

          loff_t f_ops;   //文件操作位置;

          void *private_data; //存放私有数据

      ②:inode 结构: struct inode; //用于记录文件物理信息

        生命周期:文件存在,内核创建;文件销毁,内核销毁对应的inode;

        重要成员:

          dev_t i_rdev; //存放设备号

          struct cdev *i_cdev;    //指向一个字符设备

        注:一个文件只有一个inode,可以有多个file;

      ③:文件操作结构: struct file_operations; //一个函数指针的集合,这些函数定义了能够对设备进行的操作 在 <linux/fs.h> 中定义

      ④:字符设备 struct cdev

    五:重要的函数:

      ①:分配设备号,见【三:如何分配设备号】

      ②:注册字符设备:

        1: void cdev_init(struct cdev *cdev, struct file_operations *fops);  //将字符设备与处理函数进行绑定,把 struct file_operations 注册到内核中

        2: int cdev_add(struct cdev *dev, dev_t num, unsigned int count);  //将 dev 添加到内核的cdev的数组之中 下标是以设备号为索引!一旦完成对 cdev的注册,就等于有了一个真实的字符设备,关键这个驱动就有了,对应的操作集合 fops;

        3: void cdev_del(struct cdev *dev);    //从系统中移除一个字符设备

    六:简单的设备驱动程序例子:

    Makefile:

    ifeq ($(KERNELRELEASE), )
        KERNELDIR ?= /lib/modules/$(shell uname -r)/build   
        PWD := $(shell pwd)
    all: clean
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
        -rm -rf *.o *~ core .depend .*.cmd *.mod.c .tmp_versions Module.* Makefile.xen modules.order
    clean:
        -rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions Module.* Makefile.xen modules.order
    else
    MODULE_NAME=scull
        obj-m := $(MODULE_NAME).o
        $(MODULE_NAME)-objs := file_op.o my_project.o
    endif
    

    my_project.c

    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/types.h>
    #include <linux/fs.h>
    #include <linux/cdev.h>
    #include <linux/kernel.h>
    #include <asm/uaccess.h>
    
    #include "file_op.h"
    
    uint dev_major = 60; 
    char device_name[20] = "ngnetboy";
    dev_t scull_dev;    //设备号
    static struct cdev scull_cdev;  //字符设备
    
    struct file_operations scull_fops = { 
        .owner = THIS_MODULE,
        .read = scull_read,
        .write = scull_write,
        .ioctl = scull_ioctl,
        .open = scull_open,
        .release = scull_release,
    };
    static int __init scull_init(void){
        int ret;
    
        printk("hello kernel!
    ");
        // make device num
        scull_dev = MKDEV(dev_major, 0);
        // register device 
        ret = register_chrdev_region(scull_dev, 1, device_name);
        if (ret < 0){
            printk("register chrdev region failed!
    ");
            ret = alloc_chrdev_region(&scull_dev, 0, 1, device_name);
            dev_major = MAJOR(scull_dev);
        }
        if (ret < 0){
            printk("%s register failed!
    ", device_name);
            return ret;
        }
        //scull_cdev = cdev_alloc();
        cdev_init(&scull_cdev, &scull_fops);
        ret = cdev_add(&scull_cdev, scull_dev, 1);
        if (ret < 0){
            printk("cdev add failed!
    ");
            goto chr_quit1;
        }
        return 0;
    chr_quit1:
        unregister_chrdev_region(scull_dev, 1);
        return ret;
    }
    
    static void __exit scull_exit(void){
    
        cdev_del(&scull_cdev);
        unregister_chrdev_region(scull_dev, 1);
    
        printk("bye kernel!
    ");
    }
    
    
    module_init(scull_init);
    module_exit(scull_exit);
    
    MODULE_AUTHOR("ngnetboy");
    MODULE_DESCRIPTION("a simple character utility for loading localities");
    MODULE_LICENSE("GPL");
    MODULE_VERSION("0.0.0.3");

    file_op.c

     1 #include <linux/fs.h>
     2 #include <asm/uaccess.h>
     3 #include <linux/init.h>
     4 #include <linux/module.h>
     5 #include <linux/types.h>
     6 #include <linux/cdev.h>
     7 #include <linux/kernel.h>
     8 
     9 #include "file_op.h"
    10 
    11 int scull_read(struct file *filep, char __user *buff, size_t count, loff_t *offp){
    12     char val[20] = "this is read!";
    13     int ret;
    14 
    15     ret = copy_to_user(buff, val, count);
    16     return 0;
    17 }
    18 int scull_write(struct file *filep, const char __user *buff, size_t count, loff_t *offp){
    19     char val[20];
    20     int ret;
    21 
    22     printk("hello write!
    ");
    23     ret = copy_from_user(val, buff, count);
    24     printk("ret :%d copy from user %s
    ", ret, val);
    25     return 0;
    26 }
    27 int scull_open(struct inode *inode, struct file *filp){
    28     printk("hello open!
    ");
    29     return 0;
    30 }
    31 int scull_release(struct inode *inode, struct file *filp){
    32     printk("hello release!
    ");
    33     return 0;
    34 }
    35 int scull_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg){
    36 
    37     return 0;
    38 }

    使用 mknod /dev/netboy c 60 0 即可在 /dev 下创建一个可供应用程序可用的字符设备文件结点;

    七:当应用程序 open 创建的字符设备时内核和驱动都做了那些事情?

      1:当应用程序调用 open 打开设备文件(打开设备);

      2:调用 C 库的open实现,会保存 open 的系统调用号;

      3:C库的open实现调用 swi 触发一个软中断,跳转到内核态;

      4:根据系统调用号,在系统调用表中找到open对应内核系统调用实现 sys_open;

      5:调用 sys_open, sys_open 会做如下工作:

        ①:通过 inode.i_rdev 获取设备号;

        ②:根据设备号在内核cdev数组中找到对应的字符设备驱动;

        ③:然后将找到的cdev的地址赋值给 inode.i_cdev 用于缓存和别的用途;

        ④:创建 struct file 结构体内存 用于描述打开的设备文件信息;

        ⑤:根据已经获得的 cdev,从而获得其中的驱动操作集合ops();

        ⑥:将字符设备驱动的操作接口ops再赋值给 file->ops;

        ⑦:最后再调用一次 file->f_op->open = led_open;

    八:四个重要的数据结构之间有什么联系?

      1:struct file_operations fops ;是由驱动开发者实现,包括相应的函数实现;并且由驱动开发者把fops注册到内核中与struct cdev 绑定到一起;

      2:使用 mknod 创建 /dev/netboy 时,struct inode node,便已被创建,并随着文件的销毁而被释放;

      3:当驱动程序被调用,用户程序调用open打开字符设备的时候,struct file 便被创建,内核通过设备号找到对应的字符设备【struct cdev】,然后把fops赋值给 struct file;

        

  • 相关阅读:
    有个扫描二维码的扩展,还不错
    js实现html截图生成图片
    微信小程序左右滑动切换图片酷炫效果(附效果)
    谷歌扩展程序设置ajax请求允许跨域(极少人知道的解决方案)
    h5页面使用sessionStorage滚动到上次浏览器位置《原创》
    ajax返回json数组遍历添加到html
    解决微信内置浏览器屏蔽下载链接问题
    解决html5新标签【placeholder】低版本浏览器下不兼容问题
    Web前端知识技能大汇总
    酷炫的页面滚动切换动画效果
  • 原文地址:https://www.cnblogs.com/ngnetboy/p/4605875.html
Copyright © 2020-2023  润新知