Linux内核编程(一)内核驱动及模块编程基础


  

前述:内核框架图

在这里插入图片描述

一、Linux 内核模块概述

   嵌入式设备驱动开发中将驱动程序以模块的形式发布,更是极大地提高了设备使用的灵活性,用户只需要拿到相关驱动模块,再安装到用户的内核中,即可灵活地使用你的设备。

二、Linux 模块的优点

  1. 用户可以随时扩展 Linux 系统的功能。
  2. 当设备驱动有更新时,只需要卸载旧模块,重新安装即可。
  3. 系统需要增加新的模块功能,不必重新编译内核,只要安装相应模块文件即可。
  4. 使用模块,还可以减小 Linux 内核的体积,节省 flash。
  5. 可以在不影响核心系统的情况下开发和测试新功能,降低系统崩溃或不稳定的风险。

三、知识点

1. GPL开源协议

   我们很熟悉的Linux就是采用了GPL。GPL协议和BSD, Apache Licence等鼓励代码重用的许可很不一样。GPL的出发点是代码的开源/免费使用和引用/修改/衍生代码的开源/免费使用,但不允许修改后和衍生的代码做为闭源的商业软件发布和销售。这也就是为什么我们能用免费的各种linux,包括商业公司的linux和linux上各种各样的由个人,组织,以及商业软件公司开发的免费软件了。
   GPL具有“传染性”,只要在一个软件中使用(“使用”指类库引用,修改后的代码或者衍生代码)GPL协议的产品,则该软件产品必须也采用 GPL协议,既必须也是开源和免费。
   由于GPL严格要求使用了GPL类库的软件产品必须使用GPL协议,对于使用GPL协议的开源代码,商业软件或者对代码有保密要求的部门就不适合集成/采用作为类库和二次开发的基础。其它细节如再发布的时候需要伴随GPL协议等和BSD/Apache等类似。
   GPL的出发点是代码的开源/免费使用和引用/修改/衍生代码的开源/免费使用,但不允许修改后和衍生的代码做为闭源的商业软件发布和销售。

2. 查看已安装的模块文件:lsmod

四、驱动常用API

1. 入口函数

static int __init 函数名(void)
{
   
   return 0;
}

2. 出口函数

static void __exit 函数名(void)
{
   
}

3. 声明驱动模型出/入口函数

module_init(入口函数名);
module_exit(出口函数名);

4. printk内核输出函数

   printf函数用于应用层,printk函数用于内核层。
   printk函数是Linux内核中用于输出消息的函数。它类似于C语言中的printf函数,但是用于内核空间的打印,因此它有一些特定的用法和限制。printk不能使用浮点功能,不能有 %f,%lf,其余功能和printf一样。
   printk是有消息等级的,消息等级如下所示。只有当消息等级大于控制台等级时(值越低等级越高),printk输出的消息会被打印到终端。

宏定义消息等级描述
KERN_EMERG“0”紧急级别消息(Emergency)
KERN_ALERT“1”警报级别消息(Alert)
KERN_CRIT“2”严重级别消息(Critical)
KERN_ERR“3”错误级别消息(Error)
KERN_WARNING“4”警告级别消息(Warning)
KERN_NOTICE“5”注意级别消息(Notice)
KERN_INFO“6”信息级别消息(Info)
KERN_DEBUG“7”调试级别消息(Debug)

   使用命令:cat /proc/sys/kernel/printk查看内核打印等级。一般内核的输出内容会存到日志文件中,使用命令:dmesg查看日志输出内容。
在这里插入图片描述

●如何将内核打印信息输出到控制台?

(1)方法一:设置printk的消息等级printk(KERN_ERR "hello\n"); 在输出内容前添加消息等级的宏定义。若不设置消息等级,则为默认的消息等级。
(2)方法二:修改内核的消息等级echo 0 4 1 7 >/proc/sys/kernel/printk ,修改控制台等级或默认消息等级。

5. 导出符号

用于多个ko文件公用一个函数时,需要使用导出符号,这里传入函数名即可。

EXPORT_SYMBOL( )

6. copy_from_user

   是 Linux 内核中用于从用户空间拷贝数据到内核空间的函数。这个函数是确保内核空间和用户空间之间数据安全传输的重要机制之一。因为直接访问用户空间内存是不安全的,内核必须通过这些函数来处理数据的传递,以确保不会因为非法内存访问导致系统崩溃。

unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);
//to: 指向内核空间的目标缓冲区。
//from: 指向用户空间的源缓冲区。
//n: 要拷贝的字节数。

7. copy_to_user

   用于将数据从内核空间拷贝到用户空间。这个函数用于内核模块和设备驱动程序中,以安全的方式将数据传递给用户空间。

unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
//to: 指向用户空间的目标缓冲区。
//from: 指向内核空间的源缓冲区。
//n: 要拷贝的字节数。

8. container_of(已知结构体某成员,获取结构体首地址)

  是一个非常有用的宏,广泛用于 Linux 内核代码中。它用于从结构体成员的指针获取包含该成员的结构体的指针。

//宏定义原型:
#define container_of(ptr, type, member) ({            \
    const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
    (type *)( (char *)__mptr - offsetof(type,member) );})
/*
	ptr:已知的结构体成员的指针。
	type:结构体类型。
	member:成员在结构体中的名字。
*/

举例:

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

struct my_struct {
    int field1;
    struct list_head list;  //假设已知该成员
};

static int __init my_module_init(void)
{
	struct my_struct *ptr; //获取结构体首地址。
	//初始化并填充结构体
	struct my_struct my_instance; 
	my_instance.field1 =1;
	INIT_LIST_HEAD(&my_instance.list); //初始化为空链表。    
 
    // 使用 container_of 从 my_instance.list 指针获取 my_instance 的指针
    ptr = container_of(&my_instance.list , struct my_struct, list);
    
    printk(KERN_INFO "field1: %d\n", ptr->field1);
    return 0;
}

static void __exit my_module_exit(void)
{
    printk(KERN_INFO "Module exit\n");
}

module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");

使用:dmesg查看内核日志。
在这里插入图片描述

五、内核源码头

★重要:源码头路径:/usr/src//lib/modules下。使用uname -r查看当前系统内核版本。

   在编写内核时,必须要用到开发板的内核源码文件中的最顶层Makefile文件。这里我使用香橙派5Plus开发,具体使用详情查看官方开发手册即可。里面有详细的步骤操作。

   对于香橙派5plus,通常我们在开发板中安装的镜像包会有包含内核的头文件的.deb包 (路径查看开发板手册获得) ,我们只需要安装这个.deb包即可。安装完成后,会有一个文件夹。这个文件夹里就包含了我们所需要的最底层的Makefile文件。如果没有这个.deb包,则需要我们手动下载源码文件,然后编译生成.deb文件,然后在开发板上安装。
   每个开发板不一样。最终思想就是获取到开发板内核源码文件并编译好(生成一些必须的文件)即可。

   ●注意:通常我们会将源码包文件放在Linux_X86上进行开发(有交叉编译器),这是因为如果我们在开发板上开发的话,就需要将源码文件拷贝在开发板上,这时如果源码文件较大,而开发板内存又小时,会浪费开发板资源。所以通常我们会在Linux_X86上开发,然后将生成的模块包文件上传至开发板安装。

六、编写/生成内核模块文件

Linux内核模块编写1(一个c文件生成一个ko文件)
Linux内核模块编写2(多个c文件生成一个ko文件)
Linux内核模块编写3(多个c文件生成多个ko文件)

七、注意

Makefile
改:此处为最顶层的Makefile文件。
在这里插入图片描述

hello.c
在这里插入图片描述

  • 7
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux内核一直是学习的难点:将近3000万行代码,5万多个源文件,代码庞大繁杂、代码很难看懂。《Linux内核编程》将突破以往传统的学习方式,采取更有效和科学的学习方法,多角度地对内核进行多层次分析,不局限于形式,不拘泥细节,目的只有一个:更轻松、更高效地去理解内核、学习内核。为了更好地让学员掌握内核编程技能,更好地理解内核,本课程将采用并不局限于以下学习方法进行课程的录制:降维分析,化简为繁,将复杂的系统简单化用软件工程的方法分析内核:软件分层、模块化分解、框架迭代多角度立体分析Linux内核,目的只有一个:更好地理解内核利用Linux内核中的面向对象编程思想去分析复杂的子系统、子系统交互利用多任务编程的思想去分析Linux内核本套课程预计分为20个左右的小模块,每个模块一个专题,每个专题会陆续发布。拟录制的模块包括但不限于:模块机制、内核裁剪与配置、内核编译与启动、系统调用、中断、文件系统、调度、内存管理、内核同步、设备模型、字符驱动、块驱动、定时器、input、platform设备驱动、device tree、proc、sysfs、I/O...  本课程是《Linux内核编程》的入门篇,主要给大家介绍一下Linux内核开发、Linux驱动开发的就业行情、行业生态、需要掌握哪些技能、Linux内核的学习方法、如何搭建Linux内核的学习开发环境。 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值