linux的第一个内核,第一个Linux 内核模块

二、内核模块编程的具体实现

第一步:首先我们来看一下程序的头文件

#include

#include

#include

这三个头文件是编写内核模块程序所必须的3个头文件 。说明:

1>由于内核编程和用户层编程所用的库函数不一样,所以它的头文件也和我们在用户层编写程序时所用的头文件也不一样。

2>我们在来看看在L inux中又是在那块存放它们的头文件

a.内核头文件的位置:/usr/src/linux-2.6.x/include/

b.用户层头文件的位置: /usr/include/

现在我们就明白了。其实我们在编写内核模块程序时所用的头文件和系统函数都和用层编程时所用的头文件和系统函数是不同的。

第二步:编写内核模块时必须要有的两个函数:

1>加载 函数:

static int  __init  init_fun(void)

{

//初始化代码

}

函数实例:

static int   __init hello_init(void)//不加void在调试时会出现报警

{

printk("hello world!/n");

return 0;

}

2>卸载函数无返回值

static void __exit cleaup_fun(void)

{

//释放代码

}

函数实例:

static void __exit  hello_exit(void)//不加void会出现报警,若改为static int也会报错,因为出口函数是不能返会值的

{

printk("bye,bye/n");

}

在模块编程中必须要有上面这两个函数;

_init和__exit是Linux内核的一个宏定义,使系统在初始化完成后释放该函数,并释放其所占内存。因此它的优点是显而易见的。所以建议大家啊在编写入口函数和出口函数时采用第二中方法。

(1)在 linux内核中,所有标示为 __init的函数在连接的时候都放在 .init.text这个区段内,此外,所有的 __init函数在区段 .initcall.init中还保存了一份函数指针,在初始化时内核会通过这些函数指针调用这些 __init函数,并在初始化完成后释放 init区段(包括 .init.text,.initcall.init等)。

(2)和 __init一样, __exit也可以使对应函数在运行完成后自动回收内存。

3>现在我们来看一下printk()函数

a.上面已经说了,我们在内核编程时所用的库函数和在用户态下的是不一样的。printk是内核态信息打印函数,功能和比准C库的printf类似。printk还有信息打印级别。

b.现在我们来看一下printk()函数的原型:

int printk(const char *fmt, ...)

消息打印级别:

fmt----消息级别:

#define KERN_EMERG "<0>" /*紧急事件消息,系统崩溃之前提示,表示系统不可用*/

#define KERN_ALERT "<1>" /*报告消息,表示必须立即采取措施*/

#define KERN_CRIT "<2>" /*临界条件,通常涉及严重的硬件或软件操作失败*/

#define KERN_ERR "<3>" /*错误条件,驱动程序常用KERN_ERR来报告硬件的错误*/

#define KERN_WARNING "<4>" /*警告条件,对可能出现问题的情况进行警告*/

#define KERN_NOTICE "<5>" /*正常但又重要的条件,用于提醒。常用于与安全相关的消息*/

#define KERN_INFO "<6>" /*提示信息,如驱动程序启动时,打印硬件信息*/

#define KERN_DEBUG "<7>" /*调试级别的消息*/

不同级别使用不同字符串表示,数字越小,级别越高。

c.为什么内核态使用printk()函数,而在用户态使用printf()函数。

printk()函数是直接使用了向终端写函数tty_write()。而printf()函数是调用write()系统调用函数向标准输出设备写。所以在用户态(如进程0)不能够直接使用printk()函数,而在内核态由于它已是特权级,所以无需系统调用来改变特权级,因而能够直接使用printk()函数。printk是内核输出,在终端是看不见的。我们可以看一下系统日志。

但是我们可以使用命令:cat /var/log/messages,或者使用dmesg命令看一下输出的信息。

第三步:加载模块和卸载模块

1>module_init(hello_init)

a.告诉内核你编写模块程序从那里开始执行。

b.module_init()函数中的参数就是注册函数的函数名。

2>module_exit(hello_exit)

a.告诉内核你编写模块程序从那里离开。

b.module_exit()中的参数名就是卸载函数的函数名。

说明:

我们一般在注册函数里进行一些初始化比如申请内存空间注册设备号等。那么我们就要在卸载函数进行释放我们所占有的资源。

(1)若模块加载函数注册了XXX,则模块卸载函数应该注销XXX

(2)若模块加载函数动态申请了内存,则模块卸载函数应该注销XXX

(3)若模块加载函数申请了硬件资源(中断,DMA通道)的占用,则模块卸载函数应该释放这些硬件资源。

(4)若模块加载函数开启了硬件,则卸载函数中一般要关闭硬件。

第四步:许可权限的声明

1>函数实例:

MODULE_LICENSE("Dual BSD/GPL");

2>此处可有可无,可以不加系统默认(但是会报警)

模块声明描述内核模块的许可权限,如果不声明LICENSE,模块被加载时,将收到内核的警告。

在Linux2.6内核中,可接受的LICENSE包括"GPL","GPL v2","GPL and additional rights","Dual BSD/GPL","Dual MPL/GPL","Proprietary"。

第五部:模块的声明与描述(可加可不加)

MODULE_AUTHOR(“author”);//作者

MODULE_DESCRIPTION(“description”);//描述

MODULE_VERSION(”version_string“);//版本

MODULE_DEVICE_TABLE(“table_info”);//设备表

对于USB ,PCI 等设备驱动,通常会创建一个MODULE_DEVICE_TABLE

MODULE_ALIAS(”alternate_name“);//别名

总结

经过以上五步(其实只要前四步)一个完整的模块编程就完成了。

第六步: 常用的模块编程命令:

1>在Linux系统中,使用lsmod命令可以获得系统中加载了的所有模块以及模块间的依赖关系

2>也可以用cat /proc/modules来查看加载模块信息

3>内核中已加载模块的信息也存在于/sys/module目录下,加载hello.ko后,内核中将包含/sys/module/hello目录,该目录下又包含一个refcnt文件和一个sections目录,在/sys/module/hello目录下运行tree -a可以看到他们之间的关系。

4>使用modinfo 命令可以获得模块的信息,包括模块的作者,模块的说明,某块所支持的参数以及vermagic.

但是,前面我们已经说过了。内核编程和用户层编程它们之间的编译

简单的makefile文件:

obj-m := hello.o

kernel_path=/usr/src/linux-headers-$(shell uname -r)

all:

make -C $(kernel_path) M=$(PWD) modules

clean:

make -C $(kernel_path) M=$(PWD) clean

obj -m:= hello.o //产生hello模块的目标

kernel_path //定义内核源文件目录

all:

make -C $(kernel_path) M=$(PWD) modules

//生成内核模块参数为内核源代码目录以及模块所在目录

clean:

make -C $(kernel_path) M=$(PWD) clean

//清除生成的模块文件以及中间文件

说明:

1>在all和clean下面的一行,即make之前必须用Table符隔开,不能用空格隔开,否则编译错误。

2>其中-C后指定的是Linux内核源代码的目录,而M=后指定的是hello.c和Makefile所在的目录

3.Makefile实例:

1 obj-m:=module.o

2

3

4 CURRENT_PATH :=$(shell pwd)

5 VERSION_NUM :=$(shell uname -r)

6 LINUX_PATH :=/usr/src/linux-headers-$(VERSION_NUM)

7

8 all :

9 make -C $(LINUX_PATH) M=$(CURRENT_PATH) modules

10 clean :

11 make -C $(LINUX_PATH) M=$(CURRENT_PATH) clean

现在我们就总体来实践一下,来体验一下。编写内核模块程序的乐趣

module.c

1 #include2 #include3 #include4 MODULE_LICENSE("Dual BSD/GPL");

5

6 static int __init hello_init(void)

7 {

8 printk("Hello world/n");

9 return 0;

10 }

11

12 static void __exit hello_exit(void)

13 {

14 printk("Bye Corne/n");

15

16 }

17 module_init(hello_init);

18 module_exit(hello_exit);

Makefile

1 obj-m:=module.o

2

3

4 CURRENT_PATH :=$(shell pwd)

5 VERSION_NUM :=$(shell uname -r)

6 LINUX_PATH :=/usr/src/linux-headers-$(VERSION_NUM)

7

8 all :

9 make -C $(LINUX_PATH) M=$(CURRENT_PATH) modules

10 clean :

11 make -C $(LINUX_PATH) M=$(CURRENT_PATH) clean

在终端输入make

think@ubuntu:~/work/moudule/mokua_biancheng$makemake -C /usr/src/linux-headers-2.6.32-25-generic M=/home/think/work/moudule/mokua_biancheng modules

make[1]:正在进入目录`/usr/src/linux-headers-2.6.32-25-generic'

Building modules, stage 2.

MODPOST 1 modules

make[1]:正在离开目录`/usr/src/linux-headers-2.6.32-25-generic'

think@ubuntu:~/work/moudule/mokua_biancheng$

think@ubuntu:~/work/moudule/mokua_biancheng$ sudo insmod module.ko

think@ubuntu:~/work/moudule/mokua_biancheng$ dmesg

[19011.002597] Hello world

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值