zoukankan      html  css  js  c++  java
  • 【驱动】——字符设备驱动程序

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

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

      数据类型 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;

        

  • 相关阅读:
    CQUOJ 10819 MUH and House of Cards
    CQUOJ 9920 Ladder
    CQUOJ 9906 Little Girl and Maximum XOR
    CQUOJ 10672 Kolya and Tandem Repeat
    CQUOJ 9711 Primes on Interval
    指针试水
    Another test
    Test
    二分图匹配的重要概念以及匈牙利算法
    二分图最大匹配
  • 原文地址:https://www.cnblogs.com/ngnetboy/p/4605875.html
Copyright © 2011-2022 走看看