首先弄懂linux驱动的相关信息,然后介绍最简单的内核模块,让大家了解内核模块的编写、编译和使用。
现在我们从一个比较高的高度来审视一下GNU/Linux操作系统的体系结构。如下图所示,最上面是用户(或应用程序)空间,这是用户应用程序执行的地方。用户空间之下是内核空间,Linux内核正是位于这里。C基础库(如glibc, eglibc, uclibc等)也属于应用程序空间,它提供了连接内核的系统调用接口,还提供了在用户空间应用程序和内核之间进行转换的机制。这点非常重要,因为内核和用户空间的应用程序使用的是不同的保护地址空间。每个用户空间的进程都使用自己的虚拟地址空间,而内核则占用单独的地址空间。
Linux 内核可以进一步划分成 3 层。最上面是系统调用接口(SCI,System Call Interface),它实现了一些基本的功能,例如 open()、read()、write()、close()等。系统调用接口之下是内核代码,可以更精确地定义为独立于体系结构的内核代码、这些代码是 Linux 所支持的所有处理器体系结构所通用的。在这些代码之下是依赖于体系结构的代码,构成了通常称为 BSP(Board SupportPackage)的部分。这些代码用作给定体系结构的处理器和特定于平台的代码。
Linux内核实现了很多重要的体系结构属性。在或高或低的层次上,内核被划分为多个子系统。Linux也可以看作是一个整体,因为它会将所有这些基本服务都集成到内核中。这与微内核的体系结构不同,后者会提供一些基本的服务,例如通信、I/O、内存和进程管理,更具体的服务都是插入到微内核层中的。每种内核都有自己的优点,不过这里并不对此进行讨论。随着时间的流逝,Linux内核在内存和 CPU 使用方面具有较高的效率,并且非常稳定。但是对于 Linux 来说,最为有趣的是在这种大小和复杂性的前提下,依然具有良好的可移植性。
系统调用接口
SCI 层提供了某些机制执行从用户空间到内核的函数调用。正如前面讨论的一样,这个接口依赖于体系结构,甚至在相同的处理器家族内也是如此。SCI 实际上是一个非常有用的函数调用多路复用和多路分解服务。
在应用层空间调用了系统调用,是在内核空间中运行了。我们可以time来看一下两个空间的运行时间:
wangdengtao@wangdengtao-virtual-machine:~$ time ping 4.2.2.2 -c 4
PING 4.2.2.2 (4.2.2.2) 56(84) bytes of data.
64 bytes from 4.2.2.2: icmp_seq=1 ttl=53 time=314 ms
64 bytes from 4.2.2.2: icmp_seq=2 ttl=53 time=297 ms
64 bytes from 4.2.2.2: icmp_seq=3 ttl=53 time=305 ms
64 bytes from 4.2.2.2: icmp_seq=4 ttl=53 time=287 ms--- 4.2.2.2 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3024ms
rtt min/avg/max/mdev = 287.472/300.647/313.502/9.614 msreal 0m3.287s//实际运行的时间
user 0m0.002s//用户空间运行的时间
sys 0m0.000s//内核空间运行的时间
内存管理
内核所管理的另外一个重要资源是内存。为了提高效率,如果由硬件管理虚拟内存,内存是按照所谓的内存页方式进行管理的(对于大部分体系结构来说都是4KB)。Linux包括了管理可用内存的方式,以及物理和虚拟映射所使用的硬件机制。不过内存管理要管理的可不止4KB缓冲区。Linux提供了对4KB缓冲区的抽象,例如slab分配器。这种内存管理模式使用4KB缓冲区为基数,然后从中分配结构,并跟踪内存页使用情况,比如哪些内存页是满的,哪些页面没有完全使用,哪些页面为空,这样就允许该模式根据系统需要来动态调整内存使用。为了支持多个用户使用内存,有时会出现可用内存被消耗光的情况。由于这个原因,页面可以移出内存并放入磁盘中。这个过程称为交换,因为页面会被从内存交换到硬盘上, Linux系统中,被用于交换的分区叫swap分区,在windows系统下叫做虚拟内存。
文件系统
虚拟文件系统(VFS)是Linux内核中非常有用的一个方面,因为它为文件系统提供了一个通用的接口抽象。VFS在SCI和内核所支持的文件系统之间提供了一个交换层。在VFS上面,是对诸如open,close, read 和 write之类的函数的一个通用API抽象,在 VFS下面是文件系统抽象,它定义了上层函数的实现方式。
可以理解为有了文件系统只有我们对硬盘的操作操作路径就可以了。如果没有文件系统就没有路径的概念了。
网络管理
网络堆栈在设计上遵循模拟协议本身的分层体系结构。回想一下, Internet Protocol (IP)是传输协议(通常称为传输控制协议或TCP)下面的核心网络层协议,TCP上面是socket层,它是通过SCI进行调用的。socket是网络子系统的标准API。它为各种网络协议提供了一个用户接口,从原始帧访问到IP协议数据单元( PDU) ,再到TCP和User Datagram Protocol (UDP) , socket层提供了一种标准化的方法来管理连接,并在各个终点之间移动数据。
设备管理(驱动程序)
Linux内核中有大量代码都在设备驱动程序中,它们能够运转特定的硬件设备。Linux源码树提供了一个驱动程序子目录,这个目录又进一步划分为各种支持设备,例如 Bluetooth,l2C,serial 等,设备驱动程序的代码可以在/linux/drivers中找到。
直接上代码(代码中注释解释的听清楚的,可以仔细看看):
#include
#include
#include /*定义了个名为 xxx_init 的驱动入口函数,并且使用了“__init”来修饰.*/
static __init int hello_init(void)
{/*在 Linux 内核中没有 printf 这个函数。printk 相当于 printf 的孪生兄妹,printf运行在用户态,printk运行在内核态。*/printk(KERN_ALERT "Hello world\n");return 0;
}/*定义了个名为 xxx_exit 的驱动出口函数,并且使用了“__exit”来修饰.*/
static __exit void hello_exit(void)
{printk(KERN_ALERT "Goodbye world\n");
}
/*
module_init函数用来向 Linux内核注册一个模块加载函数,参数 xxx_init就是需要注册的具体函数,当使
用“ insmod”命令加载驱动的时候 xxx_init这个函数就会被调 用。 module_exit()函数用来向 Linux内核
注册一个模块卸载函数,参数 xxx_exit就是需要注册的具体函数,当使用“ rmmod”命令卸载具体驱动的时
候 xxx_exit函数就会被调用。所以一般在xxx_init函数里进行一些驱动的初始化工作,在xxx_exit里面就
需要对驱动程序的卸载做一些回收工作。
*/
/*调用函数 module_init 来声明 xxx_init 为驱动入口函数,当加载驱动的时候 xxx_init函数就会被调用.*/
module_init(hello_init);
/*调用函数module_exit来声明xxx_exit为驱动出口函数,当卸载驱动的时候xxx_exit函数就会被调用.*/
module_exit(hello_exit);/*添加LICENSE和作者信息,是来告诉内核,该模块带有一个自由许可证;没有这样的说明,在加载模块的时内核会“抱怨”.*/
MODULE_LICENSE("Dual BSD/GPL");//许可 GPL、GPL v2、Dual MPL/GPL、Proprietary(专有)等,没有内核会提示.
MODULE_AUTHOR("WangDengtao");//作者
MODULE_VERSION("V1.0");//版本
Makefile:
KERNAL_DIR ?= /lib/modules/$(shell uname -r)/build
PWD :=$(shell pwd)
obj-m := hello.oall:$(MAKE) -C $(KERNAL_DIR) M=$(PWD) modulesclean:@rm -f *.o *.cmd *.mod *.mod.c@rm -rf *~ core .depend .tmp_versions Module.symvers modules.order -f@rm -f .*ko.cmd .*.o.cmd .*.o.d@rm -f *.unsigned@rm -f *.ko
然后执行make:
警告
这里出现了警告,主要是gcc版本不匹配,应该不影响吧,继续操作。
可以看见我们的文件夹中多了很多文件,至于其他的文件,不要紧,最主要的看hello.ko文件。
wangdengtao@wangdengtao-virtual-machine:~/wangdengtao/driver$ ls
hello.c hello.ko hello.mod hello.mod.c hello.mod.o hello.o Makefile modules.order Module.symvers
我们可以看见hello.ko文件。我们可以使用insmod和rmmod命令加载或者卸载驱动。
wangdengtao@wangdengtao-virtual-machine:~/wangdengtao/driver$ sudo insmod hello.ko
wangdengtao@wangdengtao-virtual-machine:~/wangdengtao/driver$ sudo rmmod hello
wangdengtao@wangdengtao-virtual-machine:~/wangdengtao/driver$ sudo insmod hello.ko
wangdengtao@wangdengtao-virtual-machine:~/wangdengtao/driver$ dmesg | tail -3
dmesg: 读取内核缓冲区失败: 不允许的操作
wangdengtao@wangdengtao-virtual-machine:~/wangdengtao/driver$ sudo dmesg | tail -3
[ 8577.201090] Hello world
[ 8580.442205] Goodbye world
[ 8582.097892] Hello world
用lsmod命令查看当前linux内核安装了的内核模块:
首先需要在linux上安装内核源码树的目录,也就是arm架构的。这个就需要根据开发板自身需求去安装了。
Makefile:
KERNAL_DIR ?= /home/wangdengtao/imx6ull/imx6ull/bsp/kernel/linux-imx
PWD :=$(shell pwd)
obj-m := hello.oall:$(MAKE) -C $(KERNAL_DIR) M=$(PWD) modulesclean:@rm -f *.o *.cmd *.mod *.mod.c@rm -rf *~ core .depend .tmp_versions Module.symvers modules.order -f@rm -f .*ko.cmd .*.o.cmd .*.o.d@rm -f *.unsigned@rm -f *.ko
将hello.ko文件移动到tftpboot目录下,方便开发板能够tftp获取到。
开发板获取到了我们就加载和卸载驱动来测试一下:
cp hello.ko /home/wangdengtao/wangdengtao/tftpboot/
root@igkboard:~# tftp -gr hello.ko 192.168.10.168
root@igkboard:~# ls
hello.koroot@igkboard:~# insmod hello.ko
root@igkboard:~# dmesg | tail -1
[ 820.486004] Hello worldroot@igkboard:~# lsmod
Module Size Used by
hello 16384 0
rtl8188fu 999424 0
imx_rngc 16384 0
rng_core 20480 1 imx_rngc
secvio 16384 0
error 20480 1 secvioroot@igkboard:~# rmmod hello.ko
root@igkboard:~# dmesg | tail -1
[ 861.499208] Goodbye world
测试成功。
上一篇:信号处理-傅里叶变换