• 驱动:驱动相关概念,内核模块编程,内核消息打印printk函数的使用


    一、驱动相关概念

    1.操作系统的功能

                    向下管理硬件,向上提供接口

                    操作系统向上提供的接口类型

                    内存管理:内存申请(malloc) 内存释放(free)等

                    文件管理: 通过文件系统格式对文件ext2、ext3、 ext4格式进行管理

                    进程管理: 进程的创建/调度/消亡

                    网络管理: 通过网络栈协议,完成数据的收发

                     设备管理: 字符设备、块设备、网卡设备

     2.驱动的概念

                      2.1.定义

                           驱动是能够让硬件实现某个特定功能的软件代码,根据驱动代码是否使用了系统内核提供的接口,将驱动分为裸机驱动和系统驱动

                      2.2.裸机驱动和系统驱动

                             裸机驱动:不基于操作系统提供的接口完成驱动的编写,这种驱动比较简单,由开发者独立即可编写完成,但是实现的功能比较单一

                            系统驱动(操作系统设备驱动):编写驱动时调用系统内核提供的接口,驱动程序会被加载到内核空间。系统驱动开发者无法独立完成,需要操作系统内核的辅助,但是基于系统驱动完成的硬件控制工作会更加完善和复杂。

    3.系统驱动(操作系统设备驱动)在操作系统中的层次

            设备驱动会被加载到系统内核空间,设备驱动主要完成对硬件功能的实现,相当于拓展了系统内核设备管理的功能

    4.设备驱动的类型

           设备驱动的类型时根据设备类型的不同进行划分的,一般将设备分为字符设备、块设备、和网卡设备 三类:

                    字符设备:以字节流的形式进行顺序访问的设备叫做字符设备。 90%的设备为字符设备,鼠标、键盘、摄像头...

                    块设备:以块为单位进行随机访问的设备叫做块设备。块设备一般是一些磁盘设备

                    网卡设备:用于进行网络数据传输的设备。网卡设备没有网卡设备文件,想要读取网卡设备的数据,需要基于socket套接字实现

    二、内核模块编程

    1.内核模块编程的意义

               驱动属于内核的一部分,驱动资源要加载到内核中,所以要按照内核模块的编程去编写框架

     2.内核模块的三要素

               入口:主要进行资源的申请工作,安装内核模块时执行

               出口:主要进行资源的释放工作,卸载内核模块时执行

               许可证:声明当前内核模块遵循GPL协议

     3.内核模块编写实例及代码解释

    1. #inckude
    2. #include
    3. //入口函数:安装内核模块时执行
    4. static int __init mycdev_init(void)
    5. {
    6. //static: 修饰当前函数只可在本文件使用
    7. //int:函数返回值类型,如果函数规定了返回值但是没有加返回值,编译会报错
    8. //mycdev_init:函数名,可以自己命名
    9. //void:表示函数无参数,void不可以省略
    10. //__init:告诉编译器当前函数保存在.init.text段 #define __init sectoin(".init.text")
    11. //linux内核中有自己的链接脚本文件 vmlinux.lds,这个链接脚本文件规定了不同的内容存放在内存中的哪个位置
    12. return 0;
    13. }
    14. //出口函数:卸载内核模块时执行
    15. static void __exit mycdev_exit(void)
    16. {
    17. //__exit:告诉编译器当前函数保存在.exit.text段 #define __exit section(".exit.text")
    18. }
    19. //声明入口函数
    20. module_init(mycdev_init);
    21. //声明出口函数
    22. module_exit(mycdev_exit);
    23. //声明当前内核模块遵循GPL协议
    24. MODULE_LICENSE("GPL");

    4.内核模块的编译

            编译内核镜像:make uImage

            编译设备树:make dtbs

            模块化编译:make modules

            内核模块编译有两种方式:内部编译和外部编译

            4.1内部编译

                    内部编译又称为静态编译,需要依赖于内核的源码树:

                            ① 编写内核模块

                            ② 将内核模块文件移动到内核指定路径下

                            ③ 在内核指定路径下的Makefile文件中添加编译文件

                            ④ 修改Kconfig文件,添加当前内核模块文件的菜单选项(选配项)

                            ⑤  在源码顶层目录下make menuconfig将当前文件的选配项选配为M,保存退出,此时.config被修改

                            ⑥ make modules  进行模块化编译

             4.2.外部编译

                    外部编译又称为动态编译,可以在内核路径外单独编译当前模块文件,外部编译需要我们自己手写Makefile

            通用版本的Makefile

    1. modname ?= demo #内核模块名称,询问赋值,若无对应的外部变量,则为demo,有则为外部变量
    2. arch ?= arm #架构,询问赋值,若无对应的外部变量,则为arm,有则为外部变量
    3. ifed ($(arch),arm) #通过命令行是否有对应的外部变量来判断采用什么架构进行编译
    4. #KERNRLDIR保存开发板内核源码路径
    5. KERNRLDIR := /home/ubuntu/linux.5.10.61/
    6. else
    7. #KERNRLDIR保存ubuntu内核源码路径
    8. KERNRIDIR :=/lib/kernel/$(shell uname -r)/bulid #uname -r命令是查找当前操作系统使用的内核版本
    9. endif
    10. #PWD保存当前内核模块的路径
    11. PWD := $(shell pwd)
    12. all:
    13. #make modules是模块化编译的命令
    14. #-C $(KERNELDIR) 表示执行make前到$(KERNELDIR)所保存路径下,读取该目录下的Makefile文件执行make编译
    15. #M=$(PWD) 指定模块化编译的路径,进行模块化编译的路径是PWD保存的路径
    16. make -C $(KERNELDIR) M=$(PWD) modules
    17. clean:
    18. #编译删除
    19. make -C $(KERNELDIR) m=$(PWD) clean
    20. #将obj-m保存的文件单独链接生成内核模块
    21. obj-m := $(modname).o #此处写.o不写.c,虽然当前目录下没有.o文件,但是Makefile有自动推导功
    22. 能,会自动寻找对应.c

    5.内核模板操作相关命令

            安装内核模块命令 insmod *.ko

            卸载内核模块命令 rmmod *.ko (后缀名.ko可省略)

            查看内核已经安装的内核模块 lsmod

            查看某个内核模块的信息 modinfo *.ko

    三、内核消息打印函数printk函数的使用

    1.printk函数的使用格式

            printk("格式控制符",输出列表)

                    这种格式消息按照默认输出级别进行输出

            或

            printk(消息级别 "格式控制符",输出列表)

    2.printk函数消息队列打印级别

            prink函数打印的内容属于内核消息。内核消息根据消息的轻重缓急给他们设置不同的消息级别。终端也会存在一个默认的消息级别,只有消息级别高于终端默认消息级别,消息才可以在终端进行输出,消息级别共分为0-7总共8级,数字越小代表消息级别越高,一般常用3-7级 

    #define KERN_EMERG         KERN_SOH "0"         /* system is unusable */

    #define KERN_ALERT         KERN_SOH "1"         /* action must be taken immediately */

    #define KERN_CRIT         KERN_SOH "2"         /* critical conditions */

    #define KERN_ERR         KERN_SOH "3"         /* error conditions */

    #define KERN_WARNING         KERN_SOH "4"         /* warning conditions */

    #define KERN_NOTICE         KERN_SOH "5"         /* normal but significant condition */

    #define KERN_INFO         KERN_SOH "6"         /* informational */

    #define KERN_DEBUG         KERN_SOH "7"         /* debug-level messages */

     3.查看消息默认级别

            消息默认级别保存在/proc/sys/kernel/printk文件

            查看cat /proc/sys/kernel/printk

            4        4        1        7

            4        终端默认消息级别

            4        printk默认的消息级别

            1        终端支持的消息最高级别

            7        终端支持的消息最低级别

    代码示例

    1. #include
    2. #include
    3. // 入口函数,安装内核模块时执行
    4. static int __init mycdev_init(void)
    5. {
    6. // static 修饰当前函数只能在本文件使用
    7. // int 函数的返回值类型,如果函数规定返回值但是没有加返回值,编译会报错
    8. // mycdev_init函数名,可以自己起名字
    9. // void表示函数无参数,当没有参数时void一定要加,不然报错
    10. //__init的作用是用来告诉编译器当前代码保存在.init.text段中
    11. // #define __init __section(".init.text")
    12. // linux内核也会有自己的链接脚本 vmlinux.lds,这个链接脚本里规定了不同的内容在
    13. // 内存中的什么位置
    14. printk(KERN_ERR "hello world\n");
    15. int a=10;
    16. printk(KERN_ERR "%d\n",a);
    17. return 0;
    18. }
    19. // 出口函数,卸载内核模块时执行
    20. static void __exit mycdev_exit(void)
    21. {
    22. // #define __exit __section(".exit.text")
    23. //__exit指定出口函数保存在.exit.text段中
    24. }
    25. // 用于声明入口函数
    26. module_init(mycdev_init);
    27. // 用于声明出口函数
    28. module_exit(mycdev_exit);
    29. // 声明当前内核模块遵循GPL协议
    30. MODULE_LICENSE("GPL");

     5.如何修改消息默认级别

            ubuntu:

                    先切换到管理员:sudo su

                    再输入以下内容:echo 4 3 1 7 >  /proc/sys/kernel/printk

                    注:这种修改方式ubuntn重启后,级别恢复默认设置的4 4 1 7

            开发板上linux

                    进入根文件系统下的etc下的init.d文件夹:cd ~/nfs/rootfs/etc/init.d

                    打开rcS脚本文件:vi   rcS

                    在rcS中最后一行添加:echo 4 3 1 7 > /proc/sys/kernel/printk

    6.ubuntu虚拟终端的使用

            切换到虚拟终端:ctrl+alt+[f2-f6](fn)

            退出虚拟终端:ctrl+alt+f1(fn)

    7.dmesg命令的使用

            dmesg:查看内核打印消息

            dmesg -c:先将保存的内核消息打印在终端再清除

            dmesg -C:直接清除

  • 相关阅读:
    【STM32】sct 分散加载文件的格式与应用
    HALCON reference_hdevelop翻译Chapter1 1D Measuring(二)
    常见排序算法
    《opencv学习笔记》-- SURF 特征提取
    redis持久化,主从,哨兵
    【微服务实战之Docker容器】第四章-【微服务实战之Docker容器】第三章-镜像仓库
    什么牌子的电容笔性价比高?口碑好的电容笔推荐
    【Ant Design Pro】使用ant design pro做为你的开发模板(九)开发第一个完整的后台页面(二)
    Java练手任务总结【20】
    读图数据库实战笔记01_初识图
  • 原文地址:https://blog.csdn.net/qq_46766479/article/details/133934502