linux内核编进阶,内核驱动进阶

在网上找了好久关于内核驱动学习的文章,但很多都是关注于某一点,没有系统化对于内核初学者来说,看那些文章最后你可能学到一些知识但是并不系统化,这也是为什么我写这篇博文的目的,本文是我通过看国嵌视频整理出来的笔记,如果你是个初学内核驱动的朋友,本文将会对你有些帮助!

作者:xutao (butbueatiful)

时间:2012年5月13日 17:06:20 一、内核开发基础:Linux体系结构:由[用户空间] 和 [内核空间]

图1 Linux体系结构

X86实现了4个不同的级别:Ring0 - Ring3。Ring0下,可以执行特权指令,可以访问IO设备等,Ring3下则有更多的限制。Linux系统利用了CPU的这一特性,使用了其中的两句来分别运行Linux内核与应用程序,这样使操作系统本身得到充分的保护。

内核空间与用户空间是程序执行的两种不同的状态,通过[系统调用]和[硬件中断]能够完成从用户空间到内核空间的转移。Linux内核体系结构:

图2 Linux内核体系结构图

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

进程管理:它的重点是创建进程(fork exec),停止进程(kill、exit),并控制它们之间的通信(signal、POSIX机制)。进程管理还包括控制活动进程如何共享CPU,即[进程调度]。

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

ARCH: architecture。内核支持的每种CPU体系

设备驱动程序

网络协议栈:内核协议栈为Linux提供了丰富的网络协议栈实现。

虚拟文件系统:VSF隐藏各个文件系统的具体细节,为文件操作提供统一的接口。二、Linux内核源代码:目录结构:Linux内核源代码采用树形结构进行组织,非常合理地[把功能相关的文件都放在同一个子目录下],使得程序更具可读性。

arch目录:内核所支持的每种CPU体系,在该目录下都有对应的子目录。每个CPU的子目录,又进一步分解为boot,mm,kernel等子目录。

|--x86    /*英特尔cpu及与之相兼容体系结构的子目录*/

||--boot    /*引导程序*/

|||--compressed        /*内核解压缩*/

||--tools    /*生成压缩内核映像的程序*/

||--kernel    /*相关内核特性实现方式,如信号处理,时钟处理*/

||--lib        /*硬件相关工具函数*/

block目录:部分块设备驱动程序

crypto目录:加密、压缩、CRC校验算法

documentation:内核文档

drivers目录:设备驱动程序

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

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

||--ext2    /*第二扩展文件系统*/

||--fat        /*MS的fat32文件系统*/

||--isofs    /*ISO9660光盘cd-rom上的文件系统*/

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

lib目录:库文件代码(不是指的是C库,在编译内核产生的库)

mm目录:用于实现内存管理中[与体系结构无关的部分,与体系结构有关的代码在arch目录里]

net目录:网络协议的实现代码

||--802        /*802无限通讯协议核心支持代码*/

||--appletalk    /*与苹果系统联网的协议*/

||--ax25    /*AX25无限INTERNET协议*/

||--bridge    /*桥接设备*/

||--ipv4    /*ip协议族V4版32位寻址模式*/

||--ipv6    /*ip协议族V6版*/

samples:内核编程的一些范例

scripts:配置内核的脚本

security:SElinux的模块

sound:音频设备的驱动程序

usr:cpio命令实现

virt:内核虚拟机三、内核配置与编译Linux内核具有可定制的优点配置与编译步骤:1、清除临时文件、中间文件和配置文件

make clean

remove most generated files but keep the config

make mrproper

remove all generated files + config files

make distclean

mrproper + remove editor backup and patch file

2、确定目标系统的软硬件配置情况,比如CPU类型、网卡的型号等

3、配置内核:

make config

基于文本模式的交互式配置。

make menuconfig

基于文本模式的菜单配置。(推荐使用)

make oldconfig

使用已有的配置文件(.config),但会询问新增的配置选项

make xconfig

图形化配置

配置方法:

根据已有的内核配置文件(每个体系结构目录的configs目录里有很多配置文件)去修改,将你需要的配置文件拷贝到源码顶层目录,然后再去配置、修改

4、编译内核:

make zImage

make bzImage

区别:在X86平台,zImage只能用于小于512K的内核

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

make zImage V=1

make bzImage V=1

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

5、编译内核模块:

make modules

6、安装内核模块

make modules_install

/*结果:将编译好的内核模块从内核源代码目录拷贝到 /lib/modules 下*/

7、制作init ramdisk

mkinitrd initrd-$version $version

例:

mkinitrd initrd-2.6.29 2.6.29

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

说明:2.6.29这个目录是通过第六步make modules_intall产生的内核安装(X86平台)1、cp arch/x86/boot/bzImage /boot/vmlinuz-$version

2、cp $initrd /boot/

3、修改/etc/grub.conf 或者 /etc/lilo.conf

修改方法:把原来的 title 段 拷贝一份,进行修改

/*$version 为所编译的内核的版本号*/四、内核模块开发:模块功能:Linux内核的整天结构非常庞大,其包含的组件也非常的多,如何使用需

要的组件:

方法一:把所有的组件都编译进内核文件,即:zImage 或 bzImage

这样导致两个问题:

一:生成的内核文件过大;

二:如果要添加或删除某个组件,需要重新编译整个内核.

方法二:内核模块:

让内核文件(zImage 或 bzImage)本身并不包含某个组件,而是在该组件需要被使用时,[动态地添加到正在运行的内核里]。内核模块的特点:模块本身并不被编译进内核文件(zImage 或 bzImage)可以根据需求,在内核运行期间动态的安装或卸载

范例(hello.c)

点击(此处)折叠或打开

#include

#include

static int __init hello_init(void)

{

printk("Hello my friend!\n");

return 0;

}

static void __exit hello_exit(void)

{

printk("Goodbye,friend!\n");

}

module_init(hello_init);

module_exit(hello_exit);程序结构

1、模块加载函数(必须)

安装模块时系统自动调用的函数,通过module_init宏来指定

2、模块卸载函数(必须)

卸载模块时被系统自动调用的函数,通过module_exit宏来指定

内核模块makefile的编写:

1、有一个源文件的内核模块的编写

范例(hello.c 的 makefile)

点击(此处)折叠或打开

ifneq ($(KERNELRELEASE),)

obj-m := hello.o #根据具体需要这个要变

else

KDIR := /lib/modules/2.6.18.e15/build #这个要变

all:

make -C $(KDIR) M=$(PWD) modules

clean:

rm -f *.ko *.o *.mod.o *.mod.c *.sysmvers

endif2、有多个源文件的内核模块编写(main.c add.c)

点击(此处)折叠或打开

/*main.c*/

#include

#include

MODULE_LICENSE("GPL");

MODULE_AUTHOR("ButBueatiful");

MODULE_DESCRIPTION("Hello my friend Module");

MODULE_ALIAS("a simplest module");

extern int add(int a, int b);

static int __init hello_init()

{

printk("Hello my friend!\n");

add(1, 2);

return 0;

}

static void __exit hello_exit()

{

printk("<7>hello <0>exit\n");

}

module_init(hello_init);

module_exit(hello_exit);

点击(此处)折叠或打开

/*add.c*/

int add(int a, int b)

{

return a + b;

}#####################Makefile##########################

ifneq ($(KERNELRELEASE),)

obj-m := hello.o

hello-objs := main.o add.o

else

KDIR := /lib/modules/2.6.32-71.el6.i686/build    #这个要变

all:

make -C $(KDIR) M=$(PWD) modules

clean:

rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.unsigned *.order

endif

内核模块的安装与卸载:

加载 insmod (insmod hello.ko)

卸载 rmmod (rmmod hello.ko)

查看 lsmod

加载 modprobe (modprobe hello)

modprobe 如同 insmod,也是加载一个模块到内核。它们的不同之处在于modprobe会根据文件 /lib/modules//modules.dep 来查看加载的模块,看它是否还依赖于其他模块,如果是,modprobe会首先找到这些模块,把它们先加载到内核。

模块可选信息

1、许可证申明

宏MODULE_LICENSE用来告知内核,该模块带有一个许可证,没有这样的说明,加载模块时内核会抱怨。有效的许可证有“GPL"、“GPL 2"、“GPL and additional rigths”、“Dual BSD/GPL”、“Dual MPL/GPL”和 “Proprietary"。

2、作者申明(可选)

MODULE_AUTHOR("ButBueatiful");

3、模块描述(可选)

MODULE_DESCRIPTION("Hello Module");

4、模块版本(可选)

MODULE_VERSION("V1.0");

5、模块别名(可选)

MODULE_ALIAS("a simple module");

6、模块参数

通过宏module_param指定模块参数,模块参数用于在加载模块时传递参数给模块。

module_param(name, type, perm);

name是模块参数的名称,type是这参数的类型,

perm是模块参数的访问权限。

type常见值:

bool,int,charp(字符串型)

perm常见值:

S_IRUGO:任何用户都对/sys/module中出现的该参数具有读权限

S_IWUSR:允许root用户修改/sys/module中出现的该参数

例:

int a = 3;

char *st;

module_param(a, int, S_IRUGO);

module_param(st, charp, S_IRUGO);

内核符号导出:

/proc/kallsyms记录了内核中所有导出的符号的名字与地址。

内核符号的导出使用:

EXPORT_SYMBOL(符号名)

EXPORT_SYMBOL_GPL(符号名)

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

常见问题:版本不匹配

内核模块的版本由其所依赖的内核代码版本所决定,在加载内核模块时,insmod程序会将内核模块与当前正在运行的内核模块版本比较,如果不一致时,就会出错.

解决方法:

1、使用modprobe --force-modversion 强行加载内核模块

2、确保编译内核模块时,所依赖的内核代码版本与现在的相同

uname -r 命令可查看内核版本

2.6与2.4内核模块对比

2.6是.ko 2.4是.o;.ko是由.o的进一步封装而成

总结---对比应用程序:

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

应用程序是从头 main 到尾执行任务,执行结束后从内存中消失。内核模块则是先在内核中注册自己以便服务于将来的某个请求,然后它的初始化函数结束,此时模块仍然存在与内核中,直到卸载函数被调用,模块才从内核中消失。

内核打印:

printk 与 printf区别在于:printk有优先级

函数printk在Linux内核中定义,功能和标准C库中的函数printf类似。内核需要自己单独的打印输出函数,这是因为它在运行时不能依赖于C库。模块能够调用printk是因为在insmod函数装入模块后,模块就连接到了内核,因此可以访问内核的共用符号(包括函数和变量)。

在中定义了8中记录级别。按照优先级递减的顺序是:

KERN_EMERG  "<0>"  用于紧急事件消息,他们一般是系统崩溃之前提示的消息

KERN_ALERT  "<1>"  用于需要立即采取动作的情况

KERN_CRIT   "<2>"  临界状态,通常涉及严重的硬件或软件操作失败

KERN_ERR    "<3>"  用于报告错误状态,设备驱动程序会经常使用KERN_ERR来报告来自硬件的问题。

KERN_WARNING"<4>"  对可能出现问题的情况进行警告,但这类情况通常不会对系统造成严重问题。

KERN_NOTICE "<5>"  有必要进行提示的正常情形,许多与安全相关的状况用这个级别进行汇报。

KERN_INFO   "<6>"  提示性信息,很多驱动程序在启动的时候以这个级别来打印他们找到的硬件信息。

KERN_DEBUG  "<7>"  用于调试信息。

没有指定优先级的printk默认使用DEFAULT_MESSAGE_LOGLEVEL优先级,它是一个在kernle/printk.c中定义的整数。在2.6.29内核中:

#define DEFAULT_MESSAGE_LOGLEVEL 4 /*KERN-WARNING*/

控制台优先级配置:

/proc/sys/kernel/printk

4 4 1 7

Console_loglevel

Default_message_loglevel

Minimum_console_level

Default_console_loglevel

在printk中显式地指定优先级的原因在于:具有默认优先级的消息可能不会输出在控制台上。这依赖于内核版本、klogd守护进程的版本以及具体的配置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值