[摘要]tail -2 loading module ourdev DDB symbols added: 140232 bytes [e4gle@openbsd29]# 好,我们测试一下我们新创建的设备,用...
tail -2
loading module ourdev
DDB symbols added: 140232 bytes
[e4gle@openbsd29]#
好,我们测试一下我们新创建的设备,用dd命令来测试:
[e4gle@openbsd29]# dd if=/dev/ourdev of=/dev/fd/1 count=1 bs=100
hello world!
1+0 records in
1+0 records out
100 bytes transferred in 1 secs (100 bytes/sec)
[e4gle@openbsd29]#
现在我来通过一个测试程序来测试一下我们的ioctl调用是否工作.测试程序必须包括模块代码和头文件common.h:
#define MAXMSGLEN 100
struct ourdev_io {
int value;
char msg[MAXMSGLEN];
};
#define ODREAD_IOR('O', 0, struct ourdev_io)
#define ODWRITE _IOW('O', 1, struct ourdev_io)
#ifdef _KERNEL
/* open, close, read, ioctl */
#define cdev_ourdev_init(c,n) {
dev_init(c,n,open), dev_init(c,n,close), dev_init(c,n,read),
(dev_type_write((*))) lkmenodev, dev_init(c,n,ioctl),
(dev_type_stop((*))) lkmenodev, 0, (dev_type_select((*))) lkmenodev,
(dev_type_mmap((*))) lkmenodev }
#endif /* _KERNEL */
Now this is the program we'll use to test (chardevtest.c):
#include <sys/types.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <err.h>
#include "common.h"
int
main(void)
{
struct ourdev_io a;
int error, fd;
fd = open("/dev/ourdev", O_WRONLY);
if (fd == -1)
err(1, "open");
error = ioctl(fd, ODREAD, &a);
if (error == -1)
err(1, "ioctl");
printf("%d %s", a.value, a.msg);
bzero(a.msg, MAXMSGLEN);
strlcpy(a.msg, "cowsn", sizeof(a.msg));
a.value = 42;
error = ioctl(fd, ODWRITE, &a);
if (error == -1)
err(1, "ioctl");
bzero(&a, sizeof(struct ourdev_io));
error = ioctl(fd, ODREAD, &a);
if (error == -1)
err(1, "ioctl");
printf("%d %s", a.value, a.msg);
close(fd);
exit(0);
}
首先它读取存在的值,然后自己替换掉.最后它读取这个新的值并且打印出来,用来确定它们替换成功.
编译测试程序:
[e4gle@openbsd29]# gcc -o chardevtest chardevtest.c
[e4gle@openbsd29]#
运行:
[e4gle@openbsd29]# ./chardevtest
13 hello world!
42 cows
[e4gle@openbsd29]#
再用dd命令看看现在的内部字符应该是'cows'了.
★虚拟文件系统模块
增加一个虚拟文件系统是非常简单的.假如你要开发一个新的文件系统或者支持现存的文件系统,就需要写一个模块作为接口.同样的,假如需
要调试已经存在的文件系统,也需要那样
一个接口.必须确定你的内核不支持目标文件系统.
一个虚拟文件系统的模块的结构应该象如下定义:
struct lkm_vfs {
MODTYPE lkm_type;
int lkm_ver;
char*lkm_name;
u_longlkm_offset;
struct vfsconf*lkm_vfsconf;
};
和前面的例子差不多,我们也有个模块类型(LM_VFS),一个版本号,一个模块名和一个偏移值.在这个vfs模块的例子中,offset值是用不到的.
最后我们需要一个指向vfsconf结构
的指针,它包括了虚拟文件系统的操作向量以及一些其他信息(vfsconf结构在头文件/usr/include/sys/mount.h中定义).
此结构通过MOD_VFS宏来初始化:
MOD_VFS("nullfs", -1, &nullfs_vfsconf)
我们看看上面的代码,第一个参数是我们的模块名,第二个参数offset,这个参数在我们的vfs模块中无关紧要(前面说过,可以不用).最后一个参
数是我们的文件系统的结构.
在你的模块的外部接口中,你必须调用vfs_opv_init_explicit和vfs_opv_init_default来分配和初始化默认操作向量.因为文件系统被编译
进内核,所以通过定义在
/usr/src/sys/kern/vfs_conf.c里的vfs_opv_desc[]来在系统启动的时候装载.
一个需要注意的是当用需要用ld程序来链接多个源代码文件来为modload提供目标文件时,你必须用-r标记来创建一个可重定位的目标文件.
因为modload在把你的模块链接进
内核的同时需要用到ld程序.可以用modload的-d标记来察看ld运行的内部参数.
这儿是一个fs模块的完整代码 (nullmod.c):
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/mount.h>
#include <sys/exec.h>
#include <sys/lkm.h>
#include <sys/file.h>
#include <sys/errno.h>
/*
* 文件系统的操作结构
* 参考:/usr/src/sys/miscfs/nullfs/
*/
extern struct vfsops null_vfsops;
extern struct vnodeopv_desc null_vnodeop_opv_desc;
struct vfsconf nullfs_vfsconf = {
&null_vfsops, MOUNT_NULL, 9, 0, 0, NULL, NULL
};
/*
* 声明我们的模块结构,通过我们文件系统的模块名,offset和初始的vfsconf结构
*/
MOD_VFS("nullfs", -1, &nullfs_vfsconf)
/*
* 我们的外部接口.我们初始化文件系统并且用到了DISPATCH宏,在此例中没有用到句柄
*/
int
nullfsmod(lkmtp, cmd, ver)
struct lkm_table *lkmtp;
int cmd;
int ver;
{
vfs_opv_init_explicit(&null_vnodeop_opv_desc);
vfs_opv_init_default(&null_vnodeop_opv_desc);
DISPATCH(lkmtp, cmd, ver, lkm_nofunc, lkm_nofunc, lkm_nofunc)
}
好,编译安装它:
(一些其他的附加代码在/usr/src/sys/miscfs/nullfs里)
[e4gle@openbsd29]# gcc -D_KERNEL -I/sys -c null_subr.c
[e4gle@openbsd29]# gcc -D_KERNEL -I/sys -c null_vfsops.c
[e4gle@openbsd29]# gcc -D_KERNEL -I/sys -c null_vnops.c
[e4gle@openbsd29]# gcc -D_KERNEL -I/sys -c nullmod.c
[e4gle@openbsd29]# ld -r -o nullfs.o null_vfsops.o null_vnops.o null_subr.o nullmod.o
[e4gle@openbsd29]# modload -o nullfsmod -enullfsmod nullfs.o
[e4gle@openbsd29]# modstat
Type Id Off Loadaddr Size Info Rev Module Name
VFS 0-1 e0b84000 0003 e0b860d0 2 nullfs
[e4gle@openbsd29]#
ok,虚拟文件系统模块就说到这.
★其他类型的模块
这些模块被用来执行一些预定的模块类型所没有定义的操作.在我这个例子中我们将为网络协议栈里加入控制代码,然后打印出我们接收到的
tcp包的一些信息.
当我们在书写其他类型的模块时,我们必须要完整的检查一遍,确定没有预定的操作.例如,同样的操作模块不能被加载两次.这等于我们在往内
核中去写入模块.当然,
我们都会在模块加载和卸载的控制函数里去控制.
一个其他类型的模块结构象下面这样定义:
struct lkm_misc {
MODTYPE lkm_type;
int lkm_ver;
char*lkm_name;
u_longlkm_offset;
};
同样,我们首先有一个模块的类型(在这个例子中试LM_MISC),然后是lkm的版本,再接着是模块名和offset的值.在我的这个例子中offset值
没有用到,但在/usr/share/lkm/misc
提供的例子中(增加一个系统调用)offset被用来在系统调用表里面标记一个新的系统调用的位置.
用MOD_MISC宏来初始化该结构:
MOD_MISC("tcpinfo")
这里只有一个参数,指定了模块名.
当我们的模块被加载后,该模块把tcp_input函数的指针改为我们制定的new_input函数.新的函数会打印出mbuf里的包头的一些信息,然后
再调用原来的tcp_input函数.在做这些
之前,我们一定要确定同类的模块没有被加载.
对于这个模块一些值得注意的地方:首先运行这个模块时不适合传输大量的tcp包,printf()会变的很慢.大家试一下就知道.这个例子只是做测
试之用,其实大家可以想想我们既然
可以改变tcp协议栈里的函数指针,我们用模块来做一个tcp的内核后门也应该很容易,就留给大家思考吧,呵呵.第二,此代码原来是运行在
freebsd之上的,稍微修改了一下而已,
bsd系列的内核真是很相像.
以下是该模块的完整代码(tcpmod.c):
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/exec.h>
#include <sys/conf.h>
#include <sys/lkm.h>
#include <sys/socket.h>
#include <sys/protosw.h>
#include <net/route.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/in_pcb.h>
/*
* 我们将改变protosw结构中的TCP入口.
*/
extern struct protosw inetsw[];
/*
* 我们自定义的函数
*/
extern intlkmexists __P((struct lkm_table *));
extern char*inet_ntoa __P((struct in_addr));
static void new_input __P((struct mbuf *, ...));
static void (*old_tcp_input) __P((struct mbuf *, ...));
/*
* 声明我们的模块结构
*/
MOD_MISC("tcpinfo")
/*
* 我们的句柄函数,用来加载和卸载模块.
*/
int
tcpmod_handler(lkmtp, cmd)
struct lkm_table *lkmtp;
int cmd;
{
int s;
switch(cmd) {
case LKM_E_LOAD:
/*
* 确定此模块是第一次加载使用
*/
if (lkmexists(lkmtp))
return(EEXIST);
/*
* 阻赛网络协议进程,我们把tcp_input函数指针改成我们自己的包装函数.
*/
s = splnet();
old_tcp_input = inetsw[2].pr_input;
inetsw[2].pr_input = new_input;
splx(s);
break;
case LKM_E_UNLOAD:
/*
* 当模块退出时返回原来的结构
*/
s = splnet();
inetsw[2].pr_input = old_tcp_input;
splx(s);
break;
}
return(0);
}
/*
* 我们的外部接口,没有做什么,用到了DISPATCH宏
*/
int
tcpinfo(lkmtp, cmd, ver)
struct lkm_table *lkmtp;
int cmd;
int ver;
{
DISPATCH(lkmtp, cmd, ver, tcpmod_handler, tcpmod_handler, lkm_nofunc)
}
/*
* 定义我们自己的包装的tcp_input函数.假如mbuf里有包头,则打印出网络接口接收到的包
* 的总长度以及包的源地址.然后使原来的tcp_input函数正常运行.
*/
static void
new_input(struct mbuf *m, ...)
{
va_list ap;
int iphlen;
struct ifnet *ifnp;
struct ip *ip;
va_start(ap, m);
iphlen = va_arg(ap, int);
va_end(ap);
if (m->m_flags & M_PKTHDR) {
ifnp = m->m_pkthdr.rcvif;
ip = mtod(m, struct ip *);
printf("incoming packet: %d bytes ", m->m_pkthdr.len);
printf("on %s from %sn", ifnp->if_xname, inet_ntoa(ip->ip_src));
}
(*old_tcp_input)(m, iphlen);
return;
}
好,我们编译安装它:
[e4gle@openbsd29]# gcc -D_KERNEL -I/sys -c tcpmod.c
[e4gle@openbsd29]# modload -o tcpinfo.o -etcpinfo tcpmod.o
产生一些tcp连接,用dmesg来看看是否正常工作:
[e4gle@openbsd29]# dmesg
关键词:OpenBSD可加载内核模块编程完全向导