我们平时用到的很多设备都是字符串设备,比如鼠标、键盘等等。
所以现在来编写一个字符串设备的驱动模版。
1.模块的入口和出口
因为我们为了方便调试代码,所以采用模块的方式来进行注册和卸载。既然用到了模块,那么我们就需要包含
#include<linux/module.h>
而且需要在c_cpp_properties.json中指定内核的路径,如果不指定那么它会报错,或者默认为ubunut的/usr/include下的内核头文件。但是ubuntu的kernel文件和我们开发板使用的kernel是不同的。我们要用到的kernel文件是要作为zImage烧写到系统中的,开发板内存着自己的kernel文件,但运行的是ubuntu的文件,结果会很可怕。
然后是模块的注册和模块的卸载
#include<linux/module.h>
static int __init chardev_init(void){
return 0;
}
static void __exit chardev_exit(void){
}
module_init(chardev_init);
module_exit(chardev_exit);
/—————————————————题外知识————————————————————/
在这其中使用static
关键字来声明一个函数可以使该函数仅在定义它的文件内部可见,而不会被其他文件访问。换句话说,它限制了函数的可见性和可访问性。
而使用static
关键字来声明一个变量可以改变该变量的存储方式和可见性。具体来说:
①对于在函数内部用static声明的变量,其生命周期被延长至程序的整个运行期间,而不是在函数调用期间。意味着比如在子函数中声明一个变量,这个变量在子函数结束时不会被释放,而且每一次调用该子函数,它只会被最开始初始化一次。
void func() {
static int count = 0; // 此变量只初始化一次,函数每次调用时保持其值
printf("%d\n", count++);
}
② 对于在函数外部用static声明的变量,其作用范围被限制在该文件内,其他文件无法访问。
/*—————————————————题外知识————————————————————*/
__init
是一个函数修饰符,用于标记函数是否在初始化阶段被调用。当一个内核模块被加载时,__init
修饰的函数会被自动调用。在大多数情况下,__init
修饰的函数在模块加载后就不再存在,因此它们不能在运行时被调用。
module_init()
是一个宏,用于指定模块初始化的函数。它接受一个函数名作为参数,并自动调用该函数来进行模块初始化。
__init chardev_init(void)
定义了一个名为 chardev_init
的初始化函数。 exit同理。
2.Makefile编写
接下来编写Makefile文件
KERNELDIR := /home/lcc/linux/I.MUX6ULL/Linux_YM/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENT_PATH := $(shell pwd)
obj-m := chardev.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
KERNELDIR 表示开发板所使用的 Linux 内核源码目录。
CURRENT_PATH表示当前目录。
obj-m表示将chardev.c编译为模块。
build: kernel_modules表示编译的是模块。
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules表示执行make操作。-C 转移工作目录到内核目录,因为我们这个程序用到了内核中的程序。 M表示模块源码目录, modules表示模块。
最后执行make命令,生成模块。
3.加载、卸载模块 (以下内容都是在开发板终端中执行)
正点原子是把uboot下载到sd卡中,然后开发板从SD卡启动。因为SD卡中有uboot的存在,所以就能进入uboot命令行模式。然后使用tftp把zImage和dtb直接烧写到内存中(可以试一试)。先配置开发板的IP地址:(按照自己的配置)
setenv ipaddr xxx.xx.xx.xx 开发板的IP地址
setenv ethaddr xxx.xxx.xxx.xx
setenv gatewayip xx.xx.xx 网关地址
setenv netmask xxx.xxx.xxx.xxx子网掩码
setenv serverip xx.xx.xx.xx 虚拟机IP地址
saveenv
再用tftp加载zImage和dtb(已开通tftp服务,网上有很多教程)
然后再通过nfs把ubuntu上编写的根文件系统挂载到开发板上。(命令中的ip地址按照自己之前setenv中设置的输入,对应的)
setenv bootargs 'console=ttymxc0,115200 rw root=/dev/nfs \ nfsroot=ipaddr:/home/lcc/linux/nfs/roofts/ \
roofts ip=ethaddr:ipaddr:gatewayip:netmask::eth0:off'
生成.ko文件后。有两种命令加载驱动模块:insmod modprobe。
insomd不能解决模块的依赖问题。比如第一个模块要依赖第二个模块。加载的时候就应该先加载第二个模块。modprobe更智能,它会分析模块的依赖关系,然后将所有的模块加载到内核中。
我们要使用modprobe命令,前提条件是我们在/lib/modules/下有一个4.1.15文件(文件的名称对应开发板使用的内核版本,由我们自己创建即可)。然后使用cp命名把我们刚生成的.ko文件拷贝到4.1.15文件中。
再depmod命令。它会生成
然后就可以modprobe了。最后使用rmmod卸载模块。我刚才不小心使用了rm。它的坏处在于,模块是加载到内核中的,我们可能操作失败,因为用户没有权限去动内核东西,即使操作成功,可能会导致内核崩溃。而且使用rm后,lsmod仍然显示模块存在。