【Linux驱动】linux内核模块简介

简介:

一. 摘要

这篇文章主要介绍了Linux内核模块的相关概念,以及简单的模块开发过程。主要从模块开发中的常用指令、内核模块程序的结构、模块使用计数以及模块的编译等角度对内核模块进行介绍。在Linux系统开发过程中,以模块的形式开发其重要性不言自明,而在嵌入式设备驱动开发中将驱动程序以模块的形式发布,更是极大地提高了设备使用的灵活性——用户只需要拿到相关驱动模块,再插入到用户的内核中,即可灵活地使用你的设备。

二. 文章提纲

1. 摘要

2. 文章提纲

3. 概述

4. 模块开发常用的指令

5. 内核模块程序结构

6. 模块使用计数

7. 模块的编译

8. 使用模块绕开GPL

9. 总结

三.概述

Linux内核整体结构已经很庞大,包含了很多的组件,而对于我们工程师而言,有两种方法将需要的功能包含进内核当中。

         一:将所有的功能都编译进Linux内核。

         二:将需要的功能编译成模块,在需要的时候动态地添加。

上述两种方式优缺点分析:

         第一种:

                   优点:不会有版本不兼容的问题,不需要进行严格的版本检查

                   缺点:生成的内核会很大;要在现有的内核中添加新的功能,则要编译整个内核

         第二种:

优点:模块本身不编译进内核,从而控制了内核的大小;模块一旦被加载,将和其它的部分完全一样。

缺点:可能会有内核与模块版本不兼容的问题,导致内核崩溃;会造成内存的利用率比较低。

 

四.模块开发常用的指令

在内核模块开发的过程中常用的有以下指令。

1)  insmod: 将模块插入内核中,使用方法:#insmod XXX.ko

2)  rmmod: 将模块从内核中删除,使用方法:#rmmod XXX.ko

3)  lsmod: 列表显示所有的内核模块,可以和grep指令结合使用。使用方法:#lsmod | grep XXX

4)  modprobe: modprobe可载入指定的个别模块,或是载入一组相依赖的模块。modprobe会根据depmod所产生的依赖关系,决定要载入哪些模块。若在载入过程中发生错误,在modprobe会卸载整组的模块。依赖关系是通过读取 /lib/modules/2.6.xx/modules.dep得到的。而该文件是通过depmod 所建立。

5)  modinfo: 查看模块信息。使用方法:#modinfo XXX.ko

6)  tree –a: 查看当前目录的整个树结构。使用方法:#tree -a

 

五.内核模块程序结构

1)  模块加载函数(一般需要)

在用insmod或modprobe命令加载模块时,该函数被执行。完成模块的初始化工作。

Linux内核的模块加载函数一般用__init标识声明,模块加载函数必须以module_init(函数名)的形式被指定。该函数返回整型值,如果执行成功,则返回0,初始化失败时则返回错误编码,Linux内核当中的错误编码是负值,在<linux/errno.h>中定义。

在Linux中,标识__init的函数在连接时放在.init.text这个区段,而且在.initcall.init中保留一份函数指针,初始化的时候内核会根据这些指针调用初始化函数,初始化结束后释放这些init区段(包括前两者)。

代码清单:

复制代码
 1 static int __init XXX_init(void)
 2 
 3 {
 4
 5          return 0;
 6 }
 7 
 8  
 9 
10 moudle_init(XXX_init);
复制代码

 

2)  模块卸载函数(一般需要)

在用rmmod或modprobe命令卸载模块时,该函数被执行。完成与加载相反的工作。

模块的卸载函数和模块加载函数实现相反的功能,主要包括

  1. 若模块加载函数注册了XXX,则模块卸载函数注销XXX
  2. 若模块加载函数动态分配了内存,则模块卸载函数释放这些内存
  3. 若模块加载函数申请了硬件资源,则模块卸载函数释放这些硬件资源
  4. 若模块加载函数开启了硬件资源,则模块卸载函数一定要关闭这些资源

代码清单:

复制代码
1 static void __exit XXX_exit(void)
2 
3 {
4 
5 }
6 
7  
8 
9 moudle_exit(XXX_exit);
复制代码

 

3)  模块许可证声明(必须)

如果不声明,则在模块加载时会收到内核被污染的警告,一般应遵循GPL协议。

代码清单:

1 MODULE_LICENSE("GPL");

 

4)  模块参数(可选)

模块在被加载时传递给模块的值,本身应该是模块内部的全局变量。

示例程序book.c

复制代码
 1 #include <linux/init.h>
 2 
 3 #include <linux/module.h>
 4 
 5  
 6 
 7 static char *bookName = "Good Book.";
 8 
 9 static int bookNumber = 100;
10 
11  
12 
13 static int __init book_init(void)
14 
15 {
16 
17          printk(KERN_INFO "Book name is %s\n", bookName);
18 
19          printk(KERN_INFO "Book number is %d\n", bookNumber);
20 
21          return 0;
22 
23 }
24 
25  
26 
27 static void __exit book_exit(void)
28 
29 {
30 
31          printk(KERN_INFO "Book module exit.\n");
32 
33 }
34 
35  
36 
37 module_init(book_init);
38 
39 module_exit(book_exit);
40 
41 module_param(bookName, charp, S_IRUGO);
42 
43 module_param(bookNumber, int, S_IRUGO);
44 
45  
46 
47 MODULE_LICENSE("GPL");
复制代码

 

在向内核插入模块的时候可以用以下方式,并且可以在内核日志中看到模块加载以后变量已经有了值。

 

 

5)  模块导出符号(可选)

使用模块导出符号,方便其它模块依赖于该模块,并使用模块中的变量和函数等。

在Linux2.6的内核中,/proc/kallsyms文件对应着符号表,它记录了符号和符号对应的内存地址。对于模块而言,使用下面的宏可以导出符号。

1 EXPORT_SYMBOL(符号名);

1 EXPORT_GPL_SYMBOL(符号名);

 

6)  模块信息(可选)

模块信息则是指模块的作者信息等。

 

六.模块使用计数

Linux内核提供了MOD_INC_USE_COUNT和MOD_DEC_USE_COUNT宏来管理模块使用计数。但是对于内核模块而言,一般不会自己管理使用计数。

 

七.模块的编译

将下面的Makefile文件放在book.c同级的目录下,然后使用#make命令或者#make all命令编译即可生成book.ko模块文件。

对应的Makefile:

复制代码
 1 ifneq ($(KERNELRELEASE),)
 2 
 3 mymodule_objs := book.o
 4 
 5 obj-m := book.o
 6 
 7 else
 8 
 9 PWD := $(shell pwd)
10 
11 KVER ?= $(shell uname -r)
12 
13 KDIR := /usr/src/linux-headers-2.6.38-8-generic
14 
15  
16 
17 all:
18 
19          $(MAKE) -C $(KDIR) M=$(PWD)
20 
21 clean:
22 
23          rm -rf *.mod.c *.mod.o *.ko *.o *.tmp_versions *.order *symvers
24 
25 endif
复制代码

 

八.使用模块绕开

如果功能不编译成模块,则无法绕开GPL,编译成模块后公司发布产品则只需要发布模块,而不需要发布源码。为了Linux系统能够支持模块,需要做以下的工作:

  1. 内核编译时选择“可以加载模块”,嵌入式产品一般都不需要卸载模块,则可以不选择“可卸载模块”
  2. 将我们的ko文件放在文件系统中
  3. Linux系统实现了insmod、rmmod等工具
  4. 使用时可以用insmod手动加载模块,也可以修改/etc/init.d/rcS文件,从而在系统启动的时候就加载模块。

 

九.总结

本文主要介绍内核模块的概念和基本编程方法,内核模块主要由加载、卸载函数功能函数等一系列声明组成。它可以被传入参数,也可以导出符号,供其它的模块使用。

十.补充                                                                                                                                            

   学习内核,避免不了要和模块打交道,而模块的编译一直都是用以前写好的makefile文件,没有研究过模块是如何编译的,后来在内核里面找到了一份文档,才发现模块的编译的方式很“丰富”

linux/Document/ kbuild/module.txt文件中有 详细的讲解。
---------------------------------------------------------------------------------------
一,一般的模块的编译。
我喜欢用的一种格式如下:


  1. obj-:= find_mod.o
  2. kernel_path=/home/linux/kernel/linux-3.5

  3. all:
  4.      make -C $(kernel_path) M=$(PWD) modules
  5. clean:
  6.      make -C $(kernel_path) M=$(PWD) clean
只需要指定好三个参数:
1,内核源代码的绝对路径,kernel_path。
2,模块所在的位置,一般都是当前目录,PWD变量指定。
3,产生的模块的目标文件。这文件名要和模块.c文件名一致。

------------------------------------------------------------------------------------------
二,多个C文件的模块编译。
在用户态编程时,很多时候都是多个文件编译,这么多文件组成一个项目。
这么多文件可以由makefile组织到一起,同样模块中也可以编译多个文件。
而且实现起来也特别简单。

  1. obj-:= hello.o
  2. hello-:= hello1.o hello2.
  3. kernel_path=/home/linux/kernel/linux-3.5

  4. all:
  5.      make -C $(kernel_path) M=$(PWD) modules
  6. clean:
  7.      make -C $(kernel_path) M=$(PWD) clean
假如有两个C文件,hello1.c hello2.c将这两个文件编译的方式
见上面的Makefile文件。
-----------------------------------------------------------------------------------------
三,多个模块同时编译。

C文件:hello.c find_mod.c
目标模块:hello.ko  find_mod.ko
  1. obj-:= find_mod.o
  2. obj-+= hello.o
  3. kernel_path = /home/linux/kernel/linux-3.5
  4. all:
  5.       make -C $(kernel_path) M=$(PWD) modules
  6. clean:
  7.      make -C $(kernel_path) M=$(PWD) clean
----------------------------------------------------------------------------------------
四,其他。
还有很多其他的编译方式,比如说要在包含指定目录的.h文件,该如何
包含,加入这个模块中有多个目录,又该怎么编写Makefile,等等,这 些在module.txt中就有介绍。

---------------------------------------------------------------------------------------

参考网址:http://www.cnblogs.com/tianyou/p/3448573.html

相关文章
|
6天前
|
缓存 安全 Linux
Linux系统查看操作系统版本信息、CPU信息、模块信息
在Linux系统中,常用命令可帮助用户查看操作系统版本、CPU信息和模块信息
53 23
|
6天前
|
安全 Linux 测试技术
Intel Linux 内核测试套件-LKVS介绍 | 龙蜥大讲堂104期
《Intel Linux内核测试套件-LKVS介绍》(龙蜥大讲堂104期)主要介绍了LKVS的定义、使用方法、测试范围、典型案例及其优势。LKVS是轻量级、低耦合且高代码覆盖率的测试工具,涵盖20多个硬件和内核属性,已开源并集成到多个社区CICD系统中。课程详细讲解了如何使用LKVS进行CPU、电源管理和安全特性(如TDX、CET)的测试,并展示了其在实际应用中的价值。
|
20天前
|
Ubuntu Linux 开发者
Ubuntu20.04搭建嵌入式linux网络加载内核、设备树和根文件系统
使用上述U-Boot命令配置并启动嵌入式设备。如果配置正确,设备将通过TFTP加载内核和设备树,并通过NFS挂载根文件系统。
68 15
|
1月前
|
算法 Linux
深入探索Linux内核的内存管理机制
本文旨在为读者提供对Linux操作系统内核中内存管理机制的深入理解。通过探讨Linux内核如何高效地分配、回收和优化内存资源,我们揭示了这一复杂系统背后的原理及其对系统性能的影响。不同于常规的摘要,本文将直接进入主题,不包含背景信息或研究目的等标准部分,而是专注于技术细节和实际操作。
|
1月前
|
存储 缓存 网络协议
Linux操作系统的内核优化与性能调优####
本文深入探讨了Linux操作系统内核的优化策略与性能调优方法,旨在为系统管理员和高级用户提供一套实用的指南。通过分析内核参数调整、文件系统选择、内存管理及网络配置等关键方面,本文揭示了如何有效提升Linux系统的稳定性和运行效率。不同于常规摘要仅概述内容的做法,本摘要直接指出文章的核心价值——提供具体可行的优化措施,助力读者实现系统性能的飞跃。 ####
|
1月前
|
监控 算法 Linux
Linux内核锁机制深度剖析与实践优化####
本文作为一篇技术性文章,深入探讨了Linux操作系统内核中锁机制的工作原理、类型及其在并发控制中的应用,旨在为开发者提供关于如何有效利用这些工具来提升系统性能和稳定性的见解。不同于常规摘要的概述性质,本文将直接通过具体案例分析,展示在不同场景下选择合适的锁策略对于解决竞争条件、死锁问题的重要性,以及如何根据实际需求调整锁的粒度以达到最佳效果,为读者呈现一份实用性强的实践指南。 ####
|
1月前
|
缓存 监控 网络协议
Linux操作系统的内核优化与实践####
本文旨在探讨Linux操作系统内核的优化策略与实际应用案例,深入分析内核参数调优、编译选项配置及实时性能监控的方法。通过具体实例讲解如何根据不同应用场景调整内核设置,以提升系统性能和稳定性,为系统管理员和技术爱好者提供实用的优化指南。 ####
|
1月前
|
负载均衡 算法 Linux
深入探索Linux内核调度机制:公平与效率的平衡####
本文旨在剖析Linux操作系统内核中的进程调度机制,特别是其如何通过CFS(完全公平调度器)算法实现多任务环境下资源分配的公平性与系统响应速度之间的微妙平衡。不同于传统摘要的概览性质,本文摘要将直接聚焦于CFS的核心原理、设计目标及面临的挑战,为读者揭开Linux高效调度的秘密。 ####
45 3
|
2月前
|
负载均衡 算法 Linux
深入探索Linux内核调度器:公平与效率的平衡####
本文通过剖析Linux内核调度器的工作机制,揭示了其在多任务处理环境中如何实现时间片轮转、优先级调整及完全公平调度算法(CFS),以达到既公平又高效地分配CPU资源的目标。通过对比FIFO和RR等传统调度策略,本文展示了Linux调度器如何在复杂的计算场景下优化性能,为系统设计师和开发者提供了宝贵的设计思路。 ####
50 6
|
1月前
|
消息中间件 安全 Linux
深入探索Linux操作系统的内核机制
本文旨在为读者提供一个关于Linux操作系统内核机制的全面解析。通过探讨Linux内核的设计哲学、核心组件、以及其如何高效地管理硬件资源和系统操作,本文揭示了Linux之所以成为众多开发者和组织首选操作系统的原因。不同于常规摘要,此处我们不涉及具体代码或技术细节,而是从宏观的角度审视Linux内核的架构和功能,为对Linux感兴趣的读者提供一个高层次的理解框架。