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】内核驱动篇一--编译方法



注:关于驱动的编译环境,在系统移植篇已详细讲解与安装

一、向内核添加新功能

1.1 静态加载法

新功能源码内核其它代码一起编译进uImage文件内,下面举例说明。

  1. 新功能源码与Linux内核源码在同一目录结构下,在
    linux-3.14/drivers/char/目录下编写myhello.c,文件内容如下:
#include <linux/module.h>
#include <linux/kernel.h>
   
int __init myhello_init(void)
{
	printk("#####################################################\n");
	printk("#####################################################\n");
	printk("#####################################################\n");
	printk("#####################################################\n");
    printk("myhello is running\n");
	printk("#####################################################\n");
	printk("#####################################################\n");
	printk("#####################################################\n");
	printk("#####################################################\n");
	return 0;
}

void __exit myhello_exit(void)
{
	printk("myhello will exit\n");
}
MODULE_LICENSE("GPL");
module_init(myhello_init);
module_exit(myhello_exit);
  1. 给新功能代码配置Kconfig
#进入myhello.c的同级目录
cd  ~/linux-3.14/drivers/char

#打开Kconfig文件
vim Kconfig

#39行处添加如下内容:
config MY_HELLO
	tristate "This is a hello test"
	help
		This is a test for kernel new function
  1. 改写myhello.c同级目录下的Makefile
#进入myhello.c的同级目录
cd  ~/linux-3.14/drivers/char

#改写Makefile
vim Makefile

#拷贝18行,粘贴在下一行,修改成:
obj-$(CONFIG_MY_HELLO)     += myhello.o
  1. make menuconfig 界面里将新功能对应的那项选择成<*>
cd  ~/linux-3.14
make menuconfig

#make menuconfig如果出错,一般是两个原因:
#1. libncurses5-dev没安装
#2. 命令行界面太小(太矮或太窄或字体太大了)   

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
6. make uImage
7. cp arch/arm/boot/uImage /tftpboot
8. 启动开发板观察串口终端中的打印信息
在这里插入图片描述


1.2 动态加载法

即新功能源码与内核其它源码不一起编译,而是独立编译成内核的插件(被称为内核模块)文件.ko

1.2.1 文件制作方法

方法1:新功能源码与Linux内核源码在同一目录结构下时

  1. 给新功能代码配置Kconfig(同静态配置)
  2. 给新功能代码改写Makefile(同静态配置)
  3. make menuconfig 界面里将新功能对应的那项选择成<M>
  4. make uImage
  5. cp arch/arm/boot/uImage /tftpboot
  6. make modules
    make modules会在新功能源码的同级目录下生成相应的同名.ko文件(生成的ko文件只适用于开发板linux)
    注意:此命令执行前,开发板的内核源码已被编译

方法2:新功能源码与Linux内核源码不在同一目录结构下时

  1. cd ~/linux-3.14
  2. mkdir mydrivercode
  3. cd mydrivercode
  4. cp …/linux-3.14/drivers/char/myhello.c .
  5. 添加Makefile文件,内容放在步骤末尾
  6. vim Makefile
  7. make (生成的ko文件适用于主机ubuntu linux)
  8. make ARCH=arm (生成的ko文件适用于开发板linux,注意此命令执行前,开发板的内核源码已被编译)
ifeq ($(KERNELRELEASE),)

ifeq ($(ARCH),arm)
# 自己的linux内核源码目录
KERNELDIR ?= /home/linux/linux-3.14
ROOTFS ?= /opt/4412/rootfs
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)

modules:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

modules_install:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules INSTALL_MOD_PATH=$(ROOTFS) modules_install

clean:
	rm -rf  *.o  *.ko  .*.cmd  *.mod.*  modules.order  Module.symvers   .tmp_versions

else
obj-m += myhello.o


endif
#file命令可以查看指定ko文件适用于哪种平台,用法:
file  ko文件
#结果带x86字样的适用于主机ubuntu linux,带arm字样的适用于开发板linux

1.2.2 文件使用

使用1:主机ubuntu下使用ko文件

sudo insmod ./文件名.ko  #将内核模块插入正在执行的内核中运行 ----- 相当于安装插件

lsmod #查看已被插入的内核模块有哪些,显示的是插入内核后的模块名

sudo rmmod 文件名 #,将已被插入的内核模块从内核中移除 ----- 相当于卸载插件

sudo dmesg -C  #清除内核已打印的信息

dmesg #查看内核的打印信息

使用2:开发板Linux下使用ko文件

#1.先将生成的ko文件拷贝到/opt/4412/rootfs目录下:
#2.在串口终端界面开发板Linux命令行下执行

insmod ./文件名.ko  #将内核模块插入正在执行的内核中运行 ----- 相当于安装插件

lsmod #查看已被插入的内核模块有哪些

rmmod 文件名 #将已被插入的内核模块从内核中移除 ----- 相当于卸载插件

内核随时打印信息,我们可以在串口终端界面随时看到打印信息,不需要dmesg命令查看打印信息

二、内核模块基础代码解析

Linux内核的插件机制——内核模块

类似于浏览器、eclipse这些软件的插件开发,Linux提供了一种可以向正在运行的内核中插入新的代码段、在代码段不需要继续运行时也可以从内核中移除的机制,这个可以被插入、移除的代码段被称为内核模块。

主要解决:

  1. 单内核扩展性差的缺点
  2. 减小内核镜像文件体积,一定程度上节省内存资源
  3. 提高开发效率
  4. 不能彻底解决稳定性低的缺点:内核模块代码出错可能会导致整个系统崩溃

内核模块的本质:一段隶属于内核的“动态”代码,与其它内核代码是同一个运行实体,共用同一套运行资源,只是存在形式上是独立的。

#include <linux/module.h> //包含内核编程最常用的函数声明,如printk
#include <linux/kernel.h> //包含模块编程相关的宏定义,如:MODULE_LICENSE

/*该函数在模块被插入进内核时调用,主要作用为新功能做好预备工作
  被称为模块的入口函数
  
  __init的作用 : 
1. 一个宏,展开后为:__attribute__ ((__section__ (".init.text")))   实际是gcc的一个特殊链接标记
2. 指示链接器将该函数放置在 .init.text区段
3. 在模块插入时方便内核从ko文件指定位置读取入口函数的指令到特定内存位置
*/
int __init myhello_init(void)
{
    /*内核是裸机程序,不可以调用C库中printf函数来打印程序信息,
    Linux内核源码自身实现了一个用法与printf差不多的函数,命名为printk (k-kernel)
    printk不支持浮点数打印*/
	printk("#####################################################\n");
	printk("#####################################################\n");
	printk("#####################################################\n");
	printk("#####################################################\n");
	printk("myhello is running\n");
	printk("#####################################################\n");
	printk("#####################################################\n");
	printk("#####################################################\n");
	printk("#####################################################\n");
	return 0;
}

/*该函数在模块从内核中被移除时调用,主要作用做些init函数的反操作
  被称为模块的出口函数
  
  __exit的作用:
1.一个宏,展开后为:__attribute__ ((__section__ (".exit.text")))   实际也是gcc的一个特殊链接标记
2.指示链接器将该函数放置在 .exit.text区段
3.在模块插入时方便内核从ko文件指定位置读取出口函数的指令到另一个特定内存位置
*/
void __exit myhello_exit(void)
{
	printk("myhello will exit\n");
}

/*
MODULE_LICENSE(字符串常量);
字符串常量内容为源码的许可证协议 可以是"GPL" "GPL v2"  "GPL and additional rights"  "Dual BSD/GPL"  "Dual MIT/GPL" "Dual MPL/GPL"等, "GPL"最常用

其本质也是一个宏,宏体也是一个特殊链接标记,指示链接器在ko文件指定位置说明本模块源码遵循的许可证
在模块插入到内核时,内核会检查新模块的许可证是不是也遵循GPL协议,
如果发现不遵循GPL,则在插入模块时打印信息:
	myhello:module license 'unspecified' taints kernel
	Disabling lock debugging due to kernel taint
也会导致新模块没法使用一些内核其它模块提供的高级功能
*/
MODULE_LICENSE("GPL");

/*
module_init 宏
1. 用法:module_init(模块入口函数名) 
2. 动态加载模块,对应函数被调用
3. 静态加载模块,内核启动过程中对应函数被调用
4. 对于静态加载的模块其本质是定义一个全局函数指针,并将其赋值为指定函数,链接时将地址放到特殊区段(.initcall段),方便系统初始化统一调用。
5. 对于动态加载的模块,由于内核模块的默认入口函数名是init_module,用该宏可以给对应模块入口函数起别名
*/
module_init(myhello_init);

/*
module_exit宏
1.用法:module_exit(模块出口函数名)
2.动态加载的模块在卸载时,对应函数被调用
3.静态加载的模块可以认为在系统退出时,对应函数被调用,实际上对应函数被忽略
4.对于静态加载的模块其本质是定义一个全局函数指针,并将其赋值为指定函数,链接时将地址放到特殊区段(.exitcall段),方便系统必要时统一调用,实际上该宏在静态加载时没有意义,因为静态编译的驱动无法卸载。
5.对于动态加载的模块,由于内核模块的默认出口函数名是cleanup_module,用该宏可以给对应模块出口函数起别名
*/
module_exit(myhello_exit);

模块三要素:入口函数、出口函数、MODULE__LICENSE


三、内核模块的多源文件编程

ifeq ($(KERNELRELEASE),)

ifeq ($(ARCH),arm)
KERNELDIR ?= 目标板linux内核源码顶层目录的绝对路径
ROOTFS ?= 目标板根文件系统顶层目录的绝对路径
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)

modules:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

modules_install:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) INSTALL_MOD_PATH=$(ROOTFS) modules_install

clean:
	rm -rf  *.o  *.ko  .*.cmd  *.mod.*  modules.order  Module.symvers   .tmp_versions

else
obj-m += hello.o

endif

Makefile中:

obj-m 用来指定模块名,注意模块名加.o而不是.ko

可以用 模块名-objs 变量来指定编译到ko中的所有.o文件名(每个同名的.c文件对应的.o目标文件)

一个目录下的Makefile可以编译多个模块:

添加:obj-m += 下一个模块名.o

四、 内核模块信息宏

MODULE_AUTHOR(字符串常量); //字符串常量内容为模块作者说明

MODULE_DESCRIPTION(字符串常量); //字符串常量内容为模块功能说明

MODULE_ALIAS(字符串常量); //字符串常量内容为模块别名

这些宏用来描述一些当前模块的信息,可选宏

这些宏的本质是定义static字符数组用于存放指定字符串内容,这些字符串内容链接时存放在.modinfo字段,可以用modinfo命令来查看这些模块信息,用法:

modinfo  模块文件名

到这里就结束啦!
在这里插入图片描述

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-05-13 12:00:08  更:2022-05-13 12:01:39 
 
开发: 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:56:08-

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