[摘要]tail -1 bye! # 好,现在让我们来看看设备驱动的编写. ★设备驱动程序模块 设备驱动模块和系统调用的模块的编写方法有很大的相同之处.他们有一个外部入口点,且句柄关联着特殊的模块代码.在下...
tail -1
bye!
#
好,现在让我们来看看设备驱动的编写.
★设备驱动程序模块
设备驱动模块和系统调用的模块的编写方法有很大的相同之处.他们有一个外部入口点,且句柄关联着特殊的模块代码.在下面的这段特殊的模
块代码会直接操作我们的设备.在这个
例子中我们简单的演示了一个字符设备的例子,只能支持open,close,read和ioctl操作.在我们剖析它的内部机理之前,让我们先来看看lkm
是如何来解释设备的.
下面这段代码定义了一个可加载的设备驱动:
struct lkm_dev {
MODTYPE lkm_type;
int lkm_ver;
char*lkm_name;
u_longlkm_offset;
DEVTYPE lkm_devtype;
union {
void*anon;
struct bdevsw *bdev;
struct cdevsw *cdev;
} lkm_dev;
union
{
struct bdevsw bdev;
struct cdevsw cdev;
} lkm_olddev;
};
首先我们需要一个模块的类型(这里是LM_DEV),然后是lkm的版本号,再就是它的名称和它在cdevsw[]或者bdevsw[]表中的位置
(lkm_offset).然后我们到了DEVTYPE定义的
lkm_devtype成员,它定义了我们设备的类型,或者是一个字符型设备或者是一个块设备,分别被LM_DT_CHAR或者LM_DT_BLOCK宏指
定.再下面定义了两个枚举类型的结构,在模
快被加载的时候分别定义了新的设备的操作空间以及保留了老的设备结构,此结构通过MOD_DEV宏来初始化:
MOD_DEV("ourdev", LM_DT_CHAR, -1, &cdev_ourdev)
首先我们通过我们的模块名以及设备类型,在此例中我们得知我们创建的是一个字符型的设备.接下来需要在cdevsw[]中有个入口,就象上面
的系统调用的例子那样,-1代表我们可以
不去关心放置的确切位置,让系统自己去寻找可用的入口.如果没有空闲的入口,函数ENFILE ("Too many open files in system")将会被
返回.最后我们通过初始化cdevsw
结构来对我们的设备进行操作.
我们的字符设备将会支持四种操作:open,close,read和ioctl.不能干再多的事情了,它将存储一个字符串和一个数字,该数字可以被ioctl调用
设置和返回,字符串也可以用read
调用返回.
我们定义的内部结构如下:
#define MAXMSGLEN 100
struct ourdev_io {
int value;
char msg[MAXMSGLEN];
};
当模块第一次被加载的时候,我们设置value为13并且为我们的字符串赋值"hello world!".我们定义了两个简单的ioctl调用来设置或获取内
部结构的当前的value的值.这些
都利用ourdev_io结构作为一个参数,然后利用ioctl执行一个相应的动作.
在模块的入口指针中,我这里再次用了IDSPATH宏.
以下是我们自定义的设备程序的完整代码(chardev.c):
#include <sys/param.h>
#include <sys/fcntl.h>
#include <sys/systm.h>
#include <sys/ioctl.h>
#include <sys/exec.h>
#include <sys/conf.h>
#include <sys/lkm.h>
#include "common.h"
/*
* 导入我们支持的操作:open,read,close,ioctl等
*/
int ourdevopen __P((dev_t dev, int oflags, int devtype, struct proc *p));
int ourdevclose __P((dev_t dev, int fflag, int devtype, struct proc *p));
int ourdevread __P((dev_t dev, struct uio *uio, int ioflag));
int ourdevioctl __P((dev_t dev, u_long cmd, caddr_t data, int fflag,
struct proc *p));
int ourdev_handler __P((struct lkm_table *lkmtp, int cmd));
/*
* outdev_io结构定义在头文件common.h中,我们的设备会通过ioctl调用来获取和设置它的值.
*/
static struct ourdev_io dio;
/*
* 这里我们初始化我们的设备的操作向量
*/
cdev_decl(ourdev);
static struct cdevsw cdev_ourdev = cdev_ourdev_init(1, ourdev);
/*
* 初始化lkm接口的内部结构.第一个参数是模块名,第二个参数是设备的类型,在我的例子里标记为
* LM_DT_CHAR表示是一个字符设备.第三个参数是我们存储在cdevsw[]表中的操作结构.就象系统
* 调用的例子中一样,值为-1的话系统自动找寻空闲的位置存储.最后我们初始化cdevsw结构
*/
MOD_DEV("ourdev", LM_DT_CHAR, -1, &cdev_ourdev)
/*
* 以下的动作在设备被打开的时候执行,这里打印"hello",哈,仅做测试之用:)
*/
int
ourdevopen(dev, oflags, devtype, p)
dev_t dev;
int oflags, devtype;
struct proc *p;
{
printf("device opened, hi!n");
return(0);
}
/*
* 以下动作在设备被关闭的时候执行,这里打印一段信息
*/
int
ourdevclose(dev, fflag, devtype, p)
dev_t dev;
int fflag, devtype;
struct proc *p;
{
printf("device closed! bye!n");
return(0);
}
/*
* 定义我们设备执行的read动作,这里它把存储在内部结构ourdev_io里的string的当前值读出来
*/
int
ourdevread(dev, uio, ioflag)
dev_t dev;
struct uio *uio;
int ioflag;
{
int resid = MAXMSGLEN;
int error = 0;
do {
if (uio->uio_resid < resid)
resid = uio->uio_resid;
error = uiomove(dio.msg, resid, uio);
} while (resid > 0 && error == 0);
return(error);
}
/*
* ioctl操作的代码.这里定义了两个操作,一个负责从ourdev_io中读取当前值,一个负责设置当前值.
*/
int
ourdevioctl(dev, cmd, data, fflag, p)
dev_t dev;
u_long cmd;
caddr_t data;
int fflag;
struct proc *p;
{
struct ourdev_io *d;
int error = 0;
switch(cmd) {
case ODREAD:
d = (struct ourdev_io *)data;
d->value = dio.value;
error = copyoutstr(&dio.msg, d->msg, MAXMSGLEN - 1, NULL);
break;
case ODWRITE:
if ((fflag & FWRITE) == 0)
return(EPERM);
d = (struct ourdev_io *)data;
dio.value = d->value;
bzero(&dio.msg, MAXMSGLEN);
error = copyinstr(d->msg, &dio.msg, MAXMSGLEN - 1, NULL);
break;
default:
error = ENOTTY;
break;
}
return(error);
}
/*
* 我们的外部入口点.非常象前面介绍的系统调用的例子,用来控制模块的加载,这里和系统调用模块不
* 同的是我们在模块卸载的时候没有制定特殊的动作
*/
int
ourdev(lkmtp, cmd, ver)
struct lkm_table *lkmtp;
int cmd;
int ver;
{
DISPATCH(lkmtp, cmd, ver, ourdev_handler, lkm_nofunc, lkm_nofunc)
}
/*
* 控制加载模块的代码.我们为我们的内部结构设置一些初始值,这些值以后会被ioctl改变.它仅仅
* 在模块被加载的时候用到.
*/
int
ourdev_handler(lkmtp, cmd)
struct lkm_table *lkmtp;
int cmd;
{
struct lkm_dev *args = lkmtp->private.lkm_dev;
if (cmd == LKM_E_LOAD) {
dio.value = 13;
strncpy(dio.msg,"hello world!n", MAXMSGLEN - 1);
printf("loading module %sn", args->lkm_name);
}
return 0;
}
好了,最后我们可以用modload的-p参数来安装我们的设备模块,我可以写一个脚本来完成编译安装我们的设备的任务.脚本利用mknod在
/dev目录里面创建了一个设备,就叫
'/dev/ourdev'.在此安装脚本中,我们用模块号作为第一个参数,模块的类型作为第二个参数.如果模块是一个系统调用,我们还需要指定系统
调用号作为第三个参数这里,我
们的第三个参数是主设备号.
以下就是该安装脚本(dev-install.sh):
#!/bin/sh
MAJOR=`modstat -n ourdev
关键词:OpenBSD可加载内核模块编程完全向导