• Linux设备驱动学习-first_drv.ko


    一、linux内核模块简介

    linux内核整体结构非常庞大,其包含的组件也非常多。我们怎么把需要的部分都包含在内核中呢?

         一种办法是把所有的需要的功能都编译到内核中。这会导致两个问题,一是生成的内核会很大,二是如果我们要在现有的内核中新增或删除功能,不得不重新编译内核,工作效率会非常的低,同时如果编译的模块不是很完善,很有可能会造成内核崩溃。

    二、模块特点:

    1)模块本身并不被编译入内核,从而控制了内核的大小。

     2)模块一旦被加载,他就和内核中的其他部分完全一样。

        注意:模块并不是驱动的必要形式:即:驱动不一定必须是模块,有些驱动是直接编译进内核的;同时模块也不全是驱动,例如我们写的一些很小的算法可以作为模块编译进内核,但它并不是驱动。就像烧饼不一定是圆的,圆的也不都是烧饼一样。

    三、韦东山老师的first_drv.ko分析

    示例1

    #include <linux/module.h>  /* __init __exit */
    #include <linux/kernel.h>
    #include <linux/fs.h>
    #include <linux/init.h>/* printk() */
    #include <linux/delay.h>
    ...
    
    static int first_drv_open(struct inode *inode,struct file *file)
    {
        printk("first_drv_open
    ");
        return 0;    
    }
    static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
    {
        printk("first_drv_write
    ");
        return 0;
    }
    static int  first_drv_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
    {
           printk("first_drv_read
    ");
        return 0;
    }
    /* 这个结构是字符设备驱动程序的核心
     * 当应用程序操作设备文件时所调用的open、read、write等函数,
     * 最终会调用这个结构中指定的对应函数
     */
    static struct file_operations first_drv_fops = 
    {
        .owner = THIS_MODULE,/* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
           .open  = first_drv_open,
           .read = first_drv_read,
           .write = first_drv_write,
    };
     /*模块加载函数,通过insmod命令加载模块时,被自动执行*/
    static int __init first_drv_init(void)//驱动入口函数
    {
        register_chrdev(111,"first_drv",&first_drv_fops);//注册一个主设备号为111的字符驱动设备 主设备号,设备名字,对应的结构体
        return 0;
    }
    /*模块卸载函数,当通过rmmod命令卸载时,会被自动执行*/
    static void __exit first_drv_exit(void)
    {
        unregister_chrdev(111,"first_drv");
    }
    /* 这两行指定驱动程序的初始化函数和卸载函数 */
    module_init( first_drv_init);
    module_exit(first_drv_exit);
    
    MODULE_LICENSE("GPL");/*模块许可证明,描述内核模块的许可权限,必须*/

    对应Makefile

    KERN_DIR = /home/wang/linux-2.6.22.6// //内核路径,根据实际情况换成自己的内核路径,嵌入式的换成嵌入式,PC机的指定PC机路径
    
    all:
        make -C $(KERN_DIR) M=`pwd` modules 
    
    clean:
        make -C $(KERN_DIR) M=`pwd` modules clean
        rm -rf modules.order
    
    obj-m    += first_drv.o  //目标文件

    最终会编译得到first_drv.ko文件,cp first_drv.ko "nfs文件系统" 使用insmod first_drv.ko加载模块,使用cat /proc/devices或dmesg可查看

    常用的几种模块操作:

    insmod XXX.ko    加载指定模块

    lsmod                      列举当前系统中的所有模块

    rmmod  XXX         卸载指定模块(注意没有.ko后缀)

    dmesg                    当打印等级低于默认输出等级时,采用此命令查看系统日志

    编写对应的应用测试程序

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdio.h>
    
    int main(int argc, char **argv)
    {
        int fd;
        int val = 1;
        fd = open("/dev/xyz", O_RDWR);
        if (fd < 0)
        {
            printf("can't open!
    ");
        }
        write(fd, &val, 4);
        return 0;
    }

    使用arm-linux-gcc -o firstdrvtest firstdrvtest.c 编译并拷贝到nfs文件系统

    1创建设备节点 mknod /dev/xyz c 111 0 //创建主设备号为111,刺设备号0的字符设备/dev/xyz

    2 执行测试应用程序 ./firstdrvtest

    打印输出

    first_drv_open

    first_drv_write

    上个驱动程序,需要自己指定主设备号,需要手动创建设备节点。那么是否可以自动创建?

    1驱动:可以自动分配主设备号,也可以手动指定。

    2应用 open("/dev/xxx") ->/dev/xxx怎么来?

      a.手动创建 mknod /dev/xxx c major minor。

      b自动创建 udev。在文件系统根目录下的sys目录,当注册一个驱动,会在该目录生成一个的信息,而mdev自动根据这些信息创建节点。

    所以驱动程序需要提供设备信息以建立设备节点。

    示例2

    static struct class *firstdrv_class;
    static struct class_device    *firstdrv_class_dev;
    int major;
    static int _init first_drv_init(void)//驱动入口函数
    {
        major = register_chrdev(0,"first_drv",&first_drv_fops);//注册 返回的major就是自动分配的主设备号
        firstdrv_class = class_create(THIS_MODULE, "firstdrv");//建立一个类
        firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz");//类下边建立一个设备 次设备号为0 设备节点名为xyz
        return 0;
    }
    void first_drv_exit(void)
    {
        unregister_chrdev(major,"first_drv");
        class_device_unregister(firstdrv_class_dev);
        class_destroy(firstdrv_class);
    }

    mdev为什么会根据这些信息创建设备节点?因为在etc/init.d/rcS文件中有

    echo /sbin/mdev > /proc/sys/kernel/hotplug  

    当有设备注册或卸载的时候,就会调用/proc/sys/kernel/hotplug

    /**
     * class_device_create - creates a class device and registers it with sysfs
     * @cls: pointer to the struct class that this device should be registered to.
     * @parent: pointer to the parent struct class_device of this new device, if any.
     * @devt: the dev_t for the char device to be added.
     * @device: a pointer to a struct device that is assiociated with this class device.
     * @fmt: string for the class device's name
     *
     * This function can be used by char device classes.  A struct
     * class_device will be created in sysfs, registered to the specified
     * class.
     * A "dev" file will be created, showing the dev_t for the device, if
     * the dev_t is not 0,0.
     * If a pointer to a parent struct class_device is passed in, the newly
     * created struct class_device will be a child of that device in sysfs.
     * The pointer to the struct class_device will be returned from the
     * call.  Any further sysfs files that might be required can be created
     * using this pointer.
     *
     * Note: the struct class passed to this function must have previously
     * been created with a call to class_create().
     */
    struct class_device *class_device_create(struct class *cls,
                         struct class_device *parent,
                         dev_t devt,
                         struct device *device,
                         const char *fmt, ...)

    示例3

    一个类的设备中,有多个设备,比如3个led灯

    static struct class *leds_class;
    static struct class_device    *leds_class_devs[3];
    static unsigned long gpio_va;
    
    static int s3c24xx_leds_open(struct inode *inode, struct file *file)
    {
        int minor = MINOR(inode->i_rdev); 
        switch(minor)
        {
            case 0:
             {
                  /*配置led1 gpio*/   
              }   break;
            case 1:
             {
                  /*配置led2 gpio*/   
              }   break;
            case 2:
             {
                  /*配置led3 gpio*/   
              }   break;
        }   
        return 0;
    }
    static ssize_t s3c24xx_leds_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
    {
        int minor = MINOR(file->f_dentry->d_inode->i_rdev);
         char val;
         copy_from_user(&val, buf, 1);
          switch(minor)
        {
            case 0:
             {
                  /*根据val值控制led1 gpio*/   
              }   break;
            case 1:
             {
                  /*根据val值控制led2 gpio*/   
              }   break;
            case 2:
             {
                  /*根据val值控制led3 gpio*/   
              }   break;
        }   
    }
    static struct file_operations s3c24xx_leds_fops = {
        .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
        .open   =   s3c24xx_leds_open,     
        .read    =    s3c24xx_leds_read,       
        .write    =    s3c24xx_leds_write,       
    };
    /*
     * 执行insmod命令时就会调用这个函数 
     */
    static int __init s3c24xx_leds_init(void)
    {
            int ret;
        int minor = 0;
            gpio_va = ioremap(0x56000000, 0x100000);//IO重映射 申请IM空间
        /* 注册字符设备
         * 参数为主设备号、设备名字、file_operations结构;
         * 这样,主设备号就和具体的file_operations结构联系起来了,
         * 操作主设备为LED_MAJOR的设备文件时,就会调用s3c24xx_leds_fops中的相关成员函数
         * LED_MAJOR可以设为0,表示由内核自动分配主设备号
         */
        ret = register_chrdev(231, “leds”, &s3c24xx_leds_fops);
        leds_class = class_create(THIS_MODULE, "leds");
    for (minor = 0; minor < 3; minor++)
        {
            leds_class_devs[minor] = class_device_create(leds_class, NULL, MKDEV(LED_MAJOR, minor), NULL, "led%d", minor);
        }
    }
    /*
     * 执行rmmod命令时就会调用这个函数 
     */
    static void __exit s3c24xx_leds_exit(void)
    {
        int minor;
        /* 卸载驱动程序 */
        unregister_chrdev(LED_MAJOR, DEVICE_NAME);
    
        for (minor = 0; minor < 3; minor++)
        {
            class_device_unregister(leds_class_devs[minor]);
        }
        class_destroy(leds_class);
            iounmap(gpio_va);
    }
    /* 这两行指定驱动程序的初始化函数和卸载函数 */
    module_init(s3c24xx_leds_init);
    module_exit(s3c24xx_leds_exit);
    
    MODULE_LICENSE("GPL");
    懒惰不会让你一下子跌到 但会在不知不觉中减少你的收获; 勤奋也不会让你一夜成功 但会在不知不觉中积累你的成果 越努力,越幸运。
  • 相关阅读:
    UVA10765图论+点-双连通分量性质应用
    LA4287图论+ 有向图SCC+缩点
    LA5135图论+ 割点性质运用
    LA2572计算几何+离散化+面的覆盖
    LA2402暴力枚举+计算几何+四边形面积
    UVA10566计算几何+相似三角形比例函数+二分范围的辨析
    UVA11300计算几何:正n边形内的最长的线
    UVA11524平面几何+二分法+海伦公式
    LA4986三分法求出凹性函数最小值+计算几何
    胜利大逃亡--hdu --1253(bfs)
  • 原文地址:https://www.cnblogs.com/Rainingday/p/8869538.html
Copyright © 2020-2023  润新知