原创文章:转载请说明出处:http://blog.csdn.net/crzy_sparrow/article/details/7367288
本文目录:
1. 构建内核源码树
1.1Ubuntu 10.10下构建linux-2.6.35内核源码树
1.2安装Ubuntu6.10,构建linux-2.6.17内核源码树---更接近LDD3的2.6.10内核
2. 模块Hello World!
3. 加强版本的Hello World!
4. 精彩句子摘要
5. 速查手册
1.1 Ubuntu 10.10下构建linux-2.6.35内核源码树
1)安装编译内核所需要的组件
$ sudo apt-get install build-essential kernel-package libncurses5-dev
libncurses5这个软件包在使用menuconfig配置内核的时候会用到。
2)进入/usr/src ,在这里构建源码树,我们用下面指令查看可用的源码包:
$ sudo apt-cache search linux-source
我的提示是:
linux-source -Linux kernel source with Ubuntu patches
linux-source-2.6.35- Linux kernel source for version 2.6.35 with Ubuntu patches
3)那么就让我们来下载2.6.35版的kernel,通过下边命令:
$ sudo apt-get install linux-source-2.6.35
4)下载完成,解压一下:
$ sudo tar –xjvf linux-source-2.6.32.tar.bz2
5)然后为了操作方便,我们给源码目录建立一个软连接:
$ sudo ln –s linux-source-2.6.35 MyLinux
6)这样我们就可以通过linux来操作linux-source-2.6.35目录了。
$ cd MyLinux
$ uname -r
显示:2.6.35-22-generic
7)进入源码树内,然后导入原来内核的配置文件:
$ sudo cp ../linux-headers-2.6.35-generic/.config ./
8)配置,基本不用配置,直接退出配置界面就好了:
$make menuconfig
9) 编译:
$ make –j2
$ make modules_install
10)在生成的内核源码树下尝试了下hello的模块的编译加载。
出现Error inserting 'hello.ko':-l Invalid module format错误
cat /var/log/message |tail发现是版本不匹配
google问题之间发现如下一篇关于版本检测的好文:
http://www.ibm.com/developerworks/cn/linux/l-cn-kernelmodules/?cmp=dwskl&cpb=dw&ct=dwcon&cr=cn_CCID&ccy=cn
试了了很多种方法,还是这个问题,火大,直接把源内核镜像取代了,不再报错。
操作如下(默认内核源码树为工作目录):
cp arch/i386/boot/bzImage /boot/hj_boot/vmlinuz-2.6.35.4
gedit /boot/grub/grub.cfg (早期的ubuntu是menu.list)
添加(根据你的操作系统略有不同):
menuentry "Ubuntu,Linux 2.6.35.4" {
insmod part_msdos
insmod ntfs
set root='(hd1,msdos1)'
search --no-floppy --fs-uuid --set 00039f8500039394
loopback loop0 /ubuntu/disks/root.disk
set root=(loop0)
linux /boot/hj_boot/vmlinuz-2.6.35.4 root=/dev/sdb1loop=/ubuntu/disks/root.disk ro quietsplash
initrd /boot/initrd.img-2.6.35-22-generic
}
重启选择 Ubuntu, Linux2.6.35.4成功登录,编译hello成功加载。OK!
1.2安装Ubuntu 6.10,构建linux-2.6.17内核源码树---更接近LDD3的2.6.10内核
不过LDD3所对应的内核是2.6.10版本的,2.6.18之后内核有个较大的改动,到2.6.35很多宏和符号的定义有了很大的变化,头文件的名字也有了较大的变化,有些头文件已经不见了。为了降低学习的难度,避免遇到一些奇奇怪怪问题的时候手足无措,准备找一个近似版本的内核供自己学习。辗转反侧之下,在vmware上安装了基于2.6.17内核的Ubuntu6.10-desktop版本(准备把我闲置的台式机搞成ssh服务器,方便自己学习)。内核源码树的建立和上面的差不多,就不详细描述了。
不过我觉得,驱动的调用的外部函数是基于运行内核导出符号和未加载相关模块符号的,对于我们学习ldd3而言,调用函数和全局变量一般不会依赖于内核未加载的模块,不建立内核源码树而将内核目录指向原系统自带的内核构造树就好了---/lib/modules/$(shell uname -r)/build(因为它已经包含了内核makefile构造系统和所需要的头文件)。当然,相信每一个学习LDD3的童鞋肯定抱着对内核一探究竟的态度的,下载标准版的源码还是必须的。
2. 模块Hello World!
hello.c:
//该头文件指定始化和清除函数
#include <linux/init.h>
//模块相关头文件
#include <linux/module.h>
//指定许可证
MODULE_LICENSE("Dual BSD/GPL");
//初始化函数,insmod时调用
static int hello_init(void)
{
//内核打印函数,KERN_ALERT为打印等级
printk(KERN_ALERT "Hello, world\n");
return 0;
}
//清除函数,rmmod时调用
static void hello_exit(void)
{
printk(KERN_ALERT "Goodbye, cruel world\n");
}
//指定初始化和清除函数
module_init(hello_init);
module_exit(hello_exit);
makefile:
#编译成模块,规则由内核构造环境指定
obj-m:=hello.o
#当前路径
PWD = $(shell pwd)
#内核构造环境路径
KERNEL = /lib/modules/$(shell uname -r)/build
#在内核环境下将当前目录下的obj-m对应的.o文件编译成模块
module:
$(MAKE) -C $(KERNEL) M=$(PWD) modules
clean:
rm *.o *.ko * .mod*
.PHONY: module clean
编译情况:
加载模块情况(insmod hello.ko),dmesg看打印信息:
我们发现,”Hello world”成功打印。
卸载模块情况类似:
3. 加强版本的Hello World!
#include <linux/init.h>
#include <linux/module.h>
//模块参数头文件
#include <linux/moduleparam.h>
//kmalloc头文件
#include <linux/slab.h>
MODULE_LICENSE("Dual BSD/GPL");
//初始化参数
static int howmany = 2;
static char* whom = "Jim";
/*
*指定模块参数
*module_param(name,type,perm)宏:
*name:参数名称
*type:类型:如int,charp(字符指针)
*perm:sysfs文件系统中该模块参数的使用权描述
* 0:不可见
* S_IRUGO:任何人可读取,但不可修改
* S_IRUGO|S_IWUSR:允许root用户修改
*注:参数被root修改后,内核没有任何机制通知驱动参数已修改:
* 所以如果驱动支持参数可修改,驱动中必须支持参数检测
*/
module_param(howmany, int, S_IRUGO);
module_param(whom, charp, S_IRUGO);
unsigned char *a=0,*b=0,*c=0;
//资源释放函数,按自愿申请顺序逆向释放,避免因资源间的依赖产生不可预知的错误
static void mycleanup(void)
{
if(c)
kfree(c);
if(b)
kfree(b);
if(a)
kfree(a);
}
static __init int hello_init(void)
{
int i;
for (i = 0; i < howmany; i++)
printk(KERN_ALERT "(%d) Hello, %s\n", i, whom);
a=(unsigned char*)kmalloc(10,0);
printk(KERN_ALERT "kmalloc for a\n");
if(!a)
goto fail;
b=(unsigned char*)kmalloc(10,0);
printk(KERN_ALERT "kmalloc for b\n");
if(!b)
goto fail;
c=(unsigned char*)kmalloc(10,0);
printk(KERN_ALERT "kmalloc for c\n");
if(!c)
goto fail;
return 0;
fail:
mycleanup();
printk(KERN_ALERT "error\n");
return -1;
}
static __exit void hello_exit(void)
{
if(c)
{
kfree(c);
printk(KERN_ALERT "kfree for a\n");
}
if(b)
{
kfree(b);
printk(KERN_ALERT "kfree for a\n");
}
if(a)
{
kfree(a);
printk(KERN_ALERT "kfree for a\n");
}
printk(KERN_ALERT "Goodbye, cruel world\n");
}
module_init(hello_init);
module_exit(hello_exit);
运行结果如下:
4. 精彩句子摘要
1)内核只能使用作为内核一部分的函数。
2)模块中的某些函数作为系统调用的一部分而执行,而其它函数负责中断处理。
3)内核的栈很小,如果我们需要大的结构,则应高在调用时动态分配该结构。
4)内核构造系统细节:Documentation/kbuild.。
5)对于共享数据必须考虑并发问题,因为内核中会被抢占,也有可能又中断发生,某段程序无法保证其在访问共享数据期间能够独占内核。
6)当模块被装入内核后,它所导出的任何符号将成为内核符号表的一部分。
7)每次返回合适的错误编码是个好习惯,因为用户程序可以通过perror函数或者类似的途径将它们转换为有意义的字符串。
8)在用来支持某个设施的所有内部初始化完成之前, 不要注册任何设施。
5. 速查手册
本节总结了我们在本章接触到的内核函数, 变量, 宏定义, 和 /proc 文件. 它的用意是作为一个参考. 每一项列都在相关头文件的后面。
本节中的项通常以在本章中出现的顺序排列:
insmod
modprobe
rmmod
用户空间工具, 加载模块到运行中的内核以及去除它们.
#include <linux/init.h>
module_init(init_function);
module_exit(cleanup_function);
指定模块的初始化和清理函数的宏定义.
__init
__initdata
__exit
__exitdata
函数( __init 和 __exit )和数据 (__initdata 和 __exitdata)的标记, 只用在模块初始化或者清理时
间. 为初始化所标识的项可能会在初始化完成后丢弃; 退出的项可能被丢弃如果内核没有配
置模块卸载. 这些标记通过使相关的目标在可执行文件的特定的 ELF 节里被替换来工作.
#include <linux/sched.h>
最重要的头文件中的一个. 这个文件包含很多驱动使用的内核 API 的定义, 包括睡眠函数和
许多变量声明.
struct task_struct *current;
当前进程.
current->pid
current->comm
进程 ID 和 当前进程的命令名.
obj-m
一个 makefile 符号, 内核建立系统用来决定当前目录下的哪个模块应当被建立.
/sys/module
/proc/modules
/sys/module 是一个 sysfs 目录层次, 包含当前加载模块的信息. /proc/moudles 是旧式的, 那种
信息的单个文件版本. 其中的条目包含了模块名, 每个模块占用的内存数量, 以及使用计数.
另外的字串追加到每行的末尾来指定标志, 对这个模块当前是活动的.
vermagic.o
来自内核源码目录的目标文件, 描述一个模块为之建立的环境.
#include <linux/module.h>
必需的头文件. 它必须在一个模块源码中包含.
#include <linux/version.h>
头文件, 包含在建立的内核版本信息.
LINUX_VERSION_CODE
整型宏定义, 对 #ifdef 版本依赖有用.
EXPORT_SYMBOL (symbol);
EXPORT_SYMBOL_GPL (symbol);
宏定义, 用来输出一个符号给内核. 第 2 种形式输出没有版本信息, 第 3 种限制输出给 GPL 许
可的模块.
MODULE_AUTHOR(author);
MODULE_DESCRIPTION(description);
MODULE_VERSION(version_string);
MODULE_DEVICE_TABLE(table_info);
MODULE_ALIAS(alternate_name);
放置文档在目标文件的模块中.
module_init(init_function);
module_exit(exit_function);
宏定义, 声明一个模块的初始化和清理函数.
#include <linux/moduleparam.h>
module_param(variable, type, perm);
宏定义, 创建模块参数, 可以被用户在模块加载时调整( 或者在启动时间, 对于内嵌代码). 类
型可以是 bool, charp, int, invbool, short, ushort, uint, ulong, 或者 intarray.
#include <linux/kernel.h>
int printk(const char * fmt, ...);