Linux设备驱动之模块

引言

linux设备驱动的作用是隐藏具体的硬件细节,将所有的设备都抽象成一个个文件供应用程序调用,也就是说,在应用程序员的眼中,所有的硬件都是文件,U盘是文件,磁盘是文件,LED灯是文件,引脚是文件,蜂鸣器是文件,万物皆文件。

Linux内核非常庞大,组件也非常之多,我们有两种方法来将这些组件包含到内核中

  • 直接编译到Linux内核中,这种方法会导致内核很大,而且每次需要添加或者删除组件的时候都得重新编译内核,而编译内核是一件非常耗时的事情
  • 以模块的形式动态加载,在这种方法中,模块本身不会被编译进内核镜像,从而不会使内核过大,但模块一旦被加载,它就和内核中的其它模块一样。

linux设备驱动一般是以模块的形式加载卸载的。linux内核本身就是结合了宏内核与微内核的优点,既有宏内核的高效率,又有微内核的模块化。

文章主要分成如下几个部分

  • 内核功能划分
  • helloworld驱动模块
  • 模块编译makefile解析

内核功能划分

可将内核功能分成如下几个部分

进程管理

进程管理主要负责创建和销毁进程,并处理它们和外部世界之间的连接。

内存管理

内存是计算机的主要资源之一,用来管理内存的策略是决定系统性能的一个关键因素。内核在有限的可用资源之上为每个进程创建一个虚拟地址空间。

文件系统

linux在很大程度上依赖于文件系统的概念,linux中所有对象都可以当作文件来看待。内核在没有结构的硬件上构造结构化的文件系统。,而文件抽象在整个系统中广泛使用。linux支持多种文件系统。

设备控制

几乎每一个系统操作最终都会映射到物理设备上。所有设备控制操作都由与被控制设备相关的代码来完成,这段代码就是驱动程序,这也是我接下来要学习的重点。

网络功能

网络功能也必须由操作系统来管理,因为大部分网络操作和具体进程无关。我们的重点放在设备控制上。

helloworld模块

许多编程书籍都会以一个helloworld实例来说明最简单的程序,虽然我们要学习的是设备驱动编程模型,而不是具体的程序,但提前看一下基于helloworld的小模块没准会有好处。如下所示:

#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Shijia Yin");
static int hello_init(void)
{
    printk(KERN_ALERT "Hello, world\n");
    return 0;
}

static void hello_exit(void)
{
    printk(KERN_ALERT "Goodbye, cruel world\n");
}
module_init(hello_init);
module_exit(hello_exit);
   

上面是一个完整的内核模块,在它被编译好之后就可以被insmod加载了,具体如何编译,如何加载,如何卸载,后面再说,下面做几点说明

  • hello_init:模块加载时会调用这个函数
  • hello_exit:模块卸载时会调用这个函数
  • module_init:指明模块加载时应该调用哪个函数,这里是hello_init
  • module_exit:指明模块卸载时应该调用哪个函数,这里是hello_exit
  • printk:内核打印函数,由于内核无法使用C库,所以需要自己的打印函数
  • KERN_ALERT:指明要打印信息的优先级,如果信息优先级不够高,可能不会被打印出来

模块编译makefile解析

用来编译该模块的makefile如下所示

ifneq ($(KERNELRELEASE),)
        obj-m := hello.o
else
        KERNELDIR ?=/lib/modules/$(shell uname -r)/build
        PWD :=$(shell pwd)
default:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
        @rm -rf *.o *.mod.c *.ko *.symvers *.order *.makers
endif

该makefile在构造过程中会被读取两次;执行步骤如下:

  • 当makefile从命令行调用时,它注意倒KERNELRELEASE尚未被设置,此时执行else的KERNELDIR和PWD设置;
  • 然后执行default目标,make首先切换到-C指定的目录KERNELDIR,执行那个目录下的Makefile,KERNELRELEASE定义在KERNELDIR中的makefile中,执行完之后KERNELRELEASE已被设置;
  • 通过M=$(PWD)第二次读取该makefile,它设置了obj-m,而内核的makefile负责真正的构造模块;

Tips:有个疑问,为什么第二次读取makefile不会继续往下执行,如果继续往下执行不就构成了一个无限循环了吗,也许这是语法的规定,反正设置完obj-m之后,本次makefile就算结束了,下面就是内核makefile的事情了。

模块的装载与卸载

模块的装载使用insmod和modprobe,卸载使用rmmod。

insmod

模块是一个.ko文件,insmod可以接受一些配置参数用来配置模块

modprobe

和insmod类似,modprobe也是用来将模块装载到内核的,与insmod的区别在于,它会考虑要装载的模块是否引用了一些当前内核不存在的符号。如果有这类引用,modprobe会在当前模块路径中查找定义了这些符号的其它模块。如果modprobe找到了这些模块,它会同时装载这些模块到内核。如果在这种情况下使用insmod,则该命令会失败。modprobe是处理层叠模块的一个实用工具。

rmmod

模块卸载函数

快速参考

下面是本章用到的内核函数,变量,宏及/proc文件的一个快速参考

insmod
modprobe
rmmod

用来装载模块到正在运行的内核和移除模块

__init
__initdata
__exit
__exitdata

仅用于模块初始化或清除阶段的函数和数据标记。标记为初始化的项目会在初始化结束后丢弃;而标记为清除的项目在内核未被配置为可卸载模块的情况下被丢弃。

#include <linux/sched.h>

最重要的头文件之一。该文件包含了驱动程序使用的大部分内核API的定义,包括睡眠函数以及各种变量声明

struct task_current *current;

当前进程

current->pid
current->comm

当前进程的PID和命令名

obj-m

由内核构造系统使用的makefile符号,用来确定在当前目录中应构造哪些模块

#include <linux/module.h>

必需的头文件,它必须包含在模块源代码中

#include <linux/version.h>

包含所构造内核版本信息的头文件

MODULE_AUTHER(auther);
MODULE_DESCRIPTION(description);
MODULE_VERSION(version_string);
MODULE_DEVICE_TABLE(table_info);
MODULE_ALIAS(alternate_name);

在目标文件中添加关于模块的文档信息

module_init(init_function);
module_exit(exit_function);

用来声明模块初始化和清除函数的宏

#include <linux/moduleparam.h>
module_param(variable, type, perm);

用来创建模块参数的宏,用户可在装载这些模块时调整这些参数的值

#include <linux/kernel.h>
int printk(const char * fmt, ...);

内核打印函数

引用

[1] 讯为驱动开发资料
[2] 《Linux设备驱动程序开发详解》宋宝华 第一版

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值