zoukankan      html  css  js  c++  java
  • Samsung_tiny4412(驱动笔记03)----字符设备驱动基本操作及调用流程

    /***********************************************************************************
     *                    
     *                          字符设备驱动基本操作及调用流程
     *
     *  声明:
     *      1. 本系列文档是在vim下编辑,请尽量是用vim来阅读,在其它编辑器下可能会
     *         不对齐,从而影响阅读.
     *      2. 以下所有的shell命令都是在root权限下运行的;
     *
     *                                          2015-3-7 阴 深圳 尚观 Sbin 曾剑锋
     **********************************************************************************/
    
                            \\\\\\\--*目录*--//////////////
                            |  一. make编译快捷方式;             
                            |  二. ctags使用;                    
                            |  三. menuconfig编译成内核内部模块; 
                            |  四. 编译内核模块的方法;           
                            |  五. 模块操作;                     
                            |  六. 多源文件编译模块Makefile格式; 
                            |  七. 导出符号;                     
                            |  八. printk打印等级;               
                            |  九. 模块传参;                     
                            |  十. 字符设备;                     
                            |  十一. 2种字符设备注册;            
                            |  十二. 驱动中常见的3种结构体;      
                            |  十三. 内核空间与用户空间数据拷贝; 
                            |  十四. 驱动被调用函数流程:         
                            \\\\\\\\\///////////////////
    
    一. make编译快捷方式:
        1. export CC=arm-linux-gcc 
        2. make app 
            arm-linux-gcc     app.c   -o app 
    
    二. ctags使用:
        1. 生成tags文件 ctags -Rn . 
        2. 把tags文件的路径名添加到vim的配置文件中 
            cat >> ~/.vimrc << EOF
            set tags+=/root/linux-3.5/tags #可以添加多个原文件目录
            EOF
        3. vim查找符号定义: :ts <symbols>
    
    三. menuconfig 编译成内核内部模块:
        1. cat > test.c << EOF
            #include <linux/module.h>
            int test_init(void)
            {
                printk("Hello module.
    ");
                return 0;
            }
            void test_exit(void)
            {
                printk("Bye module.
    ");
            }
            //指定模块的初始化函数与退出函数
            module_init(test_init);
            module_exit(test_exit);
            MODULE_LICENSE("GPL");
            MODULE_AUTHOR("lizhichao");
            MODULE_DESCRIPTION("simpile module.");
            MODULE_VERSION("1.0");
            EOF
        2. 在test.c所在目录的Makefile文件添加:
            obj-$(CONFIG_TEST) += test.o
        3. 在test.c所在目录的Kconfig文件添加:
            config TEST
                bool "----- test module -------"
        4. 这时候可以通过menuconfig等配置工具配置test模块的编译
        5. 重新编译内核
        6. make -j2 zImage
        7. 查看模块是否编译到内核
            1. nm vmlinux | grep test_init
                c08d0790 t __initcall_test_init6
                c030537c T test_init
            2. nm vmlinux | grep test_exit
                c0305370 T test_exit
        8. 重新把内核烧写到SD卡的kernel分区,dwn或者fastboot都行.
        9. 系统启动时将调用初始化函数test_init,使用dmesg命令查看是否有正确的输出
    
    四. 编译内核模块的方法
        1. Makefile中对变量的引用,可以是$(变量名),也可以是${变量名},但是目前看到$(变量名)居多.
        2. 以下是几个对模块编译的make命令:
            1. make -C $(内核跟目录路径) M=`pwd` modules
            2. make -C $(内核根目录路径) M=`pwd` clean
            3. make -s -C $(内核根目录路径) M=$PWD INSTALL_MOD_PATH=$(nfs文件系统根目录) modules_install
        3. 实现了上面make命令的shell脚本实例: 
            cat > mm << EOF
            #!/bin/bash
            KERNEL=/disk/A9/filesystem/linux-3.5
            ROOT_PATH=/disk/A9/filesystem
            if [ $# -eq 0 ]
            then
                make -s -C ${KERNEL} M=$PWD modules
            elif [ $# -eq 1 -a "$1" = "clean" ]
            then
                make -s -C ${KERNEL} M=$PWD modules clean
            elif [ $# -eq 1 -a "$1" = "install" ]
            then
                make -s -C ${KERNEL} M=$PWD 
                INSTALL_MOD_PATH=${ROOT_PATH} modules_install
            else
                echo "usage:"
                echo "      mm"
                echo "      mm clean"
                echo "      mm install"
            fi
            EOF
    4. 实现上面make命令的Makefile实例:
    ifneq ($(KERNELRELEASE),)
       obj-m := at24c02.o
    else 
    KDIR := /home/myzr/myandroid/kernel_imx
    all:
         make -C $(KDIR) M=$(PWD) modules

    clean:
          rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.order
    endif
    5. 将mm当常用命令来是用: cp mm /bin/ && chmod 777 /bin/mm
    
    五. 模块操作:
        1. 动态插入模块到当前运行的系统: insmod test.ko
        2. 查看当前运行的系统加载的模块信息: lsmod
        3. 查看模块的信息: modinfo test.ko 或者 modinfo test
        4. 卸载加载的模块: rmmod test
        5. 生成模块的依赖关系: depmod
        6. 加载内核模块,主要用于加载make install的模块: modprobe test
        7. 卸载内核模块,主要用于卸载make install的模块: modprobe -r test
    
    
    六. 多源文件编译模块Makefile格式:
        1. xxx是模块文件名
        2. obj-m   += xxx.o
           xxx-objs = main.o foo.o ...
    
    七. 导出符号:
        把符号导出到内核全局符号表,主要是为其他的模块提供函数调用,有两种方式:
        1. EXPORT_SYMBOL(foo);      //普通方式
        2. EXPORT_SYMBOL_GPL(foo);  //只有声明为GPL的模块才能调用
    
    
    八. printk打印等级:
        1. 数字越小,等级越高:
            #define KERN_EMERG      "<0>"   /* system is unusable           */
            #define KERN_ALERT      "<1>"   /* action must be taken immediately */
            #define KERN_CRIT       "<2>"   /* critical conditions          */
            #define KERN_ERR        "<3>"   /* error conditions         */
            #define KERN_WARNING    "<4>"   /* warning conditions           */
            #define KERN_NOTICE     "<5>"   /* normal but significant condition */
            #define KERN_INFO       "<6>"   /* informational            */
            #define KERN_DEBUG      "<7>"   /* debug-level messages         */
    
            /* Use the default kernel loglevel */
            #define KERN_DEFAULT    "<d>"
        2. cat /proc/sys/kernel/printk
            5       4       1       7
            数字解析如下:
            1. 5   ---> 打印等级小于5的内核消息输出到控制台
            2. 4   ---> 默认的打印等级
            3. 1   ---> 允许设置的最小等级
            4. 7   ---> 允许设置的最大等级
    
    九. 模块传参:
        1. 声明定义可传参变量:
            int num = 500;
            module_param(num, int, 0644);
            module_param参数说明:
                1. num     参数名
                2. int     参数类型
                3. 0644    访问权限(下面文件)
        2. 加载模块时,传参方法: insmod test.ko num=1234
            num = 1234
        3. cat /sys/module/test/parameters/num
            1234
    
    十. 字符设备:
        1. dev_t devno; ---> 设备号,设备的身份证号码 
            1. 高12位: 主设备号 
            2. 低20位: 次设备号 
        2. 设备号操作辅助宏 
            1. major = MAJOR(devno); 
            2. minor = MINOR(devno); 
            3. devno = MKDEV(major, minor); 
        3. 查看当前系统中注册的所有设备 
            cat /proc/devices 
        4. 手动创建设备节点 
            1. mknod /dev/test0 c 250 0 
            2. ls /dev/test0 -l 
                crw-r--r--  1 0  0  250,   0 Jan  1 15:31 /0 
    
    十一. 2种字符设备注册:
        字符设备底层接口实现linux-3.5/fs/char_dev.c
        1. static inline int register_chrdev(unsigned int major, const char *name,
                                             const struct file_operations *fops)
            {
                //该函数调用了下面的3步注册方式
                return __register_chrdev(major, 0, 256, name, fops);
            }
        2. 3步详细注册:
            1. struct cdev cdev; //char device
            2. 分配设备号,有2种方式:
                1. 动态分配设备号
                    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
                2. 静态指定设备号
                    int register_chrdev_region(dev_t from, unsigned count, const char *name)
            3. 初始化cdev结构
                cdev_init();
            4. 添加cdev到系统中
                cdev_add();
    
    十二. 驱动中常见的3种结构体:
        1. struct file_operations;      //每个驱动对应一个
        2. struct inode *inode;         //每个文件对应一个
        3. struct file *file;           //文件每打开一次,对应一个file结构维护着打开文件的相关信息
            1. loff_t       f_pos;      //文件指针
            3. unsigned int f_flags;    //文件访问标志
    
    十三. 内核空间与用户空间之间拷贝数据:
        #include <linux/uaccess.h>
        1. copy_to_user();
        2. copy_from_user();
        成功返回0,失败返回未完成拷贝的字节数
    
    十四. 驱动被调用函数流程:
        1. 文件IO系统调用 ---> VFS(虚拟文件系统层) ---> 设备驱动
        2. 系统调用入口定义:arch/arm/kernel/calls.S
            1. open系统调用对应的内核入口:sys_open,该函数在VFS实现对应源文件fs/open.c;
            2. sys_open函数定义:
                SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
            3. 关键函数调用do_sys_open();
        3. 跟踪该函数do_sys_open(),通过get_unused_fd_flags()返回一个可用的文件描述符,
            关键函数调用do_filp_open();
        4. 跟踪该函数do_filp_open(),
            关键函数调用path_openat();
        5. 跟踪该函数path_openat();
            关键函数调用do_last();
        6. 跟踪该函数do_last();
            关键函数调用nameidata_to_filp();
        7. 跟踪该函数nameidata_to_filp();
            1. 关键函数调用do_dentry_open();
            2. 关键步骤:
                //把文件inode的file_operations 保存在file结构里
                f->f_op = fops_get(inode->i_fop);
    
                if (!open && f->f_op)
                    open = f->f_op->open;
                if (open) {
                    //调用file_operations的open成员函数
                    error = open(inode, f);
                    if (error)
                        goto cleanup_all;
                }
    
            3. 那2中的inode里的i_fop是哪里来的
                1. linux-3.5/fs/inode.c
                2. 初始化inode结构的i_fop,调用init_special_inode函数:
                    void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
                    {
                        inode->i_mode = mode;
                        if (S_ISCHR(mode)) {
                        //如果是字符设备,使用def_chr_fops
                            inode->i_fop = &def_chr_fops;
                            inode->i_rdev = rdev;
                        } else if (S_ISBLK(mode)) {
                            inode->i_fop = &def_blk_fops;
                            inode->i_rdev = rdev;
                        } else if (S_ISFIFO(mode))
                            inode->i_fop = &def_fifo_fops;
                        else if (S_ISSOCK(mode))
                            inode->i_fop = &bad_sock_fops;
                        else
                            printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o) for"
                                      " inode %s:%lu
    ", mode, inode->i_sb->s_id,
                                      inode->i_ino);
                    }
                3. 如果是字符设备,使用def_chr_fops:
                    const struct file_operations def_chr_fops = {
                        .open = chrdev_open,
                        .llseek = noop_llseek,
                    };
                4.接下来,跟踪chrdev_open()函数
                    static int chrdev_open(struct inode *inode, struct file *filp)
                    {
                        struct cdev *p;
                        struct cdev *new = NULL;
                        int ret = 0;
    
                        spin_lock(&cdev_lock);
                        p = inode->i_cdev;
                        if (!p) {
                            struct kobject *kobj;
                            int idx;
                            spin_unlock(&cdev_lock);
                            //找到之前注册的字符设备时添加的cdev结构的kobj
                            kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);
                            if (!kobj)
                                return -ENXIO;
    
                            //通过container_of获取cdev结构的地址
                            new = container_of(kobj, struct cdev, kobj);
                            spin_lock(&cdev_lock);
                            /* Check i_cdev again in case somebody beat us to it while
                               we dropped the lock. */
                            p = inode->i_cdev;
                            if (!p) {
                                inode->i_cdev = p = new;
                                list_add(&inode->i_devices, &p->list);
                                new = NULL;
                            } else if (!cdev_get(p))
                                ret = -ENXIO;
                        } else if (!cdev_get(p))
                            ret = -ENXIO;
                        spin_unlock(&cdev_lock);
                        cdev_put(new);
                        if (ret)
                            return ret;
    
                        ret = -ENXIO;
                        //把字符设备驱动的file_operations保存在file结构里
                        filp->f_op = fops_get(p->ops);
                        if (!filp->f_op)
                            goto out_cdev_put;
    
                        if (filp->f_op->open) {
                        //调用file_operations结构的open成员
                            ret = filp->f_op->open(inode, filp);
                            if (ret)
                                goto out_cdev_put;
                        }
    
                        return 0;
    
                    out_cdev_put:
                        cdev_put(p);
                        return ret;
                    }
  • 相关阅读:
    网易2019校招C++研发工程师笔试编程题
    牛客网 数串
    ps aux 状态介绍
    阿里在线测评解析
    Ubuntu 18.04安装 Sublime
    file '/grub/i386-pc/normal.mod' not found.解决方案
    解决Windows10与Ubuntu系统时间不一致问题
    进程与线程的区别
    大端模式和小端模式
    2016湖南省赛----G
  • 原文地址:https://www.cnblogs.com/zengjfgit/p/4320892.html
Copyright © 2011-2022 走看看