Introduction to NetBSD loadable kernel modules 翻译
来源:
作者:
时间:2007-12-03
Tag:
点击:
static int fibo_refcnt = 0;
下面我们需要保存每一个子设备的信息。
struct fibo_softc {
int sc_refcnt;
u_int32_t sc_current;
u_int32_t sc_previous;
};
#define MAXFIBODEVS 8
static struct fibo_softc fibo_scs[MAXFIBODEVS];
前面以及提过,我们的驱动程序要提供8个子设备。每个子设备包含了被打开次数(在我们的例子中,为了简单起见每个子设备只能被打开一次)、用于计算FIBONACCI数的当前数字和之前的数字这些信息。如果不知道怎么计算FIBONACCI数,你应该查阅算法方面的书籍,因为,解释这个问题已经超出了本文的范围。
每个内核模块一定要有个入口函数——加载模块的时候这个入口函数由modload传给ld(1)。默认的模块入口函数名称为xxxinit。如果没有找到xxxinit,那么就尝试寻找modulename_lkmentry,其中modulename就是加载的模块去掉.o后的的文件名。通常情况下,entry函数完全是由一个DISPATCH行组成——DISPATCH是个定义在sys/lkm.h中的一个宏,用来处理模块的加载、卸载和状态查询。所以,我们的fibo_lkmentry是这样的:
int
fibo_lkmentry(struct lkm_table *lkmtp, int cmd, nt ver)
{
DISPATCH(lkmtp, cmd, ver, fibo_handle, fibo_handle, fibo_handle);
}
现在,我们需要一个句柄函数在加载、卸载和查询模块的时候完成特定的任务。句柄函数名传给DISPATCH(见上文),以告诉内核当发生特定的事件时要调用哪个函数。指向LKM表中模块入口的指针和表示预期命令(LKM_E_LOAD、LKM_E_UNLOAD或者LKM_E_STAT)的整数会被传递给句柄函数。句柄函数在模块链接和加载到内核后被调用,并带有LKM_E_LOAD命令。接下来,我们需要检查此模块是否已经加载到内核中了,也要初始化我们的数据结构。当模块卸载的时候,句柄函数被调用并传入LKM_E_UNLOAD命令,而我们则需要在确认卸载命令之前检查模块是否不再有用(例如,检查是否所有的字符或者块设备对应的设备已关闭)
static int
fibo_handle(struct lkm_table *lkmtp, int cmd)
{
switch (cmd) {
case LKM_E_LOAD:
/* check if module was already loaded */
if (lkmexists(lkmtp))
return (EEXIST);
/* initialize minor device structures */
bzero(fibo_scs, sizeof(fibo_scs));
printf("fibo: FIBONACCI driver loaded successfully\n");
break;
case LKM_E_UNLOAD:
/* check if a minor device is opened */
if (fibo_refcnt > 0)
return (EBUSY);
break;
case LKM_E_STAT:
break;
default:
return (EIO);
}
return (0);
}
open函数非常的简单,因为大部分麻烦的工作已经被NetBSD内核处理了(例如,内核会自动替你申请vnode(9))。open函数的参数是主设备号和子设备号(使用major和minor宏)、flag标记、open(2)中描述的模式参数以及指向使用open系统调用进程的proc结构体指针。
那么,首要的事情是检查设备打开时我们获得的子设备号是否超出了范围,以及子设备是否已经打开了。你应该牢记子设备的处理完全取决于你,而且其相关错误的代码用不会完结。接着我们要初始化子设备数据(the FIBONACCI starting numbers: = 1, 0 + 1 = 1, 1 + 1 = 2, 1 + 2 = 3, ...),同时增加子设备的和全局的模块引用计数器。
static int
fibo_open(dev_t dev, int flag, int mode, struct proc *p)
{
struct fibo_softc *fibosc = (fibo_scs + minor(dev));
if (minor(dev) >= MAXFIBODEVS)
return (ENODEV);
/* check if device already open */
if (fibosc->sc_refcnt > 0)
return (EBUSY);
fibosc->sc_current = 1;
fibosc->sc_previous = 0;
/* increase device reference counter */
fibosc->sc_refcnt++;
/* increase module reference counter */
fibo_refcnt++;
return (0);
}
close函数与上述open函数的参数有同样的含义。它用于释放之前打开子设备的内部数据结构。你不必担心设备之前是否已经打开或者释放设备对应的vnode之类的事情,你需要做的仅是清理模块特有的东西。在本例中,这(清理模块特有的东西)意味着减少子设备和全局的模块引用计数,所以,我们的close函数非常简单。
static int
fibo_close(dev_t dev, int flag, int mode, struct proc *p)
{
struct fibo_softc *fibosc = (fibo_scs + minor(dev));
/* decrease device reference counter */
fibosc->sc_refcnt--;
/* decrease module reference counter */
fibo_refcnt--;
return (0);
}
read函数是最后但并不是无足轻重的一个函数。read函数有三个参数:与open和close函数一样的主设备号和子设备号、一个标志域——表示如read操作是否以非阻塞的方式进行之类的标志,还有一个指向定义在sys/uio.h中的uio结构体的指针。结构体uio通常描述移动的数据,比如从内核空间移动到用户空间的read(2)系统调用的数据。你如果曾在GNU/Linux上开发过驱动程序的话,就会觉得这有点奇怪,不过,NetBSD内核中uio的概念简化了许多事情,并且为内核空间到用户空间以及内核空间到内核空间的数据移动提供了通用统一的接口。详见uiomove(9)。回到正题,我们先看一下read函数,之后再讨论细节。
static int
fibo_read(dev_t dev, struct uio *uio, int flag)
{
struct fibo_softc *fibosc = (fibo_scs + minor(dev));
if (uio->uio_resid < sizeof(u_int32_t))
return (EINVAL);
while (uio->uio_resid >= sizeof(u_int32_t)) {
int error;
/* copy to user space */
if ((error = uiomove(&(fibosc->sc_current),
sizeof(fibosc->sc_current), uio))) {
return (error);
}
/* prevent overflow */
if (fibosc->sc_current > (MAXFIBONUM - 1)) {
fibosc->sc_current = 1;
fibosc->sc_previous = 0;
continue;
}
/* calculate */ {
u_int32_t tmp;
tmp = fibosc->sc_current;
fibosc->sc_current += fibosc->sc_previous;
fibosc->sc_previous = tmp;
}
}
return (0);
}
0
最新评论共有 4 位网友发表了评论
查看所有评论
发表评论
热点关注
