Linux驱动----1、最简单的驱动hello.ko

    
    驱动程序应该处理如何使硬件可用、而将怎样使用硬件留给上层应用程序。设备分类:字符设备、块设备、网络设备。
    字符设备驱动程序至少实现open、close、read、write系统调用。字符设备可以通过文件系统节点来访问。这些设备文件和普通文件之间的唯一差别在于普通文件的访问可以前后移动访问位置,而大多字符设备是一个只能顺序访问的数据通道。
    块设备上能容纳文件系统,一个文件系统决定了如何在块设备上组织数据,以及表示目录和文件形成的树。文件系统不是设备驱动程序,它没有实际物理设备同这种信息组织方式相关联。它只是个软件驱动程序,将低层数据结构映射到高层数据结构。
    模块能调用的函数仅仅是由内核导出的那些函数,而不存在任何可连接的库函数。Linux内核代码必须是可重入的,它必须能同时运行在多个上下文。

hello.c源文件

  • module.h包含可装载模块需要的大量符号和函数定义
  • init.h指定初始化和清除函数
  • MODULE_LICENSE宏告诉内核,该模块采用的协议
  • printk可能不会将信息打印在终端上,可用dmesg命令查看(dmsg | tail -5只读最后5行)
  • module_init该宏在模块目标代码中增加一个特殊的段,用于说明内核初始化函数所在的位置。
#include 
#include 

MODULE_LICENSE("Dual BSD/GPL");
static int hello_init(void)
{
        printk(KERN_ALERT "Hello, world\n");
        return 0;
}


static void hello_exit(void)

{

        printk(KERN_ALERT "Goodbye, world\n");

}


module_init(hello_init);

module_exit(hello_exit);

编译生成hello.ko模块

方法一、Makefile+命令行
Makefile

obj-m := hello.o

命令行

  • make modules 忽略中间参数是编译模块的意思
  • -C $(KDIR)指明跳转到内核源码目录下读取那里的Makefile
  • M=$(PWD) 表明然后返回到当前目录继续读入、执行当前的Makefile
make -C /usr/src/linux-headers-4.13.0-16-generic M=`pwd` modules
//这里4.13.0-16-generic=uname -r

方法二、完整的Makefile

obj-m := hello.o
    KERNELBUILD := /lib/modules/$(shell uname -r)/build
defaule:
    make -C $(KERNELBUILD) M=$(shell pwd) modules
clean:
    rm -rf *.o *.ko *.mod.c .*.cmd *.markers *.order *.symvers 

    make会在Makefile中的第一个不是以.开头的目标作为默认的目标执行。KERNELRELEASE定义在内核Makefile中,所以执行else后面的KERNELDIE, PWD赋值,执行default:-C指明跳转到内核源码目录下读取那里的Makefile;M=$(PWD) 表明然后返回到当前目录继续读入、执行当前的Makefile。当从内核源码目录返回时,KERNELRELEASE已被定义,**kbuild也被启动去解析kbuild语法的语句,make将继续读取else之前的内容。**else之前的内容为kbuild语法的语句,指明模块源码中各文件的依赖关系,以及要生成的目标模块名

# Comment/uncomment the following line to disable/enable debugging
#DEBUG = y

# Add your debugging flag (or not) to CFLAGS
ifeq ($(DEBUG),y)
  DEBFLAGS = -O -g # "-O" is needed to expand inlines
else
  DEBFLAGS = -O2
endif

EXTRA_CFLAGS += $(DEBFLAGS) -I$(LDDINCDIR)

ifneq ($(KERNELRELEASE),)
# call from kernel build system

obj-m   := hello.o

else

KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD       := $(shell pwd)

default:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) LDDINCDIR=$(PWD)/../include modules

endif



clean:
    rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions

//make depend = make .depend = make dep
depend .depend dep:
    $(CC) $(EXTRA_CFLAGS) -M *.c > .depend


ifeq (.depend,$(wildcard .depend))
include .depend
endif

安装、卸载模块

  • sudo insmod hello.ko安装之后,调用dmesg可以看到hello,world
  • 也可以通过tail /var/log/kern.log查看
  • sudo rmmod hello 卸载后,调用dmesg可以看到goodbye,world
  • lsmod 通过读取/proc/modules虚拟文件获得已安装模块
  • 以装载的模块可以在/sys/module下找到

内核编译的注意事项

  1. 内核使用非常小的栈,可能只有4096B(一页),自己编写的驱动函数必须和整个内核空间共享这个一栈。因此需要大的结构,使用动态分配kmalloc。
  2. “_ ”双下划綫前缀的函数通常是接口的底层组件。 _ init标记表示,模块装载之后将扔掉初始化函数。 _ _exit标记表示该代码用于模块卸载,如果模块直接编译进内核或不允许卸载,这该函数被丢弃。
  3. 用户空间的驱动程序可以实现为一个服务器进程,其任务是代替内核作为硬件控制的唯一代理,客户应用程序可连接到该服务器并和设备执行实际通信。

你可能感兴趣的:(笔记-Linux设备驱动)