手机版
你好,游客 登录 注册
背景:
阅读新闻

Linux设备驱动程序学习笔记

构造和运行模块

[日期:2011-08-28] 来源:Linux社区  作者:en_wang [字体: ]
设置测试系统

1.在kernel.org的镜像网站上获得一个“主线”内核。

2.准备好一个内核源代码树。

2.6内核的模块要和内核源代码树中的目标文件链接,通过这种方式,构造一个更加健壮的模块装载器。

Hello World模块

#include<linux/init.h>

#inlcude<linux/module.h>

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);

涉及知识点:

module_init(init_function),module_exit(cleanup_function),

MODULE_LICENSE,

printk,KERN_ALERT等,

insmod,modprobe,rmmod,

makefile,make,

 

根据系统传递消息行机制的不同,读者得到的输出结果可能不一样。需要特别指出的是,上面的屏幕输出是在文本控制台上得到的;如果读者在某个与运行在windows系统下的终端仿真器中运行insmod,rmmod,则不会在屏幕上看到任何输出。实际上,他可能输出到某个系统日志文件中,比如/var/log/messages

 

核心模块与应用程序的对比

应用程序 vs 核心模块 vs 事件驱动的应用程序

 

用户空间 vs 内核空间

cpu在被设计时,有保护系统软件不被应用程序破坏的功能。且这种保护功能分为不同级别,当

cpu中存在多个级别时,unix通常使用最高级和最低级,即:超级用户级和用户级,也即内核空间和用户空间。

内核中的并发

常见引起并发原因:1.linux系统中通常正在运行多个并发进程,并且可能有多个进程同时使用我们的驱动程序2.大多数设备能够中断处理器,而中断处理程序异步运行,而且可能在驱动程序正试图处理其他任务时被调用。3.linux可以运行在多处理器上,因此可能同时有多个处理器在使用该进程。

 

当前进程

Current 在<asm.current.h>中定义,是一个指向struct task_struct的指针,而task_struct结构在<linux/sched.h>中定义。

Current指针指向当前正在运行的进程;

在open,read等系统调用的执行过程中,当前进程指的是调用这些系统调用的进程。

struct task_struct *current;

current->id :当前进程的id

current->comm. :当前进程的命令名

 

其他细节

1.      如果我们需要大的结构,应该调用动态分配该结构,而不是声明大的自动变量。

2.      常见函数前加有__两个下划线,这种函数通常是接口的底层组件,实际上,双下划线是告诉程序员:谨慎使用,后果自负

3.      内核代码不支持浮点数运算。

 

编译与装载

编译模块

1.       确保安装了正确版本的编译器,模块工具,和其它必要的工具,内核文档Documentation/Changes文件列出了需要的工具版本。

2.       makefile:obj-m:由内核构造系统使用的makefile符号,用来确定当前目录中应构造哪些模块。

 

如果已经构造了KERNELRELEASE,则说明是从内核构造系统调用的,因此可以利用其内建语句。

ifneq ($(KERNELRELEASE),)

       obj-m :=hello.o

否则,是直接从命令行调用的,这时要调用内核构造系统。

else

       KERNELDIR ?=/lib/modules/$(shell uname -r)/build\

       PWD      :=$(shell pwd)

default:

       $(MAKE) –C $(KERNELDIR) M=$(PWD) modules

endif

 

装载和卸载

1.      只有系统调用函数的名字前边带有sys_前缀。

2.      modprob区别于insmod :modprob会考虑要装载的模块是否引用了一些当前内核不存在的符号,如果是存在,modprob回查找,而insmod会失败,并在系统日志文件中记录”unresolved symbols”消息。

3.      lsmod列出当前装载到内核中的所有模块。

 

版本依赖

 

UTS_RELEASE:一个描述内核版本的字符串,例如:2.6.10

LINUX_VERSION_CODE:内核版本的二进制表示,版本中每一部分对应一个字节。如2.6.10对应的LINUX_VERSION_CODE是132618

KERNEL_VERSION(major,minor,release):创建参数版本,这个宏在我们需要将当前版本和一个已知的检查点比较时非常有用。

 


平台依赖

 

EXPORT_SYMBOL(name)

EXPORT_SYMBOL_GPL(name)

这两个宏均用于将给定的符号导出到模块外部。_GPL版本使得要导出的模块只能被GPL许可证下的模块使用。

内核符号表

 

 

 

预备知识

MODULE_LICENSE( ),MODULE_AUTHOR( ),MODULE_DESCRIPTION( ),

MODULE_VERSION( ),MODULE_ALIAS( ),MODULE_DEVICE_TABLE( ),

 

初始化和关闭

__init,__initdata

__devinit,__devinitdata,

大部分的注册函数都带有register_前缀

__exit,__exitdata

初始化函数:

Static int __init initialization_function(void)

{

/*初始化代码*/

}

Module_init(initialization_function);

 

 


清除函数:

Static void __exit cleanup_function(void){

/*清除代码*/

}

Module_exit(cleanup_function);


初始化过程中的错误处理

举例:该段代码注册了三个设备,在出错的时候使用goto语句,它将只撤销出错时刻以前所成功注册的那些设施。

Int __init my_init_function(void)

{

Int err;

/*使用指针和名称注册*/

Err = register_this(ptr1,"skull");

If(err)  goto faile_this;

Err = register_that(ptr2,"skull");

If(err)  goto faile_that;

Err = register_those(ptr3,"skull")

If(err)  goto faile_those;

Return 0;  /*成功*/

 

Faile_those:unregister_that(ptr2,"skull");

Faile_that:unregister_this(ptr1,"skull");

Faile_this:return err;   /*返回错误*/

}


模块装载竞争

在支持某个设施的所有内部初始化完成之前,不要注册任何设施。

 

模块参数

#include<linux/moduleparam.h>

Module_param(variable,type,perm);

Module_param_array(name,type,num,perm);


用来创建模块参数的宏,用户可在装载模块时(或者对内建代码引导时)调整这些参数的值,其中的类型可以是:bool,charp,int,invbool,long,short,ushort,uint,ulong,intarray

 

在用户空间编写驱动程序

 

实际操作

源代码:

#include<linux/init.h>

#inlcude<linux/module.h>

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);

makefile:

ifneq ($(KERNELRELEASE),)

       obj-m :=hello.o

else

       KERNELDIR ?=/lib/modules/$(shell uname -r)/build\

       PWD      :=$(shell pwd)

default:

       $(MAKE) –C $(KERNELDIR) M=$(PWD) modules

endif

编译模块
#make
清除
#make clean
-----------
为了能够在终端显示信息,要修改
/lib/modules/2.6.10/build/include/linux/kernel.h
文 件的KERN_ALERT宏。
#define KERN_ALERT "<1>"
修改为
#define KERN_ALERT "<0>"

实际操作并未成功,不知道原因在哪
------------
安装模块
#insmod hello.ko
终端 显示
hello module init
查看已安装的模块
#lsmod
卸载模块
#rmmod hello
终 端显示
hello module exit
-----------
有以下几点要注意:
1,hello.c文件中调用的头 文件
init.h中的module_init(),module_exit()
kernel.h中的 printk(),KERN_ALERT
module.h中的MODULE_LICENSE()
2,Makefile文件中的核心是
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
1),-C $(KERNELDIR)
表示 在$(KERNELDIR)目录下执行make命令。
2),M=$(PWD)
表示包含$(PWD)下的Makefile文件。
3),modules
表 示模块编译。
4), 用到了ifneq...else...endif语句
由于开始还没定义KERNELRELEASE,所以只能执行 else分支。
而在执行
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
后,会在内核的 Makefile中定义KERNELRELEASE,当进入本Makefile时,
则只会执行ifneq的第一个分支,即
obj-m := hello.o
这一句话是非常重要的。事实上,这个Makefile做的本份工作就是它。
我们也可以用命令行的方式来编译:
在 Makefile中的内容写为:
obj-m := hello.o
然后在终端敲入:
#make -C /lib/modules/2.6.10/build M=/home/tmp modules

linux
【内容导航】
第1页:简介 第2页:构造和运行模块
第3页:字符设备驱动程序 第4页:并发与静态
第5页:高级字符驱动程序操作--ioctl 第6页:高级字符驱动学习--阻塞型I/0
第7页:时间、延迟及延缓操作 第8页:tasklet,工作队列,共享队列
第9页:分配内存 第10页:与硬件通信
第11页:中断处理 第12页:Linux设备模型1
第13页:Linux设备模型2 第14页:Linux设备模型3
相关资讯       Linux编程 
本文评论   查看全部评论 (0)
表情: 表情 姓名: 字数

       

评论声明
  • 尊重网上道德,遵守中华人民共和国的各项有关法律法规
  • 承担一切因您的行为而直接或间接导致的民事或刑事法律责任
  • 本站管理人员有权保留或删除其管辖留言中的任意内容
  • 本站有权在网站内转载或引用您的评论
  • 参与本评论即表明您已经阅读并接受上述条款