日志标签 ‘内核模块’

中断下半部-tasklet

2010年10月4日

本文包含那些内容?
tasklet机制概述;tasklet机制的使用方法;一个阐述tasklet机制调用关系的举例。
本文适合那些人阅读?
想了解linuxer;学习驱动开发的beginner;学习内核模块编程beginner;其他super linux NBer;
参考书籍:
Linux内核设计与实现
Linux操作系统原理与应用


tasklet的实现


tasklet(小任务)机制是中断处理下半部分最常用的一种方法,其使用也是非常简单的。正如在前文中你所知道的那样,一个使用tasklet的中断程序首先会通过执行中断处理程序来快速完成上半部分的工作,接着通过调用tasklet使得下半部分的工作得以完成。可以看到,下半部分被上半部分所调用,至于下半部分何时执行则属于内核的工作。对应到我们此刻所说的tasklet就是,在中断处理程序中,除了完成对中断的响应等工作,还要调用tasklet,如下图示。

tasklet由tasklet_struct结构体来表示,每一个这样的结构体就表示一个tasklet。在<linux/interrupt.h>中可以看到如下的定义:

tasklet_struct
{
	struct tasklet_struct *next;
	unsigned long state;
	atomic_t count;
	void (*func)(unsigned long);
	unsigned long data;
};

在这个结构体中,第一个成员代表链表中的下一个tasklet。第二个变量代表此刻tasklet的状态,一般为TASKLET_STATE_SCHED,表示此tasklet已被调度且正准备运行;此变量还可取TASKLET_STATE_RUN,表示正在运行,但只用在多处理器的情况下。count成员是一个引用计数器,只有当其值为0时候,tasklet才会被激活;否则被禁止,不能被执行。而接下来的func变量很明显是一个函数指针,它指向tasklet处理函数,这个处理函数的唯一参数为data。


使用tasklet


在使用tasklet前,必须首先创建一个tasklet_struct类型的变量。通常有两种方法:静态创建和动态创建。这样官方的说法仍然使我们不能理解这两种创建到底是怎么一回事。不够透过源码来分析倒是可以搞明白。

在<linux/interrupt.h>中的两个宏:

 464#define DECLARE_TASKLET(name, func, data) \
 465struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
 466
 467#define DECLARE_TASKLET_DISABLED(name, func, data) \
 468struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }

就是我们进行静态创建tasklet的两种方法。通过第一个宏创建的tasklet处于激活状态,再通过调度函数被挂起尽而被内核执行;而通过第二个宏创建的tasklet处于禁止状态。从两个宏的定义可以看到,所谓的静态创建就是直接定义个一个名为name的tasklet_struct类型的变量,并将宏中各个参数相应的赋值给这个name变量的各个成员。注意,两个宏在功能上差异就在于对name变量count成员的赋值上,具体原因在第一部分已经说明。也许你对ATOMIC_INIT这样的初始化方式感到疑惑,那么看完定义后,你就会一目了然:

 //在arch/x86/include/asm/atomic.h中
 15#define ATOMIC_INIT(i)  { (i) }
 //在linux/types.h中
 190typedef struct {
 191        int counter;
 192} atomic_t;

与静态创建相对的是动态创建,通过给tasklet_init函数传递一个事先定义的指针,来动态创建一个tasklet。这个函数源码如下。

 470void tasklet_init(struct tasklet_struct *t,
 471                  void (*func)(unsigned long), unsigned long data)
 472{
 473        t->next = NULL;
 474        t->state = 0;
 475        atomic_set(&t->count, 0);
 476        t->func = func;
 477        t->data = data;
 478}

相信你在阅读上面的代码是基本上没有什么难以理解的地方,不过这里还是要特别说明一下atomic_set函数:

  //在arch/x86/include/asm/atomic.h中
  35static inline void atomic_set(atomic_t *v, int i)
  36{
  37        v->counter = i;
  38}

首先tasklet_init当中,将&t->count传递给了此函数。也就是说将atomic_t类型的成员count的地址传递给了atomic_set函数。而我们在此函数中却要为count变量中的成员counter赋值。如果说我们当前要使用i,那么应该是如下的引用方法:t-》count.i。明白了吗?

ok,通过上述两种方法就可以创建一个tasklet了。同时,你应该注意到不管是上述那种创建方式都有func参数。透过上述分析的源码,我们可以看到func参数是一个函数指针,它指向的是这样的一个函数:

void tasklet_handler(unsigned long data);

如同上半部分的中断处理程序一样,这个函数需要我们自己来实现。

创建好之后,我们还要通过如下的方法对tasklet进行调度:

tasklet_schedule(&my_tasklet)

通过此函数的调用,我们的tasklet就会被挂起,等待机会被执行


一个举例


在此只分析上下两部分的调用关系,完整代码在这里查看。

//define a argument of tasklet struct
static struct tasklet_struct mytasklet;

static void mytasklet_handler(unsigned long data)
{
	printk("This is tasklet handler..\n");
}

static irqreturn_t myirq_handler(int irq,void* dev)
{
	static int count=0;
	if(count<10)
	{
		printk("-----------%d start--------------------------\n",count+1);
            	printk("The interrupt handeler is working..\n");
             	printk("The most of interrupt work will be done by following tasklet..\n");
            	tasklet_init(&mytasklet,mytasklet_handler,0);
          	tasklet_schedule(&mytasklet);
             	printk("The top half has been done and bottom half will be processed..\n");
	}
	count++;
      	return IRQ_HANDLED;
}

从代码中可以看到,在上半部中通过调用tasklet,使得对时间要求宽松的那部分中断程序推后执行。

printf到printk的转变

2010年9月15日

昨天下午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!

2010年9月13日

学习内核模块编程,第一个小程序当然是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。

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

2010年9月9日

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三者的基础上更深入的去学习。

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

2010年9月8日

Step by Step

如果学习Linux下驱动开发,那么本文所述的“在内核中新增驱动代码目录”应该是一个最基本的知识点了。那么如何将自己写好的驱动程序新增到内核?本文将一步一步的教会你。

1.在正式开始之前,请先切换到root用户:su root。不过可能会会出现问题:不管你输入什么密码,都会提示你错误(很可能是因为之前你根本未设置过密码)。这时候我们来修改root用户的密码:

sudo passwd root

输入两次后,即可修改完毕,这下再su root就可以成功切换到root用户。

2.你可以现在试着在终端输入make menuconfig,终端会提示你:make: *** 没有规则可以创建目标“menuconfig”。这是因为menuconfig涉及到图形界面,所以我们得安装一些依赖包(ubuntu下):sudo apt-get install libncurses5-dev。

3.在一般的教程中,都会提到.config文件,而且这个文件就位于内核代码的根目录下。因此我会输入命令:ls -a来寻找.config。可是找来找去都没有这个文件的踪影。这是为什么?这是因为在这之前,你从来没有进行过内核配置,所以当然就不会生成.config文件了。解决的方法也很简单,有了上面两步的准备工作,那么你应该会成功进入配置用户界面,然后什么也不做,保存退出即可。那么你再ls一下,你可以发现.config已经存在了。

在开始向加入驱动代码之前,我们先了解三项基本步骤:

(1)将编好的源代码复制到Linux内核源代码的相应目录

(2)在目录的Kconfig文件中增加新源代码对应项目的编译配置选项

(3)在目录的Makefile文件中增加对新源代码的编译条目

在完成上述三项工作之前,我们先看一下我们要新增的驱动的树形结构。比如我们写的驱动程序均放在edsionteDriver目录,在此目录中包含Kconfig,Makefile和test.c三个文件,以及Key和led两个目录。我们先提前创建好这些文件,请注意本文只是为了演示说明,如果实际应用,像key,led以及test.c这样的文件都是有实际意义的。那么现在复制到内核源码目录下的driver/目录下即可。

|– edsionteDriver
|    |– Kconfig
|    |– Makefile
|    |– key
|        |– Kconfig
|        |– Makefile
|        |– mykey.c
|    |– mydriver.c
|    |– mydriver_user.c

现在我们完成了第一步工作,你应该注意到,我们现在只是创建了各个目录下的Kconfig和Makefile文件,并没添加相关内容,所以接下来我们就来进行这两个文件的编写。

对于初学者来说,直接学习Makefile以及Kconfig的编写可能会有些眩晕甚至排斥学习,不过我们可以先了解这两个文件在实际的内核分析中有什么作用。一般来说,对于内核这个庞大的网络,想要快速定位你所关心的代码就需要首先分析某个目录下的Makefile以及Kconfig文件,它们可是我们分析内核代码的goole map。

比如我们要分析ext3类型的文件系统,那么我们进入源码目录下fs/ext3/目录中,我们打开此目录的Makefile文件:

  1 #
  2 # Makefile for the linux ext3-filesystem routines.
  3 #
  4
  5 obj-$(CONFIG_EXT3_FS) += ext3.o
  6
  7 ext3-y  := balloc.o bitmap.o dir.o file.o fsync.o ialloc.o inode.o \
  8            ioctl.o namei.o super.o symlink.o hash.o resize.o ext3_jbd.o
  9
 10 ext3-$(CONFIG_EXT3_FS_XATTR)     += xattr.o xattr_user.o xattr_trusted.o
 11 ext3-$(CONFIG_EXT3_FS_POSIX_ACL) += acl.o
 12 ext3-$(CONFIG_EXT3_FS_SECURITY)  += xattr_security.o

我们应该先注意到7,8行,其定义了ext3变量(-y说明是多文件模块的定义,可以先忽略)。这里的定义变量类似于C语言中的宏定义,就是用ext3代替后面的.o文件列表。那么现在我们就可以知道与ext3模块最直接相关的就是后面这些文件对应的.c以及.h文件了,这些文件在源码相应的目录下都可以找到。那么ext3.o是否被编译取决于第的CONFIG_EXT3_FS,这个变量的值一般取y或n(甚至m)。它一般对应的是用户在配置界面时的输入。想要了解配置界面的菜单选项,就得看Kconfig文件。由于我们只关心EXT3_FS这个选项,因此我们相应的找到EXT3_FS这个选项的配置语句即可:

config EXT3_FS
	tristate "Ext3 journalling file system support"
	select JBD
	help
	  This is the journalling version of the Second extended file system
	  (often called ext3), the de facto standard Linux file system
	  (method to organize files on a storage device) for hard disks.
         #Other code was deleted

上述代码中,select说明只有JBD被配置,EXT3_FS这个配置项目才会在配置菜单上出现(事实上两者有更具体的依赖关系,可参考相关语法)。在配置菜单上会显示tristate后面的字符串,当用户选择配置此条目的情况下(有y,m和n三态选项),对应在Makefile文件中的CONFIG_EXT3_FS就对应为y。即obj-y的情况下,ext3.o菜会被编译。

现在将Makefile和Kconfig文件再串通起来想想,你应该会明白它们的作用。

一般来说,Makefile定义了根据该子目录下的源码文件构建目标文件的规则。像我们刚说的那个变量的定义以及根据CONFIG_EXT3_FS选项是否编译ext3.c文件。至于这些规则是否被执行,就取决于用户在配置菜单上是否选择配置这个选项,而这个配置菜单中配置选项就对应Kconfig文件。而且用户输入的配置结果会记录在.config文件当中。

通过上面的简单举例,我们可以先大致了解Makefile、Kconfig以及.config三者之间的关系以及作用。我们刚才分析的是内核中已经写好的代码的配置规则。对于我们上面所说的新增edsionteDriver驱动,应该如何添加?

具体添加过程请参见:在内核中新增驱动代码目录(2)。

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