Smart210学习记录(一) 之linux内核模块


 

一、怎样配置以及编译内核

1、配置内核

  Linux 驱动工程师需要牢固地掌握 Linux 内核的编译方法以为嵌入式系统构建可运行的Linux 操作系统映像。在编译 LDD6410 的内核时,需要配置内核,我们有四种配置方法:

  • make config(基于文本的最为传统的配置界面,不推荐使用)
  • make menuconfig(基于文本菜单的配置界面)
  • make xconfig(要求 QT 被安装)
  • make gconfig(要求 GTK+被安装)

  在配置Linux 2.6内核所使用的make config、make menuconfig、make xconfig和make gconfig这 4 种方式中,最值得推荐的是  make menuconfig,它不依赖于 QT 或 GTK+,且非常直观,对LDD6410 的 Linux 2.6.28 内核运行 make menuconfig。
  内核配置包含的项目相当多,arch/arm/configs/ldd6410lcd_defconfig 文件包含了 LDD6410 的默认配置,因此,只需要运行 make ldd6410lcd_defconfig 就可以为 LDD6410 开发板配置内核。

2、编译内核或内核模块

  • make zImage 编译内核
  • make modules 编译模块
      执行完上述命令后,在源代码的根目录下会得到未压缩的内核映像 vmlinux 和内核符号表文件 System.map,在 arch/arm/boot/目录会得到压缩的内核映像 zImage,在内核各对应目录得到选中的内核模块。

二、怎么去编写内核模块

1、Linux 2.6 内核的配置系统

Linux 2.6 内核的配置系统由以下 3 个部分组成:

  • Makefile:分布在 Linux 内核源代码中的 Makefile,定义 Linux 内核的编译规则。
  • 配置文件(Kconfig):给用户提供配置选择的功能。
  • 配置工具:包括配置命令解释器(对配置脚本中使用的配置命令进行解释)和配置用户界面(提供基于字符界面和图形界面)。这些配置工具都是使用脚本语言,如 Tcl/TK、Perl 等编写。

2、编译模块时要做什么

在 Linux 内核中增加程序需要完成以下 3 项工作:

  • 将编写的源代码拷入 Linux 内核源代码的相应目录。
  • 在目录的 Kconfig 文件中增加关于新源代码对应项目的编译配置选项。
  • 在目录的 Makefile 文件中增加对新源代码的编译条目。

3、一个 Linux 内核模块主要组成部分

一个 Linux 内核模块主要由如下几个部分组成:

  • 模块加载函数(一般需要)
    当通过 insmod 或 modprobe 命令加载内核模块时,模块的加载函数会自动被内核执行,完成本模块的相关初始化工作。

  • 模块卸载函数(一般需要)
    当通过 rmmod 命令卸载某模块时,模块的卸载函数会自动被内核执行,完成与模块卸载函数相反的功能。

  • 模块许可证声明(必须)
    许可证(LICENSE)声明描述内核模块的许可权限,如果不声明 LICENSE,模块被加载时,将收到内核被污染 (kernel tainted)的警告。在Linux 2.6内核中,可接受的LICENSE包括“GPL” 、 “GPL v2” 、 “GPL and additional rights” 、“Dual BSD/GPL” 、 “Dual MPL/GPL”和“Proprietary” 。大多数情况下,内核模块应遵循 GPL 兼容许可权。Linux 2.6 内核模块最常见的是以MODULE_LICENSE( “Dual BSD/GPL” )语句声明模块采用 BSD/GPL 双 LICENSE。

  • 模块参数(可选)
    模块参数是模块被加载的时候可以被传递给它的值,它本身对应模块内部的全局变量。

  • 模块导出符号(可选)
    内核模块可以导出符号(symbol,对应于函数或变量),这样其他模块可以使用本模块中的变量或函数。

  • 模块作者等信息声明(可选)

3.1 模块加载函数

  Linux 内核模块加载函数一般以_ _init 标识声明,典型的模块加载函数的形式如代码清单

static int __init initialization_function(void)
{
...
}
module_init(initialization_function);

  模块加载函数必须以“module_init(函数名)”的形式被指定。它返回整型值,若初始化成功,应返回 0。而在初始化失败时,应该返回错误编码。在 Linux 内核里,错误编码是一个负值,在<linux/errno.h>中定义,包含-ENODEV、-ENOMEM 之类的符号值。总是返回相应的错误编码是种非常好的习惯,因为只有这样,用户程序才可以利用 perror 等方法把它们转换成有意义的错误信息字符串。
  在 Linux 2.6 内核中,可以使用 request_module(const char *fmt, …)函数加载内核模块,驱动开发人员可以通过调用
request_module(module_name);

request_module(“char-major-%d-%d”, MAJOR(dev), MINOR(dev));
这种灵活的方式加载其他内核模块。
  在 Linux 中,所有标识为_ init 的函数在连接的时候都放在.init.text 这个区段内,此外,所有的 init 函数在区段.initcall.init 中还保存了一份函数指针,在初始化时内核会通过这些函数指针调用这些 _init 函数,并在初始化完成后,释放 init 区段(包括.init.text、.initcall.init 等)。

3.2 模块卸载函数

  Linux 内核模块加载函数一般以_ _exit 标识声明,典型的模块卸载函数的形式如代码清单

static void __exit cleanup_function(void)
{
...
}
module_exit(cleanup_function);

 模块卸载函数在模块卸载的时候执行,不返回任何值,必须以“module_exit(函数名)”的形式来指定。通常来说,模块卸载函数要完成与模块加载函数相反的功能,如下所示。

  • 若模块加载函数注册了 XXX,则模块卸载函数应该注销 XXX。
  • 若模块加载函数动态申请了内存,则模块卸载函数应释放该内存。
  • 若模块加载函数申请了硬件资源(中断、DMA 通道、I/O 端口和 I/O 内存等)的占用,则模块卸载函数应释放这些硬件资源。
  • 若模块加载函数开启了硬件,则卸载函数中一般要关闭之。和_ init一样, exit也可以使对应函数在运行完成后自动回收内存。实际上, init和 _exit都是宏,其定义分别为:
#define _ _init _ _attribute_ _ ((_ _section_ _ (".init.text")))

#ifdef MODULE
#define _ _exit _ _attribute_ _ ((_ _section_ _(".exit.text")))
#else
#define _ _exit _ _attribute_used_ _attribute_ _ ((_ _section_ _(".exit.text")))
#endif

数据也可以被定义为_ initdata 和 _exitdata,这两个宏分别为:

#define _ _initdata _ _attribute_ _ ((_ _section_ _ (".init.data")))

#define _ _exitdata _ _attribute_ _ ((_ _section_ _(".exit.data")))

3.3 模块参数

  我们可以用“module_param(参数名,参数类型,参数读/写权限)”为模块定义一个参数,例如下列代码定义了 1 个整型参数和 1 个字符指针参数:

static char* pr = "hello world";
static int num = 5;
module_param(pr, charp, S_IRUGO);
module_param(num, int, S_IRUGO);

  在装载内核模块时,用户可以向模块传递参数,形式为“insmode(或 modprobe)模块名 参数名=参数值” ,如果不传递,参数将使用模块内定义的缺省值。
  参数类型可以是byte、short、ushort、int、uint、long、ulong、charp(字符指针)、bool或invbool(布尔的反),在模块被编译时会将 module_param 中声明的类型与变量定义的类型进行比较,判断是否一致。
  模块被加载后,在/sys/module/目录下将出现以此模块名命名的目录。当“参数读/写权限”为 0时,表示此参数不存在 sysfs 文件系统下对应的文件节点,如果此模块存在“参数读/写权限”不为 0的命令行参数,在此模块的目录下还将出现parameters目录,包含一系列以参数名命名的文件节点,这些文件的权限值就是传入module_param()的“参数读/写权限” ,而文件的内容为参数的值。
  除此之外,模块也可以拥有参数数组,形式为“module_param_array(数组名,数组类型,数组长,参数读/写权限)” 。从2.6.0~2.6.10版本,需将数组长变量名赋给“数组长” ,从2.6.10 版本开始,需将数组长变量的指针赋给“数组长” ,当不需要保存实际输入的数组元素个数时,可以设置“数组长”为NULL。运行 insmod 或 modprobe 命令时,应使用逗号分隔输入的数组元素。

3.4 导出符号

  Linux 2.6的“/proc/kallsyms”文件对应着内核符号表,它记录了符号以及符号所在的内存地址。模块可以使用如下宏导出符号到内核符号表:

EXPORT_SYMBOL(符号名);
EXPORT_SYMBOL_GPL(符号名);

导出的符号将可以被其他模块使用,使用前声明一下即可。

3.5 模块声明与描述

  在 Linux 内核模块中,我们可以用 MODULE_AUTHOR、MODULE_DESCRIPTION、MODULE_VERSION、MODULE_DEVICE_TABLE、MODULE_ALIAS 分别声明模块的作者、描述、版本、设备表和别名,例如:

MODULE_AUTHOR(author);
MODULE_DESCRIPTION(description);
MODULE_VERSION(version_string);
MODULE_DEVICE_TABLE(table_info);
MODULE_ALIAS(alternate_name);

3.6 模块的使用计数

  try_module_get ()与 module_put()的引入与使用与Linux 2.6内核下的设备模型密切相关。Linux2.6 内核为不同类型的设备定义了 struct module *owner 域,用来指向管理此设备的模块。当开始使用某个设备时,内核使用 try_module_get(dev->owner)去增加管理此设备的 owner 模块的使用计数;当不再使用此设备时,内核使用 module_put(dev->owner)减少对管理此设备的 owner 模块的使用计数。这样,当设备在使用时,管理此设备的模块将不能被卸载。只有当设备不再被使用时,模块才允许被卸载。
  在 Linux 2.6 内核下,对于设备驱动工程师而言,很少需要亲自调用 try_module_get()与module_put(),因为此时开发人员所写的驱动通常为支持某具体设备的owner模块,对此设备owner模块的计数管理由内核里更底层的代码如总线驱动或是此类设备共用的核心模块来实现,从而简化了设备驱动开发。

 
 
 
 
 
 
 
关注公众号"小败日记",搬砖过程遇到的问题,大家一起探讨,资源共享

小败日记公众号

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
友善之臂Smart210是一款基于安卓系统的智能设备。它采用了先进的ARM架构,具备强大的处理能力和稳定的性能。友善之臂Smart210的安卓系统通过不断的优化和更新,提供了丰富的功能和良好的用户体验。 首先,友善之臂Smart210搭载的安卓系统具有开放性和自由度高的特点。用户可以根据自己的需求和喜好自由地选择、安装、卸载各种应用程序,丰富了设备的功能。同时,友善之臂Smart210的安卓系统支持大量的第三方应用商店和开发者社区,用户可以从中获取各种优质的应用程序,满足不同的个性化需求。 其次,友善之臂Smart210的安卓系统提供了良好的用户界面和操作体验。系统界面简洁明了,功能布局合理,用户可以方便地找到所需的功能和设置。另外,友善之臂Smart210的安卓系统支持多点触控和手势操作,用户可以通过手指轻松地进行滑动、缩放、旋转等操作,增加了设备的易用性。 最后,友善之臂Smart210的安卓系统还具备很好的兼容性和扩展性。它支持各种主流的应用程序和文件格式,用户可以自由地进行文件的传输和共享。同时,友善之臂Smart210的安卓系统也支持各种接口和外设的连接,用户可以通过USB、HDMI等接口连接打印机、电视、摄像头等设备,扩展设备的功能。 总之,友善之臂Smart210的安卓系统以其强大的处理能力、丰富的功能、良好的用户体验而受到用户的喜爱。它的开放性、简洁明了的界面、多点触控和手势操作、兼容性和扩展性等特点使得用户能够获得更加便捷、多样化的使用体验。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值