IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> linux驱动开发篇(一)—— Linux 内核模块介绍 -> 正文阅读

[系统运维]linux驱动开发篇(一)—— Linux 内核模块介绍

linux系列目录:
linux基础篇(一)——GCC和Makefile编译过程
linux基础篇(二)——静态和动态链接
ARM裸机篇(一)——i.MX6ULL介绍
ARM裸机篇(二)——i.MX6ULL启动过程
ARM裸机篇(三)——i.MX6ULL第一个裸机程序
ARM裸机篇(四)——重定位和地址无关码
ARM裸机篇(五)——异常和中断
linux系统移植篇(一)—— linux系统组成
linux系统移植篇(二)—— Uboot使用介绍
linux系统移植篇(三)—— Linux 内核使用介绍
linux系统移植篇(四)—— 根文件系统使用介绍
linux驱动开发篇(一)—— Linux 内核模块介绍


一、Linux 内核模块

本篇文章是介绍Linux 驱动开发承上启下的一个知识点。在 Linux 系统中,设备驱动会以内核模块的形式出现,学习 Linux 内核模块编程是驱动开发的先决条件。

1、内核

内核按照体系结构分为两类: 微内核(Micro Kernel) 和 宏内核(Monolithic Kernel)。在微内核架构中,内核只提供操作系统核心功能,如实现进程管理、存储器管理、进程间通信、 I/O 设备管理等,而其它的应用层 IPC、文件系统功能、设备驱动模块则不被包含到内核功能中,属于微内核之外的模块,所以针对这些模块的修改不会影响到微内核的核心功能。微内核具有动态扩展性强的优点。 Windows 操作系统、华为的鸿蒙操作系统就属于这类微内核架构。

而宏内核架构是将上述包括微内核以及微内核之外的应用层 IPC、文件系统功能、设备驱动模块都编译成一个整体。其优点是执行效率非常高,但缺点也是十分明显的,一旦我们想要修改、增加内核某个功能时(如增加设备驱动程序)都需要重新编译一遍内核。 Linux 操作系统正是采用了宏内核结构。为了解决这一缺点, linux 中引入了内核模块这一机制。

![在这里插入图片描述](https://img-blog.csdnimg.cn/c3dad8d874274cfea5236e574c67d5aa.png

2、内核模块引入原因

Linux 是一个跨平台的操作系统,支持众多的设备,在 Linux 内核源码中有超过 50% 的代码都与设备驱动相关。 Linux 为宏内核架构,如果开启所有的功能,内核就会变得十分臃肿。内核模块就是实现了某个功能的一段内核代码,在内核运行过程,可以加载这部分代码到内核中,从而动态地增加了内核的功能。基于这种特性,我们进行设备驱动开发时,以内核模块的形式编写设备驱动,只需要编译相关的驱动代码即可,无需对整个内核进行编译。

3、内核模块的定义和特点

核模块的具体的定义:内核模块全称 Loadable Kernel Module(LKM), 是一种在内核运行时加载一组目标代码来实现某个特定功能的机制。
模块是具有独立功能的程序,它可以被单独编译,但不能独立运行,在运行时它被链接到内核作为内核的一部分在内核空间运行,这与运行在用户空间的进程是不一样的。模块由一组函数和数据结构组成,用来实现一种文件系统、一个驱动程序和其他内核上层功能。因此内核模块具备如下特点:

  • 模块本身不被编译入内核映像,这控制了内核的大小。
  • 模块一旦被加载,它就和内核中的其它部分完全一样。

二、Linux 内核模块的工作机制

我们编写的内核模块,经过编译,最终形成.ko 为后缀的 ELF 文件。ko 文件在数据组织形式上是 ELF(Excutable And Linking Format) 格式,是一种普通的可重定位目标文件。这类文件包含了代码和数据,可以被用来链接成可执行文件或共享目标文件,静态链接库也可以归为这一类。在学习linux基础篇(二)——静态和动态链接时,有讲解过ELF文件的详细信息。内核就是利用ELF文件留在内核模块里的信息,对内核模块进行利用的。

1、内核模块组成部分

Linux 内核模块的代码框架通常由下面几个部分组成:

  • 模块加载函数 (必须): 当通过 insmod 或 modprobe 命令加载内核模块时,模块的加载函数就会自动被内核执行,完成本模块相关的初始化工作。
  • 模块卸载函数 (必须): 当执行 rmmod 命令卸载模块时,模块卸载函数就会自动被内核自动执行,完成相关清理工作。
  • 模块许可证声明 (必须): 许可证声明描述内核模块的许可权限,如果模块不声明,模块被加载时,将会有内核被污染的警告。
  • 模块参数: 模块参数是模块被加载时,可以传值给模块中的参数。
  • 模块导出符号: 模块可以导出准备好的变量或函数作为符号,以便其他内核模块调用。
  • 模块的其他相关信息: 可以声明模块作者等信息。

2、内核模块的加载过程

linux内核模块加载函数一般以__init标识声明,模块加载函数以module_init(函数名)的形式被指定。在编写时,若初始化成功应该返回0,若初始化失败应该返回错误编码,方便用户程序利用perror等方法将它们转换为有意义的错误信息字符串。

static int __init hello_init(void)
{
    printk(KERN_INFO "Hello  Module Init\n");
    return 0;
}
module_init(hello_init);

__init 和module_init宏定义介绍:

#define __init __attribute__((__section__(".init.text")))

带有 __init 的修饰符,表示将该函数放到可执行文件的 __init 节区中,该节区的内容只能用于模块的初始化阶段,初始化阶段执行完毕之后,这部分的内容就会被释放掉。

#define module_init(x) __initcall(x);

宏定义 module_init 用于通知内核初始化模块的时候,要使用哪个函数进行初始化。它会将函数地址加入到相应的节区 section 中,这样的话,开机的时候就可以自动加载模块了。

3、内核模块的卸载过程

当模块从内核被卸载时,系统会调用模块的模块卸载函数,该函数使用__exit来标识。

static void __exit hello_exit(void)
{
    printk("KERN_INFO  Hello  Module Exit\n");
}
module_exit(hello_exit);

4、模块声明与描述

  • MODULE_AUTHOR:模块作者
  • MODULE_DESCRIPTION:模块描述
  • MODULE_VERSION:模块版本
  • MODULE_DEVICE_TABLE:设备表
  • MODULE_ALIAS:别名

完整代码的例子:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>

static int __init hello_init(void)
{
    printk(KERN_INFO "Hello  Module Init\n");
    return 0;
}
module_init(hello_init);
static void __exit hello_exit(void)
{
    printk("KERN_INFO  Hello  Module Exit\n");
}
module_exit(hello_exit);

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("kk");
MODULE_DESCRIPTION("hello world module");
MODULE_ALIAS("test_module");

5、模块参数

Linux 内核提供一个宏来实现模块的参数传递

#define module_param(name, type, perm) \
 module_param_named(name, name, type, perm)

#define module_param_array(name, type, nump, perm) \
module_param_array_named(name, name, type, nump, perm)
  • name: 我们定义的变量名;
  • type: 参数的类型;
  • perm: 表示的是该文件的权限,具体参数值见下表。

6、导出符号

模块可以使用如下宏导出符号到内核符号表中

#define EXPORT_SYMBOL(符号名) 
#define EXPORT_SYMBOL_GPL(符号名) 

导出符号可以被其他模块使用,只需要使用前声明一下即可。EXPORT_SYMBOL_GPL()只适用于包含GPL许可权的模块,一般认为,保守做法是linux内核不能使用非GPL许可权。
完整例子:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>

static int itype=0;
module_param(itype,int,0);

static bool btype=0;
module_param(btype,bool,0700);

static char ctype=0;
module_param(ctype,byte,0);

static char  *stype=0;
module_param(stype,charp,0644);

static int __init param_init(void)
{
   printk(KERN_ALERT "param init!\n");
   printk(KERN_ALERT "itype=%d\n",itype);
   printk(KERN_ALERT "btype=%d\n",btype);
   printk(KERN_ALERT "ctype=%d\n",ctype);
   printk(KERN_ALERT "stype=%s\n",stype);
   return 0;
}

static void __exit param_exit(void)
{
   printk(KERN_ALERT "module exit!\n");
}

EXPORT_SYMBOL(itype);

int my_add(int a, int b)
{
   return a+b;
}

EXPORT_SYMBOL(my_add);

int my_sub(int a, int b)
{
   return a-b;
}

EXPORT_SYMBOL(my_sub);

module_init(param_init);
module_exit(param_exit);

MODULE_LICENSE("GPL2");
MODULE_AUTHOR("embedfire ");
MODULE_DESCRIPTION("module_param");
MODULE_ALIAS("module_param");

三、Linux内核模块的编译

四、Linux内核模块的使用

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-05-21 19:18:51  更:2022-05-21 19:21:50 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 15:41:47-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码