linux内核驱动-内核初涉

本文介绍了Linux内核的基础知识,包括内核的学习方法、Linux内核的结构与配置过程、内核模块的开发流程等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、为什么要学习内核?

有些人要学习内核,而有些人则可以不学习它。你如果以后要从事系统研发或驱动开发的话,就要学习内核。

刚刚接触内核,主要学习内核的接口函数。不要深入的去读内核,因为你读也读不懂,内核代码庞大如野兽一般不可驾驭。

学习内核主要掌握层次学习法,即从头开始学习,一环紧扣一环。

内核学习的四步学习法:1、核心理论学习-概念与函数原型2、范例程序分析3、思维导图设计4、亲自编写代码

二、linux内核简介

1、linux体系结构

分为两部分:用户空间和内核空间


2、linux为什么要分为用户空间和内核空间

现代CPU通常实现了不同的工作模式,以ARM为例,实现了7中工作模式。X86实现了4中不同的级别:Ring0-Ring3. Ring0下可以执行特权指令,可以访问IO设备等,在Ring3则有很多限制。linux系统利用CPU的这一特性,使用了其中两个级别分别运行linux内核与应用程序,这样使操作系统本身得到充分的保护。例如:如果使用X86,用户代码运行在Ring3,内核代码运行在Ring0.内核空间与用户空间是程序执行的两种不同状态,通过系统调用和硬件中断能够完成从用户空间到内核空间的转移。

3.linux的内核构架


系统调用接口

SCI层为用户空间提供了一套标准的系统调用函数来访问Linux内核,搭起了用户空间到内核空间的桥梁。

进程管理(PM)是创建进程,停止进程,并控制它们之间的通信。进程管理还包括控制活动进程如何共享CPU,即进程调度。

内存管理(MM)的主要作用是控制多个进程安全地共享内存区域。

网络协议栈(Network Stack)为linux提供了丰富的网络协议实现。

虚拟文件系统(VFS)隐藏各个文件系统的具体细节,为文件操作提供统一的接口。


设备驱动(DD):linux内核中有大量代码都在设备驱动程序中,它们控制特定的硬件设备。

三、linux内核源代码结构

1、下载源代码地址www.kernel.org

2、linux内核源代码采用树形结构进行组织,非常合理地把功能相关的文件都放在同一个子目录下,使得程序更具可读性。


arch目录:arch是architecture的缩写。内核所支持的每种CPU体系,在该目录下都有对应的子目录。每个cpu的子目录,又进一步分解为boot,mm,kernel等子目录,分别包含控制系统引导,内存管理,系统调用等。/* X86英特尔cpu与之相兼容体系结构的子目录:boot 引导程序compressed内核解压缩 tools生成压缩内核映像的程序 kernel相关内核特性实现方式,如信号处理、时钟处理 lib 硬件相关工具函数*/

documentation内核文档

drivers设备驱动文档

include内核所需要的头文件。与平台无关的头文件在include/linux子目录下,与平台有关的头文件则放在相应的子目录中。

fs目录存放各种文件系统的实现代码。每个子目录对应一种文件系统的实现,公用的源程序用于实现虚拟文件系统vfs

 ||--devpts是/dev/pts虚拟文件系统

||--ext2是第二扩展文件系统

||--fat是MS的fat32文件系统

||--isofs 是IsO9660光盘cd-rom上的文件系统

net是网络协议的实现代码

||--802        802无线通讯协议核心支持代码

||--appletalk 与苹果系统连网的协议

||--ax25  AX25无线INTERNET协议

||--bridge 桥接设备

||--ipv4 IP协议族V4版32位寻址模式

||--ipv6 IP协议族V6版

四、linux内核的配置和编译

代码是如何转化为烧写或安装到硬件平台中的系统映像文件的?

1、为什么要配置内核

选出需要的,去掉不要的!1、硬件的需求2、软件的需求

下载内核后要在linux中解压缩,不要在windows下解压缩因为windows下不区分大小写,而linux操作系统区分大小写。

进入内核的文件下:

2、内核的配置:

make config:基于文本模式的交互式配置

make menuconfig:基于文本模式的菜单型配置


<*>文件经过编译由.c文件到.o文件,最后链接压缩为内核镜像,它存放在内存。

<M>内核模块,同上经过编译后会把.o文件安装到硬盘。

< >表示不选择该功能

配置结果文件是隐藏文件,可以用ls -a 在内核文件下查看.config version

上面介绍的是一种方法,不过作为初学者我们往往是在一个已有的配置文件基础上,通过修改得到新的配置文件,linux内核提供了一系列可供参考的内核配置文件,位于Arch/cpu/configs

接下来,我们利用虚拟机上的linux系统的配置文件来创建自己的升级版内核,并在虚拟机上运行该内核。


3、编译内核(编译内核、编译内核模块、制作ramdisk)

3.1、编译内核

make zImage只能编译小于512K的内核

make bzImage我们一般会使用这种方法编译内核,下图为编译成功时界面,内核的编译大约要持续10多分钟。我们可以看到,编译好内核镜像存放arch/x86/boot/bzImage


如需获取详细编译信息,可使用:

make zImage V=1

make bzImage V=1

编译好的内核位于arch/cpu/boot/目录下***

3.2编译内核模块

make modules 编译内核模块

make modules_install 将编译好的内核模块,从内核源代码目录复制到/lib/modules下**,为打包做好准备


经过第一步编译后,散落在各个文件下的.ko文件为内存模块。需要集中移动到/lib/modules这个就由make modules_install来完成

3.3制作init ramdisk

方法:mkinitrd initrd-$version $version

例如:mkinitrd initrd-2.6.39 2.6.39 可以看到已经生成了initrd-2.6.39

通过uname -r 获得正在运行的内核版本

*$version可以通过查询/lib/modules下的目录得到

4、安装内核

1、cp arch/x86/boot/bzImage

/boot/vmlinuz-$version

2、cp initrd-$version /boot/


3、修改/etc/grub.conf的后四行.修改完成后重启,在倒计时的地方回车选择2.6.39内核,就可以登录到2.6.39内核的系统了,这个时候你会发现所以的文件都没有变,因为只是内核做了修改,文件系统是没有变化的。


5、清理内核

make clean 清理编译内核生产的.o文件

make distclean 清理编译内核生产的.o文件和.config $version

记忆几个命令

rpm -qa | grep kernel 找到内核包信息

rpm -e kernel-内核包名

重启后出现了下面问题,由于初学,目前不能解决,留作以后处理。


五、内核模块的开发

1、什么是内核模块

linux内核的整体结构非常庞大,其包含的组件也非常多,如何使用这些组件呢,方法1:把所有的组件都编译进内核文件,即:zImage或bzImage,但这样会导致一个问题:内核文件过大,占用内存过大。

有没有一种机制能让内核文件本身并不包含某组件,而是在该组件需要被使用的时候,动态地添加到正在运行的内核中呢?

内核模块具有以下特点:

模块本身并不被编译进内核文件(zImage或bzImage)

可以根据需求,在内核运行期间动态的安装或卸载

2、内核模块设计


设计方法:范例分析->思维导图设计->自己动手编写程序

整个代码:

//hello.c
#include <linux/init.h>
#include <linux/module.h> 头文件

extern int add();

MODULE_LICENSE("GPL");
MODULE_AUTHOR("DAVID");
MODULE_DESCRIPTION("DRIVER");
MODULE_VERSION("V1.0");

int a = 9;
char *st;

module_param(a, int, S_IRUGO);
module_param(st,charp,S_IRUGO);

static int hello_init()
{
add();
printk("<0> a is %d\n", a);
printk("<0> st is %s\n", st); printk比printf的优势是它有不同的打印级别
return 0;
}
static void hello_exit()
{

}

module_init(hello_init); 入口函数
module_exit(hello_exit);出口函数


//add.c
int add()
{
        return 2;
}


//Makefile
obj-m := text.o
text-objs :=hello.o add.o  只有一个编译模块
KDIR := /lib/modules/2.6.18-53.el5xen/build/  使用的是PC机中的模块编译器
all:
        make -C $(KDIR) M=$(PWD) modules

安装与卸载内核模块

安装insmod text.ko 

查看lsmod

卸载rmmod

通过命令行传递参数

insmod text.ko a=4

2.4.1模块申明

1、MODULE_LICENSE(“遵守的协议”)

申明该模块遵守的许可证协议,如:“GPL”、“GPL v2”等

2、MODULE_AUTHOR(“作者”)

申明模块的作者

3、MODULE_DESCRIPTION("模块功能描述")

申明模块的功能

4、MODULE_VERSION(“V1.0”)

申明模块的版本

2.4.2、模块参数

在应用程序中

int main(int argc, char** argv)

argc表示命令行输入的参数个数,argv中保存输入的参数

同样的内核模块中也可以通过命令行输入参数。

通过宏module_param指定保存模块参数的变量。模块参数用于在加载模块时传递参数给模块。

module_param(name,type,perm)

name:变量名称

type:变量类型,bool:布尔型 init:整型 charp:字符串型

perm:访问权限。S_IRUGO : 读权限 S_IWUSR : 写权限

2.4.3、符号导出

什么是内核符号?

为什么要导出模块中的内核符号?

内核模块的函数或变量只有输出到模块外才可以被其它模块使用,即使用宏将函数名输出到模块外,可被其它模块使用

内核符号的导出使用宏

EXPORT_SYMBOL(符号名)

EXPORT_SYMBOL_GPL(符号名)

说明:其中EXPORT_SYMBOL_GPL只能用于包含GPL许可证的模块

添加选项信息后代码:

//helo.c

#include <linux/init.h>
#include <linux/module.h> header file

extern int add(); //extern function

MODULE_LICENSE("GPL"); modules declaring
MODULE_AUTHOR("DAVID");
MODULE_DESCRIPTION("DRIVER");
MODULE_VERSION("V1.0");

int a = 9;
char *st;

module_param(a, int, S_IRUGO); modules parameter
module_param(st,charp,S_IRUGO);

static int hello_init()
{
add();
printk("<0> a is %d\n", a);
printk("<0> st is %s\n", st);
return 0;
}

static void hello_exit()
{


}

module_init(hello_init);   entry function
module_exit(hello_exit); exit function


//add.c

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

int add()
{
return 2;
}

static int add_init()
{
return 0;
}


static int add_exit()
{
return 0;
}

module_init(add_init);
module_exit(add_exit);
EXPORT_SYMBOL(add);symbol export


//Makefile

obj-m := hello.o add.o //two modules, so this pragam needs add export

KDIR := /lib/modules/2.6.18-53.el5xen/build/ 
all:
make -C $(KDIR) M=$(PWD) modules

总结:对比应用程序,内核模块具有以下不同:

1、应用程序是从头(main)到尾执行任务,执行结束后就从内存中消失。

2、内核模块的初始化函数结束时,模块仍然存在内核中,直到卸载函数被调用,模块才从内核中消失。

3、内核打印:printk与printf的区别是printk在内核中使用,printf在应用程序中使用。printk允许根据严重程度,通过附加不同的“优先级”来对消息分类

在<linux/kernel.h>中定义了8种记录级别。按照优先级递减的顺序分别是:

KERN_EMERG    "<0>"

用于紧急消息,常常是那些崩溃前的消息。

KERN_ALERT  "<1>"

需要立刻行动的消息。

KERN_CRIT "<2>"

严重情况。

KERN_ERR "<3>"

错误情况。

KERN_WARNING "<4>"

有问题的警告。

KERN_NOTICE "<5>"

正常情况,但是仍然值得注意。

KERN_INFO "<6>"

信息型消息

KERN_DEBUG "7"

用作调试消息

没有指定优先级的printk默认使用 4 级别,KERN_WARNING

所以可以通过优先级控制消息是否打印到屏幕

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值