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

/***********************************************************************************
 *
 *      Samsung_tiny4412(笔记)-->字符设备驱动基本操作及调用流程
 *
 *  声明:
 *      以下所有的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.\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

五. 模块操作:
    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\n", 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;
                }
时间: 2024-10-24 06:07:06

Samsung_tiny4412(笔记)-->字符设备驱动基本操作及调用流程的相关文章

LDD3阅读笔记-字符设备驱动

主要开发流程介绍 module_init宏和module_exit宏 当模块装载时需要调用module_init宏指定的函数,卸载时需要调用 module_exit宏指定的函数 以下是简单的init流程: 初始化设备 初始化file_operation 获取字符设备号 注册字符设备 当卸载模块时,需要释放申请的设备号. 主设备号和次设备号 对字符设备的访问是通过文件系统内的设备名称进行的.那些名称被称为特殊 文件.设备文件,或者简单称为文件系统树的节点,他们通常位于/dev目录. 通常而言,主设

Linux内核分析(五)----字符设备驱动实现

原文:Linux内核分析(五)----字符设备驱动实现 Linux内核分析(五) 昨天我们对linux内核的子系统进行简单的认识,今天我们正式进入驱动的开发,我们今后的学习为了避免大家没有硬件的缺陷,我们都会以虚拟的设备为例进行学习,所以大家不必害怕没有硬件的问题. 今天我们会分析到以下内容: 1.      字符设备驱动基础 2.      简单字符设备驱动实现 3.      驱动测试 l  字符设备基础 1.       字符设备描述结构 在linux2.6内核中,使用cdev结构体描述一

Android字符设备驱动开发基于高通msm8916【原创 】

本人才疏浅学,写一篇文档总结自己在msm8916平台上移植自己编写的简单的字符设备驱动开发的整个流程.这个小项目的主要功能是开发一个简单的APP,APP通过JNI去调用位于kernel的字符设备驱动. APP的设计,开发平台Android Studio 主要的文件是下面的三个文件: MainActivity.java文件的内容如下: 1 package com.example.administrator.myled; 2 3 import android.nfc.Tag; 4 import an

register_chrdev_region/alloc_chrdev_region和cdev注册字符设备驱动

内核提供了三个函数来注册一组字符设备编号,这三个函数分别是 register_chrdev_region().alloc_chrdev_region() 和 register_chrdev(). (1)register_chrdev  比较老的内核注册的形式   早期的驱动(2)register_chrdev_region/alloc_chrdev_region + cdev  新的驱动形式 (3)register_chrdev()函数是老版本里面的设备号注册函数,可以实现静态和动态注册两种方法

linux字符设备驱动

一.字符设备.字符设备驱动与用户空间访问该设备的程序三者之间的关系. 如图,在Linux内核中使用cdev结构体来描述字符设备,通过其成员dev_t来定义设备号(分为主.次设备号)以确定字符设备的唯一性.通过其成员file_operations来定义字符设备驱动提供给VFS的接口函数,如常见的open().read().write()等. 在Linux字符设备驱动中,模块加载函数通过register_chrdev_region( ) 或alloc_chrdev_region( )来静态或者动态获

linux 字符设备驱动开发详解

一.设备的分类及特点 1.字符设备 字符设备是面向数据流的设备,没有请求缓冲区,对设备的存取只能按顺序按字节的存取而不能随机访问.    Linux下的大多设备都是字符设备.应用程序是通过字符设备节点来访问字符设备的.通常至少需要实现 open, close, read, 和 write 等系统调用.    设备节点一般都由mknod命令都创建在/dev目录下,包含了设备的类型.主/次设备号以及设备的访问权限控制等,如:crw-rw----  1 root  root 4, 64 Feb 18

linux设备驱动第三篇:写一个简单的字符设备驱动

在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存. 下面就开始学习如何写一个简单的字符设备驱动.首先我们来分解一下字符设备驱动都有那些结构或者方法组成,也就是说实现一个可以使用的字符设备驱动我们必须做些什么工作. 1.主设备号和次设备号 对于字符设备的访问是通过文件系统中的设备名称进行的.他们通常位于/dev目录下.如下: [plain] vie

linux设备驱动之字符设备驱动模型(2)

在上一篇中我们已经了解了字符设备驱动的原理,也了解了应用层调用内核函数的机制,但是我们每次操作设备,都必须首先通过mknod命令创建一个设备文件名,比如说我们要打开u盘,硬盘等这些设备,难道我们还要自己创建,就如同刘老师常说的一句话,这也太山寨了吧,所以我们今天我们来点比较专业的,让函数帮我们自动创建: 在Linux 下,设备和驱动通常都需要挂接在一种总线上,总线有PCI.USB.I2C.SPI 等等,总线是处理器和设备之间的通道,在设备模型中,所有的设备都通过总线相连,一总线来管理设备和驱动函

字符设备驱动模型

1.设备描述结构cdev驱动模型种类繁多,这就需要我从众多的模型中提取出他们的一些共性:a.驱动初始化a.1 分配设备描述结构a.2 初始化设备描述结构a.3 注册设备描述结构a.4 硬件初始化b.实现设备操作c.驱动注销 ------------------------------------------------------------------ 设备描述结构:在任何一种驱动模型中,设备都会用的内核中的一种结构来描述,我们的字符设备在内核中使用struct cdev 来来描述.struc