Posts Tagged ‘kernel’

字符设备驱动分析(1)

16 9 月, 2010

Last Update:9/24(斜体字为更新内容)

Last Update:9/20

Last Update:9/19

熟悉了模块编程的基本框架后,我们就可以试着分析一个简单的字符设备驱动。下面以《设备驱动开发详解》一书中的代码6.17为例来分析这个字符设备驱动的代码。

我们现在对于对前文中hello,kernel内核模块进行稍微的改动。我们都知道内核模块的入口函数是module_init(function name)内注册的函数。也就是告诉内核“从这个函数入口”。那么我们分析字符设备驱动模块,首先应该去看globalmem_init函数。

module_init(globalmem_init);
module_exit(globalmem_exit);

在globalmem_init函数中,首先通过宏MKDEV获得32位的设备驱动号。通常linux中的设备号由主设备号和次设备号组成,主设备号对应每一类设备,而次设备号对应该类设备的具体一个设备。dev_t类型前12位是主设备号(但事实上主设备号的前四位被屏蔽了,如果去看源码就可得知),后20位为次设备号。由于可以事先指定主设备号globalmem_major,因此我们需要用宏MKDEV来获得dev_t类型的设备号:

#define MINORBITS       20
#define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))

这个宏通过移位和或运算,巧妙的得到dev_t类型的设备号。这个宏可以在include/linux/kdev_t.h中查找到。网上有的资料中会给出MINORBITS为8,这个应该是适合16位的设备号的情况。

接着通过全局变量globamem_major来判断是否事先分配了起始的设备号。如果是,则继续分配连续的一段设备号,否则动态分配设备号,并且通过MAJOR宏获得分配以后的主设备号。这里需要强调的是,下面的两种设备号分配函数,都是一次性分配一组连续的设备号(当然也可以只分配一个设备号,调整参数即可)。

首先我们分析已知起始设备号的情况。通过调用register_chrdev_region函数,便可以申请到一组连续范围的设备号。在linux/fs/char_dev.c中可以看到此函数的原型。

 int register_chrdev_region(dev_t from, unsigned count, const char *name);

其中from是首设备号,而count是这组连续设备号的数目。name为设备名。而《设备》一书中的count为1,也就是说,这组设备号的数量为1。

其次,当起始设备号并未指定时就要动态的申请了,使用下面的函数:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);

与上面静态分配函数不同的是,此时dev是一个指针类型,因为要返回将要分配的设备号。关于以上两个函数的内核源码分析,具体参见这里

以上都成功执行后,我们给globalmem_devp指针分配内存空间。其中globalmem_devp是一个指向globalmem设备结构体的指针。我们通过mmset对此块内存空间进行初始化之后。接着globalmem_setup_cdev函数对cdev初始化并将此字符设备注册到内核。

//设备驱动模块加载函数
int globalmem_init(void)
{
	int result;
	dev_t devno=MKDEV(globalmem_major,0);

	if(globalmem_major)
	{
		result=register_chrdev_region(devno,1,"globalmem");
	}
	else
	{
		result=alloc_chrdev_region(&devno,0,1,"globalmem");
		globalmem_major=MAJOR(devno);
	}
	if(result<0)
		return result;
	globalmem_devp=kmalloc(sizeof(struct globalmem_dev),GFP_KERNEL);
	if(!globalmem_devp)
	{
		result=-ENOMEM;
		goto fail_malloc;
	}

	memset(globalmem_devp,0,sizeof(struct globalmem_dev));

	globalmem_setup_cdev(globalmem_devp,0);
	return 0;
fail_malloc:unregister_chrdev_region(devno,1);
	return result;
}

上面的程序已经很“整齐”的说明了init函数的主要作用。具体如下:

1.申请设备号

2.为设备相关的数据结构分配内存

3.初始化并注册cdev(globalmem_setup_cdev函数实现)

有了字符设备驱动的加载函数,那么肯定有卸载函数:

void globalmem_exit(void)
{
cdev_del(&globalmem_devp->cdev);
kfree(globalmem_devp);
unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);
}

总体来看,globalmem_init函数完成的是字符设备的一些初始化工作,以及向系统内注册。而globalmem_exit就是进行字符设备的释放工作:从内核中删除这个字符设备,释放设备结构体所占的内存,以及释放申请的设备号。从结构上看,它并没有偏离内核模块编程的结构范围,仍然是我们熟悉的hello,kernel。

我们在此先暂时将globalmem_setup_cdev函数内部的实现用return 0;来代替。那么我们现在用插入内核模块的命令将我们这个字符设备驱动(尽管许多功能还未实现)插入到内核中。成功后我们可以通过cat /proc/devices 命令来查看刚刚字符设备名称以及主设备号。/proc是一个虚拟的文件系统,这里的虚拟是指这个文件系统并不占用磁盘空间而只存在于内存中。而该目录下的devices文件中存储着系统字符和块设备的驱动名称以及设备编号。

接下来,我们可以通过:mknod /dev/globalmem c 250 0命令创建一个设备节点。有了这个设备节点后,就可以对它进行类似普通文件那样的操作了(当然现在还不能,因为并未实现具体的操作函数)。

这样一个字符驱动的大致上有了雏形。

printf到printk的转变

15 9 月, 2010

昨天下午stepbystep的为其他同学演示了内核模块编程hello,kernel!在陈老师的指导下,先为大家演示了最基本的C程序hello,world。然后又一步步的转换成内核模块程序。在这一步步的转变过程中,我也发现了自己在学习内核模块中的不足,下面将下午遇到的一些问题总结如下。

一个简单的hello,world程序如下:

#include <\stdio.h\>
int main()
{
	printf("hello,world\n");
	return 0;
}

对于上面的程序,我们通常一步到位进行编译:

gcc hello.c -o hello

这样的命令简单方便,不过通常会让我们忽略从.c文件到可执行文件的整个编译过程。通常在使用gcc编译程序时,编译过程通常会分为4个阶段,即预处理(pre-processing),编译(compiling),汇编(assembling),链接(linking)。

在预处理阶段,一般输入的是.c文件,而输出的是.i文件。在此阶段中通常会处理源文件中的预处理命令,比如#define,#include,#ifdef等命令。如果想要生成这种.i中间文件,那么可以使用下面命令:

gcc -E hello.c -o hello.i

在编译阶段,输入的是.i中间文件,输出的是.s汇编语言文件。可以使用下面的命令:

gcc -S hello.i -o test.s

在汇编阶段,输入的是上一步产生的.s文件,产生的是二进制机器代码.o文件。此阶段对应的命令如下:

gcc -c test.s -o test.o

在链接阶段,输入的是.o二进制机器代码文件,连同其他的(如果有的话)机器代码文件和库文件一起汇集成一个可执行的二进制代码文件。

gcc test.o -o test

以上是一个程序编译完整的四个阶段。一般来说我们会将前三个阶段一步搞定,那么整个编译过程可以用下面两条命令就完成:

gcc -c test.c -o test.o
gcc test.o -o test

也就是先生成目标文件,再将目标文件连接成可执行文件。当你熟悉了整个编译过程后,可以用一开始我们说的一条命令来完成。

了解普通文件的编译过程,我们现在就将hello.c中的代码转换成模块编程中的.c代码。首先我们要更改头文件:

#include <\linux/kernel.h\>
#include <\linux/module.h\>

这与我们一般的头文件不同。一般我们在用户态下编写C程序,头文件会放在:/usr/include/下,而我们模块编程时,它使用的是内核中的头文件,一般在:cd /usr/src/linux-headers-2.6.32-21/include/。特别的我们这里使用的是include/目录下linux/这个子目录中的头文件,因此模块编译的时候会自动在内核中的include/目录下找linux/kernel.h这样的头文件。

其次,printf到printk是一个典型的用户态下编程与内核模块编程的不同。可能我们一开始会比较奇怪,为什么我make成功,加载也成功,但是就是不能显示printk里面的语句呢?我们可以这么想printk就是专门为内核“服务”。它一般输出的语句都在内核的日志文件当中。

Hello,Kernel!

13 9 月, 2010

学习内核模块编程,第一个小程序当然是hello,kernel!了,这应当算是一个惯例了。以前大三的时候在实验课上做过模块编程,记得当时还是许师兄带我们的实验,不过现在又忘了。晚上试了试,很快就运行成功了,不过还是出现了一些问题。现在将我的步骤记录如下,供和我一样的初学者学习。

1.首先编写hello.c文件

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
//必选
//模块许可声明
MODULE_LICENSE("GPL");
//模块加载函数
static int hello_init(void)
{
	printk(KERN_ALERT "hello,I am edsionte\n");
	return 0;
}
//模块卸载函数
static void hello_exit(void)
{
	printk(KERN_ALERT "goodbye,kernel\n");
}
//模块注册
module_init(hello_init);
module_exit(hello_exit);
//可选
MODULE_AUTHOR("edsionte Wu");
MODULE_DESCRIPTION("This is a simple example!\n");
MODULE_ALIAS("A simplest example");

通常一个模块程序的中,模块加载函数,模块卸载函数以及模块许可声明是必须有的,而象模块参数,模块导出符号以及模块作者信息声明等都是可选的。

我们编写了模块加载函数后,还必须用module_init(mode_name);的形式注册这个函数。因为当我们接下来用insmod加载模块时,内核会自动去寻找并执行内核加载函数,完成一些初始化工作。类似的当我们使用rmmod命令时,内核会自动去执行内核卸载函数。

请注意这里的printk函数,可以简单的理解为它是内核中的printf函数,初次使用很容易将其打成printf。

2.编写Makefile文件

记得大三,那时候实验课上接触到Makefile,只是按照书上的内容敲上去。不过有了上一周对Makefile相关语法的了解,现在看起来已经基本知道为什么要这么写了。那么下面我们看Makefile文件。

obj-m += hello.o
#generate the path
CURRENT_PATH:=$(shell pwd)
#the current kernel version number
LINUX_KERNEL:=$(shell uname -r)
#the absolute path
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)
#complie object
all:
	make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
#clean
clean:
	make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean

首先第一句话指定要被编译的文件。其实Makefile中有这样一句话就可以了,但是这样的话每次make时都要加入其他命令,所以我们不妨就在Make中加入每次要执行的命令(脚本语言的功能体现出来了)。每次只要输入make命令即可。

我们首先获得当前的相对路径(你可以在终端输入pwd试一下这个命令),然后再获得当前内核的版本号,这样就可以直接获得当前内核的绝对路径。当然你可以直接输入当前内核版本,不过这样不方便移植,如果当前内核版本号与此文件中的版本号不同时,就得修改。所以上面的方法有很好的移植性,而且可读性也强。

这里会经常出现$(variable name)这样的字符串,其实这是对括号内变量的一种引用,具体可参考Makefile的相关语法规则。

3.make

完成上述两个文件后,在当前目录下运行make命令,就会生成hello.ko文件,即模块目标文件。

4.insmod,rmmod和dmesg

insmod命令可以使我们写的这个模块加入到内核中,但是一般我们要加上sudo。rmmod当然就是卸载这个模块了。我们在加载或卸载模块时都有一些提示语,即我们printk中显示的语句,这时候可以用dmesg命令来查看。

ok,第一个模块编程就这么简单,try一下!

Update 2011/04/03

本文所描述的程序在ubuntu系统下测试成功。其他的Linux发行版应适当修改源码目录,即修改LINUX_KERNEL_PATH。

对Makefile、Kconfig与.config文件的再次理解

12 9 月, 2010

虽然前文中对Makefile、Kconfig以及.config三个文件又过解释,但是在做过几个简单的例子后,对这三个文件有了更深入的理解,(本文参考了苏锦秀师姐的PPT)现在总结如下:

1.我们要在内核中增加程序(比如驱动程序),并且使这个驱动程序能够编译进内核,基本分为两大部分。首先我们要告诉内核“请您下次编译的时候捎带上我”,即需要我们进行内核的相关配置,这就需要对相关Makefie和Kconfig文件进行修改,以便让内核知道将要对这个新的驱动程序进行编译。而仅仅只告诉内核“我需要你编译我”还不行,更重要的是让内核真正的去“行动”,即编译内核。

2.Makefile文集是整个内核工程编译命令的集合。它根据配置情况,构造出需要编译的内核源码文件列表,然后分别编译,并把目标代码链接到一起,形成内核二进制文件。也就是说Makefile只是存储了源码文件构建目标文件的规则,具体是否按着规则去执行还要看那些配置变量。

3.我们进行make menuconfig时,会出现一个配置菜单,它是由各层Kconfig文件组成。Kconfig文件是以分布式的方式位于源码的各个子目录当中。最底层的Kconfig位于源码目录下的arch/x86/Kconfig。由此入口,使用source语句把需要的子Kconfig文件加入到上级目录的Kconfig中,以此递归下去。Kconfig文件控制配置菜单是否出现新驱动的配置选项。用户通过Kconfig文件产生的配置选项,来控制对新驱动的配置。

4.我们在配置菜单中进行的相关配置(【】,【*】,【M】),最终都会存储于.config文件当中,因此Kconfig文件跟这些配置结果并没 有直接的关系,只是提供了配置菜单中的配置选项。

在内核中新增驱动代码目录(2)

9 9 月, 2010

Step by Step

上文中,我们已经理解了Makefile与Kconfig的作用,那么我们现在要在内核中增加edsionteDriver驱动代码,并告诉内核“请您下次编译的时候捎带上我”。具体应该如何来做?首先应该在Makefile中添加相关驱动文件的编译信息,然后还得在Kconfig中添加这个新驱动对应的配置选项。

好了,开始吧!

我们首先在内核根目录下添加edsioteDriver驱动目录。如何将此驱动目录与上一级目录连接起来?那么就需要我们来做一些修改。在驱动目录的上一级目录中,我们找到Makefile文件,然后在在此文件中添加下面代码(最好在自己添加的代码前后加上注释,以便与原有代码区别):

#edsionteDriver 's starting
obj-y+=edsionteDriver/
#edsinteDriver's ending

这句话使得kbulid会将edsionteDriver目录列入到向下迭代的目标当中,通俗一点说就是在编译过程中会将我们写的这个驱动目录列入编译的范围之内。obj-y文件列表将会被链接到built-in.o,并最终编译到vmlinux的目标文件列表 。这个解释属于官方说法,事实上当前我也不是很清除这是神马意思,不过你只需了解obj-y开始的文件列表会被编译并连接至内核就可以了。

我们上文中所说obj-$(CONFIG_xxx):=(文件列表)这样的语句是定义变量,其中具体规则是:变量名 变量符 变量值。变量符可以是=,+=,:=,?=中的一个,其中+=是追加赋值符,实现对变量的追加赋值。其中$(CONFIG_XXX)是对CONFIG_XXX的引用,CONFIG_XXX的值会根据依赖关系以及用户输入来决定。比如如果是depend on依赖的话,那么用户可选择y或n;如果是上文中的tristate那么就是三态选项,即y、n或m。obj-m表示会将文件当作内核模块来编译,obj-n就可以忽略不进行处理。

注意:我们刚添加的语句只是对edsionteDriver目录起作用,至于这个目录下的各个子文件是要模块编译还是链入你和还要具体看我们edsionteDriver目录下的Makefile文件。

下面修改驱动目录子目录的Kconfig文件:

#edsionteDriver 's starting
source "drivers/edsionteDriver/Kconfig"
#edsinteDriver's ending

这条脚本是将edsionteDriver目录中的Kconfig加入到上级目录中的Kconfig中。这样上级目录的Kconfig会引用到edsionteDriver目录中的Kconfig文件。通过这条语句我们也可以体会Kconfig的另一个作用:Kconfig文件将分布在当前目录下各个子目录的Kconfig都集中在自己的Kconfig当中。

通过上述两步修改就会让父目录感知到edsionteDriver的存在了。接下来我们来编写edsionteDriver目录以及其子目录下的各个Kconfig和Makefile文件。首先是edsionteDriver目录下的两个文件配置:

Makefile文件如下:

# drivers/edsionteDriver/Makefile
# just a test

obj-$(CONFIG_MYDRIVER) +=mydriver.o
obj-$(CONFIG_MYDRIVER_USER) +=mydriver_user.o
obj-$(CONFIG_KEY) +=key/

我们需要用户来选择是否编译mydriver.c,所以需要用CONFIG_MYDRIVER变量来保存选项值。下面两句类似我们一开始在父目录下增加edsionteDriver目录。
Kconfig文件如下:

# dirvers/edsionteDriver/Kconfig

menu "Test edsionteDriver"

config MYDRIVER
    tristate "mydriver test!"
config MYDRIVER_USER
    bool "user-space test"
    depends on MYDRIVER

source "drivers/edsionteDriver/key/Kconfig"
endmenu

首先menu和endmenu之间代码创建了一个菜单Test edsionteDriver,然后第一条config语句会建立一个名为”mydriver test”的配置菜单,用户在尽享此项目的配置时输入的配置信息(Y,N或M),会存储在config后面的MYDRIVER变量中。而这个变量会直接关系到Makefile文件中是否编译相应的文件。第二个config语句还是创建一个配置菜单的条目,只不过比较特殊的是这个菜单依赖于上面我们创建的菜单“mydriver test”(语法上是MYDRIVER_USER依赖于MYDRIVER)。具体含义是,只有当用户选择配置“mydriver test”时,才会出现下级菜单”user-sapce test”;否则这个下级菜单是不会出现的。

最后一条source语句的作用是将edsionteDriver目录下的子目录key/也加入到内核编译时扫描的对应当中。

接下来,就应该修改key目录下的相应文件了,这里不再详细说明,如果你成功修改了上述文件,修改key目录下的文件应该不困难吧?

这样的step by step并不是为了教会各位如何产生那个配置菜单,而是让各位在基本理解Kconfig,Makefile以及.config三者的基础上更深入的去学习。

windows 7 ultimate product key

windows 7 ultimate product key

winrar download free

winrar download free

winzip registration code

winzip registration code

winzip free download

winzip free download

winzip activation code

winzip activation code

windows 7 key generator

windows 7 key generator

winzip freeware

winzip freeware

winzip free download full version

winzip free download full version

free winrar download

free winrar download

free winrar

free winrar

windows 7 crack

windows 7 crack

windows xp product key

windows xp product key

windows 7 activation crack

windows7 activation crack

free winzip

free winzip

winrar free download

winrar free download

winrar free

winrar free

download winrar free

download winrar free

windows 7 product key

windows 7 product key