Chinaunix首页 | 论坛 | 博客
  • 博客访问: 120358
  • 博文数量: 61
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 230
  • 用 户 组: 普通用户
  • 注册时间: 2014-04-26 11:35
个人简介

实践Linux的理论

文章存档

2015年(1)

2014年(60)

我的朋友

分类: 其他平台

2014-04-28 20:51:51

1.模块代码的编写

以简单的hello world模块为例
创建文件/home/adamas/hello-module/hello.c
/*                                                     
 * $Id: hello.c,v 1.5 2004/10/26 03:32:21 corbet Exp $ 
 */                                                    
#include
#include
MODULE_LICENSE("Dual BSD/GPL");

static int hello_init(void)
{
 printk(KERN_ALERT "Hello, world\n");
 return 0;
}

static void hello_exit(void)
{
 printk(KERN_ALERT "Goodbye, cruel world\n");
}

module_init(hello_init);
module_exit(hello_exit);

2.构建模块(写Makefile)
1)你可以把模块代码放入到内核源代码树中,如在/drivers/char/下创建自定义的字符设备模块。
需要修改父目录的Makefile以及Kconfig,若自己创建了一个文件夹,还需要在这个文件夹中自定义Makefile和Kconfig。方法省略了。
2)你也可以在内核源代码树外构建模块。如我们现在采用的方式:
创建文件/home/adamas/hello-module/Makefile
  obj-m  := hello.o
  KERNELDIR ?= /lib/modules/$(shell uname -r)/build   #若目标内核不是对应本系统可直接指定
  PWD       := $(shell pwd)
 default:                   #或者是modules:
      $(MAKE) -C $(KERNELDIR) SUBDIRS=$(PWD) modules
 这是一个最简单的。
若有多个模块,可以写成
obj-m  := hello.o another.o 
若一个模块有多个源文件,那么再增加一行就可以了
obj-m  := hello.o
hello-objs  := hello1.o hello2.o hello3.o

然后make就可以看到多了一些文件,其中就有hello.ko
-bash-3.2$ make
make -C /lib/modules/2.6.26.6-49.fc8/build SUBDIRS=/home/b/w/adamas/C/hello-module LDDINCDIR=/home/b/w/adamas/C/hello-module/../include modules
make[1]: Entering directory `/usr/src/kernels/2.6.26.6-49.fc8-i686'
  CC [M]  /home/b/w/adamas/C/hello-module/hello.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/b/w/adamas/C/hello-module/hello.mod.o
  LD [M]  /home/b/w/adamas/C/hello-module/hello.ko
make[1]: Leaving directory `/usr/src/kernels/2.6.26.6-49.fc8-i686'
-bash-3.2$ ll
total 152
-rw-r--r-- 1 adamas member   400 2008-12-28 18:56 hello.c
-rw-r--r-- 1 adamas member 61842 2008-12-28 19:15 hello.ko
-rw-r--r-- 1 adamas member   497 2008-12-28 19:15 hello.mod.c
-rw-r--r-- 1 adamas member 33436 2008-12-28 19:15 hello.mod.o
-rw-r--r-- 1 adamas member 29424 2008-12-28 19:15 hello.o
-rw-r--r-- 1 adamas member   314 2008-12-28 19:15 Makefile
-rw-r--r-- 1 adamas member     0 2008-12-28 19:01 Module.markers
-rw-r--r-- 1 adamas member    48 2008-12-28 19:15 modules.order
-rw-r--r-- 1 adamas member     0 2008-12-28 19:01 Module.symvers

3.安装模块
此步骤是“把模块代码放入到内核源代码树中”时,需要做的
在Makefile中增加:
modules_install:
 $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install

然后指行:
-bash-3.2$ make modules_install
将模块安装到/lib/modules/$(VERSION) /kernel/下的相应位置

下面给出一个典型的Makefile
obj-m := hello.o
# Assume the source tree is where the running kernel was built
# You should set KERNELDIR in the environment if it's elsewhere
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
# The current directory is passed to sub-makes as argument
PWD := $(shell pwd)

modules:
 $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

modules_install:
 $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install

clean:
 rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
.PHONY: modules modules_install clean

4.载入模块

加载模块需要超级用户权限
-bash-3.2$ insmod ./hello.ko 
程序的输出如果不出现在终端 则会写进 /var/log/syslog 文件中 
-bash-3.2$ cat /var/log/syslog |grep world 
Mar 16 12:14:53 shana kernel: [ 5937.529297] Hello, world 
Mar 16 12:16:05 shana kernel: [ 6009.439036] Goodbye, cruel world 
查看加载模块 
-bash-3.2$ lsmod 
删除模块 
-bash-3.2$ rmmod hello 
modprobe提供了模块依赖性分析,错误智能检查,错误报告以及许多其它功能和选项。强烈建议使用!

-bash-3.2$ modeprobe hello

下面转一下陈莉君老师博客的文章
**************************************************************************************************************************************
内核模块编程之入门

内核模块是Linux内核向外部提供的一个插口,其全称为动态可加载内核模块(Loadable Kernel Module,LKM),我们简称为模块。Linux内核之所以提供模块机制,是因为它本身是一个单内核(monolithic kernel)。单内核的最大优点是效率高,因为所有的内容都集成在一起,但其缺点是可扩展性和可维护性相对较差,模块机制就是为了弥补这一缺陷。

一、 什么是模块

模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行,这与运行在用户空间的进程是不同的。模块通常由一组函数和数据结构组成,用来实现一种文件系统、一个驱动程序或其他内核上层的功能。

二、 编写一个简单的模块

模块和内核都在内核空间运行,模块编程在一定意义上说就是内核编程。因为内核版本的每次变化,其中的某些函数名也会相应地发生变化,因此模块编程与内核版本密切相关。

针对2.6内核

1.程序举例
hellomod.c
001
// hello world driver for Linux 2.6
004  #include
005  #include
006  #include
/* 必要的头文件*/
009  static int __init lkp_init( void )
010 {
011      printk("<1>Hello,World! from the kernel space...\n");
012      return 0;
013  }
015  static void __exit lkp_cleanup( void )
016  {
017           printk("<1>Goodbye, World! leaving kernel space...\n");
018  }
020  module_init(lkp_init);
021  module_exit(lkp_cleanup);
022 MODULE_LICENSE("GPL");
模块编程属于内核编程,因此,除了对内核相关知识有所了解外,还需要了解与模块相关的知识。
1.应用程序与内核模块的比较
为了加深对内核模块的了解,表一给出应用程序与内核模块程序的比较。
表一 应用程序与内核模块程序的比较 
C语言应用程序  内核模块程序
使用函数 Libc库 内核函数
运行空间 用户空间 内核空间
运行权限 普通用户 超级用户
入口函数 main() module_init()
出口函数 exit() module_exit()
编译 Gcc –c Makefile
连接 Gcc insmod
运行 直接运行 insmod
调试
Gdb
kdbug, kdb,kgdb等
 
从表一我们可以看出,内核模块程序不能调用libc库中的函数,它运行在内核空间,且只有超级用户可以对其运行。另外,模块程序必须通过module_init()和module-exit()函数来告诉内核“我来了”和“我走了”。

2.内核符号表
如前所述,Linux内核是一个整体结构,像一个圆球,而模块是插入到内核中的插件。尽管内核不是一个可安装模块,但为了方便起见,Linux把内核也看作一个“母”模块。那么模块与模块之间如何进行交互呢,一种常用的方法就是共享变量和函数。但并不是模块中的每个变量和函数都能被共享,内核只把各个模块中主要的变量和函数放在一个特定的区段,这些变量和函数就统称为符号。到低哪些符号可以被共享? Linux内核有自己的规定。对于内核这个特殊的母模块,在kernel/ksyms.c中定义了从中可以“移出”的符号,例如进程管理子系统可以“移出”的符号定义如下:
/* 进程管理 */
EXPORT_SYMBOL(do_mmap_pgoff);
EXPORT_SYMBOL(do_munmap);
EXPORT_SYMBOL(do_brk);
EXPORT_SYMBOL(exit_mm);
    EXPORT_SYMBOL(schedule);
    EXPORT_SYMBOL(jiffies);
    EXPORT_SYMBOL(xtime);
你可能对这些变量和函数已经很熟悉。其中宏定义EXPORT_SYMBOL()本身的含义是“移出符号”。为什么说是“移出”呢?因为这些符号本来是内核内部的符号,通过这个宏放在一个公开的地方,使得装入到内核中的其他模块可以引用它们。
实际上,仅仅知道这些符号的名字是不够的,还得知道它们在内核地址空间中的地址才有意义。因此,内核中定义了如下结构来描述模块的符号:
struct module_symbol
{
        unsigned long value; /*符号在内核地址空间中的地址*/
        const char *name;   /*符号名*/

};
我们可以从/proc/ksyms文件中读取所有内核模块“移出”的符号,这所有符号就形成内核符号表,其格式如下:
内存地址      符号名       [所属模块]
在模块编程中,可以根据符号名从这个文件中检索出其对应的地址,然后直接访问该地址从而获得内核数据。第三列“所属模块”指符号所在的模块名,对于从内核这一母模块移出的符号,这一列为空。
  模块加载后,2.4内核下可通过 /proc/ksyms、 2.6 内核下可通过/proc/kallsyms查看模块输出的内核符号

3.模块依赖

   如前所述,内核符号表记录了所有模块可以访问的符号及相应的地址。当一个新的模块被装入内核后,它所申明的某些符号就会被登记到这个表中,而这些符号可能被其他模块所引用,这就引出了模块依赖这个问题。

   一个模块A引用另一个模块B所移出的符号,我们就说模块B被模块A引用,或者说模块A依赖模块B。如果要链接模块A,必须先链接模块B。这种模块间相互依赖的关系就叫模块依赖。

4.模块引用计数器

   为了确保模块安全地卸载,每个模块都有一个引用计数器。当执行模块所涉及的操作时就递增计数器,在操作结束时就递减这个计数器;另外,当模块B被模块A引用时,模块B的引用计数就递增,引用结束,计数器递减。什么时候可以卸载这个模块?当然只有这个计数器值为0的时候,例如,当一个文件系统还被安装在系统上时就不能将其卸载,当这个文件系统不再被使用时,引用计数器就为0,于是可以卸载。

四.模块编译

   Linux 中最重要的软件开发工具是 GCC。GCC 是 GNU 的 C 和 C++ 编译器。但是,在大型的开发项目中,通常有几十到上百个的源文件,如果每次均手工键入 gcc 命令进行编译的话,则会非常不方便。因此,人们通常利用 make 工具来自动完成编译工作。利用这种自动编译可大大简化开发工作,避免不必要的重新编译。这些工作包括:如果仅修改了某几个源文件,则只重新编译这几个源文件;如果某个头文件被修改了,则重新编译所有包含该头文件的源文件。

1.编译工具make

实际上,make 工具通过一个称为 Makefile 的文件来完成并自动维护编译工作。Makefile 需要按照某种语法进行编写,其中说明了如何编译各个源文件并连接生成可执行文件,并定义了源文件之间的依赖关系。下面给出2.6 内核模块的Makefile模板(有点复杂,实在看不懂,可以仅作参考)
# Makefile2.6
ifneq ($(KERNELRELEASE),)
#kbuild syntax. dependency relationshsip of files and target modules are listed here.
mymodule-objs := file1.o file2.o
   obj-m := mymodule.o 
else
  PWD  := $(shell pwd)
  KVER ?= $(shell uname -r)
  KDIR := /lib/modules/$(KVER)/build
all:       
    $(MAKE) -C $(KDIR) M=$(PWD) 
clean:
   rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions
endif
 
KERNELRELEASE是在内核源码的顶层Makefile中定义的一个变量,在第一次读取执行此Makefile时,KERNELRELEASE没有被定义, 所以make将读取执行else之后的内容。如果make的目标是clean,直接执行clean操作,然后结束。当make的目标为all时,-C $(KDIR) 指明跳转到内核源码目录下读取那里的Makefile;M=$(PWD) 表明然后返回到当前目录继续读入、执行当前的Makefile。当从内核源码目录返回时,KERNELRELEASE已被被定义,kbuild也被启动去解析kbuild语法的语句,make将继续读取else之前的内容。

我们可以用KERNELRELEASE是否定义,来区别是在内核树内构造;还是在内核树外构造。

else之前的内容为kbuild语法的语句, 指明模块源码中各文件的依赖关系,以及要生成的目标模块名。mymodule-objs := file1.o file2.o表示mymoudule.o 由file1.o与file2.o 连接生成。obj-m := mymodule.o表示编译连接后将生成mymodule.o模块。
补充一点,"$(MAKE) -C $(KDIR) M=$(PWD)"与"$(MAKE) -C $(KDIR) SUBDIRS =$(PWD)"的作用是等效的,后者是较老的使用方法。推荐使用M而不是SUBDIRS,前者更明确。
通过以上比较可以看到,从Makefile编写来看,在2.6内核下,内核模块编译不必定义复杂的CFLAGS,而且模块中各文件依赖关系的表示简洁清晰。

  例如,要把第一部分中所提到的hellomod.c编译成一个模块,简单的使用GCC无法完成。最好写一个Makefile文件,内容如下(以后写其他模块的Makefile可以如法炮制,改变一下模块名字即可)
  
obj-m += hellomod.o
all:
    make -C /usr/src/linux  M=$(PWD) modules
clean:
    make -C /usr/src/linux  M=$(PWD) clean

   在这里要特别说明的是,/usr/src/linux 中的Linux目录,因发布版不同而不同,比如在我安装的Ubuntu下为linux-headers-2.6.20.16-generic,因此,只需要建立一个针对Linux的符号链接就可以。
有了Makefile,执行make命令,会自动形成相关的后缀为.o和.ko文件。
  到此,模块编译好了,该把它插入到内核了:
  如:insmod hellomod.ko

  是否插入成功可以通过dmesg命令查看,屏幕最后几行的输出就是你程序中输出的内容。
  当不再模块需要时,可以通过rmmod命令移去。

modutils是管理内核模块的一个软件包。可以在任何获得内核源代码的地方获取Modutils(modutils-x.y.z.tar.gz)源代码,然后选择最高级别的patch.x.y.z等于或小于当前的内核版本,安装后在/sbin目录下就会有insomod、rmmod、ksyms、lsmod、modprobe等实用程序。当然,通常我们在加载Linux内核时,modutils已经被载入。
1.Insmod命令
   调用insmod程序把需要插入的模块以目标代码的形式插入到内核中。在插入的时候,insmod自动调用init_module()函数运行。注意,只有超级用户才能使用这个命令,其命令格式为:
# insmod  [path] modulename.c
2. rmmod命令
   调用rmmod程序将已经插入内核的模块从内核中移出,rmmod会自动运行cleanup_module()函数,其命令格式为:
  #rmmod  [path] modulename.c
3.lsmod命令
   调用lsmod程序将显示当前系统中正在使用的模块信息。实际上这个程序的功能就是读取/proc文件系统中的文件/proc/modules中的信息,其命令格式为:
    #lsmod
4.ksyms命令
   ksyms这个程序用来显示内核符号和模块符号表的信息。与lsmod相似,它的功能是读取/proc文件系统中的另一个文件/proc/kallsyms
*************************************************************************************************************************************
内核模块编程之进阶
编写带参数的中断模块
在此,我们将编写一个模块,其中有一个中断函数,当内核接收到某个 IRQ 上的一个中断时会调用它。先给出全部代码,读者自己调试,把对该程序的理解跟到本贴后面。

----------------------------------------
#include
#include
#include

static int irq;
static char *interface;

//MODULE_PARM_DESC(interface,"A network interface");  2.4内核中该宏的用法
molule_parm(interface,charp,0644)                             //2.6内核中的宏
//MODULE_PARM_DESC(irq,"The IRQ of the network interface");
module_param(irq,int,0644);

static irqreturn_t myinterrupt(int irq, void *dev_id, struct pt_regs *regs)
{
    static int mycount = 0;
    if (mycount < 10) {
        printk("Interrupt!\n");
        mycount++;
    }
    return IRQ_NONE;
}
static int __init myirqtest_init(void)

    printk ("My module worked!11111\n");
    if (request_irq(irq, &myinterrupt, SA_SHIRQ,interface, &irq)) {
        printk(KERN_ERR "myirqtest: cannot register IRQ %d\n", irq);
        return -EIO;
    }
    printk("%s Request on IRQ %d succeeded\n",interface,irq);
    return 0;
}
static void __exit myirqtest_exit(void)
{
    printk ("Unloading my module.\n");
    free_irq(irq, &irq);
    printk("Freeing IRQ %d\n", irq);
    return;
}
module_init(myirqtest_init);
module_exit(myirqtest_exit);
MODULE_LICENSE("GPL");
----------------------------------------
这里要说明的是,在插入模块时,可以带两个参数,例如
insmod myirq.ko interface=eth0 irq=9
其中 具体网卡 irq的值可以查看 cat /proc/interrupts
动手吧!以此为例,可以设计出各种各样有价值的内核模块,贴出来体验分享的快乐吧。

在上一部分“编写带有参数的中断模块”中,这个看似简单的程序,你调试并运行以后思考了哪些方面的问题?

(1)给模块传递参数,使得这个模块的扩展和应用有了空间,例如,在我的机器上查看/proc/interrupts
   
          CPU0       
  0:   10655925    IO-APIC-edge      timer
  1:       9148         IO-APIC-edge      i8042
  6:          4          I O-APIC-edge      floppy
  7:          0          IO-APIC-edge      parport0
  8:          3           IO-APIC-edge      rtc
  9:          0           IO-APIC-fasteoi   acpi
12:      41970      IO-APIC-edge      i8042
15:     106157      IO-APIC-edge      ide1
16:      57823      IO-APIC-fasteoi   ioc0
17:       8090       IO-APIC-fasteoi   eth0
18:        245       IO-APIC-fasteoi   uhci_hcd:usb1, Ensoniq AudioPCI, usb
NMI:          0 
LOC:   10249542 
ERR:          0
MIS:          0

然后,在插入模块时,你对每个中断都作为参数试运行一下,看看会出现什么问题?思考一下irq为0,3等值时,
为什么插入失败?这就引出中断的共享和非共享问题,从而促使你分析Linux对共享的中断到底如何处理,
共享同一个中断号的中断处理程序到底如何执行?

2. 对于myinterrupt()函数,可以进行怎样的改进,使得这个自定义的中断处理程序变得有实际意义?
static irqreturn_t myinterrupt(int irq, void *dev_id, struct pt_regs *regs)
{
    static int mycount = 0;
    if (mycount < 10) {
        printk("Interrupt!\n");
        mycount++;
    }
    return IRQ_NONE;
}

比如,对于网卡中断,在此收集每一次中断发生时,从网卡接收到的数据,把其存入到文件中。以此思路,随你考虑应用场景了。

3. 模块机制给Linux内核的扩展和应用提供了方便的入口,在我们内核之旅
 的电子杂志部分,针对内核相关的内容,每一部分都有相对比较实际的内核应用题目,感兴趣者可以去实践,
 前提是对内核相关内容的彻透理解。
阅读(1108) | 评论(0) | 转发(0) |
0

上一篇:linux下VFS

下一篇:Linux字符设备驱动

给主人留下些什么吧!~~