Linux的模块驱动
- 接下来写个最简单的驱动程序,就像程序语言的hello world程序。
- 首先是:hello.c的代码:
这是个最简单的驱动程序。就是打印hello的信息。驱动程序和我们的程序语言结果有点不大一样。驱动模块的入口是倒数第二行的module_init()的函数。驱动模块的出口是module_exit()的函数。
3.接着是makfile文件:
这也是一个很简单的Makefile文件了。Obj-m后面跟的是我们的最终目标依赖的文件hello.o。第三行的KDIR是我们编译进的内核的路径。All是执行make得到的目标,$(KDIR)指定内核的路径,就是第三行的路径。M=$(PWD)是模块存放的路径。接着就是清除生成的文件的命令。
4.make的执行过程:
从上面的执行的过程,我们可以看到makefile的执行的过程。
如果在一个工程里,当有两个.c文件的时候的编写:
Hello.c:
Function.c:
Makefile修改为:
最后编译的结果如下图:
内核模块的安装和卸载:
insmod hello.ko
卸载内核模块:
rmmod hello(卸载的时候不用加.ko)
查看模块:
lsmod
执行的结果:
注意:内核模块只有当没有用户用时才可以卸载,如上图:我们的test是没有被使用,而fuse有两个用户在使用。我们试着卸载这两个内核模块的截图:
内核模块的可选的信息:模块申明、模块参数、符号信息。
模块的申明:
MODULE_LICENSE("遵守的协议")
申明该模块遵守的许可证协议,如:"GPL"、"GPL v2"等。
MODULE_AUTHOR("作者")
申明模块的作者
MODULE_DESCRIPTION("该模块的功能描述")
MODULE_VERSION("v1.0")
申明模块的版本
模块申明可以让读者知道该模块所遵守的协议,增加模块代码的可读性。
只是一个提示,增加可读性的作用。
模块参数的传递:
在我们的应用程序中:int main(int argc,char** argv):argc表示命令行输入的参数个数,argv中保存输入端的参数。
那么我们的内核模块中是怎么传入参数的呢?:
模块参数跟我们程序语言的参数有点不大一样,除了用一般的数据类型来申明变量参数,我们还得用module_param()这个宏来指定它是模块参数:
Module_param(name,type,perm):
Name:变量的名称
Type:变量的类型,bool,int,charp。
Perm:访问权限。S_IRUGO:读权限。S_IWUSR:写权限。
例如:
Int a=33;
Char *st;
Module_param(a,int ,S_IRUGO);
Module_param(st,charp,S_IRUGO);
下面是执行的实例:
我们定义了一个a=99;然后在14行打印出来。运行的结果:
上面是执行的过程,我们也可以在执行的时候给它加参数:
字符串也是一样:
运行的结果:
最后是符号导出:
符号导出的实例:
修改Makefile:
修改function.c为:
执行的过程:
同时产生了两个.ko模块。
当我们去安装hello.ko的时候,出现了这个错误:未定义的符号:
这是因为我们程序中的extern int function();现在的系统中不存在这个函数。那是不是得先insmod function.ko呢!?
虽然系统已经出现了function函数,可是系统还是找不到。可以看出错误依然存在。
这就是模块导出的问题:当我们要去使用一个模块里面的变量,函数的时候,必须使用符号导出。也就是把变量和函数输出到我们的系统当中,使整个系统都可以使用。
修改function.c为:
用EXPORT_SYMBOL()来申明,我的function是可以被系统的其他模块使用的。不过,我们应该先编译function.ko,在编译hello.ko。结果:
这就是符号输出的使用。
总结与应用程序的区别:
内核的打印:
Printf和printk都是打印信息的。但是printk还有级别打印:
Hello.c:
输出结果:
结果只有KERN_EMERG级别的才在屏幕打印出来。这样的打印级别,我们可以控制在那些地方可以打印什么。当然,我们也可以用输入来代替级别。例如上面的"<0>",就是KERN_EMER。