复杂度3/5
机密度4/5
最后更新2021/05/18
前面介绍过内核扩展也是一种执行程序,与普通执行程序不同的是它需要被加载到内核中执行,拥有内核权限。我们先来看看加载、卸载和内核扩展程序本体。
加载需要用到cfg_kmod结构和sysconfig系统调用,前者对要加载的程序进行描述,后者完成加载(或卸载)过程。要注意,不止sysconfig,还有好几个类似的系统调用都完成类似的功能,他们之间没有本质区别,只是适用场景(方便程度)不同,例如,有的调用一步完成加载及初始化,有的则分为若干步,需要使用不同参数,重复执行几次。
int sysconfig (Cmd, Parmp, Parmlen) int Cmd; /* 子功能,使用同一调用,设置不同功能可以分别完成加载、 初始化、卸载等多个功能,例如: SYS_KLOAD 加载内核扩展; SYS_SINGLELOAD 同上,不同的是会检测内核扩展是否已加载,如果没加载过,则加载,而前者则是不管是否加载过,都再加载一次。此功能等于帮你做了查询+加载; SYS_QUERYLOAD 查询内核扩展是否已经被加载。那查询的依据是什么? 很傻的,依据是被加载程序的路径,而且只是路径字符,如果分别给绝对 路径和相对路径,那该命令会人为是不同的内核扩展。再回到SINGLELOAD 的疑问,如果使用SYS_KLOAD加载了两次怎么办?凉拌,内核不管的, 随便你加载几次,卸载的时候根据kmid这个唯一值卸载。 另一个问题?谁会被使用?如果能定位(通过kmid)则用确定的程序, 如果不能定位,一般是最后加载的最优先,前面的就变成死代码; SYS_KULOAD 卸载; SYS_QDVSW 查询设备表,设备表是让被加载程序能被执行的关键方案之一, 我们以后说(另一个方案是export 函数名,把加载的程序当成库函数使用) 当然还有一次性的,由用用户态加载程序触发的初始化dd方案,既下一个命令; SYS_CFGDD 告知内核去执行被加载的程序,一般内核扩展被加载到内存, 下一件事就是执行这个,让它自己把自己初始化好。sysconfig调用执行这个命令后 会让内核从被加载程序的入口点执行(关于入口设置稍后介绍); SYS_CFGKMOD 与上一个命令没啥区别,不知道为啥弄出两个来,为了满足 “命名”需求?一个叫device driver,一个叫kernel model? SYS_GETPARMS 告诉内核程序把你需要返回的参数返回,其实是提供了一个简单的,从内核扩展取数据的方案; SYS_SETPARMS 如果你想发参数给内核扩展,可以用这个; */ void *Parmp; /* 输入参数地址,这个参数其实是个结构,对应不同子功能, * 需要指向不同结构 */ int Parmlen; /* 输入参数长度,既对应结构的具体长度 */ /* cfg_load结构是与SYS_KLOAD或SINGLELOAD对应的,别搞错了 */ struct cfg_load { caddr_t path; /* 被加载程序地址,可以是绝对路径,也可以是相对路径(相对执行加载程序所在当前目录) */ caddr_t libpath; /* 如果被加载程序有动态链接库,指明链接库所在目录 */ mid_t kmid; /* 这个是返回值,加载成功会返回一个数值,其实是被加载程序所加载的地址,通过它可以唯一确定被加载程序 */ }; /* 加载之后不能扔在那里不管,还要最后交代两句,你要怎么做,这就是 SYS_KMOD(或者SYS_CFGDD)命令对应的操作,这两个命令发出去后, 内核会从被加载程序的入口点执行,具体执行什么工作,那就是你写的 扩展程序自己要求啦,可以为所欲为,当然,还是有一些约定熟成的套 路的。此处注意结构和命令要配对,如果用SYS_KMOD用cfg_kmod struct, 而如果用SYS_CFGDD,则要用sys_cfgdd struct */ struct cfg_kmod { mid_t kmid; /* 前面load返回的kmid,错了就对不上了 */ int cmd; /* 自己定义的命令,会发送给扩展程序 ,两边对应上就好*/ caddr_t mdiptr; /* 如果要发送更多信息,就通过这个指针传送数据结构所在位置 */ int mdilen; /* 传送数据结构的程度,这个结构是自己定义的,还是 * 要和被加载程序能识别的结构对应上 */ };
下面是loader程序:
/* kloader.c kernel extension loader example */ #include <sys/types.h> #include <sys/sysconfig.h> #include <errno.h> int rc=0; struct cfg_load queryLoad; /* 先定义一个结构 ,query使用cfg_load结构 */ bzero (&queryLoad, sizeof (queryLoad)); /* 清零,准备填充数据 */ queryLoad.path = "./kernExt"; /* 被加载程序所在位置,程序中用命令行传进来的参数替代 */ queryLoad.libpath = NULL; /* 无链接库 */ rc=sysconfig (SYS_QUERYLOAD, &queryLoad, sizeof (queryLoad)); /* 前面介绍过要先查一下是否已经加载过 */ if (rc != 0 ) { printf(stderr, "query load error: %d!\n", errno); exit(errno); } if ( queryLoad.kmid != 0 ) { printf(stderr, "Kernel extension has been loaded already!\n"); exit(1); } /* 查询过,没有被加载,万事大吉,可以load了!下面复用了query时定义的load结构,不推荐。。。但省事*/ rc=sysconfig (SYS_KLOAD|SYS_64BIT, &queryLoad, sizeof(queryLoad) ); if (rc != 0 ) { printf(stderr, "Load kernel extension error: %d!\n", errno); exit(errno); } /* 初始化kernel extension */ struct cfg_kmod configLoad; /* 定义结构 */ configLoad.kmid = queryLoad.kmid; /* 需要kmid确定是哪个程序 */ configLoad.cmd = CFG_INIT; /* 子命令,这个其实随便写点啥,是由你的被加载程序处理的,只要是int就可以 */ configLoad.mdiptr = NULL; /* 先简单点,没有别的参数 */ configLoad.mdilen = 0; rc=sysconfig (SYS_CFGKMOD|SYS_64BIT, &configLoad, sizeof(configLoad)); /* 为什么要置SYS_64BIT位呢?因为我们要写一个64位的扩展程序,需要让加载函数知道要加载的是64位的 */ if (rc != 0 ) { printf(stderr, "Init kernel extension error: %d!\n", rc); /* 这里其实应当增加unload过程*/ exit(rc); } /* 其它工作。。。。 略,然后准备unload,先terminal,又复用结构,坏习惯!我知道我自己的程序是怎么写的呀!气死你! */ configLoad.cmd = CFG_TERM; /* 子命令,被加载程序识别为TERM就OK */ configLoad.mdiptr = NULL; /* 还是没有别的参数 */ configLoad.mdilen = 0; rc=sysconfig (SYS_CFGKMOD|SYS_64BIT, &configLoad, sizeof(configLoad)); if (rc != 0 ) { printf(stderr, "Term kernel extension error: %d!\n", rc); /* 这里不能设置unload过程了,为什么?自己想 */ exit(rc); } /* 复用queryLoad 。。。只需要kmid */ rc = sysconfig (SYS_KULOAD|SYS_64BIT, &queryLoad, sizeof (queryLoad)); if (rc != 0) { printf(stderr, "Unload kernel extension error: %d!\n", rc); } exit(rc);
好,这是全部的查询、加载、初始化、终止、卸载过程,当然还不够精细,不过都不是大问题了。下一节介绍被加载的主体:kernel extension模块。