单片机
返回首页

Samsung_tiny4412(驱动笔记03)----字符设备驱动基本操作及调用流程

2025-01-15 来源:cnblogs

/***********************************************************************************

 *                    

 *                          字符设备驱动基本操作及调用流程

 *

 *  声明:

 *      1. 本系列文档是在vim下编辑,请尽量是用vim来阅读,在其它编辑器下可能会

 *         不对齐,从而影响阅读.

 *      2. 以下所有的shell命令都是在root权限下运行的;


 **********************************************************************************/


                        \\\\\\\--*目录*--//////////////

                        |  一. 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


三. menuconfig 编译成内核内部模块:

    1. cat > test.c << EOF

        #include

        int test_init(void)

        {

            printk('Hello module.n');

            return 0;

        }

        void test_exit(void)

        {

            printk('Bye module.n');

        }

        //指定模块的初始化函数与退出函数

        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    ''

    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

    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:%lun', 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;

                }


进入单片机查看更多内容>>
相关视频
  • 【TI MSPM0 应用实战】智能小车+工业角度编码器+血氧仪+烟雾探测器!硬核参考设计详解!

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

  • 直播回放: Microchip Timberwolf™ 音频处理器在线研讨会

  • 基于灵动MM32W0系列MCU的指夹血氧仪控制及OTA升级应用方案分享

精选电路图
  • 1瓦线性调频增强器

  • 家用电器遥控器

  • 12V 转 28V DC-DC 变换器(基于 LM2585)

  • 红外开关

  • DS1669数字电位器

  • HA1377 桥式放大器 BCL 电容 17W(汽车音频)

    相关电子头条文章